Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(testing): add raw_path to ASGI scope #2331

Merged
merged 12 commits into from
Dec 8, 2024
5 changes: 5 additions & 0 deletions docs/_newsfragments/2262.newandimproved.rst
Original file line number Diff line number Diff line change
@@ -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
<https://asgi.readthedocs.io/en/latest/specs/www.html#http-connection-scope>`__).
9 changes: 8 additions & 1 deletion falcon/testing/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ''
Expand All @@ -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,
}

Expand Down
18 changes: 18 additions & 0 deletions tests/asgi/test_testing_asgi.py
aarcex3 marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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&param2=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()
22 changes: 8 additions & 14 deletions tests/test_recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading