From af1b5050abbde029474b1c45544e8b27b6e17138 Mon Sep 17 00:00:00 2001 From: Agustin Arce Date: Sun, 6 Oct 2024 03:18:03 +0000 Subject: [PATCH 1/6] style (testing): fix errors in gates --- falcon/testing/helpers.py | 3 ++- tests/asgi/test_testing_asgi.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/falcon/testing/helpers.py b/falcon/testing/helpers.py index 97392d57a..1b858a328 100644 --- a/falcon/testing/helpers.py +++ b/falcon/testing/helpers.py @@ -977,7 +977,7 @@ def create_scope( """ http_version = _fixup_http_version(http_version) - + raw_path = path.split('?')[0] path = uri.decode(path, unquote_plus=False) # NOTE(kgriffs): Handles both None and '' @@ -995,6 +995,7 @@ def create_scope( 'http_version': http_version, 'method': method.upper(), 'path': path, + 'raw_path': raw_path.encode(), 'query_string': query_string_bytes, } diff --git a/tests/asgi/test_testing_asgi.py b/tests/asgi/test_testing_asgi.py index 8ec041361..1cb182061 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -153,3 +153,14 @@ def test_immediate_disconnect(): with pytest.raises(ConnectionError): client.simulate_get('/', asgi_disconnect_ttl=0) + + +def test_create_scope_preserve_raw_path(): + path_no_queries = '/cache/http%3A%2F%2Ffalconframework.org/status' + scope = testing.create_scope(path=path_no_queries) + assert scope['raw_path'] == path_no_queries.encode() + path_with_queries = ( + '/cache/http%3A%2F%2Ffalconframework.org/status?param1=value1¶m2=value2' + ) + scope = testing.create_scope(path=path_with_queries) + assert scope['raw_path'] != path_with_queries.encode() From 728ff1145f3849fc1e3fb7f1fd3dce833a42ed43 Mon Sep 17 00:00:00 2001 From: Agustin Arce Date: Sun, 6 Oct 2024 21:13:52 +0000 Subject: [PATCH 2/6] docs (newsfragment): add newsfragment --- docs/_newsfragments/2262.newandimproved.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/_newsfragments/2262.newandimproved.rst diff --git a/docs/_newsfragments/2262.newandimproved.rst b/docs/_newsfragments/2262.newandimproved.rst new file mode 100644 index 000000000..c9f09c208 --- /dev/null +++ b/docs/_newsfragments/2262.newandimproved.rst @@ -0,0 +1 @@ +:py:func:`falcon.testing.helpers.create_scope` preserves the raw_path. This is to keep consistency with `#2159 `_ \ No newline at end of file From 44ecff33dc1936bd35a3293af59c8f7e61fdd8ac Mon Sep 17 00:00:00 2001 From: Agustin Arce Date: Sun, 6 Oct 2024 21:20:52 +0000 Subject: [PATCH 3/6] refactor (testing): refactor asgi test --- tests/asgi/test_testing_asgi.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/tests/asgi/test_testing_asgi.py b/tests/asgi/test_testing_asgi.py index 1cb182061..212f21072 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -155,12 +155,13 @@ def test_immediate_disconnect(): client.simulate_get('/', asgi_disconnect_ttl=0) -def test_create_scope_preserve_raw_path(): - path_no_queries = '/cache/http%3A%2F%2Ffalconframework.org/status' - scope = testing.create_scope(path=path_no_queries) - assert scope['raw_path'] == path_no_queries.encode() - path_with_queries = ( - '/cache/http%3A%2F%2Ffalconframework.org/status?param1=value1¶m2=value2' - ) - scope = testing.create_scope(path=path_with_queries) - assert scope['raw_path'] != path_with_queries.encode() +@pytest.mark.parametrize('path, expected', [ + ('/cache/http%3A%2F%2Ffalconframework.org/status', True), + ('/cache/http%3A%2F%2Ffalconframework.org/status?param1=value1¶m2=value2', False) +]) +def test_create_scope_preserve_raw_path(path: str, expected: bool): + scope = testing.create_scope(path=path) + if expected: + assert scope['raw_path'] == path.encode() + else: + assert scope['raw_path'] != path.encode() From b153614b221c702a639c83c92e60da1b97ca66fc Mon Sep 17 00:00:00 2001 From: Agustin Arce Date: Sun, 6 Oct 2024 21:46:23 +0000 Subject: [PATCH 4/6] test (test_recipes): update test for asgi scope --- tests/asgi/test_testing_asgi.py | 16 +++++++++++----- tests/test_recipes.py | 4 ++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/asgi/test_testing_asgi.py b/tests/asgi/test_testing_asgi.py index 212f21072..67db94cc2 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -155,11 +155,17 @@ def test_immediate_disconnect(): client.simulate_get('/', asgi_disconnect_ttl=0) -@pytest.mark.parametrize('path, expected', [ - ('/cache/http%3A%2F%2Ffalconframework.org/status', True), - ('/cache/http%3A%2F%2Ffalconframework.org/status?param1=value1¶m2=value2', False) -]) -def test_create_scope_preserve_raw_path(path: str, expected: bool): +@pytest.mark.parametrize( + 'path, expected', + [ + ('/cache/http%3A%2F%2Ffalconframework.org/status', True), + ( + '/cache/http%3A%2F%2Ffalconframework.org/status?param1=value1¶m2=value2', + False, + ), + ], +) +def test_create_scope_preserve_raw_path(path, expected): scope = testing.create_scope(path=path) if expected: assert scope['raw_path'] == path.encode() diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 9b2160087..af47ed3fd 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -132,12 +132,16 @@ def test_raw_path(self, asgi, app_kind, util): result1 = falcon.testing.simulate_get( recipe.app, url1, extras=self.path_extras(asgi, url1) ) + scope1 = falcon.testing.create_scope(url1) assert result1.status_code == 200 assert result1.json == {'url': 'http://falconframework.org'} + assert scope1['raw_path'] == url1.encode() url2 = '/cache/http%3A%2F%2Ffalconframework.org/status' result2 = falcon.testing.simulate_get( recipe.app, url2, extras=self.path_extras(asgi, url2) ) + scope2 = falcon.testing.create_scope(url2) assert result2.status_code == 200 assert result2.json == {'cached': True} + assert scope2['raw_path'] == url2.encode() From 7add3db5c711d097340a67c1cd6006c05c74837d Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Sun, 8 Dec 2024 21:52:56 +0100 Subject: [PATCH 5/6] docs(testing): clean up the docs, tests and notes before merging --- docs/_newsfragments/2262.newandimproved.rst | 6 +++++- tests/test_recipes.py | 22 ++++++--------------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/docs/_newsfragments/2262.newandimproved.rst b/docs/_newsfragments/2262.newandimproved.rst index c9f09c208..374c5b642 100644 --- a/docs/_newsfragments/2262.newandimproved.rst +++ b/docs/_newsfragments/2262.newandimproved.rst @@ -1 +1,5 @@ -:py:func:`falcon.testing.helpers.create_scope` preserves the raw_path. This is to keep consistency with `#2159 `_ \ No newline at end of file +Similar to :func:`~falcon.testing.create_environ`, +the :func:`~falcon.testing.create_scope` testing helper now preserves the raw URI path, +and propagates it to the created ASGI connection scope as the ``raw_path`` byte string +(according to the `ASGI specification +`__). diff --git a/tests/test_recipes.py b/tests/test_recipes.py index af47ed3fd..be2e9af9e 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -115,33 +115,23 @@ def test_optional_indent(self, util): class TestRawURLPath: - def path_extras(self, asgi, url): - if asgi: - return {'raw_path': url.encode()} - return None - def test_raw_path(self, asgi, app_kind, util): recipe = util.load_module( 'raw_url_path', parent_dir='examples/recipes', suffix=app_kind ) - # TODO(vytas): Improve TestClient to automatically add ASGI raw_path - # (as it does for WSGI): GH #2262. - url1 = '/cache/http%3A%2F%2Ffalconframework.org' - result1 = falcon.testing.simulate_get( - recipe.app, url1, extras=self.path_extras(asgi, url1) - ) - scope1 = falcon.testing.create_scope(url1) + result1 = falcon.testing.simulate_get(recipe.app, url1) assert result1.status_code == 200 assert result1.json == {'url': 'http://falconframework.org'} + + scope1 = falcon.testing.create_scope(url1) assert scope1['raw_path'] == url1.encode() url2 = '/cache/http%3A%2F%2Ffalconframework.org/status' - result2 = falcon.testing.simulate_get( - recipe.app, url2, extras=self.path_extras(asgi, url2) - ) - scope2 = falcon.testing.create_scope(url2) + result2 = falcon.testing.simulate_get(recipe.app, url2) assert result2.status_code == 200 assert result2.json == {'cached': True} + + scope2 = falcon.testing.create_scope(url2) assert scope2['raw_path'] == url2.encode() From a604eec8763762f7078992b78b56e6e5304f0559 Mon Sep 17 00:00:00 2001 From: Vytautas Liuolia Date: Sun, 8 Dec 2024 22:05:03 +0100 Subject: [PATCH 6/6] docs(testing): add a versionadded note, improve impl --- falcon/testing/helpers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/falcon/testing/helpers.py b/falcon/testing/helpers.py index 1b858a328..0fc95bc8f 100644 --- a/falcon/testing/helpers.py +++ b/falcon/testing/helpers.py @@ -974,10 +974,16 @@ def create_scope( iterable yielding a series of two-member (*name*, *value*) iterables. Each pair of items provides the name and value for the 'Set-Cookie' header. + + .. versionadded:: 4.1 + The raw (i.e., not URL-decoded) version of the provided `path` is now + preserved in the returned scope as the ``raw_path`` byte string. + According to the ASGI specification, ``raw_path`` **does not include** + any query string. """ http_version = _fixup_http_version(http_version) - raw_path = path.split('?')[0] + raw_path, _, _ = path.partition('?') path = uri.decode(path, unquote_plus=False) # NOTE(kgriffs): Handles both None and ''