aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/yarl/tests/test_url.py
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/test_url.py
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/test_url.py')
-rw-r--r--contrib/python/yarl/tests/test_url.py1732
1 files changed, 1732 insertions, 0 deletions
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/"