From: Tom Christie Date: Tue, 5 Dec 2023 15:36:05 +0000 (+0000) Subject: Moving test cases into 'test_url.py' (#2982) X-Git-Tag: 0.26.0~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5b5f6d8e172910371b5901f6e21717097274c513;p=thirdparty%2Fhttpx.git Moving test cases into 'test_url.py' (#2982) * Moving test cases into 'test_url.py' * Move test_url to test_basic_url * Linting * Move TypeError test case. Move basic httpx.URL cases. * Linting * Merge invalid URL cases * Move percent encoding test cases * Move remaining test cases * Linting * Add missing test cases --- diff --git a/tests/models/test_url.py b/tests/models/test_url.py index 7e8307d3..34250e38 100644 --- a/tests/models/test_url.py +++ b/tests/models/test_url.py @@ -2,8 +2,26 @@ import pytest import httpx +# Tests for `httpx.URL` instantiation and property accessors. -def test_url(): + +def test_basic_url(): + url = httpx.URL("https://www.example.com/") + + assert url.scheme == "https" + assert url.userinfo == b"" + assert url.netloc == b"www.example.com" + assert url.host == "www.example.com" + assert url.port is None + assert url.path == "/" + assert url.query == b"" + assert url.fragment == "" + + assert str(url) == "https://www.example.com/" + assert repr(url) == "URL('https://www.example.com/')" + + +def test_complete_url(): url = httpx.URL("https://example.org:123/path/to/somewhere?abc=123#anchor") assert url.scheme == "https" assert url.host == "example.org" @@ -12,14 +30,12 @@ def test_url(): assert url.query == b"abc=123" assert url.raw_path == b"/path/to/somewhere?abc=123" assert url.fragment == "anchor" + + assert str(url) == "https://example.org:123/path/to/somewhere?abc=123#anchor" assert ( repr(url) == "URL('https://example.org:123/path/to/somewhere?abc=123#anchor')" ) - new = url.copy_with(scheme="http", port=None) - assert new == httpx.URL("http://example.org/path/to/somewhere?abc=123#anchor") - assert new.scheme == "http" - def test_url_with_empty_query(): """ @@ -37,6 +53,50 @@ def test_url_with_empty_query(): assert url.raw_path == b"/path?" +def test_url_no_scheme(): + url = httpx.URL("://example.com") + assert url.scheme == "" + assert url.host == "example.com" + assert url.path == "/" + + +def test_url_no_authority(): + url = httpx.URL("http://") + assert url.scheme == "http" + assert url.host == "" + assert url.path == "/" + + +# Tests for percent encoding across path, query, and fragment... + + +def test_path_percent_encoding(): + # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS + url = httpx.URL("https://example.com/!$&'()*+,;= abc ABC 123 :/[]@") + assert url.raw_path == b"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@" + assert url.path == "/!$&'()*+,;= abc ABC 123 :/[]@" + assert url.query == b"" + assert url.fragment == "" + + +def test_query_percent_encoding(): + # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS + url = httpx.URL("https://example.com/?!$&'()*+,;= abc ABC 123 :/[]@" + "?") + assert url.raw_path == b"/?!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" + assert url.path == "/" + assert url.query == b"!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" + assert url.fragment == "" + + +def test_fragment_percent_encoding(): + # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS + url = httpx.URL("https://example.com/#!$&'()*+,;= abc ABC 123 :/[]@" + "?#") + assert url.raw_path == b"/" + assert url.path == "/" + assert url.query == b"" + assert url.fragment == "!$&'()*+,;= abc ABC 123 :/[]@?#" + + def test_url_query_encoding(): """ URL query parameters should use '%20' to encoding spaces, @@ -60,17 +120,102 @@ def test_url_with_url_encoded_path(): assert url.raw_path == b"/path%20to%20somewhere" -def test_url_raw_compatibility(): - url = httpx.URL("https://www.example.com/path") - scheme, host, port, raw_path = url.raw +def test_url_params(): + url = httpx.URL("https://example.org:123/path/to/somewhere", params={"a": "123"}) + assert str(url) == "https://example.org:123/path/to/somewhere?a=123" + assert url.params == httpx.QueryParams({"a": "123"}) - assert scheme == b"https" - assert host == b"www.example.com" - assert port is None - assert raw_path == b"/path" + url = httpx.URL( + "https://example.org:123/path/to/somewhere?b=456", params={"a": "123"} + ) + assert str(url) == "https://example.org:123/path/to/somewhere?a=123" + assert url.params == httpx.QueryParams({"a": "123"}) + + +# Tests for different host types + + +def test_url_valid_host(): + url = httpx.URL("https://example.com/") + assert url.host == "example.com" + + +def test_url_normalized_host(): + url = httpx.URL("https://EXAMPLE.com/") + assert url.host == "example.com" + + +def test_url_ipv4_like_host(): + """rare host names used to quality as IPv4""" + url = httpx.URL("https://023b76x43144/") + assert url.host == "023b76x43144" + + +# Tests for different port types + + +def test_url_valid_port(): + url = httpx.URL("https://example.com:123/") + assert url.port == 123 + + +def test_url_normalized_port(): + # If the port matches the scheme default it is normalized to None. + url = httpx.URL("https://example.com:443/") + assert url.port is None + + +def test_url_invalid_port(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://example.com:abc/") + assert str(exc.value) == "Invalid port: 'abc'" + + +# Tests for path handling -def test_url_invalid(): +def test_url_normalized_path(): + url = httpx.URL("https://example.com/abc/def/../ghi/./jkl") + assert url.path == "/abc/ghi/jkl" + + +def test_url_escaped_path(): + url = httpx.URL("https://example.com/ /🌟/") + assert url.raw_path == b"/%20/%F0%9F%8C%9F/" + + +def test_url_leading_dot_prefix_on_absolute_url(): + url = httpx.URL("https://example.com/../abc") + assert url.path == "/abc" + + +def test_url_leading_dot_prefix_on_relative_url(): + url = httpx.URL("../abc") + assert url.path == "../abc" + + +# Tests for optional percent encoding + + +def test_param_requires_encoding(): + url = httpx.URL("http://webservice", params={"u": "with spaces"}) + assert str(url) == "http://webservice?u=with%20spaces" + + +def test_param_does_not_require_encoding(): + url = httpx.URL("http://webservice", params={"u": "with%20spaces"}) + assert str(url) == "http://webservice?u=with%20spaces" + + +def test_param_with_existing_escape_requires_encoding(): + url = httpx.URL("http://webservice", params={"u": "http://example.com?q=foo%2Fa"}) + assert str(url) == "http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa" + + +# Tests for invalid URLs + + +def test_url_invalid_hostname(): """ Ensure that invalid URLs raise an `httpx.InvalidURL` exception. """ @@ -78,10 +223,94 @@ def test_url_invalid(): httpx.URL("https://😇/") +def test_url_excessively_long_url(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://www.example.com/" + "x" * 100_000) + assert str(exc.value) == "URL too long" + + +def test_url_excessively_long_component(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://www.example.com", path="/" + "x" * 100_000) + assert str(exc.value) == "URL component 'path' too long" + + +def test_url_non_printing_character_in_url(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://www.example.com/\n") + assert str(exc.value) == "Invalid non-printable ASCII character in URL" + + +def test_url_non_printing_character_in_component(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://www.example.com", path="/\n") + assert ( + str(exc.value) + == "Invalid non-printable ASCII character in URL component 'path'" + ) + + +# Test for url components + + +def test_url_with_components(): + url = httpx.URL(scheme="https", host="www.example.com", path="/") + + assert url.scheme == "https" + assert url.userinfo == b"" + assert url.host == "www.example.com" + assert url.port is None + assert url.path == "/" + assert url.query == b"" + assert url.fragment == "" + + assert str(url) == "https://www.example.com/" + + +def test_urlparse_with_invalid_component(): + with pytest.raises(TypeError) as exc: + httpx.URL(scheme="https", host="www.example.com", incorrect="/") + assert str(exc.value) == "'incorrect' is an invalid keyword argument for URL()" + + +def test_urlparse_with_invalid_scheme(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL(scheme="~", host="www.example.com", path="/") + assert str(exc.value) == "Invalid URL component 'scheme'" + + +def test_urlparse_with_invalid_path(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL(scheme="https", host="www.example.com", path="abc") + assert str(exc.value) == "For absolute URLs, path must be empty or begin with '/'" + + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL(path="//abc") + assert ( + str(exc.value) + == "URLs with no authority component cannot have a path starting with '//'" + ) + + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL(path=":abc") + assert ( + str(exc.value) + == "URLs with no scheme component cannot have a path starting with ':'" + ) + + +def test_url_with_relative_path(): + # This path would be invalid for an absolute URL, but is valid as a relative URL. + url = httpx.URL(path="abc") + assert url.path == "abc" + + +# Tests for `httpx.URL` python built-in operators. + + def test_url_eq_str(): """ - Ensure that `httpx.URL` supports the equality operator, - and can be compared against plain strings. + Ensure that `httpx.URL` supports the equality operator. """ url = httpx.URL("https://example.org:123/path/to/somewhere?abc=123#anchor") assert url == "https://example.org:123/path/to/somewhere?abc=123#anchor" @@ -102,6 +331,9 @@ def test_url_set(): assert all(url in urls for url in url_set) +# Tests for TypeErrors when instantiating `httpx.URL`. + + def test_url_invalid_type(): """ Ensure that invalid types on `httpx.URL()` raise a `TypeError`. @@ -114,19 +346,10 @@ def test_url_invalid_type(): httpx.URL(ExternalURLClass()) # type: ignore -# Tests for `QueryParams`. - - -def test_url_params(): - url = httpx.URL("https://example.org:123/path/to/somewhere", params={"a": "123"}) - assert str(url) == "https://example.org:123/path/to/somewhere?a=123" - assert url.params == httpx.QueryParams({"a": "123"}) - - url = httpx.URL( - "https://example.org:123/path/to/somewhere?b=456", params={"a": "123"} - ) - assert str(url) == "https://example.org:123/path/to/somewhere?a=123" - assert url.params == httpx.QueryParams({"a": "123"}) +def test_url_with_invalid_component(): + with pytest.raises(TypeError) as exc: + httpx.URL(scheme="https", host="www.example.com", incorrect="/") + assert str(exc.value) == "'incorrect' is an invalid keyword argument for URL()" # Tests for `URL.join()`. @@ -221,6 +444,23 @@ def test_resolution_error_1833(): # Tests for `URL.copy_with()`. +def test_copy_with(): + url = httpx.URL("https://www.example.com/") + assert str(url) == "https://www.example.com/" + + url = url.copy_with() + assert str(url) == "https://www.example.com/" + + url = url.copy_with(scheme="http") + assert str(url) == "http://www.example.com/" + + url = url.copy_with(netloc=b"example.com") + assert str(url) == "http://example.com/" + + url = url.copy_with(path="/abc") + assert str(url) == "http://example.com/abc" + + def test_url_copywith_authority_subcomponents(): copy_with_kwargs = { "username": "username", @@ -425,6 +665,36 @@ def test_idna_url(given, idna, host, raw_host, scheme, port): assert url.port == port +def test_url_unescaped_idna_host(): + url = httpx.URL("https://中国.icom.museum/") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + +def test_url_escaped_idna_host(): + url = httpx.URL("https://xn--fiqs8s.icom.museum/") + assert url.raw_host == b"xn--fiqs8s.icom.museum" + + +def test_url_invalid_idna_host(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://☃.com/") + assert str(exc.value) == "Invalid IDNA hostname: '☃.com'" + + +# Tests for IPv4 hostname support. + + +def test_url_valid_ipv4(): + url = httpx.URL("https://1.2.3.4/") + assert url.host == "1.2.3.4" + + +def test_url_invalid_ipv4(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://999.999.999.999/") + assert str(exc.value) == "Invalid IPv4 address: '999.999.999.999'" + + # Tests for IPv6 hostname support. @@ -435,6 +705,17 @@ def test_ipv6_url(): assert url.netloc == b"[::ffff:192.168.0.1]:5678" +def test_url_valid_ipv6(): + url = httpx.URL("https://[2001:db8::ff00:42:8329]/") + assert url.host == "2001:db8::ff00:42:8329" + + +def test_url_invalid_ipv6(): + with pytest.raises(httpx.InvalidURL) as exc: + httpx.URL("https://[2001]/") + assert str(exc.value) == "Invalid IPv6 address: '[2001]'" + + @pytest.mark.parametrize("host", ["[::ffff:192.168.0.1]", "::ffff:192.168.0.1"]) def test_ipv6_url_from_raw_url(host): url = httpx.URL(scheme="https", host=host, port=443, path="/") @@ -459,3 +740,19 @@ def test_ipv6_url_copy_with_host(url_str, new_host): assert url.host == "::ffff:192.168.0.1" assert url.netloc == b"[::ffff:192.168.0.1]:1234" assert str(url) == "http://[::ffff:192.168.0.1]:1234" + + +# Test for deprecated API + + +def test_url_raw_compatibility(): + """ + Test case for the (to-be-deprecated) `url.raw` accessor. + """ + url = httpx.URL("https://www.example.com/path") + scheme, host, port, raw_path = url.raw + + assert scheme == b"https" + assert host == b"www.example.com" + assert port is None + assert raw_path == b"/path" diff --git a/tests/test_urlparse.py b/tests/test_urlparse.py deleted file mode 100644 index b03291b4..00000000 --- a/tests/test_urlparse.py +++ /dev/null @@ -1,285 +0,0 @@ -import pytest - -import httpx - - -def test_urlparse(): - url = httpx.URL("https://www.example.com/") - - assert url.scheme == "https" - assert url.userinfo == b"" - assert url.netloc == b"www.example.com" - assert url.host == "www.example.com" - assert url.port is None - assert url.path == "/" - assert url.query == b"" - assert url.fragment == "" - - assert str(url) == "https://www.example.com/" - - -def test_urlparse_no_scheme(): - url = httpx.URL("://example.com") - assert url.scheme == "" - assert url.host == "example.com" - assert url.path == "/" - - -def test_urlparse_no_authority(): - url = httpx.URL("http://") - assert url.scheme == "http" - assert url.host == "" - assert url.path == "/" - - -# Tests for different host types - - -def test_urlparse_valid_host(): - url = httpx.URL("https://example.com/") - assert url.host == "example.com" - - -def test_urlparse_normalized_host(): - url = httpx.URL("https://EXAMPLE.com/") - assert url.host == "example.com" - - -def test_urlparse_ipv4_like_host(): - """rare host names used to quality as IPv4""" - url = httpx.URL("https://023b76x43144/") - assert url.host == "023b76x43144" - - -def test_urlparse_valid_ipv4(): - url = httpx.URL("https://1.2.3.4/") - assert url.host == "1.2.3.4" - - -def test_urlparse_invalid_ipv4(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://999.999.999.999/") - assert str(exc.value) == "Invalid IPv4 address: '999.999.999.999'" - - -def test_urlparse_valid_ipv6(): - url = httpx.URL("https://[2001:db8::ff00:42:8329]/") - assert url.host == "2001:db8::ff00:42:8329" - - -def test_urlparse_invalid_ipv6(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://[2001]/") - assert str(exc.value) == "Invalid IPv6 address: '[2001]'" - - -def test_urlparse_unescaped_idna_host(): - url = httpx.URL("https://中国.icom.museum/") - assert url.raw_host == b"xn--fiqs8s.icom.museum" - - -def test_urlparse_escaped_idna_host(): - url = httpx.URL("https://xn--fiqs8s.icom.museum/") - assert url.raw_host == b"xn--fiqs8s.icom.museum" - - -def test_urlparse_invalid_idna_host(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://☃.com/") - assert str(exc.value) == "Invalid IDNA hostname: '☃.com'" - - -# Tests for different port types - - -def test_urlparse_valid_port(): - url = httpx.URL("https://example.com:123/") - assert url.port == 123 - - -def test_urlparse_normalized_port(): - # If the port matches the scheme default it is normalized to None. - url = httpx.URL("https://example.com:443/") - assert url.port is None - - -def test_urlparse_invalid_port(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://example.com:abc/") - assert str(exc.value) == "Invalid port: 'abc'" - - -# Tests for path handling - - -def test_urlparse_normalized_path(): - url = httpx.URL("https://example.com/abc/def/../ghi/./jkl") - assert url.path == "/abc/ghi/jkl" - - -def test_urlparse_escaped_path(): - url = httpx.URL("https://example.com/ /🌟/") - assert url.raw_path == b"/%20/%F0%9F%8C%9F/" - - -def test_urlparse_leading_dot_prefix_on_absolute_url(): - url = httpx.URL("https://example.com/../abc") - assert url.path == "/abc" - - -def test_urlparse_leading_dot_prefix_on_relative_url(): - url = httpx.URL("../abc") - assert url.path == "../abc" - - -# Tests for optional percent encoding - - -def test_param_requires_encoding(): - url = httpx.URL("http://webservice", params={"u": "with spaces"}) - assert str(url) == "http://webservice?u=with%20spaces" - - -def test_param_does_not_require_encoding(): - url = httpx.URL("http://webservice", params={"u": "with%20spaces"}) - assert str(url) == "http://webservice?u=with%20spaces" - - -def test_param_with_existing_escape_requires_encoding(): - url = httpx.URL("http://webservice", params={"u": "http://example.com?q=foo%2Fa"}) - assert str(url) == "http://webservice?u=http%3A%2F%2Fexample.com%3Fq%3Dfoo%252Fa" - - -# Tests for invalid URLs - - -def test_urlparse_excessively_long_url(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://www.example.com/" + "x" * 100_000) - assert str(exc.value) == "URL too long" - - -def test_urlparse_excessively_long_component(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://www.example.com", path="/" + "x" * 100_000) - assert str(exc.value) == "URL component 'path' too long" - - -def test_urlparse_non_printing_character_in_url(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://www.example.com/\n") - assert str(exc.value) == "Invalid non-printable ASCII character in URL" - - -def test_urlparse_non_printing_character_in_component(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL("https://www.example.com", path="/\n") - assert ( - str(exc.value) - == "Invalid non-printable ASCII character in URL component 'path'" - ) - - -# Test for urlparse components - - -def test_urlparse_with_components(): - url = httpx.URL(scheme="https", host="www.example.com", path="/") - - assert url.scheme == "https" - assert url.userinfo == b"" - assert url.host == "www.example.com" - assert url.port is None - assert url.path == "/" - assert url.query == b"" - assert url.fragment == "" - - assert str(url) == "https://www.example.com/" - - -def test_urlparse_with_invalid_component(): - with pytest.raises(TypeError) as exc: - httpx.URL(scheme="https", host="www.example.com", incorrect="/") - assert str(exc.value) == "'incorrect' is an invalid keyword argument for URL()" - - -def test_urlparse_with_invalid_scheme(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL(scheme="~", host="www.example.com", path="/") - assert str(exc.value) == "Invalid URL component 'scheme'" - - -def test_urlparse_with_invalid_path(): - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL(scheme="https", host="www.example.com", path="abc") - assert str(exc.value) == "For absolute URLs, path must be empty or begin with '/'" - - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL(path="//abc") - assert ( - str(exc.value) - == "URLs with no authority component cannot have a path starting with '//'" - ) - - with pytest.raises(httpx.InvalidURL) as exc: - httpx.URL(path=":abc") - assert ( - str(exc.value) - == "URLs with no scheme component cannot have a path starting with ':'" - ) - - -def test_urlparse_with_relative_path(): - # This path would be invalid for an absolute URL, but is valid as a relative URL. - url = httpx.URL(path="abc") - assert url.path == "abc" - - -# Tests for accessing and modifying `urlparse` results. - - -def test_copy_with(): - url = httpx.URL("https://www.example.com/") - assert str(url) == "https://www.example.com/" - - url = url.copy_with() - assert str(url) == "https://www.example.com/" - - url = url.copy_with(scheme="http") - assert str(url) == "http://www.example.com/" - - url = url.copy_with(netloc=b"example.com") - assert str(url) == "http://example.com/" - - url = url.copy_with(path="/abc") - assert str(url) == "http://example.com/abc" - - -# Tests for percent encoding across path, query, and fragement... - - -def test_path_percent_encoding(): - # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS - url = httpx.URL("https://example.com/!$&'()*+,;= abc ABC 123 :/[]@") - assert url.raw_path == b"/!$&'()*+,;=%20abc%20ABC%20123%20:/[]@" - assert url.path == "/!$&'()*+,;= abc ABC 123 :/[]@" - assert url.query == b"" - assert url.fragment == "" - - -def test_query_percent_encoding(): - # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS - url = httpx.URL("https://example.com/?!$&'()*+,;= abc ABC 123 :/[]@" + "?") - assert url.raw_path == b"/?!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" - assert url.path == "/" - assert url.query == b"!$&'()*+,;=%20abc%20ABC%20123%20:%2F[]@?" - assert url.fragment == "" - - -def test_fragment_percent_encoding(): - # Test percent encoding for SUB_DELIMS ALPHA NUM and allowable GEN_DELIMS - url = httpx.URL("https://example.com/#!$&'()*+,;= abc ABC 123 :/[]@" + "?#") - assert url.raw_path == b"/" - assert url.path == "/" - assert url.query == b"" - assert url.fragment == "!$&'()*+,;= abc ABC 123 :/[]@?#"