Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions pyramid_openapi3/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,8 @@ def check_all_routes(event: ApplicationCreated) -> None:
def remove_prefixes(path: str) -> str:
path = f"/{path}" if not path.startswith("/") else path
for prefix in prefixes:
if path.startswith(prefix):
prefix_length = len(prefix)
return path[prefix_length:]
if path == prefix or path.startswith(prefix + "/"):
return path[len(prefix) :] or "/"
return path

app = event.app
Expand Down
87 changes: 87 additions & 0 deletions pyramid_openapi3/tests/test_app_construction.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,43 @@
description: A bar
"""

# A test for when a `server.url` prefix is a string-prefix, but not a
# path-segment-prefix, of a route's pattern (see GH #146): neither `/v` nor
# `/api` should be treated as a prefix of `/version`/`/apidocs` respectively
# (the two literal cases from the issue). `/foo` and `/` are genuinely under
# the `/v` prefix (registered as routes `/v/foo` and `/v` respectively) and
# must still be recognized as such.
PREFIX_BOUNDARY_DOCUMENT = b"""
openapi: "3.1.0"
info:
version: "1.0.0"
title: Foo API
servers:
- url: /v
- url: /api
paths:
/version:
get:
responses:
200:
description: Not under the /v prefix, just shares a string prefix
/apidocs:
get:
responses:
200:
description: Not under the /api prefix, just shares a string prefix
/foo:
get:
responses:
200:
description: A foo, genuinely under the /v prefix
/:
get:
responses:
200:
description: Root, registered at a route matching the prefix exactly
"""


def foo_view(request: Request) -> str:
"""Return a dummy string."""
Expand Down Expand Up @@ -138,6 +175,16 @@ def root_server_document() -> t.Generator[t.IO, None, None]:
yield document


@pytest.fixture
def prefix_boundary_document() -> t.Generator[t.IO, None, None]:
"""Load the PREFIX_BOUNDARY_DOCUMENT into a temp file."""
with tempfile.NamedTemporaryFile() as document:
document.write(PREFIX_BOUNDARY_DOCUMENT)
document.seek(0)

yield document


@pytest.fixture
def simple_config() -> t.Generator[Configurator, None, None]:
"""Config fixture."""
Expand Down Expand Up @@ -178,6 +225,17 @@ def root_server_app_config(
return simple_config


@pytest.fixture
def prefix_boundary_app_config(
simple_config: Configurator, prefix_boundary_document: t.IO
) -> Configurator:
"""Incremented fixture loading the PREFIX_BOUNDARY_DOCUMENT above into the config."""
simple_config.pyramid_openapi3_spec(
prefix_boundary_document.name, route="/foo.yaml", route_name="foo_api_spec"
)
return simple_config


app_config = pytest.mark.parametrize(
"app_config",
[
Expand Down Expand Up @@ -325,3 +383,32 @@ def test_root_server_routes(root_server_app_config: Configurator) -> None:
)

root_server_app_config.make_wsgi_app()


def test_prefix_boundary_routes(prefix_boundary_app_config: Configurator) -> None:
"""Test case for GH #146.

A server prefix that is a string-prefix, but not a path-segment-prefix,
of a route pattern must not be stripped from it -- for both literal cases
from the issue (`/v`+`/version`, `/api`+`/apidocs`). Also covers routes
that are genuinely under a prefix (`/v/foo`), and a route that matches a
prefix exactly (`/v`).
"""
prefix_boundary_app_config.add_route(name="version", pattern="/version")
prefix_boundary_app_config.add_route(name="apidocs", pattern="/apidocs")
prefix_boundary_app_config.add_route(name="foo", pattern="/v/foo")
prefix_boundary_app_config.add_route(name="root", pattern="/v")
prefix_boundary_app_config.add_view(
foo_view, route_name="version", renderer="string", request_method="GET"
)
prefix_boundary_app_config.add_view(
foo_view, route_name="apidocs", renderer="string", request_method="GET"
)
prefix_boundary_app_config.add_view(
foo_view, route_name="foo", renderer="string", request_method="GET"
)
prefix_boundary_app_config.add_view(
foo_view, route_name="root", renderer="string", request_method="GET"
)

prefix_boundary_app_config.make_wsgi_app()