From 11076b8fc78bb61707c271953783198f4b6a04f1 Mon Sep 17 00:00:00 2001 From: Agustin Arce <59893355+aarcex3@users.noreply.github.com> Date: Sun, 8 Dec 2024 18:12:29 -0300 Subject: [PATCH] feat(testing): add `raw_path` to ASGI scope (#2331) * style (testing): fix errors in gates * docs (newsfragment): add newsfragment * refactor (testing): refactor asgi test * test (test_recipes): update test for asgi scope * docs(testing): clean up the docs, tests and notes before merging * docs(testing): add a versionadded note, improve impl --------- Co-authored-by: Vytautas Liuolia --- docs/_newsfragments/2262.newandimproved.rst | 5 +++++ falcon/testing/helpers.py | 9 ++++++++- tests/asgi/test_testing_asgi.py | 18 +++++++++++++++++ tests/test_recipes.py | 22 ++++++++------------- 4 files changed, 39 insertions(+), 15 deletions(-) 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..374c5b642 --- /dev/null +++ b/docs/_newsfragments/2262.newandimproved.rst @@ -0,0 +1,5 @@ +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/falcon/testing/helpers.py b/falcon/testing/helpers.py index 97392d57a..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.partition('?') path = uri.decode(path, unquote_plus=False) # NOTE(kgriffs): Handles both None and '' @@ -995,6 +1001,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..67db94cc2 100644 --- a/tests/asgi/test_testing_asgi.py +++ b/tests/asgi/test_testing_asgi.py @@ -153,3 +153,21 @@ def test_immediate_disconnect(): with pytest.raises(ConnectionError): 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, expected): + scope = testing.create_scope(path=path) + if expected: + assert scope['raw_path'] == path.encode() + else: + assert scope['raw_path'] != path.encode() diff --git a/tests/test_recipes.py b/tests/test_recipes.py index 9b2160087..be2e9af9e 100644 --- a/tests/test_recipes.py +++ b/tests/test_recipes.py @@ -115,29 +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) - ) + 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) - ) + 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()