Repository: Pylons/pyramid_openapi3 (language: python)

Issue:
Fix for #104, adding support for mounted apps



Discussion / hints:
@zupo this is the fix I mentioned in #104 ... let me know if it looks ok to you and if you'd want it and I'll add tests
Can you please add a functional test for this?
Hi @zupo. I cleaned up the fix and updated the existing test for `test_relative_app_request` which tests path_pattern and it looks like that's the only test that needed to be done.

The PR checks above fail on `pyupgrade` run and I have not been able to reproduce that locally either via `pyupgrade` or `pre-commit` run. Not sure what to do there.
> The PR checks above fail on `pyupgrade` run and I have not been able to reproduce that locally either via `pyupgrade` or `pre-commit` run. Not sure what to do there.

I'll have a look.


Fix patch:
diff --git a/pyramid_openapi3/wrappers.py b/pyramid_openapi3/wrappers.py
index 3762d36..6c77211 100644
--- a/pyramid_openapi3/wrappers.py
+++ b/pyramid_openapi3/wrappers.py
@@ -41,7 +41,7 @@ def path_pattern(self) -> str:
             if self.request.matched_route
             else self.request.path_info
         )
-        return path_pattern
+        return self.request.script_name + path_pattern
 
     @property
     def method(self) -> str:


Test patch:
diff --git a/pyramid_openapi3/tests/test_validation.py b/pyramid_openapi3/tests/test_validation.py
index 540b625..02a9909 100644
--- a/pyramid_openapi3/tests/test_validation.py
+++ b/pyramid_openapi3/tests/test_validation.py
@@ -363,6 +363,110 @@ def test_request_validation_disabled_response_validation_enabled(self) -> None:
         self.assertEqual(start_response.status, "500 Internal Server Error")
 
 
+class TestServerRequestValidation(RequestValidationBase):  # noqa: D101
+
+    openapi_spec = (
+        b"openapi: '3.1.0'\n"
+        b"info:\n"
+        b"  version: '1.0.0'\n"
+        b"  title: Foo API\n"
+        b"servers:\n"
+        b"  - url: /prefix/v1\n"
+        b"  - url: http://example.com/prefix\n"
+        b"paths:\n"
+        b"  /foo:\n"
+        b"    get:\n"
+        b"      responses:\n"
+        b"        200:\n"
+        b"          description: A foo\n"
+        b"          content:\n"
+        b"            application/json:\n"
+        b"              schema:\n"
+        b"                type: object\n"
+        b"                properties:\n"
+        b"                  test:\n"
+        b"                    type: string\n"
+        b"        400:\n"
+        b"          description: Bad Request\n"
+    )
+
+    def test_server_validation_works_with_script_name(self) -> None:
+        """Expect to find a match for http://localhost:8080/prefix/v1/foo."""
+        self._add_view(lambda *arg: {"test": "correct"})
+        # run request through router
+        router = Router(self.config.registry)
+        environ = {
+            "wsgi.url_scheme": "http",
+            "SERVER_NAME": "localhost",
+            "SERVER_PORT": "8080",
+            "REQUEST_METHOD": "GET",
+            "SCRIPT_NAME": "/prefix/v1",
+            "PATH_INFO": "/foo",
+            "HTTP_ACCEPT": "application/json",
+        }
+        start_response = DummyStartResponse()
+        response = router(environ, start_response)
+
+        self.assertEqual(start_response.status, "200 OK")
+        self.assertEqual(json.loads(response[0]), {"test": "correct"})
+
+    def test_server_validation_works_with_script_name_and_hostname(self) -> None:
+        """Expect to find a match for http://example.com/prefix/foo."""
+        self._add_view(lambda *arg: {"test": "correct"})
+        # run request through router
+        router = Router(self.config.registry)
+        environ = {
+            "wsgi.url_scheme": "http",
+            "SERVER_NAME": "localhost",
+            "SERVER_PORT": "8080",
+            "REQUEST_METHOD": "GET",
+            "SCRIPT_NAME": "/prefix",
+            "PATH_INFO": "/foo",
+            "HTTP_HOST": "example.com",
+            "HTTP_ACCEPT": "application/json",
+        }
+        start_response = DummyStartResponse()
+        response = router(environ, start_response)
+
+        self.assertEqual(start_response.status, "200 OK")
+        self.assertEqual(json.loads(response[0]), {"test": "correct"})
+
+    def test_server_validation_fails_with_bad_hostname(self) -> None:
+        """Expect to fail for /prefix/v2/foo."""
+        self._add_view()
+        # run request through router
+        router = Router(self.config.registry)
+        environ = {
+            "wsgi.url_scheme": "http",
+            "SERVER_NAME": "localhost",
+            "SERVER_PORT": "8080",
+            "REQUEST_METHOD": "GET",
+            "SCRIPT_NAME": "/prefix/v2",
+            "PATH_INFO": "/foo",
+            "HTTP_HOST": "example.com",
+            "HTTP_ACCEPT": "application/json",
+        }
+        start_response = DummyStartResponse()
+        with self.assertLogs(level="ERROR") as cm:
+            response = router(environ, start_response)
+        self.assertEqual(start_response.status, "500 Internal Server Error")
+        self.assertEqual(
+            json.loads(response[0]),
+            [
+                {
+                    "exception": "ServerNotFound",
+                    "message": "Server not found for http://example.com/prefix/v2/foo",
+                }
+            ],
+        )
+        self.assertEqual(
+            cm.output,
+            [
+                "ERROR:pyramid_openapi3:Server not found for http://example.com/prefix/v2/foo"
+            ],
+        )
+
+
 class TestImproperAPISpecValidation(RequestValidationBase):  # noqa: D101
 
     openapi_spec = (
diff --git a/pyramid_openapi3/tests/test_wrappers.py b/pyramid_openapi3/tests/test_wrappers.py
index 5bae1ba..87d46b3 100644
--- a/pyramid_openapi3/tests/test_wrappers.py
+++ b/pyramid_openapi3/tests/test_wrappers.py
@@ -69,7 +69,7 @@ def test_relative_app_request() -> None:
     )
     assert openapi_request.host_url == "http://example.com"
     assert openapi_request.path == "/subpath/foo"
-    assert openapi_request.path_pattern == "/foo"
+    assert openapi_request.path_pattern == "/subpath/foo"
     assert openapi_request.method == "get"
     assert openapi_request.body == b""
     assert openapi_request.mimetype == "text/html"