aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/yarl/tests
diff options
context:
space:
mode:
authorrekby <rekby@ydb.tech>2023-12-14 16:56:50 +0300
committerrekby <rekby@ydb.tech>2023-12-14 18:09:44 +0300
commitb2b2bb5997507072ca64548efe64447dd6395426 (patch)
treebbfbf77d11f1972c93ae4101fe561fd440d6ad6a /contrib/python/yarl/tests
parent8b8678a6a4f57c62e348cdad8afd3849011a5f11 (diff)
downloadydb-b2b2bb5997507072ca64548efe64447dd6395426.tar.gz
KIKIMR-19900 switch arcadia to python ydb sdk from contrib
Этот PR создан скриптом - для переключения зависимостей на python ydb sdk с версии внутри ydb на код, приезжающий через контриб. Код в обеих версиях одинаковый, так что поломок/изменения функционала на ожидается. На всякий случай посмотрите свои проекты и если будут возражения пишите сюда в issues или в тикет KIKIMR-19900. Если всё ок - шипните, для определённости. При отсутствии блокеров PR будет перегенерирован и влит с force-мёрджем в четверг, 14 декабря.
Diffstat (limited to 'contrib/python/yarl/tests')
-rw-r--r--contrib/python/yarl/tests/test_cache.py28
-rw-r--r--contrib/python/yarl/tests/test_cached_property.py45
-rw-r--r--contrib/python/yarl/tests/test_normalize_path.py34
-rw-r--r--contrib/python/yarl/tests/test_pickle.py23
-rw-r--r--contrib/python/yarl/tests/test_quoting.py450
-rw-r--r--contrib/python/yarl/tests/test_update_query.py366
-rw-r--r--contrib/python/yarl/tests/test_url.py1732
-rw-r--r--contrib/python/yarl/tests/test_url_build.py259
-rw-r--r--contrib/python/yarl/tests/test_url_cmp_and_hash.py88
-rw-r--r--contrib/python/yarl/tests/test_url_parsing.py582
-rw-r--r--contrib/python/yarl/tests/test_url_query.py173
-rw-r--r--contrib/python/yarl/tests/test_url_update_netloc.py228
-rw-r--r--contrib/python/yarl/tests/ya.make24
13 files changed, 4032 insertions, 0 deletions
diff --git a/contrib/python/yarl/tests/test_cache.py b/contrib/python/yarl/tests/test_cache.py
new file mode 100644
index 0000000000..22141dd085
--- /dev/null
+++ b/contrib/python/yarl/tests/test_cache.py
@@ -0,0 +1,28 @@
+import yarl
+
+# Don't check the actual behavior but make sure that calls are allowed
+
+
+def teardown_module():
+ yarl.cache_configure()
+
+
+def test_cache_clear() -> None:
+ yarl.cache_clear()
+
+
+def test_cache_info() -> None:
+ info = yarl.cache_info()
+ assert info.keys() == {"idna_encode", "idna_decode"}
+
+
+def test_cache_configure_default() -> None:
+ yarl.cache_configure()
+
+
+def test_cache_configure_None() -> None:
+ yarl.cache_configure(idna_encode_size=None, idna_decode_size=None)
+
+
+def test_cache_configure_explicit() -> None:
+ yarl.cache_configure(idna_encode_size=128, idna_decode_size=128)
diff --git a/contrib/python/yarl/tests/test_cached_property.py b/contrib/python/yarl/tests/test_cached_property.py
new file mode 100644
index 0000000000..5dcb5ece23
--- /dev/null
+++ b/contrib/python/yarl/tests/test_cached_property.py
@@ -0,0 +1,45 @@
+import pytest
+
+from yarl._url import cached_property
+
+
+def test_reify():
+ class A:
+ def __init__(self):
+ self._cache = {}
+
+ @cached_property
+ def prop(self):
+ return 1
+
+ a = A()
+ assert 1 == a.prop
+
+
+def test_reify_class():
+ class A:
+ def __init__(self):
+ self._cache = {}
+
+ @cached_property
+ def prop(self):
+ """Docstring."""
+ return 1
+
+ assert isinstance(A.prop, cached_property)
+ assert "Docstring." == A.prop.__doc__
+
+
+def test_reify_assignment():
+ class A:
+ def __init__(self):
+ self._cache = {}
+
+ @cached_property
+ def prop(self):
+ return 1
+
+ a = A()
+
+ with pytest.raises(AttributeError):
+ a.prop = 123
diff --git a/contrib/python/yarl/tests/test_normalize_path.py b/contrib/python/yarl/tests/test_normalize_path.py
new file mode 100644
index 0000000000..defc4d8dd7
--- /dev/null
+++ b/contrib/python/yarl/tests/test_normalize_path.py
@@ -0,0 +1,34 @@
+import pytest
+
+from yarl import URL
+
+PATHS = [
+ # No dots
+ ("", ""),
+ ("/", "/"),
+ ("//", "//"),
+ ("///", "///"),
+ # Single-dot
+ ("path/to", "path/to"),
+ ("././path/to", "path/to"),
+ ("path/./to", "path/to"),
+ ("path/././to", "path/to"),
+ ("path/to/.", "path/to/"),
+ ("path/to/./.", "path/to/"),
+ # Double-dots
+ ("../path/to", "path/to"),
+ ("path/../to", "to"),
+ ("path/../../to", "to"),
+ # absolute path root / is maintained; tests based on two
+ # tests from web-platform-tests project's urltestdata.json
+ ("/foo/../../../ton", "/ton"),
+ ("/foo/../../../..bar", "/..bar"),
+ # Non-ASCII characters
+ ("μονοπάτι/../../να/ᴜɴɪ/ᴄᴏᴅᴇ", "να/ᴜɴɪ/ᴄᴏᴅᴇ"),
+ ("μονοπάτι/../../να/𝕦𝕟𝕚/𝕔𝕠𝕕𝕖/.", "να/𝕦𝕟𝕚/𝕔𝕠𝕕𝕖/"),
+]
+
+
+@pytest.mark.parametrize("original,expected", PATHS)
+def test__normalize_path(original, expected):
+ assert URL._normalize_path(original) == expected
diff --git a/contrib/python/yarl/tests/test_pickle.py b/contrib/python/yarl/tests/test_pickle.py
new file mode 100644
index 0000000000..a1f29ab68c
--- /dev/null
+++ b/contrib/python/yarl/tests/test_pickle.py
@@ -0,0 +1,23 @@
+import pickle
+
+from yarl import URL
+
+# serialize
+
+
+def test_pickle():
+ u1 = URL("test")
+ hash(u1)
+ v = pickle.dumps(u1)
+ u2 = pickle.loads(v)
+ assert u1._cache
+ assert not u2._cache
+ assert hash(u1) == hash(u2)
+
+
+def test_default_style_state():
+ u = URL("test")
+ hash(u)
+ u.__setstate__((None, {"_val": "test", "_strict": False, "_cache": {"hash": 1}}))
+ assert not u._cache
+ assert u._val == "test"
diff --git a/contrib/python/yarl/tests/test_quoting.py b/contrib/python/yarl/tests/test_quoting.py
new file mode 100644
index 0000000000..7ebc0f9b04
--- /dev/null
+++ b/contrib/python/yarl/tests/test_quoting.py
@@ -0,0 +1,450 @@
+import pytest
+
+from yarl._quoting import NO_EXTENSIONS
+from yarl._quoting_py import _Quoter as _PyQuoter
+from yarl._quoting_py import _Unquoter as _PyUnquoter
+
+if not NO_EXTENSIONS:
+ from yarl._quoting_c import _Quoter as _CQuoter
+ from yarl._quoting_c import _Unquoter as _CUnquoter
+
+ @pytest.fixture(params=[_PyQuoter, _CQuoter], ids=["py_quoter", "c_quoter"])
+ def quoter(request):
+ return request.param
+
+ @pytest.fixture(params=[_PyUnquoter, _CUnquoter], ids=["py_unquoter", "c_unquoter"])
+ def unquoter(request):
+ return request.param
+
+else:
+
+ @pytest.fixture(params=[_PyQuoter], ids=["py_quoter"])
+ def quoter(request):
+ return request.param
+
+ @pytest.fixture(params=[_PyUnquoter], ids=["py_unquoter"])
+ def unquoter(request):
+ return request.param
+
+
+def hexescape(char):
+ """Escape char as RFC 2396 specifies"""
+ hex_repr = hex(ord(char))[2:].upper()
+ if len(hex_repr) == 1:
+ hex_repr = "0%s" % hex_repr
+ return "%" + hex_repr
+
+
+def test_quote_not_allowed_non_strict(quoter):
+ assert quoter()("%HH") == "%25HH"
+
+
+def test_quote_unfinished_tail_percent_non_strict(quoter):
+ assert quoter()("%") == "%25"
+
+
+def test_quote_unfinished_tail_digit_non_strict(quoter):
+ assert quoter()("%2") == "%252"
+
+
+def test_quote_unfinished_tail_safe_non_strict(quoter):
+ assert quoter()("%x") == "%25x"
+
+
+def test_quote_unfinished_tail_unsafe_non_strict(quoter):
+ assert quoter()("%#") == "%25%23"
+
+
+def test_quote_unfinished_tail_non_ascii_non_strict(quoter):
+ assert quoter()("%ß") == "%25%C3%9F"
+
+
+def test_quote_unfinished_tail_non_ascii2_non_strict(quoter):
+ assert quoter()("%€") == "%25%E2%82%AC"
+
+
+def test_quote_unfinished_tail_non_ascii3_non_strict(quoter):
+ assert quoter()("%🐍") == "%25%F0%9F%90%8D"
+
+
+def test_quote_from_bytes(quoter):
+ assert quoter()("archaeological arcana") == "archaeological%20arcana"
+ assert quoter()("") == ""
+
+
+def test_quote_ignore_broken_unicode(quoter):
+ s = quoter()(
+ "j\u001a\udcf4q\udcda/\udc97g\udcee\udccb\u000ch\udccb"
+ "\u0018\udce4v\u001b\udce2\udcce\udccecom/y\udccepj\u0016"
+ )
+
+ assert s == "j%1Aq%2Fg%0Ch%18v%1Bcom%2Fypj%16"
+ assert quoter()(s) == s
+
+
+def test_unquote_to_bytes(unquoter):
+ assert unquoter()("abc%20def") == "abc def"
+ assert unquoter()("") == ""
+
+
+def test_never_quote(quoter):
+ # Make sure quote() does not quote letters, digits, and "_,.-~"
+ do_not_quote = (
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "_.-~"
+ )
+ assert quoter()(do_not_quote) == do_not_quote
+ assert quoter(qs=True)(do_not_quote) == do_not_quote
+
+
+def test_safe(quoter):
+ # Test setting 'safe' parameter does what it should do
+ quote_by_default = "<>"
+ assert quoter(safe=quote_by_default)(quote_by_default) == quote_by_default
+
+ ret = quoter(safe=quote_by_default, qs=True)(quote_by_default)
+ assert ret == quote_by_default
+
+
+_SHOULD_QUOTE = [chr(num) for num in range(32)]
+_SHOULD_QUOTE.append(r'<>#"{}|\^[]`')
+_SHOULD_QUOTE.append(chr(127)) # For 0x7F
+SHOULD_QUOTE = "".join(_SHOULD_QUOTE)
+
+
+@pytest.mark.parametrize("char", SHOULD_QUOTE)
+def test_default_quoting(char, quoter):
+ # Make sure all characters that should be quoted are by default sans
+ # space (separate test for that).
+ result = quoter()(char)
+ assert hexescape(char) == result
+ result = quoter(qs=True)(char)
+ assert hexescape(char) == result
+
+
+# TODO: should it encode percent?
+def test_default_quoting_percent(quoter):
+ result = quoter()("%25")
+ assert "%25" == result
+ result = quoter(qs=True)("%25")
+ assert "%25" == result
+ result = quoter(requote=False)("%25")
+ assert "%2525" == result
+
+
+def test_default_quoting_partial(quoter):
+ partial_quote = "ab[]cd"
+ expected = "ab%5B%5Dcd"
+ result = quoter()(partial_quote)
+ assert expected == result
+ result = quoter(qs=True)(partial_quote)
+ assert expected == result
+
+
+def test_quoting_space(quoter):
+ # Make sure quote() and quote_plus() handle spaces as specified in
+ # their unique way
+ result = quoter()(" ")
+ assert result == hexescape(" ")
+ result = quoter(qs=True)(" ")
+ assert result == "+"
+
+ given = "a b cd e f"
+ expect = given.replace(" ", hexescape(" "))
+ result = quoter()(given)
+ assert expect == result
+ expect = given.replace(" ", "+")
+ result = quoter(qs=True)(given)
+ assert expect == result
+
+
+def test_quoting_plus(quoter):
+ assert quoter(qs=False)("alpha+beta gamma") == "alpha+beta%20gamma"
+ assert quoter(qs=True)("alpha+beta gamma") == "alpha%2Bbeta+gamma"
+ assert quoter(safe="+", qs=True)("alpha+beta gamma") == "alpha+beta+gamma"
+
+
+def test_quote_with_unicode(quoter):
+ # Characters in Latin-1 range, encoded by default in UTF-8
+ given = "\u00a2\u00d8ab\u00ff"
+ expect = "%C2%A2%C3%98ab%C3%BF"
+ result = quoter()(given)
+ assert expect == result
+ # Characters in BMP, encoded by default in UTF-8
+ given = "\u6f22\u5b57" # "Kanji"
+ expect = "%E6%BC%A2%E5%AD%97"
+ result = quoter()(given)
+ assert expect == result
+
+
+def test_quote_plus_with_unicode(quoter):
+ # Characters in Latin-1 range, encoded by default in UTF-8
+ given = "\u00a2\u00d8ab\u00ff"
+ expect = "%C2%A2%C3%98ab%C3%BF"
+ result = quoter(qs=True)(given)
+ assert expect == result
+ # Characters in BMP, encoded by default in UTF-8
+ given = "\u6f22\u5b57" # "Kanji"
+ expect = "%E6%BC%A2%E5%AD%97"
+ result = quoter(qs=True)(given)
+ assert expect == result
+
+
+@pytest.mark.parametrize("num", list(range(128)))
+def test_unquoting(num, unquoter):
+ # Make sure unquoting of all ASCII values works
+ given = hexescape(chr(num))
+ expect = chr(num)
+ result = unquoter()(given)
+ assert expect == result
+ if expect not in "+=&;":
+ result = unquoter(qs=True)(given)
+ assert expect == result
+
+
+# Expected value should be the same as given.
+# See https://url.spec.whatwg.org/#percent-encoded-bytes
+@pytest.mark.parametrize(
+ ("input", "expected"),
+ [
+ ("%", "%"),
+ ("%2", "%2"),
+ ("%x", "%x"),
+ ("%€", "%€"),
+ ("%2x", "%2x"),
+ ("%2 ", "%2 "),
+ ("% 2", "% 2"),
+ ("%xa", "%xa"),
+ ("%%", "%%"),
+ ("%%3f", "%?"),
+ ("%2%", "%2%"),
+ ("%2%3f", "%2?"),
+ ("%x%3f", "%x?"),
+ ("%€%3f", "%€?"),
+ ],
+)
+def test_unquoting_bad_percent_escapes(unquoter, input, expected):
+ assert unquoter()(input) == expected
+
+
+@pytest.mark.xfail
+# FIXME: After conversion to bytes, should not cause UTF-8 decode fail.
+# See https://url.spec.whatwg.org/#percent-encoded-bytes
+def test_unquoting_invalid_utf8_sequence(unquoter):
+ with pytest.raises(ValueError):
+ unquoter()("%AB")
+ with pytest.raises(ValueError):
+ unquoter()("%AB%AB")
+
+
+def test_unquoting_mixed_case_percent_escapes(unquoter):
+ expected = "𝕦"
+ assert expected == unquoter()("%F0%9D%95%A6")
+ assert expected == unquoter()("%F0%9d%95%a6")
+ assert expected == unquoter()("%f0%9D%95%a6")
+ assert expected == unquoter()("%f0%9d%95%a6")
+
+
+def test_unquoting_parts(unquoter):
+ # Make sure unquoting works when have non-quoted characters
+ # interspersed
+ given = "ab" + hexescape("c") + "d"
+ expect = "abcd"
+ result = unquoter()(given)
+ assert expect == result
+ result = unquoter(qs=True)(given)
+ assert expect == result
+
+
+def test_quote_None(quoter):
+ assert quoter()(None) is None
+
+
+def test_unquote_None(unquoter):
+ assert unquoter()(None) is None
+
+
+def test_quote_empty_string(quoter):
+ assert quoter()("") == ""
+
+
+def test_unquote_empty_string(unquoter):
+ assert unquoter()("") == ""
+
+
+def test_quote_bad_types(quoter):
+ with pytest.raises(TypeError):
+ quoter()(123)
+
+
+def test_unquote_bad_types(unquoter):
+ with pytest.raises(TypeError):
+ unquoter()(123)
+
+
+def test_quote_lowercase(quoter):
+ assert quoter()("%d1%84") == "%D1%84"
+
+
+def test_quote_unquoted(quoter):
+ assert quoter()("%41") == "A"
+
+
+def test_quote_space(quoter):
+ assert quoter()(" ") == "%20" # NULL
+
+
+# test to see if this would work to fix
+# coverage on this file.
+def test_quote_percent_last_character(quoter):
+ # % is last character in this case.
+ assert quoter()("%") == "%25"
+
+
+def test_unquote_unsafe(unquoter):
+ assert unquoter(unsafe="@")("%40") == "%40"
+
+
+def test_unquote_unsafe2(unquoter):
+ assert unquoter(unsafe="@")("%40abc") == "%40abc"
+
+
+def test_unquote_unsafe3(unquoter):
+ assert unquoter(qs=True)("a%2Bb=?%3D%2B%26") == "a%2Bb=?%3D%2B%26"
+
+
+def test_unquote_unsafe4(unquoter):
+ assert unquoter(unsafe="@")("a@b") == "a%40b"
+
+
+@pytest.mark.parametrize(
+ ("input", "expected"),
+ [
+ ("%e2%82", "%e2%82"),
+ ("%e2%82ac", "%e2%82ac"),
+ ("%e2%82%f8", "%e2%82%f8"),
+ ("%e2%82%2b", "%e2%82+"),
+ ("%e2%82%e2%82%ac", "%e2%82€"),
+ ("%e2%82%e2%82", "%e2%82%e2%82"),
+ ],
+)
+def test_unquote_non_utf8(unquoter, input, expected):
+ assert unquoter()(input) == expected
+
+
+def test_unquote_unsafe_non_utf8(unquoter):
+ assert unquoter(unsafe="\n")("%e2%82%0a") == "%e2%82%0A"
+
+
+def test_unquote_plus_non_utf8(unquoter):
+ assert unquoter(qs=True)("%e2%82%2b") == "%e2%82%2B"
+
+
+def test_quote_non_ascii(quoter):
+ assert quoter()("%F8") == "%F8"
+
+
+def test_quote_non_ascii2(quoter):
+ assert quoter()("a%F8b") == "a%F8b"
+
+
+def test_quote_percent_percent_encoded(quoter):
+ assert quoter()("%%3f") == "%25%3F"
+
+
+def test_quote_percent_digit_percent_encoded(quoter):
+ assert quoter()("%2%3f") == "%252%3F"
+
+
+def test_quote_percent_safe_percent_encoded(quoter):
+ assert quoter()("%x%3f") == "%25x%3F"
+
+
+def test_quote_percent_unsafe_percent_encoded(quoter):
+ assert quoter()("%#%3f") == "%25%23%3F"
+
+
+def test_quote_percent_non_ascii_percent_encoded(quoter):
+ assert quoter()("%ß%3f") == "%25%C3%9F%3F"
+
+
+def test_quote_percent_non_ascii2_percent_encoded(quoter):
+ assert quoter()("%€%3f") == "%25%E2%82%AC%3F"
+
+
+def test_quote_percent_non_ascii3_percent_encoded(quoter):
+ assert quoter()("%🐍%3f") == "%25%F0%9F%90%8D%3F"
+
+
+class StrLike(str):
+ pass
+
+
+def test_quote_str_like(quoter):
+ assert quoter()(StrLike("abc")) == "abc"
+
+
+def test_unquote_str_like(unquoter):
+ assert unquoter()(StrLike("abc")) == "abc"
+
+
+def test_quote_sub_delims(quoter):
+ assert quoter()("!$&'()*+,;=") == "!$&'()*+,;="
+
+
+def test_requote_sub_delims(quoter):
+ assert quoter()("%21%24%26%27%28%29%2A%2B%2C%3B%3D") == "!$&'()*+,;="
+
+
+def test_unquoting_plus(unquoter):
+ assert unquoter(qs=False)("a+b") == "a+b"
+
+
+def test_unquote_plus_to_space(unquoter):
+ assert unquoter(qs=True)("a+b") == "a b"
+
+
+def test_unquote_plus_to_space_unsafe(unquoter):
+ assert unquoter(unsafe="+", qs=True)("a+b") == "a+b"
+
+
+def test_quote_qs_with_colon(quoter):
+ s = quoter(safe="=+&?/:@", qs=True)("next=http%3A//example.com/")
+ assert s == "next=http://example.com/"
+
+
+def test_quote_protected(quoter):
+ s = quoter(protected="/")("/path%2fto/three")
+ assert s == "/path%2Fto/three"
+
+
+def test_quote_fastpath_safe(quoter):
+ s1 = "/path/to"
+ s2 = quoter(safe="/")(s1)
+ assert s1 is s2
+
+
+def test_quote_fastpath_pct(quoter):
+ s1 = "abc%A0"
+ s2 = quoter()(s1)
+ assert s1 is s2
+
+
+def test_quote_very_large_string(quoter):
+ # more than 8 KiB
+ s = "abcфух%30%0a" * 1024
+ assert quoter()(s) == "abc%D1%84%D1%83%D1%850%0A" * 1024
+
+
+def test_space(quoter):
+ s = "% A"
+ assert quoter()(s) == "%25%20A"
+
+
+def test_quoter_path_with_plus(quoter):
+ s = "/test/x+y%2Bz/:+%2B/"
+ assert "/test/x+y%2Bz/:+%2B/" == quoter(safe="@:", protected="/+")(s)
+
+
+def test_unquoter_path_with_plus(unquoter):
+ s = "/test/x+y%2Bz/:+%2B/"
+ assert "/test/x+y+z/:++/" == unquoter(unsafe="+")(s)
diff --git a/contrib/python/yarl/tests/test_update_query.py b/contrib/python/yarl/tests/test_update_query.py
new file mode 100644
index 0000000000..e47c468341
--- /dev/null
+++ b/contrib/python/yarl/tests/test_update_query.py
@@ -0,0 +1,366 @@
+import enum
+
+import pytest
+from multidict import MultiDict
+
+from yarl import URL
+
+# with_query
+
+
+def test_with_query():
+ url = URL("http://example.com")
+ assert str(url.with_query({"a": "1"})) == "http://example.com/?a=1"
+
+
+def test_update_query():
+ url = URL("http://example.com/")
+ assert str(url.update_query({"a": "1"})) == "http://example.com/?a=1"
+ assert str(URL("test").update_query(a=1)) == "test?a=1"
+
+ url = URL("http://example.com/?foo=bar")
+ expected_url = URL("http://example.com/?foo=bar&baz=foo")
+
+ assert url.update_query({"baz": "foo"}) == expected_url
+ assert url.update_query(baz="foo") == expected_url
+ assert url.update_query("baz=foo") == expected_url
+
+
+def test_update_query_with_args_and_kwargs():
+ url = URL("http://example.com/")
+
+ with pytest.raises(ValueError):
+ url.update_query("a", foo="bar")
+
+
+def test_update_query_with_multiple_args():
+ url = URL("http://example.com/")
+
+ with pytest.raises(ValueError):
+ url.update_query("a", "b")
+
+
+def test_update_query_with_none_arg():
+ url = URL("http://example.com/?foo=bar&baz=foo")
+ expected_url = URL("http://example.com/")
+ assert url.update_query(None) == expected_url
+
+
+def test_update_query_with_empty_dict():
+ url = URL("http://example.com/?foo=bar&baz=foo")
+ assert url.update_query({}) == url
+
+
+def test_with_query_list_of_pairs():
+ url = URL("http://example.com")
+ assert str(url.with_query([("a", "1")])) == "http://example.com/?a=1"
+
+
+def test_with_query_list_non_pairs():
+ url = URL("http://example.com")
+ with pytest.raises(ValueError):
+ url.with_query(["a=1", "b=2", "c=3"])
+
+
+def test_with_query_kwargs():
+ url = URL("http://example.com")
+ q = url.with_query(query="1", query2="1").query
+ assert q == dict(query="1", query2="1")
+
+
+def test_with_query_kwargs_and_args_are_mutually_exclusive():
+ url = URL("http://example.com")
+ with pytest.raises(ValueError):
+ url.with_query({"a": "2", "b": "4"}, a="1")
+
+
+def test_with_query_only_single_arg_is_supported():
+ url = URL("http://example.com")
+ u1 = url.with_query(b=3)
+ u2 = URL("http://example.com/?b=3")
+ assert u1 == u2
+ with pytest.raises(ValueError):
+ url.with_query("a=1", "a=b")
+
+
+def test_with_query_empty_dict():
+ url = URL("http://example.com/?a=b")
+ new_url = url.with_query({})
+ assert new_url.query_string == ""
+ assert str(new_url) == "http://example.com/"
+
+
+def test_with_query_empty_str():
+ url = URL("http://example.com/?a=b")
+ assert str(url.with_query("")) == "http://example.com/"
+
+
+def test_with_query_empty_value():
+ url = URL("http://example.com/")
+ assert str(url.with_query({"a": ""})) == "http://example.com/?a="
+
+
+def test_with_query_str():
+ url = URL("http://example.com")
+ assert str(url.with_query("a=1&b=2")) == "http://example.com/?a=1&b=2"
+
+
+def test_with_query_str_non_ascii_and_spaces():
+ url = URL("http://example.com")
+ url2 = url.with_query("a=1 2&b=знач")
+ assert url2.raw_query_string == "a=1+2&b=%D0%B7%D0%BD%D0%B0%D1%87"
+ assert url2.query_string == "a=1 2&b=знач"
+
+
+def test_with_query_int():
+ url = URL("http://example.com")
+ assert url.with_query({"a": 1}) == URL("http://example.com/?a=1")
+
+
+def test_with_query_kwargs_int():
+ url = URL("http://example.com")
+ assert url.with_query(b=2) == URL("http://example.com/?b=2")
+
+
+def test_with_query_list_int():
+ url = URL("http://example.com")
+ assert str(url.with_query([("a", 1)])) == "http://example.com/?a=1"
+
+
+@pytest.mark.parametrize(
+ ("query", "expected"),
+ [
+ pytest.param({"a": []}, "", id="empty list"),
+ pytest.param({"a": ()}, "", id="empty tuple"),
+ pytest.param({"a": [1]}, "/?a=1", id="single list"),
+ pytest.param({"a": (1,)}, "/?a=1", id="single tuple"),
+ pytest.param({"a": [1, 2]}, "/?a=1&a=2", id="list"),
+ pytest.param({"a": (1, 2)}, "/?a=1&a=2", id="tuple"),
+ pytest.param({"a[]": [1, 2]}, "/?a%5B%5D=1&a%5B%5D=2", id="key with braces"),
+ pytest.param({"&": [1, 2]}, "/?%26=1&%26=2", id="quote key"),
+ pytest.param({"a": ["1", 2]}, "/?a=1&a=2", id="mixed types"),
+ pytest.param({"&": ["=", 2]}, "/?%26=%3D&%26=2", id="quote key and value"),
+ pytest.param({"a": 1, "b": [2, 3]}, "/?a=1&b=2&b=3", id="single then list"),
+ pytest.param({"a": [1, 2], "b": 3}, "/?a=1&a=2&b=3", id="list then single"),
+ pytest.param({"a": ["1&a=2", 3]}, "/?a=1%26a%3D2&a=3", id="ampersand then int"),
+ pytest.param({"a": [1, "2&a=3"]}, "/?a=1&a=2%26a%3D3", id="int then ampersand"),
+ ],
+)
+def test_with_query_sequence(query, expected):
+ url = URL("http://example.com")
+ expected = "http://example.com{expected}".format_map(locals())
+ assert str(url.with_query(query)) == expected
+
+
+@pytest.mark.parametrize(
+ "query",
+ [
+ pytest.param({"a": [[1]]}, id="nested"),
+ pytest.param([("a", [1, 2])], id="tuple list"),
+ ],
+)
+def test_with_query_sequence_invalid_use(query):
+ url = URL("http://example.com")
+ with pytest.raises(TypeError, match="Invalid variable type"):
+ url.with_query(query)
+
+
+class _CStr(str):
+ pass
+
+
+class _EmptyStrEr:
+ def __str__(self):
+ return ""
+
+
+class _CInt(int, _EmptyStrEr):
+ pass
+
+
+class _CFloat(float, _EmptyStrEr):
+ pass
+
+
+@pytest.mark.parametrize(
+ ("value", "expected"),
+ [
+ pytest.param("1", "1", id="str"),
+ pytest.param(_CStr("1"), "1", id="custom str"),
+ pytest.param(1, "1", id="int"),
+ pytest.param(_CInt(1), "1", id="custom int"),
+ pytest.param(1.1, "1.1", id="float"),
+ pytest.param(_CFloat(1.1), "1.1", id="custom float"),
+ ],
+)
+def test_with_query_valid_type(value, expected):
+ url = URL("http://example.com")
+ expected = "http://example.com/?a={expected}".format_map(locals())
+ assert str(url.with_query({"a": value})) == expected
+
+
+@pytest.mark.parametrize(
+ ("value", "exc_type"),
+ [
+ pytest.param(True, TypeError, id="bool"),
+ pytest.param(None, TypeError, id="none"),
+ pytest.param(float("inf"), ValueError, id="non-finite float"),
+ pytest.param(float("nan"), ValueError, id="NaN float"),
+ ],
+)
+def test_with_query_invalid_type(value, exc_type):
+ url = URL("http://example.com")
+ with pytest.raises(exc_type):
+ url.with_query({"a": value})
+
+
+@pytest.mark.parametrize(
+ ("value", "expected"),
+ [
+ pytest.param("1", "1", id="str"),
+ pytest.param(_CStr("1"), "1", id="custom str"),
+ pytest.param(1, "1", id="int"),
+ pytest.param(_CInt(1), "1", id="custom int"),
+ pytest.param(1.1, "1.1", id="float"),
+ pytest.param(_CFloat(1.1), "1.1", id="custom float"),
+ ],
+)
+def test_with_query_list_valid_type(value, expected):
+ url = URL("http://example.com")
+ expected = "http://example.com/?a={expected}".format_map(locals())
+ assert str(url.with_query([("a", value)])) == expected
+
+
+@pytest.mark.parametrize(
+ ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")]
+)
+def test_with_query_list_invalid_type(value):
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_query([("a", value)])
+
+
+def test_with_int_enum():
+ class IntEnum(int, enum.Enum):
+ A = 1
+
+ url = URL("http://example.com/path")
+ url2 = url.with_query(a=IntEnum.A)
+ assert str(url2) == "http://example.com/path?a=1"
+
+
+def test_with_float_enum():
+ class FloatEnum(float, enum.Enum):
+ A = 1.1
+
+ url = URL("http://example.com/path")
+ url2 = url.with_query(a=FloatEnum.A)
+ assert str(url2) == "http://example.com/path?a=1.1"
+
+
+def test_with_query_multidict():
+ url = URL("http://example.com/path")
+ q = MultiDict([("a", "b"), ("c", "d")])
+ assert str(url.with_query(q)) == "http://example.com/path?a=b&c=d"
+
+
+def test_with_multidict_with_spaces_and_non_ascii():
+ url = URL("http://example.com")
+ url2 = url.with_query({"a b": "ю б"})
+ assert url2.raw_query_string == "a+b=%D1%8E+%D0%B1"
+
+
+def test_with_query_multidict_with_unsafe():
+ url = URL("http://example.com/path")
+ url2 = url.with_query({"a+b": "?=+&;"})
+ assert url2.raw_query_string == "a%2Bb=?%3D%2B%26%3B"
+ assert url2.query_string == "a%2Bb=?%3D%2B%26%3B"
+ assert url2.query == {"a+b": "?=+&;"}
+
+
+def test_with_query_None():
+ url = URL("http://example.com/path?a=b")
+ assert url.with_query(None).query_string == ""
+
+
+def test_with_query_bad_type():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_query(123)
+
+
+def test_with_query_bytes():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_query(b"123")
+
+
+def test_with_query_bytearray():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_query(bytearray(b"123"))
+
+
+def test_with_query_memoryview():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_query(memoryview(b"123"))
+
+
+@pytest.mark.parametrize(
+ ("query", "expected"),
+ [
+ pytest.param([("key", "1;2;3")], "?key=1%3B2%3B3", id="tuple list semicolon"),
+ pytest.param({"key": "1;2;3"}, "?key=1%3B2%3B3", id="mapping semicolon"),
+ pytest.param([("key", "1&a=2")], "?key=1%26a%3D2", id="tuple list ampersand"),
+ pytest.param({"key": "1&a=2"}, "?key=1%26a%3D2", id="mapping ampersand"),
+ pytest.param([("&", "=")], "?%26=%3D", id="tuple list quote key"),
+ pytest.param({"&": "="}, "?%26=%3D", id="mapping quote key"),
+ pytest.param(
+ [("a[]", "3")],
+ "?a%5B%5D=3",
+ id="quote one key braces",
+ ),
+ pytest.param(
+ [("a[]", "3"), ("a[]", "4")],
+ "?a%5B%5D=3&a%5B%5D=4",
+ id="quote many key braces",
+ ),
+ ],
+)
+def test_with_query_params(query, expected):
+ url = URL("http://example.com/get")
+ url2 = url.with_query(query)
+ assert str(url2) == ("http://example.com/get" + expected)
+
+
+def test_with_query_only():
+ url = URL()
+ url2 = url.with_query(key="value")
+ assert str(url2) == "?key=value"
+
+
+def test_with_query_complex_url():
+ target_url = "http://example.com/?game=bulls+%26+cows"
+ url = URL("/redir").with_query({"t": target_url})
+ assert url.query["t"] == target_url
+
+
+def test_update_query_multiple_keys():
+ url = URL("http://example.com/path?a=1&a=2")
+ u2 = url.update_query([("a", "3"), ("a", "4")])
+
+ assert str(u2) == "http://example.com/path?a=3&a=4"
+
+
+# mod operator
+
+
+def test_update_query_with_mod_operator():
+ url = URL("http://example.com/")
+ assert str(url % {"a": "1"}) == "http://example.com/?a=1"
+ assert str(url % [("a", "1")]) == "http://example.com/?a=1"
+ assert str(url % "a=1&b=2") == "http://example.com/?a=1&b=2"
+ assert str(url % {"a": "1"} % {"b": "2"}) == "http://example.com/?a=1&b=2"
+ assert str(url % {"a": "1"} % {"a": "3", "b": "2"}) == "http://example.com/?a=3&b=2"
+ assert str(url / "foo" % {"a": "1"}) == "http://example.com/foo?a=1"
diff --git a/contrib/python/yarl/tests/test_url.py b/contrib/python/yarl/tests/test_url.py
new file mode 100644
index 0000000000..af13d0b5d5
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url.py
@@ -0,0 +1,1732 @@
+from enum import Enum
+from urllib.parse import SplitResult
+
+import pytest
+
+from yarl import URL
+
+
+def test_inheritance():
+ with pytest.raises(TypeError) as ctx:
+
+ class MyURL(URL): # type: ignore[misc]
+ pass
+
+ assert (
+ "Inheriting a class "
+ "<class '__tests__.test_url.test_inheritance.<locals>.MyURL'> "
+ "from URL is forbidden" == str(ctx.value)
+ )
+
+
+def test_str_subclass():
+ class S(str):
+ pass
+
+ assert str(URL(S("http://example.com"))) == "http://example.com"
+
+
+def test_is():
+ u1 = URL("http://example.com")
+ u2 = URL(u1)
+ assert u1 is u2
+
+
+def test_bool():
+ assert URL("http://example.com")
+ assert not URL()
+ assert not URL("")
+
+
+def test_absolute_url_without_host():
+ with pytest.raises(ValueError):
+ URL("http://:8080/")
+
+
+def test_url_is_not_str():
+ url = URL("http://example.com")
+ assert not isinstance(url, str)
+
+
+def test_str():
+ url = URL("http://example.com:8888/path/to?a=1&b=2")
+ assert str(url) == "http://example.com:8888/path/to?a=1&b=2"
+
+
+def test_repr():
+ url = URL("http://example.com")
+ assert "URL('http://example.com')" == repr(url)
+
+
+def test_origin():
+ url = URL("http://user:password@example.com:8888/path/to?a=1&b=2")
+ assert URL("http://example.com:8888") == url.origin()
+
+
+def test_origin_nonascii():
+ url = URL("http://user:password@историк.рф:8888/path/to?a=1&b=2")
+ assert str(url.origin()) == "http://xn--h1aagokeh.xn--p1ai:8888"
+
+
+def test_origin_ipv6():
+ url = URL("http://user:password@[::1]:8888/path/to?a=1&b=2")
+ assert str(url.origin()) == "http://[::1]:8888"
+
+
+def test_origin_not_absolute_url():
+ url = URL("/path/to?a=1&b=2")
+ with pytest.raises(ValueError):
+ url.origin()
+
+
+def test_origin_no_scheme():
+ url = URL("//user:password@example.com:8888/path/to?a=1&b=2")
+ with pytest.raises(ValueError):
+ url.origin()
+
+
+def test_drop_dots():
+ u = URL("http://example.com/path/../to")
+ assert str(u) == "http://example.com/to"
+
+
+def test_abs_cmp():
+ assert URL("http://example.com:8888") == URL("http://example.com:8888")
+ assert URL("http://example.com:8888/") == URL("http://example.com:8888/")
+ assert URL("http://example.com:8888/") == URL("http://example.com:8888")
+ assert URL("http://example.com:8888") == URL("http://example.com:8888/")
+
+
+def test_abs_hash():
+ url = URL("http://example.com:8888")
+ url_trailing = URL("http://example.com:8888/")
+ assert hash(url) == hash(url_trailing)
+
+
+# properties
+
+
+def test_scheme():
+ url = URL("http://example.com")
+ assert "http" == url.scheme
+
+
+def test_raw_user():
+ url = URL("http://user@example.com")
+ assert "user" == url.raw_user
+
+
+def test_raw_user_non_ascii():
+ url = URL("http://вася@example.com")
+ assert "%D0%B2%D0%B0%D1%81%D1%8F" == url.raw_user
+
+
+def test_no_user():
+ url = URL("http://example.com")
+ assert url.user is None
+
+
+def test_user_non_ascii():
+ url = URL("http://вася@example.com")
+ assert "вася" == url.user
+
+
+def test_raw_password():
+ url = URL("http://user:password@example.com")
+ assert "password" == url.raw_password
+
+
+def test_raw_password_non_ascii():
+ url = URL("http://user:пароль@example.com")
+ assert "%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C" == url.raw_password
+
+
+def test_password_non_ascii():
+ url = URL("http://user:пароль@example.com")
+ assert "пароль" == url.password
+
+
+def test_password_without_user():
+ url = URL("http://:password@example.com")
+ assert url.user is None
+ assert "password" == url.password
+
+
+def test_user_empty_password():
+ url = URL("http://user:@example.com")
+ assert "user" == url.user
+ assert "" == url.password
+
+
+def test_raw_host():
+ url = URL("http://example.com")
+ assert "example.com" == url.raw_host
+
+
+def test_raw_host_non_ascii():
+ url = URL("http://историк.рф")
+ assert "xn--h1aagokeh.xn--p1ai" == url.raw_host
+
+
+def test_host_non_ascii():
+ url = URL("http://историк.рф")
+ assert "историк.рф" == url.host
+
+
+def test_localhost():
+ url = URL("http://[::1]")
+ assert "::1" == url.host
+
+
+def test_host_with_underscore():
+ url = URL("http://abc_def.com")
+ assert "abc_def.com" == url.host
+
+
+def test_raw_host_when_port_is_specified():
+ url = URL("http://example.com:8888")
+ assert "example.com" == url.raw_host
+
+
+def test_raw_host_from_str_with_ipv4():
+ url = URL("http://127.0.0.1:80")
+ assert url.raw_host == "127.0.0.1"
+
+
+def test_raw_host_from_str_with_ipv6():
+ url = URL("http://[::1]:80")
+ assert url.raw_host == "::1"
+
+
+def test_authority_full() -> None:
+ url = URL("http://user:passwd@host.com:8080/path")
+ assert url.raw_authority == "user:passwd@host.com:8080"
+ assert url.authority == "user:passwd@host.com:8080"
+
+
+def test_authority_short() -> None:
+ url = URL("http://host.com/path")
+ assert url.raw_authority == "host.com"
+
+
+def test_authority_full_nonasci() -> None:
+ url = URL("http://ваня:пароль@айдеко.рф:8080/path")
+ assert url.raw_authority == (
+ "%D0%B2%D0%B0%D0%BD%D1%8F:%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C@"
+ "xn--80aidohy.xn--p1ai:8080"
+ )
+ assert url.authority == "ваня:пароль@айдеко.рф:8080"
+
+
+def test_lowercase():
+ url = URL("http://gitHUB.com")
+ assert url.raw_host == "github.com"
+ assert url.host == url.raw_host
+
+
+def test_lowercase_nonascii():
+ url = URL("http://Айдеко.Рф")
+ assert url.raw_host == "xn--80aidohy.xn--p1ai"
+ assert url.host == "айдеко.рф"
+
+
+def test_compressed_ipv6():
+ url = URL("http://[1DEC:0:0:0::1]")
+ assert url.raw_host == "1dec::1"
+ assert url.host == url.raw_host
+
+
+def test_ipv4_zone():
+ # I'm unsure if it is correct.
+ url = URL("http://1.2.3.4%тест%42:123")
+ assert url.raw_host == "1.2.3.4%тест%42"
+ assert url.host == url.raw_host
+
+
+def test_port_for_explicit_port():
+ url = URL("http://example.com:8888")
+ assert 8888 == url.port
+
+
+def test_port_for_implicit_port():
+ url = URL("http://example.com")
+ assert 80 == url.port
+
+
+def test_port_for_relative_url():
+ url = URL("/path/to")
+ assert url.port is None
+
+
+def test_port_for_unknown_scheme():
+ url = URL("unknown://example.com")
+ assert url.port is None
+
+
+def test_explicit_port_for_explicit_port():
+ url = URL("http://example.com:8888")
+ assert 8888 == url.explicit_port
+
+
+def test_explicit_port_for_implicit_port():
+ url = URL("http://example.com")
+ assert url.explicit_port is None
+
+
+def test_explicit_port_for_relative_url():
+ url = URL("/path/to")
+ assert url.explicit_port is None
+
+
+def test_explicit_port_for_unknown_scheme():
+ url = URL("unknown://example.com")
+ assert url.explicit_port is None
+
+
+def test_raw_path_string_empty():
+ url = URL("http://example.com")
+ assert "/" == url.raw_path
+
+
+def test_raw_path():
+ url = URL("http://example.com/path/to")
+ assert "/path/to" == url.raw_path
+
+
+def test_raw_path_non_ascii():
+ url = URL("http://example.com/путь/сюда")
+ assert "/%D0%BF%D1%83%D1%82%D1%8C/%D1%81%D1%8E%D0%B4%D0%B0" == url.raw_path
+
+
+def test_path_non_ascii():
+ url = URL("http://example.com/путь/сюда")
+ assert "/путь/сюда" == url.path
+
+
+def test_path_with_spaces():
+ url = URL("http://example.com/a b/test")
+ assert "/a b/test" == url.path
+
+ url = URL("http://example.com/a b")
+ assert "/a b" == url.path
+
+
+def test_raw_path_for_empty_url():
+ url = URL()
+ assert "" == url.raw_path
+
+
+def test_raw_path_for_colon_and_at():
+ url = URL("http://example.com/path:abc@123")
+ assert url.raw_path == "/path:abc@123"
+
+
+def test_raw_query_string():
+ url = URL("http://example.com?a=1&b=2")
+ assert url.raw_query_string == "a=1&b=2"
+
+
+def test_raw_query_string_non_ascii():
+ url = URL("http://example.com?б=в&ю=к")
+ assert url.raw_query_string == "%D0%B1=%D0%B2&%D1%8E=%D0%BA"
+
+
+def test_query_string_non_ascii():
+ url = URL("http://example.com?б=в&ю=к")
+ assert url.query_string == "б=в&ю=к"
+
+
+def test_path_qs():
+ url = URL("http://example.com/")
+ assert url.path_qs == "/"
+ url = URL("http://example.com/?б=в&ю=к")
+ assert url.path_qs == "/?б=в&ю=к"
+ url = URL("http://example.com/path?б=в&ю=к")
+ assert url.path_qs == "/path?б=в&ю=к"
+
+
+def test_raw_path_qs():
+ url = URL("http://example.com/")
+ assert url.raw_path_qs == "/"
+ url = URL("http://example.com/?б=в&ю=к")
+ assert url.raw_path_qs == "/?%D0%B1=%D0%B2&%D1%8E=%D0%BA"
+ url = URL("http://example.com/path?б=в&ю=к")
+ assert url.raw_path_qs == "/path?%D0%B1=%D0%B2&%D1%8E=%D0%BA"
+ url = URL("http://example.com/путь?a=1&b=2")
+ assert url.raw_path_qs == "/%D0%BF%D1%83%D1%82%D1%8C?a=1&b=2"
+
+
+def test_query_string_spaces():
+ url = URL("http://example.com?a+b=c+d&e=f+g")
+ assert url.query_string == "a b=c d&e=f g"
+
+
+# raw fragment
+
+
+def test_raw_fragment_empty():
+ url = URL("http://example.com")
+ assert "" == url.raw_fragment
+
+
+def test_raw_fragment():
+ url = URL("http://example.com/path#anchor")
+ assert "anchor" == url.raw_fragment
+
+
+def test_raw_fragment_non_ascii():
+ url = URL("http://example.com/path#якорь")
+ assert "%D1%8F%D0%BA%D0%BE%D1%80%D1%8C" == url.raw_fragment
+
+
+def test_raw_fragment_safe():
+ url = URL("http://example.com/path#a?b/c:d@e")
+ assert "a?b/c:d@e" == url.raw_fragment
+
+
+def test_fragment_non_ascii():
+ url = URL("http://example.com/path#якорь")
+ assert "якорь" == url.fragment
+
+
+def test_raw_parts_empty():
+ url = URL("http://example.com")
+ assert ("/",) == url.raw_parts
+
+
+def test_raw_parts():
+ url = URL("http://example.com/path/to")
+ assert ("/", "path", "to") == url.raw_parts
+
+
+def test_raw_parts_without_path():
+ url = URL("http://example.com")
+ assert ("/",) == url.raw_parts
+
+
+def test_raw_path_parts_with_2F_in_path():
+ url = URL("http://example.com/path%2Fto/three")
+ assert ("/", "path%2Fto", "three") == url.raw_parts
+
+
+def test_raw_path_parts_with_2f_in_path():
+ url = URL("http://example.com/path%2fto/three")
+ assert ("/", "path%2Fto", "three") == url.raw_parts
+
+
+def test_raw_parts_for_relative_path():
+ url = URL("path/to")
+ assert ("path", "to") == url.raw_parts
+
+
+def test_raw_parts_for_relative_path_starting_from_slash():
+ url = URL("/path/to")
+ assert ("/", "path", "to") == url.raw_parts
+
+
+def test_raw_parts_for_relative_double_path():
+ url = URL("path/to")
+ assert ("path", "to") == url.raw_parts
+
+
+def test_parts_for_empty_url():
+ url = URL()
+ assert ("",) == url.raw_parts
+
+
+def test_raw_parts_non_ascii():
+ url = URL("http://example.com/путь/сюда")
+ assert (
+ "/",
+ "%D0%BF%D1%83%D1%82%D1%8C",
+ "%D1%81%D1%8E%D0%B4%D0%B0",
+ ) == url.raw_parts
+
+
+def test_parts_non_ascii():
+ url = URL("http://example.com/путь/сюда")
+ assert ("/", "путь", "сюда") == url.parts
+
+
+def test_name_for_empty_url():
+ url = URL()
+ assert "" == url.raw_name
+
+
+def test_raw_name():
+ url = URL("http://example.com/path/to#frag")
+ assert "to" == url.raw_name
+
+
+def test_raw_name_root():
+ url = URL("http://example.com/#frag")
+ assert "" == url.raw_name
+
+
+def test_raw_name_root2():
+ url = URL("http://example.com")
+ assert "" == url.raw_name
+
+
+def test_raw_name_root3():
+ url = URL("http://example.com/")
+ assert "" == url.raw_name
+
+
+def test_relative_raw_name():
+ url = URL("path/to")
+ assert "to" == url.raw_name
+
+
+def test_relative_raw_name_starting_from_slash():
+ url = URL("/path/to")
+ assert "to" == url.raw_name
+
+
+def test_relative_raw_name_slash():
+ url = URL("/")
+ assert "" == url.raw_name
+
+
+def test_name_non_ascii():
+ url = URL("http://example.com/путь")
+ assert url.name == "путь"
+
+
+def test_suffix_for_empty_url():
+ url = URL()
+ assert "" == url.raw_suffix
+
+
+def test_raw_suffix():
+ url = URL("http://example.com/path/to.txt#frag")
+ assert ".txt" == url.raw_suffix
+
+
+def test_raw_suffix_root():
+ url = URL("http://example.com/#frag")
+ assert "" == url.raw_suffix
+
+
+def test_raw_suffix_root2():
+ url = URL("http://example.com")
+ assert "" == url.raw_suffix
+
+
+def test_raw_suffix_root3():
+ url = URL("http://example.com/")
+ assert "" == url.raw_suffix
+
+
+def test_relative_raw_suffix():
+ url = URL("path/to")
+ assert "" == url.raw_suffix
+
+
+def test_relative_raw_suffix_starting_from_slash():
+ url = URL("/path/to")
+ assert "" == url.raw_suffix
+
+
+def test_relative_raw_suffix_dot():
+ url = URL(".")
+ assert "" == url.raw_suffix
+
+
+def test_suffix_non_ascii():
+ url = URL("http://example.com/путь.суффикс")
+ assert url.suffix == ".суффикс"
+
+
+def test_suffix_with_empty_name():
+ url = URL("http://example.com/.hgrc")
+ assert "" == url.raw_suffix
+
+
+def test_suffix_multi_dot():
+ url = URL("http://example.com/doc.tar.gz")
+ assert ".gz" == url.raw_suffix
+
+
+def test_suffix_with_dot_name():
+ url = URL("http://example.com/doc.")
+ assert "" == url.raw_suffix
+
+
+def test_suffixes_for_empty_url():
+ url = URL()
+ assert () == url.raw_suffixes
+
+
+def test_raw_suffixes():
+ url = URL("http://example.com/path/to.txt#frag")
+ assert (".txt",) == url.raw_suffixes
+
+
+def test_raw_suffixes_root():
+ url = URL("http://example.com/#frag")
+ assert () == url.raw_suffixes
+
+
+def test_raw_suffixes_root2():
+ url = URL("http://example.com")
+ assert () == url.raw_suffixes
+
+
+def test_raw_suffixes_root3():
+ url = URL("http://example.com/")
+ assert () == url.raw_suffixes
+
+
+def test_relative_raw_suffixes():
+ url = URL("path/to")
+ assert () == url.raw_suffixes
+
+
+def test_relative_raw_suffixes_starting_from_slash():
+ url = URL("/path/to")
+ assert () == url.raw_suffixes
+
+
+def test_relative_raw_suffixes_dot():
+ url = URL(".")
+ assert () == url.raw_suffixes
+
+
+def test_suffixes_non_ascii():
+ url = URL("http://example.com/путь.суффикс")
+ assert url.suffixes == (".суффикс",)
+
+
+def test_suffixes_with_empty_name():
+ url = URL("http://example.com/.hgrc")
+ assert () == url.raw_suffixes
+
+
+def test_suffixes_multi_dot():
+ url = URL("http://example.com/doc.tar.gz")
+ assert (".tar", ".gz") == url.raw_suffixes
+
+
+def test_suffixes_with_dot_name():
+ url = URL("http://example.com/doc.")
+ assert () == url.raw_suffixes
+
+
+def test_plus_in_path():
+ url = URL("http://example.com/test/x+y%2Bz/:+%2B/")
+ assert "/test/x+y+z/:++/" == url.path
+
+
+def test_nonascii_in_qs():
+ url = URL("http://example.com")
+ url2 = url.with_query({"f\xf8\xf8": "f\xf8\xf8"})
+ assert "http://example.com/?f%C3%B8%C3%B8=f%C3%B8%C3%B8" == str(url2)
+
+
+def test_percent_encoded_in_qs():
+ url = URL("http://example.com")
+ url2 = url.with_query({"k%cf%80": "v%cf%80"})
+ assert str(url2) == "http://example.com/?k%25cf%2580=v%25cf%2580"
+ assert url2.raw_query_string == "k%25cf%2580=v%25cf%2580"
+ assert url2.query_string == "k%cf%80=v%cf%80"
+ assert url2.query == {"k%cf%80": "v%cf%80"}
+
+
+# modifiers
+
+
+def test_parent_raw_path():
+ url = URL("http://example.com/path/to")
+ assert url.parent.raw_path == "/path"
+
+
+def test_parent_raw_parts():
+ url = URL("http://example.com/path/to")
+ assert url.parent.raw_parts == ("/", "path")
+
+
+def test_double_parent_raw_path():
+ url = URL("http://example.com/path/to")
+ assert url.parent.parent.raw_path == "/"
+
+
+def test_empty_parent_raw_path():
+ url = URL("http://example.com/")
+ assert url.parent.parent.raw_path == "/"
+
+
+def test_empty_parent_raw_path2():
+ url = URL("http://example.com")
+ assert url.parent.parent.raw_path == "/"
+
+
+def test_clear_fragment_on_getting_parent():
+ url = URL("http://example.com/path/to#frag")
+ assert URL("http://example.com/path") == url.parent
+
+
+def test_clear_fragment_on_getting_parent_toplevel():
+ url = URL("http://example.com/#frag")
+ assert URL("http://example.com/") == url.parent
+
+
+def test_clear_query_on_getting_parent():
+ url = URL("http://example.com/path/to?a=b")
+ assert URL("http://example.com/path") == url.parent
+
+
+def test_clear_query_on_getting_parent_toplevel():
+ url = URL("http://example.com/?a=b")
+ assert URL("http://example.com/") == url.parent
+
+
+# truediv
+
+
+def test_div_root():
+ url = URL("http://example.com") / "path" / "to"
+ assert str(url) == "http://example.com/path/to"
+ assert url.raw_path == "/path/to"
+
+
+def test_div_root_with_slash():
+ url = URL("http://example.com/") / "path" / "to"
+ assert str(url) == "http://example.com/path/to"
+ assert url.raw_path == "/path/to"
+
+
+def test_div():
+ url = URL("http://example.com/path") / "to"
+ assert str(url) == "http://example.com/path/to"
+ assert url.raw_path == "/path/to"
+
+
+def test_div_with_slash():
+ url = URL("http://example.com/path/") / "to"
+ assert str(url) == "http://example.com/path/to"
+ assert url.raw_path == "/path/to"
+
+
+def test_div_path_starting_from_slash_is_forbidden():
+ url = URL("http://example.com/path/")
+ with pytest.raises(ValueError):
+ url / "/to/others"
+
+
+class StrEnum(str, Enum):
+ spam = "ham"
+
+ def __str__(self):
+ return self.value
+
+
+def test_div_path_srting_subclass():
+ url = URL("http://example.com/path/") / StrEnum.spam
+ assert str(url) == "http://example.com/path/ham"
+
+
+def test_div_bad_type():
+ url = URL("http://example.com/path/")
+ with pytest.raises(TypeError):
+ url / 3
+
+
+def test_div_cleanup_query_and_fragment():
+ url = URL("http://example.com/path?a=1#frag")
+ assert str(url / "to") == "http://example.com/path/to"
+
+
+def test_div_for_empty_url():
+ url = URL() / "a"
+ assert url.raw_parts == ("a",)
+
+
+def test_div_for_relative_url():
+ url = URL("a") / "b"
+ assert url.raw_parts == ("a", "b")
+
+
+def test_div_for_relative_url_started_with_slash():
+ url = URL("/a") / "b"
+ assert url.raw_parts == ("/", "a", "b")
+
+
+def test_div_non_ascii():
+ url = URL("http://example.com/сюда")
+ url2 = url / "туда"
+ assert url2.path == "/сюда/туда"
+ assert url2.raw_path == "/%D1%81%D1%8E%D0%B4%D0%B0/%D1%82%D1%83%D0%B4%D0%B0"
+ assert url2.parts == ("/", "сюда", "туда")
+ assert url2.raw_parts == (
+ "/",
+ "%D1%81%D1%8E%D0%B4%D0%B0",
+ "%D1%82%D1%83%D0%B4%D0%B0",
+ )
+
+
+def test_div_percent_encoded():
+ url = URL("http://example.com/path")
+ url2 = url / "%cf%80"
+ assert url2.path == "/path/%cf%80"
+ assert url2.raw_path == "/path/%25cf%2580"
+ assert url2.parts == ("/", "path", "%cf%80")
+ assert url2.raw_parts == ("/", "path", "%25cf%2580")
+
+
+def test_div_with_colon_and_at():
+ url = URL("http://example.com/base") / "path:abc@123"
+ assert url.raw_path == "/base/path:abc@123"
+
+
+def test_div_with_dots():
+ url = URL("http://example.com/base") / "../path/./to"
+ assert url.raw_path == "/path/to"
+
+
+# joinpath
+
+
+@pytest.mark.parametrize(
+ "base,to_join,expected",
+ [
+ pytest.param("", ("path", "to"), "http://example.com/path/to", id="root"),
+ pytest.param(
+ "/", ("path", "to"), "http://example.com/path/to", id="root-with-slash"
+ ),
+ pytest.param("/path", ("to",), "http://example.com/path/to", id="path"),
+ pytest.param(
+ "/path/", ("to",), "http://example.com/path/to", id="path-with-slash"
+ ),
+ pytest.param(
+ "/path?a=1#frag",
+ ("to",),
+ "http://example.com/path/to",
+ id="cleanup-query-and-fragment",
+ ),
+ pytest.param("", ("path/",), "http://example.com/path/", id="trailing-slash"),
+ pytest.param(
+ "", ("path/", "to/"), "http://example.com/path/to/", id="duplicate-slash"
+ ),
+ pytest.param("", (), "http://example.com", id="empty-segments"),
+ pytest.param(
+ "/", ("path/",), "http://example.com/path/", id="base-slash-trailing-slash"
+ ),
+ pytest.param(
+ "/",
+ ("path/", "to/"),
+ "http://example.com/path/to/",
+ id="base-slash-duplicate-slash",
+ ),
+ pytest.param("/", (), "http://example.com", id="base-slash-empty-segments"),
+ ],
+)
+def test_joinpath(base, to_join, expected):
+ url = URL(f"http://example.com{base}")
+ assert str(url.joinpath(*to_join)) == expected
+
+
+@pytest.mark.parametrize(
+ "url,to_join,expected",
+ [
+ pytest.param(URL(), ("a",), ("a",), id="empty-url"),
+ pytest.param(URL("a"), ("b",), ("a", "b"), id="relative-path"),
+ pytest.param(URL("a"), ("b", "", "c"), ("a", "b", "c"), id="empty-element"),
+ pytest.param(URL("/a"), ("b"), ("/", "a", "b"), id="absolute-path"),
+ pytest.param(URL(), ("a/",), ("a", ""), id="trailing-slash"),
+ pytest.param(URL(), ("a/", "b/"), ("a", "b", ""), id="duplicate-slash"),
+ pytest.param(URL(), (), ("",), id="empty-segments"),
+ ],
+)
+def test_joinpath_relative(url, to_join, expected):
+ assert url.joinpath(*to_join).raw_parts == expected
+
+
+@pytest.mark.parametrize(
+ "url,to_join,encoded,e_path,e_raw_path,e_parts,e_raw_parts",
+ [
+ pytest.param(
+ "http://example.com/сюда",
+ ("туда",),
+ False,
+ "/сюда/туда",
+ "/%D1%81%D1%8E%D0%B4%D0%B0/%D1%82%D1%83%D0%B4%D0%B0",
+ ("/", "сюда", "туда"),
+ ("/", "%D1%81%D1%8E%D0%B4%D0%B0", "%D1%82%D1%83%D0%B4%D0%B0"),
+ id="non-ascii",
+ ),
+ pytest.param(
+ "http://example.com/path",
+ ("%cf%80",),
+ False,
+ "/path/%cf%80",
+ "/path/%25cf%2580",
+ ("/", "path", "%cf%80"),
+ ("/", "path", "%25cf%2580"),
+ id="percent-encoded",
+ ),
+ pytest.param(
+ "http://example.com/path",
+ ("%cf%80",),
+ True,
+ "/path/π",
+ "/path/%cf%80",
+ ("/", "path", "π"),
+ ("/", "path", "%cf%80"),
+ id="encoded-percent-encoded",
+ ),
+ ],
+)
+def test_joinpath_encoding(
+ url, to_join, encoded, e_path, e_raw_path, e_parts, e_raw_parts
+):
+ joined = URL(url).joinpath(*to_join, encoded=encoded)
+ assert joined.path == e_path
+ assert joined.raw_path == e_raw_path
+ assert joined.parts == e_parts
+ assert joined.raw_parts == e_raw_parts
+
+
+@pytest.mark.parametrize(
+ "to_join,expected",
+ [
+ pytest.param(("path:abc@123",), "/base/path:abc@123", id="with-colon-and-at"),
+ pytest.param(("..", "path", ".", "to"), "/path/to", id="with-dots"),
+ ],
+)
+def test_joinpath_edgecases(to_join, expected):
+ url = URL("http://example.com/base").joinpath(*to_join)
+ assert url.raw_path == expected
+
+
+def test_joinpath_path_starting_from_slash_is_forbidden():
+ url = URL("http://example.com/path/")
+ with pytest.raises(
+ ValueError, match="Appending path .* starting from slash is forbidden"
+ ):
+ assert url.joinpath("/to/others")
+
+
+# with_path
+
+
+def test_with_path():
+ url = URL("http://example.com")
+ url2 = url.with_path("/test")
+ assert str(url2) == "http://example.com/test"
+ assert url2.raw_path == "/test"
+ assert url2.path == "/test"
+
+
+def test_with_path_nonascii():
+ url = URL("http://example.com")
+ url2 = url.with_path("/π")
+ assert str(url2) == "http://example.com/%CF%80"
+ assert url2.raw_path == "/%CF%80"
+ assert url2.path == "/π"
+
+
+def test_with_path_percent_encoded():
+ url = URL("http://example.com")
+ url2 = url.with_path("/%cf%80")
+ assert str(url2) == "http://example.com/%25cf%2580"
+ assert url2.raw_path == "/%25cf%2580"
+ assert url2.path == "/%cf%80"
+
+
+def test_with_path_encoded():
+ url = URL("http://example.com")
+ url2 = url.with_path("/test", encoded=True)
+ assert str(url2) == "http://example.com/test"
+ assert url2.raw_path == "/test"
+ assert url2.path == "/test"
+
+
+def test_with_path_encoded_nonascii():
+ url = URL("http://example.com")
+ url2 = url.with_path("/π", encoded=True)
+ assert str(url2) == "http://example.com/π"
+ assert url2.raw_path == "/π"
+ assert url2.path == "/π"
+
+
+def test_with_path_encoded_percent_encoded():
+ url = URL("http://example.com")
+ url2 = url.with_path("/%cf%80", encoded=True)
+ assert str(url2) == "http://example.com/%cf%80"
+ assert url2.raw_path == "/%cf%80"
+ assert url2.path == "/π"
+
+
+def test_with_path_dots():
+ url = URL("http://example.com")
+ assert str(url.with_path("/test/.")) == "http://example.com/test/"
+
+
+def test_with_path_relative():
+ url = URL("/path")
+ assert str(url.with_path("/new")) == "/new"
+
+
+def test_with_path_query():
+ url = URL("http://example.com?a=b")
+ assert str(url.with_path("/test")) == "http://example.com/test"
+
+
+def test_with_path_fragment():
+ url = URL("http://example.com#frag")
+ assert str(url.with_path("/test")) == "http://example.com/test"
+
+
+def test_with_path_empty():
+ url = URL("http://example.com/test")
+ assert str(url.with_path("")) == "http://example.com"
+
+
+def test_with_path_leading_slash():
+ url = URL("http://example.com")
+ assert url.with_path("test").path == "/test"
+
+
+# with_fragment
+
+
+def test_with_fragment():
+ url = URL("http://example.com")
+ url2 = url.with_fragment("frag")
+ assert str(url2) == "http://example.com/#frag"
+ assert url2.raw_fragment == "frag"
+ assert url2.fragment == "frag"
+
+
+def test_with_fragment_safe():
+ url = URL("http://example.com")
+ u2 = url.with_fragment("a:b?c@d/e")
+ assert str(u2) == "http://example.com/#a:b?c@d/e"
+
+
+def test_with_fragment_non_ascii():
+ url = URL("http://example.com")
+ url2 = url.with_fragment("фрагм")
+ assert url2.raw_fragment == "%D1%84%D1%80%D0%B0%D0%B3%D0%BC"
+ assert url2.fragment == "фрагм"
+
+
+def test_with_fragment_percent_encoded():
+ url = URL("http://example.com")
+ url2 = url.with_fragment("%cf%80")
+ assert str(url2) == "http://example.com/#%25cf%2580"
+ assert url2.raw_fragment == "%25cf%2580"
+ assert url2.fragment == "%cf%80"
+
+
+def test_with_fragment_None():
+ url = URL("http://example.com/path#frag")
+ url2 = url.with_fragment(None)
+ assert str(url2) == "http://example.com/path"
+
+
+def test_with_fragment_None_matching():
+ url = URL("http://example.com/path")
+ url2 = url.with_fragment(None)
+ assert url is url2
+
+
+def test_with_fragment_matching():
+ url = URL("http://example.com/path#frag")
+ url2 = url.with_fragment("frag")
+ assert url is url2
+
+
+def test_with_fragment_bad_type():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ url.with_fragment(123)
+
+
+# with_name
+
+
+def test_with_name():
+ url = URL("http://example.com/a/b")
+ assert url.raw_parts == ("/", "a", "b")
+ url2 = url.with_name("c")
+ assert url2.raw_parts == ("/", "a", "c")
+ assert url2.parts == ("/", "a", "c")
+ assert url2.raw_path == "/a/c"
+ assert url2.path == "/a/c"
+
+
+def test_with_name_for_naked_path():
+ url = URL("http://example.com")
+ url2 = url.with_name("a")
+ assert url2.raw_parts == ("/", "a")
+
+
+def test_with_name_for_relative_path():
+ url = URL("a")
+ url2 = url.with_name("b")
+ assert url2.raw_parts == ("b",)
+
+
+def test_with_name_for_relative_path2():
+ url = URL("a/b")
+ url2 = url.with_name("c")
+ assert url2.raw_parts == ("a", "c")
+
+
+def test_with_name_for_relative_path_starting_from_slash():
+ url = URL("/a")
+ url2 = url.with_name("b")
+ assert url2.raw_parts == ("/", "b")
+
+
+def test_with_name_for_relative_path_starting_from_slash2():
+ url = URL("/a/b")
+ url2 = url.with_name("c")
+ assert url2.raw_parts == ("/", "a", "c")
+
+
+def test_with_name_empty():
+ url = URL("http://example.com/path/to").with_name("")
+ assert str(url) == "http://example.com/path/"
+
+
+def test_with_name_non_ascii():
+ url = URL("http://example.com/path").with_name("путь")
+ assert url.path == "/путь"
+ assert url.raw_path == "/%D0%BF%D1%83%D1%82%D1%8C"
+ assert url.parts == ("/", "путь")
+ assert url.raw_parts == ("/", "%D0%BF%D1%83%D1%82%D1%8C")
+
+
+def test_with_name_percent_encoded():
+ url = URL("http://example.com/path")
+ url2 = url.with_name("%cf%80")
+ assert url2.raw_parts == ("/", "%25cf%2580")
+ assert url2.parts == ("/", "%cf%80")
+ assert url2.raw_path == "/%25cf%2580"
+ assert url2.path == "/%cf%80"
+
+
+def test_with_name_with_slash():
+ with pytest.raises(ValueError):
+ URL("http://example.com").with_name("a/b")
+
+
+def test_with_name_non_str():
+ with pytest.raises(TypeError):
+ URL("http://example.com").with_name(123)
+
+
+def test_with_name_within_colon_and_at():
+ url = URL("http://example.com/oldpath").with_name("path:abc@123")
+ assert url.raw_path == "/path:abc@123"
+
+
+def test_with_name_dot():
+ with pytest.raises(ValueError):
+ URL("http://example.com").with_name(".")
+
+
+def test_with_name_double_dot():
+ with pytest.raises(ValueError):
+ URL("http://example.com").with_name("..")
+
+
+# with_suffix
+
+
+def test_with_suffix():
+ url = URL("http://example.com/a/b")
+ assert url.raw_parts == ("/", "a", "b")
+ url2 = url.with_suffix(".c")
+ assert url2.raw_parts == ("/", "a", "b.c")
+ assert url2.parts == ("/", "a", "b.c")
+ assert url2.raw_path == "/a/b.c"
+ assert url2.path == "/a/b.c"
+
+
+def test_with_suffix_for_naked_path():
+ url = URL("http://example.com")
+ with pytest.raises(ValueError) as excinfo:
+ url.with_suffix(".a")
+ (msg,) = excinfo.value.args
+ assert msg == f"{url!r} has an empty name"
+
+
+def test_with_suffix_for_relative_path():
+ url = URL("a")
+ url2 = url.with_suffix(".b")
+ assert url2.raw_parts == ("a.b",)
+
+
+def test_with_suffix_for_relative_path2():
+ url = URL("a/b")
+ url2 = url.with_suffix(".c")
+ assert url2.raw_parts == ("a", "b.c")
+
+
+def test_with_suffix_for_relative_path_starting_from_slash():
+ url = URL("/a")
+ url2 = url.with_suffix(".b")
+ assert url2.raw_parts == ("/", "a.b")
+
+
+def test_with_suffix_for_relative_path_starting_from_slash2():
+ url = URL("/a/b")
+ url2 = url.with_suffix(".c")
+ assert url2.raw_parts == ("/", "a", "b.c")
+
+
+def test_with_suffix_empty():
+ url = URL("http://example.com/path/to").with_suffix("")
+ assert str(url) == "http://example.com/path/to"
+
+
+def test_with_suffix_non_ascii():
+ url = URL("http://example.com/path").with_suffix(".путь")
+ assert url.path == "/path.путь"
+ assert url.raw_path == "/path.%D0%BF%D1%83%D1%82%D1%8C"
+ assert url.parts == ("/", "path.путь")
+ assert url.raw_parts == ("/", "path.%D0%BF%D1%83%D1%82%D1%8C")
+
+
+def test_with_suffix_percent_encoded():
+ url = URL("http://example.com/path")
+ url2 = url.with_suffix(".%cf%80")
+ assert url2.raw_parts == ("/", "path.%25cf%2580")
+ assert url2.parts == ("/", "path.%cf%80")
+ assert url2.raw_path == "/path.%25cf%2580"
+ assert url2.path == "/path.%cf%80"
+
+
+def test_with_suffix_without_dot():
+ with pytest.raises(ValueError) as excinfo:
+ URL("http://example.com/a").with_suffix("b")
+ (msg,) = excinfo.value.args
+ assert msg == "Invalid suffix 'b'"
+
+
+def test_with_suffix_non_str():
+ with pytest.raises(TypeError) as excinfo:
+ URL("http://example.com").with_suffix(123)
+ (msg,) = excinfo.value.args
+ assert msg == "Invalid suffix type"
+
+
+def test_with_suffix_dot():
+ with pytest.raises(ValueError) as excinfo:
+ URL("http://example.com").with_suffix(".")
+ (msg,) = excinfo.value.args
+ assert msg == "Invalid suffix '.'"
+
+
+def test_with_suffix_with_slash():
+ with pytest.raises(ValueError) as excinfo:
+ URL("http://example.com/a").with_suffix("/.b")
+ (msg,) = excinfo.value.args
+ assert msg == "Invalid suffix '/.b'"
+
+
+def test_with_suffix_with_slash2():
+ with pytest.raises(ValueError) as excinfo:
+ URL("http://example.com/a").with_suffix(".b/.d")
+ (msg,) = excinfo.value.args
+ assert msg == "Slash in name is not allowed"
+
+
+def test_with_suffix_replace():
+ url = URL("/a.b")
+ url2 = url.with_suffix(".c")
+ assert url2.raw_parts == ("/", "a.c")
+
+
+# is_absolute
+
+
+def test_is_absolute_for_relative_url():
+ url = URL("/path/to")
+ assert not url.is_absolute()
+
+
+def test_is_absolute_for_absolute_url():
+ url = URL("http://example.com")
+ assert url.is_absolute()
+
+
+def test_is_non_absolute_for_empty_url():
+ url = URL()
+ assert not url.is_absolute()
+
+
+def test_is_non_absolute_for_empty_url2():
+ url = URL("")
+ assert not url.is_absolute()
+
+
+def test_is_absolute_path_starting_from_double_slash():
+ url = URL("//www.python.org")
+ assert url.is_absolute()
+
+
+# is_default_port
+
+
+def test_is_default_port_for_relative_url():
+ url = URL("/path/to")
+ assert not url.is_default_port()
+
+
+def test_is_default_port_for_absolute_url_without_port():
+ url = URL("http://example.com")
+ assert url.is_default_port()
+
+
+def test_is_default_port_for_absolute_url_with_default_port():
+ url = URL("http://example.com:80")
+ assert url.is_default_port()
+
+
+def test_is_default_port_for_absolute_url_with_nondefault_port():
+ url = URL("http://example.com:8080")
+ assert not url.is_default_port()
+
+
+def test_is_default_port_for_unknown_scheme():
+ url = URL("unknown://example.com:8080")
+ assert not url.is_default_port()
+
+
+#
+
+
+def test_no_scheme():
+ url = URL("example.com")
+ assert url.raw_host is None
+ assert url.raw_path == "example.com"
+ assert str(url) == "example.com"
+
+
+def test_no_scheme2():
+ url = URL("example.com/a/b")
+ assert url.raw_host is None
+ assert url.raw_path == "example.com/a/b"
+ assert str(url) == "example.com/a/b"
+
+
+def test_from_non_allowed():
+ with pytest.raises(TypeError):
+ URL(1234)
+
+
+def test_from_idna():
+ url = URL("http://xn--jxagkqfkduily1i.eu")
+ assert "http://xn--jxagkqfkduily1i.eu" == str(url)
+ url = URL("http://xn--einla-pqa.de/") # needs idna 2008
+ assert "http://xn--einla-pqa.de/" == str(url)
+
+
+def test_to_idna():
+ url = URL("http://εμπορικόσήμα.eu")
+ assert "http://xn--jxagkqfkduily1i.eu" == str(url)
+ url = URL("http://einlaß.de/")
+ assert "http://xn--einla-pqa.de/" == str(url)
+
+
+def test_from_ascii_login():
+ url = URL("http://" "%D0%B2%D0%B0%D1%81%D1%8F" "@host:1234/")
+ assert ("http://" "%D0%B2%D0%B0%D1%81%D1%8F" "@host:1234/") == str(url)
+
+
+def test_from_non_ascii_login():
+ url = URL("http://вася@host:1234/")
+ assert ("http://" "%D0%B2%D0%B0%D1%81%D1%8F" "@host:1234/") == str(url)
+
+
+def test_from_ascii_login_and_password():
+ url = URL(
+ "http://"
+ "%D0%B2%D0%B0%D1%81%D1%8F"
+ ":%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C"
+ "@host:1234/"
+ )
+ assert (
+ "http://"
+ "%D0%B2%D0%B0%D1%81%D1%8F"
+ ":%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C"
+ "@host:1234/"
+ ) == str(url)
+
+
+def test_from_non_ascii_login_and_password():
+ url = URL("http://вася:пароль@host:1234/")
+ assert (
+ "http://"
+ "%D0%B2%D0%B0%D1%81%D1%8F"
+ ":%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C"
+ "@host:1234/"
+ ) == str(url)
+
+
+def test_from_ascii_path():
+ url = URL("http://example.com/" "%D0%BF%D1%83%D1%82%D1%8C/%D1%82%D1%83%D0%B4%D0%B0")
+ assert (
+ "http://example.com/" "%D0%BF%D1%83%D1%82%D1%8C/%D1%82%D1%83%D0%B4%D0%B0"
+ ) == str(url)
+
+
+def test_from_ascii_path_lower_case():
+ url = URL("http://example.com/" "%d0%bf%d1%83%d1%82%d1%8c/%d1%82%d1%83%d0%b4%d0%b0")
+ assert (
+ "http://example.com/" "%D0%BF%D1%83%D1%82%D1%8C/%D1%82%D1%83%D0%B4%D0%B0"
+ ) == str(url)
+
+
+def test_from_non_ascii_path():
+ url = URL("http://example.com/путь/туда")
+ assert (
+ "http://example.com/" "%D0%BF%D1%83%D1%82%D1%8C/%D1%82%D1%83%D0%B4%D0%B0"
+ ) == str(url)
+
+
+def test_bytes():
+ url = URL("http://example.com/путь/туда")
+ assert (
+ b"http://example.com/%D0%BF%D1%83%D1%82%D1%8C/%D1%82%D1%83%D0%B4%D0%B0"
+ == bytes(url)
+ )
+
+
+def test_from_ascii_query_parts():
+ url = URL(
+ "http://example.com/"
+ "?%D0%BF%D0%B0%D1%80%D0%B0%D0%BC"
+ "=%D0%B7%D0%BD%D0%B0%D1%87"
+ )
+ assert (
+ "http://example.com/"
+ "?%D0%BF%D0%B0%D1%80%D0%B0%D0%BC"
+ "=%D0%B7%D0%BD%D0%B0%D1%87"
+ ) == str(url)
+
+
+def test_from_non_ascii_query_parts():
+ url = URL("http://example.com/?парам=знач")
+ assert (
+ "http://example.com/"
+ "?%D0%BF%D0%B0%D1%80%D0%B0%D0%BC"
+ "=%D0%B7%D0%BD%D0%B0%D1%87"
+ ) == str(url)
+
+
+def test_from_non_ascii_query_parts2():
+ url = URL("http://example.com/?п=з&ю=б")
+ assert "http://example.com/?%D0%BF=%D0%B7&%D1%8E=%D0%B1" == str(url)
+
+
+def test_from_ascii_fragment():
+ url = URL("http://example.com/" "#%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82")
+ assert (
+ "http://example.com/" "#%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82"
+ ) == str(url)
+
+
+def test_from_bytes_with_non_ascii_fragment():
+ url = URL("http://example.com/#фрагмент")
+ assert (
+ "http://example.com/" "#%D1%84%D1%80%D0%B0%D0%B3%D0%BC%D0%B5%D0%BD%D1%82"
+ ) == str(url)
+
+
+def test_to_str():
+ url = URL("http://εμπορικόσήμα.eu/")
+ assert "http://xn--jxagkqfkduily1i.eu/" == str(url)
+
+
+def test_to_str_long():
+ url = URL(
+ "https://host-12345678901234567890123456789012345678901234567890" "-name:8888/"
+ )
+ expected = (
+ "https://host-"
+ "12345678901234567890123456789012345678901234567890"
+ "-name:8888/"
+ )
+ assert expected == str(url)
+
+
+def test_decoding_with_2F_in_path():
+ url = URL("http://example.com/path%2Fto")
+ assert "http://example.com/path%2Fto" == str(url)
+ assert url == URL(str(url))
+
+
+def test_decoding_with_26_and_3D_in_query():
+ url = URL("http://example.com/?%26=%3D")
+ assert "http://example.com/?%26=%3D" == str(url)
+ assert url == URL(str(url))
+
+
+def test_fragment_only_url():
+ url = URL("#frag")
+ assert str(url) == "#frag"
+
+
+def test_url_from_url():
+ url = URL("http://example.com")
+ assert URL(url) == url
+ assert URL(url).raw_parts == ("/",)
+
+
+def test_lowercase_scheme():
+ url = URL("HTTP://example.com")
+ assert str(url) == "http://example.com"
+
+
+def test_str_for_empty_url():
+ url = URL()
+ assert "" == str(url)
+
+
+def test_parent_for_empty_url():
+ url = URL()
+ assert url is url.parent
+
+
+def test_empty_value_for_query():
+ url = URL("http://example.com/path").with_query({"a": ""})
+ assert str(url) == "http://example.com/path?a="
+
+
+def test_none_value_for_query():
+ with pytest.raises(TypeError):
+ URL("http://example.com/path").with_query({"a": None})
+
+
+def test_decode_pct_in_path():
+ url = URL("http://www.python.org/%7Eguido")
+ assert "http://www.python.org/~guido" == str(url)
+
+
+def test_decode_pct_in_path_lower_case():
+ url = URL("http://www.python.org/%7eguido")
+ assert "http://www.python.org/~guido" == str(url)
+
+
+# join
+
+
+def test_join():
+ base = URL("http://www.cwi.nl/%7Eguido/Python.html")
+ url = URL("FAQ.html")
+ url2 = base.join(url)
+ assert str(url2) == "http://www.cwi.nl/~guido/FAQ.html"
+
+
+def test_join_absolute():
+ base = URL("http://www.cwi.nl/%7Eguido/Python.html")
+ url = URL("//www.python.org/%7Eguido")
+ url2 = base.join(url)
+ assert str(url2) == "http://www.python.org/~guido"
+
+
+def test_join_non_url():
+ base = URL("http://example.com")
+ with pytest.raises(TypeError):
+ base.join("path/to")
+
+
+NORMAL = [
+ ("g:h", "g:h"),
+ ("g", "http://a/b/c/g"),
+ ("./g", "http://a/b/c/g"),
+ ("g/", "http://a/b/c/g/"),
+ ("/g", "http://a/g"),
+ ("//g", "http://g"),
+ ("?y", "http://a/b/c/d;p?y"),
+ ("g?y", "http://a/b/c/g?y"),
+ ("#s", "http://a/b/c/d;p?q#s"),
+ ("g#s", "http://a/b/c/g#s"),
+ ("g?y#s", "http://a/b/c/g?y#s"),
+ (";x", "http://a/b/c/;x"),
+ ("g;x", "http://a/b/c/g;x"),
+ ("g;x?y#s", "http://a/b/c/g;x?y#s"),
+ ("", "http://a/b/c/d;p?q"),
+ (".", "http://a/b/c/"),
+ ("./", "http://a/b/c/"),
+ ("..", "http://a/b/"),
+ ("../", "http://a/b/"),
+ ("../g", "http://a/b/g"),
+ ("../..", "http://a/"),
+ ("../../", "http://a/"),
+ ("../../g", "http://a/g"),
+]
+
+
+@pytest.mark.parametrize("url,expected", NORMAL)
+def test_join_from_rfc_3986_normal(url, expected):
+ # test case from https://tools.ietf.org/html/rfc3986.html#section-5.4
+ base = URL("http://a/b/c/d;p?q")
+ url = URL(url)
+ expected = URL(expected)
+ assert base.join(url) == expected
+
+
+ABNORMAL = [
+ ("../../../g", "http://a/g"),
+ ("../../../../g", "http://a/g"),
+ ("/./g", "http://a/g"),
+ ("/../g", "http://a/g"),
+ ("g.", "http://a/b/c/g."),
+ (".g", "http://a/b/c/.g"),
+ ("g..", "http://a/b/c/g.."),
+ ("..g", "http://a/b/c/..g"),
+ ("./../g", "http://a/b/g"),
+ ("./g/.", "http://a/b/c/g/"),
+ ("g/./h", "http://a/b/c/g/h"),
+ ("g/../h", "http://a/b/c/h"),
+ ("g;x=1/./y", "http://a/b/c/g;x=1/y"),
+ ("g;x=1/../y", "http://a/b/c/y"),
+ ("g?y/./x", "http://a/b/c/g?y/./x"),
+ ("g?y/../x", "http://a/b/c/g?y/../x"),
+ ("g#s/./x", "http://a/b/c/g#s/./x"),
+ ("g#s/../x", "http://a/b/c/g#s/../x"),
+]
+
+
+@pytest.mark.parametrize("url,expected", ABNORMAL)
+def test_join_from_rfc_3986_abnormal(url, expected):
+ # test case from https://tools.ietf.org/html/rfc3986.html#section-5.4.2
+ base = URL("http://a/b/c/d;p?q")
+ url = URL(url)
+ expected = URL(expected)
+ assert base.join(url) == expected
+
+
+def test_split_result_non_decoded():
+ with pytest.raises(ValueError):
+ URL(SplitResult("http", "example.com", "path", "qs", "frag"))
+
+
+def test_human_repr():
+ url = URL("http://вася:пароль@хост.домен:8080/путь/сюда?арг=вал#фраг")
+ s = url.human_repr()
+ assert URL(s) == url
+ assert s == "http://вася:пароль@хост.домен:8080/путь/сюда?арг=вал#фраг"
+
+
+def test_human_repr_defaults():
+ url = URL("путь")
+ s = url.human_repr()
+ assert s == "путь"
+
+
+def test_human_repr_default_port():
+ url = URL("http://вася:пароль@хост.домен/путь/сюда?арг=вал#фраг")
+ s = url.human_repr()
+ assert URL(s) == url
+ assert s == "http://вася:пароль@хост.домен/путь/сюда?арг=вал#фраг"
+
+
+def test_human_repr_ipv6():
+ url = URL("http://[::1]:8080/path")
+ s = url.human_repr()
+ url2 = URL(s)
+ assert url2 == url
+ assert url2.host == "::1"
+ assert s == "http://[::1]:8080/path"
+
+
+def test_human_repr_delimiters():
+ url = URL.build(
+ scheme="http",
+ user=" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ password=" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ host="хост.домен",
+ port=8080,
+ path="/ !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ query={
+ " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~": " !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
+ },
+ fragment=" !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~",
+ )
+ s = url.human_repr()
+ assert URL(s) == url
+ assert (
+ s == "http:// !\"%23$%25&'()*+,-.%2F%3A;<=>%3F%40%5B\\%5D^_`{|}~"
+ ": !\"%23$%25&'()*+,-.%2F%3A;<=>%3F%40%5B\\%5D^_`{|}~"
+ "@хост.домен:8080"
+ "/ !\"%23$%25&'()*+,-./:;<=>%3F@[\\]^_`{|}~"
+ "? !\"%23$%25%26'()*%2B,-./:%3B<%3D>?@[\\]^_`{|}~"
+ "= !\"%23$%25%26'()*%2B,-./:%3B<%3D>?@[\\]^_`{|}~"
+ "# !\"#$%25&'()*+,-./:;<=>?@[\\]^_`{|}~"
+ )
+
+
+def test_human_repr_non_printable():
+ url = URL.build(
+ scheme="http",
+ user="вася\n\xad\u200b",
+ password="пароль\n\xad\u200b",
+ host="хост.домен",
+ port=8080,
+ path="/путь\n\xad\u200b",
+ query={"арг\n\xad\u200b": "вал\n\xad\u200b"},
+ fragment="фраг\n\xad\u200b",
+ )
+ s = url.human_repr()
+ assert URL(s) == url
+ assert (
+ s == "http://вася%0A%C2%AD%E2%80%8B:пароль%0A%C2%AD%E2%80%8B"
+ "@хост.домен:8080"
+ "/путь%0A%C2%AD%E2%80%8B"
+ "?арг%0A%C2%AD%E2%80%8B=вал%0A%C2%AD%E2%80%8B"
+ "#фраг%0A%C2%AD%E2%80%8B"
+ )
+
+
+# relative
+
+
+def test_relative():
+ url = URL("http://user:pass@example.com:8080/path?a=b#frag")
+ rel = url.relative()
+ assert str(rel) == "/path?a=b#frag"
+
+
+def test_relative_is_relative():
+ url = URL("http://user:pass@example.com:8080/path?a=b#frag")
+ rel = url.relative()
+ assert not rel.is_absolute()
+
+
+def test_relative_abs_parts_are_removed():
+ url = URL("http://user:pass@example.com:8080/path?a=b#frag")
+ rel = url.relative()
+ assert not rel.scheme
+ assert not rel.user
+ assert not rel.password
+ assert not rel.host
+ assert not rel.port
+
+
+def test_relative_fails_on_rel_url():
+ with pytest.raises(ValueError):
+ URL("/path?a=b#frag").relative()
+
+
+def test_slash_and_question_in_query():
+ u = URL("http://example.com/path?http://example.com/p?a#b")
+ assert u.query_string == "http://example.com/p?a"
+
+
+def test_slash_and_question_in_fragment():
+ u = URL("http://example.com/path#http://example.com/p?a")
+ assert u.fragment == "http://example.com/p?a"
+
+
+def test_requoting():
+ u = URL("http://127.0.0.1/?next=http%3A//example.com/")
+ assert u.raw_query_string == "next=http://example.com/"
+ assert str(u) == "http://127.0.0.1/?next=http://example.com/"
diff --git a/contrib/python/yarl/tests/test_url_build.py b/contrib/python/yarl/tests/test_url_build.py
new file mode 100644
index 0000000000..51969fa849
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url_build.py
@@ -0,0 +1,259 @@
+import pytest
+
+from yarl import URL
+
+# build classmethod
+
+
+def test_build_without_arguments():
+ u = URL.build()
+ assert str(u) == ""
+
+
+def test_build_simple():
+ u = URL.build(scheme="http", host="127.0.0.1")
+ assert str(u) == "http://127.0.0.1"
+
+
+def test_build_with_scheme():
+ u = URL.build(scheme="blob", path="path")
+ assert str(u) == "blob:path"
+
+
+def test_build_with_host():
+ u = URL.build(host="127.0.0.1")
+ assert str(u) == "//127.0.0.1"
+ assert u == URL("//127.0.0.1")
+
+
+def test_build_with_scheme_and_host():
+ u = URL.build(scheme="http", host="127.0.0.1")
+ assert str(u) == "http://127.0.0.1"
+ assert u == URL("http://127.0.0.1")
+
+
+def test_build_with_port():
+ with pytest.raises(ValueError):
+ URL.build(port=8000)
+
+ u = URL.build(scheme="http", host="127.0.0.1", port=8000)
+ assert str(u) == "http://127.0.0.1:8000"
+
+
+def test_build_with_user():
+ u = URL.build(scheme="http", host="127.0.0.1", user="foo")
+ assert str(u) == "http://foo@127.0.0.1"
+
+
+def test_build_with_user_password():
+ u = URL.build(scheme="http", host="127.0.0.1", user="foo", password="bar")
+ assert str(u) == "http://foo:bar@127.0.0.1"
+
+
+def test_build_with_query_and_query_string():
+ with pytest.raises(ValueError):
+ URL.build(
+ scheme="http",
+ host="127.0.0.1",
+ user="foo",
+ password="bar",
+ port=8000,
+ path="/index.html",
+ query=dict(arg="value1"),
+ query_string="arg=value1",
+ fragment="top",
+ )
+
+
+def test_build_with_all():
+ u = URL.build(
+ scheme="http",
+ host="127.0.0.1",
+ user="foo",
+ password="bar",
+ port=8000,
+ path="/index.html",
+ query_string="arg=value1",
+ fragment="top",
+ )
+ assert str(u) == "http://foo:bar@127.0.0.1:8000/index.html?arg=value1#top"
+
+
+def test_build_with_authority_and_host():
+ with pytest.raises(ValueError):
+ URL.build(authority="host.com", host="example.com")
+
+
+def test_build_with_authority():
+ url = URL.build(scheme="http", authority="ваня:bar@host.com:8000", path="path")
+ assert str(url) == "http://%D0%B2%D0%B0%D0%BD%D1%8F:bar@host.com:8000/path"
+
+
+def test_build_with_authority_without_encoding():
+ url = URL.build(
+ scheme="http", authority="foo:bar@host.com:8000", path="path", encoded=True
+ )
+ assert str(url) == "http://foo:bar@host.com:8000/path"
+
+
+def test_query_str():
+ u = URL.build(scheme="http", host="127.0.0.1", path="/", query_string="arg=value1")
+ assert str(u) == "http://127.0.0.1/?arg=value1"
+
+
+def test_query_dict():
+ u = URL.build(scheme="http", host="127.0.0.1", path="/", query=dict(arg="value1"))
+
+ assert str(u) == "http://127.0.0.1/?arg=value1"
+
+
+def test_build_path_quoting():
+ u = URL.build(
+ scheme="http", host="127.0.0.1", path="/файл.jpg", query=dict(arg="Привет")
+ )
+
+ assert u == URL("http://127.0.0.1/файл.jpg?arg=Привет")
+ assert str(u) == (
+ "http://127.0.0.1/%D1%84%D0%B0%D0%B9%D0%BB.jpg?"
+ "arg=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82"
+ )
+
+
+def test_build_query_quoting():
+ u = URL.build(scheme="http", host="127.0.0.1", path="/файл.jpg", query="arg=Привет")
+
+ assert u == URL("http://127.0.0.1/файл.jpg?arg=Привет")
+ assert str(u) == (
+ "http://127.0.0.1/%D1%84%D0%B0%D0%B9%D0%BB.jpg?"
+ "arg=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82"
+ )
+
+
+def test_build_query_only():
+ u = URL.build(query={"key": "value"})
+
+ assert str(u) == "?key=value"
+
+
+def test_build_drop_dots():
+ u = URL.build(scheme="http", host="example.com", path="/path/../to")
+ assert str(u) == "http://example.com/to"
+
+
+def test_build_encode():
+ u = URL.build(
+ scheme="http",
+ host="историк.рф",
+ path="/путь/файл",
+ query_string="ключ=знач",
+ fragment="фраг",
+ )
+ expected = (
+ "http://xn--h1aagokeh.xn--p1ai"
+ "/%D0%BF%D1%83%D1%82%D1%8C/%D1%84%D0%B0%D0%B9%D0%BB"
+ "?%D0%BA%D0%BB%D1%8E%D1%87=%D0%B7%D0%BD%D0%B0%D1%87"
+ "#%D1%84%D1%80%D0%B0%D0%B3"
+ )
+ assert str(u) == expected
+
+
+def test_build_already_encoded():
+ # resulting URL is invalid but not encoded
+ u = URL.build(
+ scheme="http",
+ host="историк.рф",
+ path="/путь/файл",
+ query_string="ключ=знач",
+ fragment="фраг",
+ encoded=True,
+ )
+ assert str(u) == "http://историк.рф/путь/файл?ключ=знач#фраг"
+
+
+def test_build_percent_encoded():
+ u = URL.build(
+ scheme="http",
+ host="%2d.org",
+ user="u%2d",
+ password="p%2d",
+ path="/%2d",
+ query_string="k%2d=v%2d",
+ fragment="f%2d",
+ )
+ assert str(u) == "http://u%252d:p%252d@%2d.org/%252d?k%252d=v%252d#f%252d"
+ assert u.raw_host == "%2d.org"
+ assert u.host == "%2d.org"
+ assert u.raw_user == "u%252d"
+ assert u.user == "u%2d"
+ assert u.raw_password == "p%252d"
+ assert u.password == "p%2d"
+ assert u.raw_authority == "u%252d:p%252d@%2d.org"
+ assert u.authority == "u%2d:p%2d@%2d.org:80"
+ assert u.raw_path == "/%252d"
+ assert u.path == "/%2d"
+ assert u.query == {"k%2d": "v%2d"}
+ assert u.raw_query_string == "k%252d=v%252d"
+ assert u.query_string == "k%2d=v%2d"
+ assert u.raw_fragment == "f%252d"
+ assert u.fragment == "f%2d"
+
+
+def test_build_with_authority_percent_encoded():
+ u = URL.build(scheme="http", authority="u%2d:p%2d@%2d.org")
+ assert str(u) == "http://u%252d:p%252d@%2d.org"
+ assert u.raw_host == "%2d.org"
+ assert u.host == "%2d.org"
+ assert u.raw_user == "u%252d"
+ assert u.user == "u%2d"
+ assert u.raw_password == "p%252d"
+ assert u.password == "p%2d"
+ assert u.raw_authority == "u%252d:p%252d@%2d.org"
+ assert u.authority == "u%2d:p%2d@%2d.org:80"
+
+
+def test_build_with_authority_percent_encoded_already_encoded():
+ u = URL.build(scheme="http", authority="u%2d:p%2d@%2d.org", encoded=True)
+ assert str(u) == "http://u%2d:p%2d@%2d.org"
+ assert u.raw_host == "%2d.org"
+ assert u.host == "%2d.org"
+ assert u.user == "u-"
+ assert u.raw_user == "u%2d"
+ assert u.password == "p-"
+ assert u.raw_password == "p%2d"
+ assert u.authority == "u-:p-@%2d.org:80"
+ assert u.raw_authority == "u%2d:p%2d@%2d.org"
+
+
+def test_build_with_authority_with_path_with_leading_slash():
+ u = URL.build(scheme="http", host="example.com", path="/path_with_leading_slash")
+ assert str(u) == "http://example.com/path_with_leading_slash"
+
+
+def test_build_with_authority_with_empty_path():
+ u = URL.build(scheme="http", host="example.com", path="")
+ assert str(u) == "http://example.com"
+
+
+def test_build_with_authority_with_path_without_leading_slash():
+ with pytest.raises(ValueError):
+ URL.build(scheme="http", host="example.com", path="path_without_leading_slash")
+
+
+def test_build_with_none_host():
+ with pytest.raises(TypeError, match="NoneType is illegal for.*host"):
+ URL.build(scheme="http", host=None)
+
+
+def test_build_with_none_path():
+ with pytest.raises(TypeError):
+ URL.build(scheme="http", host="example.com", path=None)
+
+
+def test_build_with_none_query_string():
+ with pytest.raises(TypeError):
+ URL.build(scheme="http", host="example.com", query_string=None)
+
+
+def test_build_with_none_fragment():
+ with pytest.raises(TypeError):
+ URL.build(scheme="http", host="example.com", fragment=None)
diff --git a/contrib/python/yarl/tests/test_url_cmp_and_hash.py b/contrib/python/yarl/tests/test_url_cmp_and_hash.py
new file mode 100644
index 0000000000..17c42e3566
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url_cmp_and_hash.py
@@ -0,0 +1,88 @@
+from yarl import URL
+
+# comparison and hashing
+
+
+def test_ne_str():
+ url = URL("http://example.com/")
+ assert url != "http://example.com/"
+
+
+def test_eq():
+ url = URL("http://example.com/")
+ assert url == URL("http://example.com/")
+
+
+def test_hash():
+ assert hash(URL("http://example.com/")) == hash(URL("http://example.com/"))
+
+
+def test_hash_double_call():
+ url = URL("http://example.com/")
+ assert hash(url) == hash(url)
+
+
+def test_le_less():
+ url1 = URL("http://example1.com/")
+ url2 = URL("http://example2.com/")
+
+ assert url1 <= url2
+
+
+def test_le_eq():
+ url1 = URL("http://example.com/")
+ url2 = URL("http://example.com/")
+
+ assert url1 <= url2
+
+
+def test_le_not_implemented():
+ url = URL("http://example1.com/")
+
+ assert url.__le__(123) is NotImplemented
+
+
+def test_lt():
+ url1 = URL("http://example1.com/")
+ url2 = URL("http://example2.com/")
+
+ assert url1 < url2
+
+
+def test_lt_not_implemented():
+ url = URL("http://example1.com/")
+
+ assert url.__lt__(123) is NotImplemented
+
+
+def test_ge_more():
+ url1 = URL("http://example1.com/")
+ url2 = URL("http://example2.com/")
+
+ assert url2 >= url1
+
+
+def test_ge_eq():
+ url1 = URL("http://example.com/")
+ url2 = URL("http://example.com/")
+
+ assert url2 >= url1
+
+
+def test_ge_not_implemented():
+ url = URL("http://example1.com/")
+
+ assert url.__ge__(123) is NotImplemented
+
+
+def test_gt():
+ url1 = URL("http://example1.com/")
+ url2 = URL("http://example2.com/")
+
+ assert url2 > url1
+
+
+def test_gt_not_implemented():
+ url = URL("http://example1.com/")
+
+ assert url.__gt__(123) is NotImplemented
diff --git a/contrib/python/yarl/tests/test_url_parsing.py b/contrib/python/yarl/tests/test_url_parsing.py
new file mode 100644
index 0000000000..cc753fcd0c
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url_parsing.py
@@ -0,0 +1,582 @@
+import sys
+
+import pytest
+
+from yarl import URL
+
+
+class TestScheme:
+ def test_scheme_path(self):
+ u = URL("scheme:path")
+ assert u.scheme == "scheme"
+ assert u.host is None
+ assert u.path == "path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_path_other(self):
+ u = URL("scheme:path:other")
+ assert u.scheme == "scheme"
+ assert u.host is None
+ assert u.path == "path:other"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_complex_scheme(self):
+ u = URL("allow+chars-33.:path")
+ assert u.scheme == "allow+chars-33."
+ assert u.host is None
+ assert u.path == "path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_only(self):
+ u = URL("simple:")
+ assert u.scheme == "simple"
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_no_scheme1(self):
+ u = URL("google.com:80")
+ # See: https://bugs.python.org/issue27657
+ if (
+ sys.version_info[:3] == (3, 7, 6)
+ or sys.version_info[:3] == (3, 8, 1)
+ or sys.version_info >= (3, 9, 0)
+ ):
+ assert u.scheme == "google.com"
+ assert u.host is None
+ assert u.path == "80"
+ else:
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == "google.com:80"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_no_scheme2(self):
+ u = URL("google.com:80/root")
+ assert u.scheme == "google.com"
+ assert u.host is None
+ assert u.path == "80/root"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_not_a_scheme1(self):
+ u = URL("not_cheme:path")
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == "not_cheme:path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_not_a_scheme2(self):
+ u = URL("signals37:book")
+ assert u.scheme == "signals37"
+ assert u.host is None
+ assert u.path == "book"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_rel_path1(self):
+ u = URL(":relative-path")
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == ":relative-path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_rel_path2(self):
+ u = URL(":relative/path")
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == ":relative/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_weird(self):
+ u = URL("://and-this")
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == "://and-this"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+
+class TestHost:
+ def test_canonical(self):
+ u = URL("scheme://host/path")
+ assert u.scheme == "scheme"
+ assert u.host == "host"
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_absolute_no_scheme(self):
+ u = URL("//host/path")
+ assert u.scheme == ""
+ assert u.host == "host"
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_absolute_no_scheme_complex_host(self):
+ u = URL("//host+path")
+ assert u.scheme == ""
+ assert u.host == "host+path"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_absolute_no_scheme_simple_host(self):
+ u = URL("//host")
+ assert u.scheme == ""
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_weird_host(self):
+ u = URL("//this+is$also&host!")
+ assert u.scheme == ""
+ assert u.host == "this+is$also&host!"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_no_host(self):
+ u = URL("scheme:/host/path")
+ assert u.scheme == "scheme"
+ assert u.host is None
+ assert u.path == "/host/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_scheme_no_host2(self):
+ u = URL("scheme:///host/path")
+ assert u.scheme == "scheme"
+ assert u.host is None
+ assert u.path == "/host/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_no_scheme_no_host(self):
+ u = URL("scheme//host/path")
+ assert u.scheme == ""
+ assert u.host is None
+ assert u.path == "scheme//host/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_ipv4(self):
+ u = URL("//127.0.0.1/")
+ assert u.scheme == ""
+ assert u.host == "127.0.0.1"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_ipv6(self):
+ u = URL("//[::1]/")
+ assert u.scheme == ""
+ assert u.host == "::1"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_ipvfuture_address(self):
+ u = URL("//[v1.-1]/")
+ assert u.scheme == ""
+ assert u.host == "v1.-1"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+
+class TestPort:
+ def test_canonical(self):
+ u = URL("//host:80/path")
+ assert u.scheme == ""
+ assert u.host == "host"
+ assert u.port == 80
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_no_path(self):
+ u = URL("//host:80")
+ assert u.scheme == ""
+ assert u.host == "host"
+ assert u.port == 80
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ @pytest.mark.xfail(reason="https://github.com/aio-libs/yarl/issues/821")
+ def test_no_host(self):
+ u = URL("//:80")
+ assert u.scheme == ""
+ assert u.host == ""
+ assert u.port == 80
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_double_port(self):
+ with pytest.raises(ValueError):
+ URL("//h:22:80/")
+
+ def test_bad_port(self):
+ with pytest.raises(ValueError):
+ URL("//h:no/path")
+
+ def test_another_bad_port(self):
+ with pytest.raises(ValueError):
+ URL("//h:22:no/path")
+
+ def test_bad_port_again(self):
+ with pytest.raises(ValueError):
+ URL("//h:-80/path")
+
+
+class TestUserInfo:
+ def test_canonical(self):
+ u = URL("sch://user@host/")
+ assert u.scheme == "sch"
+ assert u.user == "user"
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_user_pass(self):
+ u = URL("//user:pass@host")
+ assert u.scheme == ""
+ assert u.user == "user"
+ assert u.password == "pass"
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_complex_userinfo(self):
+ u = URL("//user:pas:and:more@host")
+ assert u.scheme == ""
+ assert u.user == "user"
+ assert u.password == "pas:and:more"
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_no_user(self):
+ u = URL("//:pas:@host")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password == "pas:"
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_weird_user(self):
+ u = URL("//!($&')*+,;=@host")
+ assert u.scheme == ""
+ assert u.user == "!($&')*+,;="
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_weird_user2(self):
+ u = URL("//user@info@ya.ru")
+ assert u.scheme == ""
+ assert u.user == "user@info"
+ assert u.password is None
+ assert u.host == "ya.ru"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_weird_user3(self):
+ u = URL("//%5Bsome%5D@host")
+ assert u.scheme == ""
+ assert u.user == "[some]"
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+
+class TestQuery_String:
+ def test_simple(self):
+ u = URL("?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == "query"
+ assert u.fragment == ""
+
+ def test_scheme_query(self):
+ u = URL("http:?query")
+ assert u.scheme == "http"
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == "query"
+ assert u.fragment == ""
+
+ def test_abs_url_query(self):
+ u = URL("//host?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == "query"
+ assert u.fragment == ""
+
+ def test_abs_url_path_query(self):
+ u = URL("//host/path?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/path"
+ assert u.query_string == "query"
+ assert u.fragment == ""
+
+ def test_double_question_mark(self):
+ u = URL("//ho?st/path?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "ho"
+ assert u.path == "/"
+ assert u.query_string == "st/path?query"
+ assert u.fragment == ""
+
+ def test_complex_query(self):
+ u = URL("?a://b:c@d.e/f?g#h")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == "a://b:c@d.e/f?g"
+ assert u.fragment == "h"
+
+ def test_query_in_fragment(self):
+ u = URL("#?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == "?query"
+
+
+class TestFragment:
+ def test_simple(self):
+ u = URL("#frag")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == "frag"
+
+ def test_scheme_frag(self):
+ u = URL("http:#frag")
+ assert u.scheme == "http"
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == "frag"
+
+ def test_host_frag(self):
+ u = URL("//host#frag")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == "frag"
+
+ def test_scheme_path_frag(self):
+ u = URL("//host/path#frag")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == "frag"
+
+ def test_scheme_query_frag(self):
+ u = URL("//host?query#frag")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == "query"
+ assert u.fragment == "frag"
+
+ def test_host_frag_query(self):
+ u = URL("//ho#st/path?query")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "ho"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == "st/path?query"
+
+ def test_complex_frag(self):
+ u = URL("#a://b:c@d.e/f?g#h")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == "a://b:c@d.e/f?g#h"
+
+
+class TestStripEmptyParts:
+ def test_all_empty(self):
+ with pytest.raises(ValueError):
+ URL("//@:?#")
+
+ def test_path_only(self):
+ u = URL("///path")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_user(self):
+ u = URL("//@host")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_port(self):
+ u = URL("//host:")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_port_and_path(self):
+ u = URL("//host:/")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host == "host"
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_path_only(self):
+ u = URL("/")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "/"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_relative_path_only(self):
+ u = URL("path")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_path(self):
+ u = URL("/path")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_query_with_path(self):
+ u = URL("/path?")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_query(self):
+ u = URL("?")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_query_with_frag(self):
+ u = URL("?#frag")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == "frag"
+
+ def test_path_empty_frag(self):
+ u = URL("/path#")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == "/path"
+ assert u.query_string == ""
+ assert u.fragment == ""
+
+ def test_empty_path(self):
+ u = URL("#")
+ assert u.scheme == ""
+ assert u.user is None
+ assert u.password is None
+ assert u.host is None
+ assert u.path == ""
+ assert u.query_string == ""
+ assert u.fragment == ""
diff --git a/contrib/python/yarl/tests/test_url_query.py b/contrib/python/yarl/tests/test_url_query.py
new file mode 100644
index 0000000000..bcd2433cbc
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url_query.py
@@ -0,0 +1,173 @@
+from typing import List, Tuple
+from urllib.parse import parse_qs, urlencode
+
+import pytest
+from multidict import MultiDict, MultiDictProxy
+
+from yarl import URL
+
+# ========================================
+# Basic chars in query values
+# ========================================
+
+URLS_WITH_BASIC_QUERY_VALUES: List[Tuple[URL, MultiDict]] = [
+ # Empty strings, keys and values
+ (
+ URL("http://example.com"),
+ MultiDict(),
+ ),
+ (
+ URL("http://example.com?a="),
+ MultiDict([("a", "")]),
+ ),
+ # ASCII chars
+ (
+ URL("http://example.com?a+b=c+d"),
+ MultiDict({"a b": "c d"}),
+ ),
+ (
+ URL("http://example.com?a=1&b=2"),
+ MultiDict([("a", "1"), ("b", "2")]),
+ ),
+ (
+ URL("http://example.com?a=1&b=2&a=3"),
+ MultiDict([("a", "1"), ("b", "2"), ("a", "3")]),
+ ),
+ # Non-ASCI BMP chars
+ (
+ URL("http://example.com?ключ=знач"),
+ MultiDict({"ключ": "знач"}),
+ ),
+ (
+ URL("http://example.com?foo=ᴜɴɪᴄᴏᴅᴇ"),
+ MultiDict({"foo": "ᴜɴɪᴄᴏᴅᴇ"}),
+ ),
+ # Non-BMP chars
+ (
+ URL("http://example.com?bar=𝕦𝕟𝕚𝕔𝕠𝕕𝕖"),
+ MultiDict({"bar": "𝕦𝕟𝕚𝕔𝕠𝕕𝕖"}),
+ ),
+]
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query",
+ URLS_WITH_BASIC_QUERY_VALUES,
+)
+def test_query_basic_parsing(original_url, expected_query):
+ assert isinstance(original_url.query, MultiDictProxy)
+ assert original_url.query == expected_query
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query",
+ URLS_WITH_BASIC_QUERY_VALUES,
+)
+def test_query_basic_update_query(original_url, expected_query):
+ new_url = original_url.update_query({})
+ assert new_url == original_url
+
+
+def test_query_dont_unqoute_twice():
+ sample_url = "http://base.place?" + urlencode({"a": "/////"})
+ query = urlencode({"url": sample_url})
+ full_url = "http://test_url.aha?" + query
+
+ url = URL(full_url)
+ assert url.query["url"] == sample_url
+
+
+# ========================================
+# Reserved chars in query values
+# ========================================
+
+# See https://github.com/python/cpython#87133, which introduced a new
+# `separator` keyword argument to `urllib.parse.parse_qs` (among others).
+# If the name doesn't exist as a variable in the function bytecode, the
+# test is expected to fail.
+_SEMICOLON_XFAIL = pytest.mark.xfail(
+ condition="separator" not in parse_qs.__code__.co_varnames,
+ reason=(
+ "Python versions < 3.7.10, < 3.8.8 and < 3.9.2 lack a fix for "
+ 'CVE-2021-23336 dropping ";" as a valid query parameter separator, '
+ "making this test fail."
+ ),
+ strict=True,
+)
+
+
+URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES = [
+ # Ampersand
+ (URL("http://127.0.0.1/?a=10&b=20"), 2, "10"),
+ (URL("http://127.0.0.1/?a=10%26b=20"), 1, "10&b=20"),
+ (URL("http://127.0.0.1/?a=10%3Bb=20"), 1, "10;b=20"),
+ # Semicolon, which is *not* a query parameter separator as of RFC3986
+ (URL("http://127.0.0.1/?a=10;b=20"), 1, "10;b=20"),
+ (URL("http://127.0.0.1/?a=10%26b=20"), 1, "10&b=20"),
+ (URL("http://127.0.0.1/?a=10%3Bb=20"), 1, "10;b=20"),
+]
+URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL = [
+ # Ampersand
+ *URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[:3],
+ # Semicolon, which is *not* a query parameter separator as of RFC3986
+ # Mark the first of these as expecting to fail on old Python patch releases.
+ pytest.param(*URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[3], marks=_SEMICOLON_XFAIL),
+ *URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES[4:],
+]
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query_len, expected_value_a",
+ URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL,
+)
+def test_query_separators_from_parsing(
+ original_url,
+ expected_query_len,
+ expected_value_a,
+):
+ assert len(original_url.query) == expected_query_len
+ assert original_url.query["a"] == expected_value_a
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query_len, expected_value_a",
+ URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES_W_XFAIL,
+)
+def test_query_separators_from_update_query(
+ original_url,
+ expected_query_len,
+ expected_value_a,
+):
+ new_url = original_url.update_query({"c": expected_value_a})
+ assert new_url.query["a"] == expected_value_a
+ assert new_url.query["c"] == expected_value_a
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query_len, expected_value_a",
+ URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES,
+)
+def test_query_separators_from_with_query(
+ original_url,
+ expected_query_len,
+ expected_value_a,
+):
+ new_url = original_url.with_query({"c": expected_value_a})
+ assert new_url.query["c"] == expected_value_a
+
+
+@pytest.mark.parametrize(
+ "original_url, expected_query_len, expected_value_a",
+ URLS_WITH_RESERVED_CHARS_IN_QUERY_VALUES,
+)
+def test_query_from_empty_update_query(
+ original_url,
+ expected_query_len,
+ expected_value_a,
+):
+ new_url = original_url.update_query({})
+
+ assert new_url.query["a"] == original_url.query["a"]
+
+ if "b" in original_url.query:
+ assert new_url.query["b"] == original_url.query["b"]
diff --git a/contrib/python/yarl/tests/test_url_update_netloc.py b/contrib/python/yarl/tests/test_url_update_netloc.py
new file mode 100644
index 0000000000..cf0cc1c44c
--- /dev/null
+++ b/contrib/python/yarl/tests/test_url_update_netloc.py
@@ -0,0 +1,228 @@
+import pytest
+
+from yarl import URL
+
+# with_*
+
+
+def test_with_scheme():
+ url = URL("http://example.com")
+ assert str(url.with_scheme("https")) == "https://example.com"
+
+
+def test_with_scheme_uppercased():
+ url = URL("http://example.com")
+ assert str(url.with_scheme("HTTPS")) == "https://example.com"
+
+
+def test_with_scheme_for_relative_url():
+ with pytest.raises(ValueError):
+ URL("path/to").with_scheme("http")
+
+
+def test_with_scheme_invalid_type():
+ url = URL("http://example.com")
+ with pytest.raises(TypeError):
+ assert str(url.with_scheme(123))
+
+
+def test_with_user():
+ url = URL("http://example.com")
+ assert str(url.with_user("john")) == "http://john@example.com"
+
+
+def test_with_user_non_ascii():
+ url = URL("http://example.com")
+ url2 = url.with_user("вася")
+ assert url2.raw_user == "%D0%B2%D0%B0%D1%81%D1%8F"
+ assert url2.user == "вася"
+ assert url2.raw_authority == "%D0%B2%D0%B0%D1%81%D1%8F@example.com"
+ assert url2.authority == "вася@example.com:80"
+
+
+def test_with_user_percent_encoded():
+ url = URL("http://example.com")
+ url2 = url.with_user("%cf%80")
+ assert url2.raw_user == "%25cf%2580"
+ assert url2.user == "%cf%80"
+ assert url2.raw_authority == "%25cf%2580@example.com"
+ assert url2.authority == "%cf%80@example.com:80"
+
+
+def test_with_user_for_relative_url():
+ with pytest.raises(ValueError):
+ URL("path/to").with_user("user")
+
+
+def test_with_user_invalid_type():
+ url = URL("http://example.com:123")
+ with pytest.raises(TypeError):
+ url.with_user(123)
+
+
+def test_with_user_None():
+ url = URL("http://john@example.com")
+ assert str(url.with_user(None)) == "http://example.com"
+
+
+def test_with_user_ipv6():
+ url = URL("http://john:pass@[::1]:8080/")
+ assert str(url.with_user(None)) == "http://[::1]:8080/"
+
+
+def test_with_user_None_when_password_present():
+ url = URL("http://john:pass@example.com")
+ assert str(url.with_user(None)) == "http://example.com"
+
+
+def test_with_password():
+ url = URL("http://john@example.com")
+ assert str(url.with_password("pass")) == "http://john:pass@example.com"
+
+
+def test_with_password_ipv6():
+ url = URL("http://john:pass@[::1]:8080/")
+ assert str(url.with_password(None)) == "http://john@[::1]:8080/"
+
+
+def test_with_password_non_ascii():
+ url = URL("http://john@example.com")
+ url2 = url.with_password("пароль")
+ assert url2.raw_password == "%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C"
+ assert url2.password == "пароль"
+ assert url2.raw_authority == "john:%D0%BF%D0%B0%D1%80%D0%BE%D0%BB%D1%8C@example.com"
+ assert url2.authority == "john:пароль@example.com:80"
+
+
+def test_with_password_percent_encoded():
+ url = URL("http://john@example.com")
+ url2 = url.with_password("%cf%80")
+ assert url2.raw_password == "%25cf%2580"
+ assert url2.password == "%cf%80"
+ assert url2.raw_authority == "john:%25cf%2580@example.com"
+ assert url2.authority == "john:%cf%80@example.com:80"
+
+
+def test_with_password_non_ascii_with_colon():
+ url = URL("http://john@example.com")
+ url2 = url.with_password("п:а")
+ assert url2.raw_password == "%D0%BF%3A%D0%B0"
+ assert url2.password == "п:а"
+
+
+def test_with_password_for_relative_url():
+ with pytest.raises(ValueError):
+ URL("path/to").with_password("pass")
+
+
+def test_with_password_None():
+ url = URL("http://john:pass@example.com")
+ assert str(url.with_password(None)) == "http://john@example.com"
+
+
+def test_with_password_invalid_type():
+ url = URL("http://example.com:123")
+ with pytest.raises(TypeError):
+ url.with_password(123)
+
+
+def test_with_password_and_empty_user():
+ url = URL("http://example.com")
+ url2 = url.with_password("pass")
+ assert url2.password == "pass"
+ assert url2.user is None
+ assert str(url2) == "http://:pass@example.com"
+
+
+def test_from_str_with_host_ipv4():
+ url = URL("http://host:80")
+ url = url.with_host("192.168.1.1")
+ assert url.raw_host == "192.168.1.1"
+
+
+def test_from_str_with_host_ipv6():
+ url = URL("http://host:80")
+ url = url.with_host("::1")
+ assert url.raw_host == "::1"
+
+
+def test_with_host():
+ url = URL("http://example.com:123")
+ assert str(url.with_host("example.org")) == "http://example.org:123"
+
+
+def test_with_host_empty():
+ url = URL("http://example.com:123")
+ with pytest.raises(ValueError):
+ url.with_host("")
+
+
+def test_with_host_non_ascii():
+ url = URL("http://example.com:123")
+ url2 = url.with_host("историк.рф")
+ assert url2.raw_host == "xn--h1aagokeh.xn--p1ai"
+ assert url2.host == "историк.рф"
+ assert url2.raw_authority == "xn--h1aagokeh.xn--p1ai:123"
+ assert url2.authority == "историк.рф:123"
+
+
+def test_with_host_percent_encoded():
+ url = URL("http://%25cf%2580%cf%80:%25cf%2580%cf%80@example.com:123")
+ url2 = url.with_host("%cf%80.org")
+ assert url2.raw_host == "%cf%80.org"
+ assert url2.host == "%cf%80.org"
+ assert url2.raw_authority == "%25cf%2580%CF%80:%25cf%2580%CF%80@%cf%80.org:123"
+ assert url2.authority == "%cf%80π:%cf%80π@%cf%80.org:123"
+
+
+def test_with_host_for_relative_url():
+ with pytest.raises(ValueError):
+ URL("path/to").with_host("example.com")
+
+
+def test_with_host_invalid_type():
+ url = URL("http://example.com:123")
+ with pytest.raises(TypeError):
+ url.with_host(None)
+
+
+def test_with_port():
+ url = URL("http://example.com")
+ assert str(url.with_port(8888)) == "http://example.com:8888"
+
+
+def test_with_port_with_no_port():
+ url = URL("http://example.com")
+ assert str(url.with_port(None)) == "http://example.com"
+
+
+def test_with_port_ipv6():
+ url = URL("http://[::1]:8080/")
+ assert str(url.with_port(80)) == "http://[::1]:80/"
+
+
+def test_with_port_keeps_query_and_fragment():
+ url = URL("http://example.com/?a=1#frag")
+ assert str(url.with_port(8888)) == "http://example.com:8888/?a=1#frag"
+
+
+def test_with_port_percent_encoded():
+ url = URL("http://user%name:pass%word@example.com/")
+ assert str(url.with_port(808)) == "http://user%25name:pass%25word@example.com:808/"
+
+
+def test_with_port_for_relative_url():
+ with pytest.raises(ValueError):
+ URL("path/to").with_port(1234)
+
+
+def test_with_port_invalid_type():
+ with pytest.raises(TypeError):
+ URL("http://example.com").with_port("123")
+ with pytest.raises(TypeError):
+ URL("http://example.com").with_port(True)
+
+
+def test_with_port_invalid_range():
+ with pytest.raises(ValueError):
+ URL("http://example.com").with_port(-1)
diff --git a/contrib/python/yarl/tests/ya.make b/contrib/python/yarl/tests/ya.make
new file mode 100644
index 0000000000..f86b0f6380
--- /dev/null
+++ b/contrib/python/yarl/tests/ya.make
@@ -0,0 +1,24 @@
+PY3TEST()
+
+PEERDIR(
+ contrib/python/yarl
+)
+
+TEST_SRCS(
+ test_cache.py
+ test_cached_property.py
+ test_normalize_path.py
+ test_pickle.py
+ test_quoting.py
+ test_update_query.py
+ test_url.py
+ test_url_build.py
+ test_url_cmp_and_hash.py
+ test_url_parsing.py
+ test_url_query.py
+ test_url_update_netloc.py
+)
+
+NO_LINT()
+
+END()