test:options: update tests results to pydantic parsing

This commit is contained in:
axolotle 2023-04-17 15:37:56 +02:00
parent ec5da99a79
commit c428ba616a

View file

@ -20,7 +20,6 @@ from yunohost.utils.form import (
BaseChoicesOption, BaseChoicesOption,
BaseInputOption, BaseInputOption,
BaseReadonlyOption, BaseReadonlyOption,
PasswordOption,
DomainOption, DomainOption,
WebPathOption, WebPathOption,
BooleanOption, BooleanOption,
@ -436,6 +435,10 @@ class BaseTest:
@classmethod @classmethod
def _test_basic_attrs(self): def _test_basic_attrs(self):
raw_option = self.get_raw_option(optional=True) raw_option = self.get_raw_option(optional=True)
if raw_option["type"] == "select":
raw_option["choices"] = ["one"]
id_ = raw_option["id"] id_ = raw_option["id"]
option, value = _fill_or_prompt_one_option(raw_option, None) option, value = _fill_or_prompt_one_option(raw_option, None)
@ -481,6 +484,7 @@ class BaseTest:
base_raw_option = prefill_data["raw_option"] base_raw_option = prefill_data["raw_option"]
prefill = prefill_data["prefill"] prefill = prefill_data["prefill"]
# FIXME could patch prompt with prefill if we switch to "do not apply default if value is None|''"
with patch_prompt("") as prompt: with patch_prompt("") as prompt:
raw_option = self.get_raw_option( raw_option = self.get_raw_option(
raw_option=base_raw_option, raw_option=base_raw_option,
@ -583,9 +587,7 @@ class TestAlert(TestDisplayText):
(None, None, {"ask": "Some text\na new line"}), (None, None, {"ask": "Some text\na new line"}),
(None, None, {"ask": {"en": "Some text\na new line", "fr": "Un peu de texte\nune nouvelle ligne"}}), (None, None, {"ask": {"en": "Some text\na new line", "fr": "Un peu de texte\nune nouvelle ligne"}}),
*[(None, None, {"ask": "question", "style": style}) for style in ("success", "info", "warning", "danger")], *[(None, None, {"ask": "question", "style": style}) for style in ("success", "info", "warning", "danger")],
*xpass(scenarios=[ (None, FAIL, {"ask": "question", "style": "nimp"}),
(None, None, {"ask": "question", "style": "nimp"}),
], reason="Should fail, wrong style"),
] ]
# fmt: on # fmt: on
@ -643,11 +645,15 @@ class TestString(BaseTest):
scenarios = [ scenarios = [
*nones(None, "", output=""), *nones(None, "", output=""),
# basic typed values # basic typed values
*unchanged(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}), # FIXME should output as str? (False, "False"),
(True, "True"),
(0, "0"),
(1, "1"),
(-1, "-1"),
(1337, "1337"),
(13.37, "13.37"),
*all_fails([], ["one"], {}),
*unchanged("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", raw_option={"optional": True}), *unchanged("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", raw_option={"optional": True}),
*xpass(scenarios=[
([], []),
], reason="Should fail"),
# test strip # test strip
("value", "value"), ("value", "value"),
("value\n", "value"), ("value\n", "value"),
@ -660,7 +666,7 @@ class TestString(BaseTest):
(" ##value \n \tvalue\n ", "##value \n \tvalue"), (" ##value \n \tvalue\n ", "##value \n \tvalue"),
], reason=r"should fail or without `\n`?"), ], reason=r"should fail or without `\n`?"),
# readonly # readonly
("overwrite", "expected value", {"readonly": True, "current_value": "expected value"}), ("overwrite", "expected value", {"readonly": True, "default": "expected value"}), # FIXME do we want to fail instead?
] ]
# fmt: on # fmt: on
@ -680,11 +686,15 @@ class TestText(BaseTest):
scenarios = [ scenarios = [
*nones(None, "", output=""), *nones(None, "", output=""),
# basic typed values # basic typed values
*unchanged(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}), # FIXME should fail or output as str? (False, "False"),
(True, "True"),
(0, "0"),
(1, "1"),
(-1, "-1"),
(1337, "1337"),
(13.37, "13.37"),
*all_fails([], ["one"], {}),
*unchanged("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", raw_option={"optional": True}), *unchanged("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", raw_option={"optional": True}),
*xpass(scenarios=[
([], [])
], reason="Should fail"),
("value", "value"), ("value", "value"),
("value\n value", "value\n value"), ("value\n value", "value\n value"),
# test no strip # test no strip
@ -697,7 +707,7 @@ class TestText(BaseTest):
(r" ##value \n \tvalue\n ", r"##value \n \tvalue\n"), (r" ##value \n \tvalue\n ", r"##value \n \tvalue\n"),
], reason="Should not be stripped"), ], reason="Should not be stripped"),
# readonly # readonly
("overwrite", "expected value", {"readonly": True, "current_value": "expected value"}), ("overwrite", "expected value", {"readonly": True, "default": "expected value"}),
] ]
# fmt: on # fmt: on
@ -715,7 +725,7 @@ class TestPassword(BaseTest):
} }
# fmt: off # fmt: off
scenarios = [ scenarios = [
*all_fails(False, True, 0, 1, -1, 1337, 13.37, raw_option={"optional": True}, error=TypeError), # FIXME those fails with TypeError *all_fails(False, True, 0, 1, -1, 1337, 13.37, raw_option={"optional": True}),
*all_fails([], ["one"], {}, raw_option={"optional": True}, error=AttributeError), # FIXME those fails with AttributeError *all_fails([], ["one"], {}, raw_option={"optional": True}, error=AttributeError), # FIXME those fails with AttributeError
*all_fails("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}), *all_fails("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}),
*nones(None, "", output=""), *nones(None, "", output=""),
@ -729,9 +739,9 @@ class TestPassword(BaseTest):
], reason="Should output exactly the same"), ], reason="Should output exactly the same"),
("s3cr3t!!", "s3cr3t!!"), ("s3cr3t!!", "s3cr3t!!"),
("secret", FAIL), ("secret", FAIL),
*[("supersecret" + char, FAIL) for char in PasswordOption.forbidden_chars], # FIXME maybe add ` \n` to the list? *[("supersecret" + char, FAIL) for char in FORBIDDEN_PASSWORD_CHARS], # FIXME maybe add ` \n` to the list?
# readonly # readonly
("s3cr3t!!", YunohostError, {"readonly": True, "current_value": "isforbidden"}), # readonly is forbidden ("s3cr3t!!", FAIL, {"readonly": True, "current_value": "isforbidden"}), # readonly is forbidden
] ]
# fmt: on # fmt: on
@ -744,35 +754,31 @@ class TestPassword(BaseTest):
class TestColor(BaseTest): class TestColor(BaseTest):
raw_option = {"type": "color", "id": "color_id"} raw_option = {"type": "color", "id": "color_id"}
prefill = { prefill = {
"raw_option": {"default": "#ff0000"}, "raw_option": {"default": "red"},
"prefill": "#ff0000", "prefill": "red",
# "intake": "#ff00ff",
} }
# fmt: off # fmt: off
scenarios = [ scenarios = [
*all_fails(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}), *all_fails(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}),
*all_fails("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}), *all_fails("none", "_none", "False", "True", "0", "1", "-1", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}),
*nones(None, "", output=""), *nones(None, "", output=""),
# custom valid # custom valid
("#000000", "#000000"), (" #fe1 ", "#fe1"),
("#000000", "#000"),
("#000", "#000"), ("#000", "#000"),
("#fe100", "#fe100"), ("#ABCDEF", "#abcdef"),
(" #fe100 ", "#fe100"), ('1337', "#1337"), # rgba=(17, 51, 51, 0.47)
("#ABCDEF", "#ABCDEF"), ("000000", "#000"),
("#feaf", "#fea"), # `#feaf` is `#fea` with alpha at `f|100%` -> equivalent to `#fea`
# named
("red", "#f00"),
("yellow", "#ff0"),
# custom fail # custom fail
*xpass(scenarios=[
("#feaf", "#feaf"),
], reason="Should fail; not a legal color value"),
("000000", FAIL),
("#12", FAIL), ("#12", FAIL),
("#gggggg", FAIL), ("#gggggg", FAIL),
("#01010101af", FAIL), ("#01010101af", FAIL),
*xfail(scenarios=[
("red", "#ff0000"),
("yellow", "#ffff00"),
], reason="Should work with pydantic"),
# readonly # readonly
("#ffff00", "#fe100", {"readonly": True, "current_value": "#fe100"}), ("#ffff00", "#000", {"readonly": True, "default": "#000"}),
] ]
# fmt: on # fmt: on
@ -796,10 +802,8 @@ class TestNumber(BaseTest):
*nones(None, "", output=None), *nones(None, "", output=None),
*unchanged(0, 1, -1, 1337), *unchanged(0, 1, -1, 1337),
*xpass(scenarios=[(False, False)], reason="should fail or output as `0`"), *all_as(False, "0", 0, output=0), # FIXME should `False` fail instead?
*xpass(scenarios=[(True, True)], reason="should fail or output as `1`"), *all_as(True, "1", 1, output=1), # FIXME should `True` fail instead?
*all_as("0", 0, output=0),
*all_as("1", 1, output=1),
*all_as("1337", 1337, output=1337), *all_as("1337", 1337, output=1337),
*xfail(scenarios=[ *xfail(scenarios=[
("-1", -1) ("-1", -1)
@ -814,7 +818,7 @@ class TestNumber(BaseTest):
(-10, -10, {"default": 10}), (-10, -10, {"default": 10}),
(-10, -10, {"default": 10, "optional": True}), (-10, -10, {"default": 10, "optional": True}),
# readonly # readonly
(1337, 10000, {"readonly": True, "current_value": 10000}), (1337, 10000, {"readonly": True, "default": "10000"}),
] ]
# fmt: on # fmt: on
# FIXME should `step` be some kind of "multiple of"? # FIXME should `step` be some kind of "multiple of"?
@ -839,14 +843,20 @@ class TestBoolean(BaseTest):
*all_fails("none", "None"), # FIXME should output as `0` (default) like other none values when required? *all_fails("none", "None"), # FIXME should output as `0` (default) like other none values when required?
*all_as(None, "", output=0, raw_option={"optional": True}), # FIXME should output as `None`? *all_as(None, "", output=0, raw_option={"optional": True}), # FIXME should output as `None`?
*all_as("none", "None", output=None, raw_option={"optional": True}), *all_as("none", "None", output=None, raw_option={"optional": True}),
# FIXME even if default is explicity `None|""`, it ends up with class_default `0` {
*all_as(None, "", output=0, raw_option={"default": None}), # FIXME this should fail, default is `None` "raw_options": [
*all_as(None, "", output=0, raw_option={"optional": True, "default": None}), # FIXME even if default is explicity None, it ends up with class_default {"default": None},
*all_as(None, "", output=0, raw_option={"default": ""}), # FIXME this should fail, default is `""` {"default": ""},
*all_as(None, "", output=0, raw_option={"optional": True, "default": ""}), # FIXME even if default is explicity None, it ends up with class_default {"default": "none"},
# With "none" behavior is ok {"default": "None"}
*all_fails(None, "", raw_option={"default": "none"}), ],
*all_as(None, "", output=None, raw_option={"optional": True, "default": "none"}), "scenarios": [
# All none values fails if default is overriden
*all_fails(None, "", "none", "None"),
# All none values ends up as None if default is overriden
*all_as(None, "", "none", "None", output=None, raw_option={"optional": True}),
]
},
# Unhandled types should fail # Unhandled types should fail
*all_fails(1337, "1337", "string", [], "[]", ",", "one,two"), *all_fails(1337, "1337", "string", [], "[]", ",", "one,two"),
*all_fails(1337, "1337", "string", [], "[]", ",", "one,two", {"optional": True}), *all_fails(1337, "1337", "string", [], "[]", ",", "one,two", {"optional": True}),
@ -879,7 +889,7 @@ class TestBoolean(BaseTest):
"scenarios": all_fails("", "y", "n", error=AssertionError), "scenarios": all_fails("", "y", "n", error=AssertionError),
}, },
# readonly # readonly
(1, 0, {"readonly": True, "current_value": 0}), (1, 0, {"readonly": True, "default": 0}),
] ]
@ -896,8 +906,12 @@ class TestDate(BaseTest):
} }
# fmt: off # fmt: off
scenarios = [ scenarios = [
*all_fails(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}), # Those passes since False|True are parsed as 0|1 then int|float are considered a timestamp in seconds which ends up as default Unix date
*all_fails("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}), *all_as(False, True, 0, 1, 1337, 13.37, "0", "1", "1337", "13.37", output="1970-01-01"),
# Those are negative one second timestamp ending up as Unix date - 1 sec (so day change)
*all_as(-1, "-1", output="1969-12-31"),
*all_fails([], ["one"], {}, raw_option={"optional": True}),
*all_fails("none", "_none", "False", "True", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}),
*nones(None, "", output=""), *nones(None, "", output=""),
# custom valid # custom valid
("2070-12-31", "2070-12-31"), ("2070-12-31", "2070-12-31"),
@ -906,18 +920,16 @@ class TestDate(BaseTest):
("2025-06-15T13:45:30", "2025-06-15"), ("2025-06-15T13:45:30", "2025-06-15"),
("2025-06-15 13:45:30", "2025-06-15") ("2025-06-15 13:45:30", "2025-06-15")
], reason="iso date repr should be valid and extra data striped"), ], reason="iso date repr should be valid and extra data striped"),
*xfail(scenarios=[ (1749938400, "2025-06-14"),
(1749938400, "2025-06-15"), (1749938400.0, "2025-06-14"),
(1749938400.0, "2025-06-15"), ("1749938400", "2025-06-14"),
("1749938400", "2025-06-15"), ("1749938400.0", "2025-06-14"),
("1749938400.0", "2025-06-15"),
], reason="timestamp could be an accepted value"),
# custom invalid # custom invalid
("29-12-2070", FAIL), ("29-12-2070", FAIL),
("12-01-10", FAIL), ("12-01-10", FAIL),
("2022-02-29", FAIL), ("2022-02-29", FAIL),
# readonly # readonly
("2070-12-31", "2024-02-29", {"readonly": True, "current_value": "2024-02-29"}), ("2070-12-31", "2024-02-29", {"readonly": True, "default": "2024-02-29"}),
] ]
# fmt: on # fmt: on
@ -935,22 +947,26 @@ class TestTime(BaseTest):
} }
# fmt: off # fmt: off
scenarios = [ scenarios = [
*all_fails(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}), # Those passes since False|True are parsed as 0|1 then int|float are considered a timestamp in seconds but we don't take seconds into account so -> 00:00
*all_fails("none", "_none", "False", "True", "0", "1", "-1", "1337", "13.37", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}), *all_as(False, True, 0, 1, 13.37, "0", "1", "13.37", output="00:00"),
# 1337 seconds == 22 minutes
*all_as(1337, "1337", output="00:22"),
# Negative timestamp fails
*all_fails(-1, "-1", error=OverflowError), # FIXME should handle that as a validation error
# *all_fails(False, True, 0, 1, -1, 1337, 13.37, [], ["one"], {}, raw_option={"optional": True}),
*all_fails("none", "_none", "False", "True", "[]", ",", "['one']", "one,two", r"{}", "value", "value\n", raw_option={"optional": True}),
*nones(None, "", output=""), *nones(None, "", output=""),
# custom valid # custom valid
*unchanged("00:00", "08:00", "12:19", "20:59", "23:59"), *unchanged("00:00", "08:00", "12:19", "20:59", "23:59"),
("3:00", "3:00"), # FIXME should fail or output as `"03:00"`? ("3:00", "03:00"),
*xfail(scenarios=[ ("23:1", "23:01"),
("22:35:05", "22:35"), ("22:35:05", "22:35"),
("22:35:03.514", "22:35"), ("22:35:03.514", "22:35"),
], reason="time as iso format could be valid"),
# custom invalid # custom invalid
("24:00", FAIL), ("24:00", FAIL),
("23:1", FAIL),
("23:005", FAIL), ("23:005", FAIL),
# readonly # readonly
("00:00", "08:00", {"readonly": True, "current_value": "08:00"}), ("00:00", "08:00", {"readonly": True, "default": "08:00"}),
] ]
# fmt: on # fmt: on
@ -973,72 +989,75 @@ class TestEmail(BaseTest):
*nones(None, "", output=""), *nones(None, "", output=""),
("\n Abc@example.tld ", "Abc@example.tld"), ("\n Abc@example.tld ", "Abc@example.tld"),
*xfail(scenarios=[("admin@ynh.local", "admin@ynh.local")], reason="Should this pass?"),
# readonly # readonly
("Abc@example.tld", "admin@ynh.local", {"readonly": True, "current_value": "admin@ynh.local"}), ("Abc@example.tld", "admin@ynh.org", {"readonly": True, "default": "admin@ynh.org"}),
# Next examples are from https://github.com/JoshData/python-email-validator/blob/main/tests/test_syntax.py # Next examples are from https://github.com/JoshData/python-email-validator/blob/main/tests/test_syntax.py
# valid email values # valid email values
("Abc@example.tld", "Abc@example.tld"), *unchanged(
("Abc.123@test-example.com", "Abc.123@test-example.com"), "Abc@example.tld",
("user+mailbox/department=shipping@example.tld", "user+mailbox/department=shipping@example.tld"), "Abc.123@test-example.com",
("伊昭傑@郵件.商務", "伊昭傑@郵件.商務"), "user+mailbox/department=shipping@example.tld",
("राम@मोहन.ईन्फो", "राम@मोहन.ईन्फो"), "伊昭傑@郵件.商務",
("юзер@екзампл.ком", "юзер@екзампл.ком"), "राम@मोहन.ईन्फो",
("θσερ@εχαμπλε.ψομ", "θσερ@εχαμπλε.ψομ"), "юзер@екзампл.ком",
("葉士豪@臺網中心.tw", "葉士豪@臺網中心.tw"), "θσερ@εχαμπλε.ψομ",
("jeff@臺網中心.tw", "jeff@臺網中心.tw"), "葉士豪@臺網中心.tw",
("葉士豪@臺網中心.台灣", "葉士豪@臺網中心.台灣"), "jeff@臺網中心.tw",
("jeff葉@臺網中心.tw", "jeff葉@臺網中心.tw"), "葉士豪@臺網中心.台灣",
("ñoñó@example.tld", "ñoñó@example.tld"), "jeff葉@臺網中心.tw",
("甲斐黒川日本@example.tld", "甲斐黒川日本@example.tld"), "ñoñó@example.tld",
("чебурашкаящик-с-апельсинами.рф@example.tld", "чебурашкаящик-с-апельсинами.рф@example.tld"), "甲斐黒川日本@example.tld",
("उदाहरण.परीक्ष@domain.with.idn.tld", "उदाहरण.परीक्ष@domain.with.idn.tld"), "чебурашкаящик-с-апельсинами.рф@example.tld",
("ιωάννης@εεττ.gr", "ιωάννης@εεττ.gr"), "उदाहरण.परीक्ष@domain.with.idn.tld",
"ιωάννης@εεττ.gr",
),
# invalid email (Hiding because our current regex is very permissive) # invalid email (Hiding because our current regex is very permissive)
# ("my@localhost", FAIL), *all_fails(
# ("my@.leadingdot.com", FAIL), "my@localhost",
# ("my@leadingfwdot.com", FAIL), "my@.leadingdot.com",
# ("my@twodots..com", FAIL), "my@leadingfwdot.com",
# ("my@twofwdots.com", FAIL), "my@twodots..com",
# ("my@trailingdot.com.", FAIL), "my@twofwdots.com",
# ("my@trailingfwdot.com", FAIL), "my@trailingdot.com.",
# ("me@-leadingdash", FAIL), "my@trailingfwdot.com",
# ("me@leadingdashfw", FAIL), "me@-leadingdash",
# ("me@trailingdash-", FAIL), "me@leadingdashfw",
# ("me@trailingdashfw", FAIL), "me@trailingdash-",
# ("my@baddash.-.com", FAIL), "me@trailingdashfw",
# ("my@baddash.-a.com", FAIL), "my@baddash.-.com",
# ("my@baddash.b-.com", FAIL), "my@baddash.-a.com",
# ("my@baddashfw..com", FAIL), "my@baddash.b-.com",
# ("my@baddashfw.a.com", FAIL), "my@baddashfw..com",
# ("my@baddashfw.b.com", FAIL), "my@baddashfw.a.com",
# ("my@example.com\n", FAIL), "my@baddashfw.b.com",
# ("my@example\n.com", FAIL), "my@example\n.com",
# ("me@x!", FAIL), "me@x!",
# ("me@x ", FAIL), "me@x ",
# (".leadingdot@domain.com", FAIL), ".leadingdot@domain.com",
# ("twodots..here@domain.com", FAIL), "twodots..here@domain.com",
# ("trailingdot.@domain.email", FAIL), "trailingdot.@domain.email",
# ("me@⒈wouldbeinvalid.com", FAIL), "me@⒈wouldbeinvalid.com",
("@example.com", FAIL), "@example.com",
# ("\nmy@example.com", FAIL), "m\ny@example.com",
("m\ny@example.com", FAIL), "my\n@example.com",
("my\n@example.com", FAIL), "11111111112222222222333333333344444444445555555555666666666677777@example.com",
# ("11111111112222222222333333333344444444445555555555666666666677777@example.com", FAIL), "111111111122222222223333333333444444444455555555556666666666777777@example.com",
# ("111111111122222222223333333333444444444455555555556666666666777777@example.com", FAIL), "me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444444444455555555556.com",
# ("me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444444444455555555556.com", FAIL), "me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com",
# ("me@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com", FAIL), "me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com",
# ("me@中1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555566.com", FAIL), "my.long.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444.info",
# ("my.long.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333333344444.info", FAIL), "my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333.info",
# ("my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.11111111112222222222333333.info", FAIL), "my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info",
# ("my.long.address@λ111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info", FAIL), "my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444.info",
# ("my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.111111111122222222223333333333444.info", FAIL), "my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info",
# ("my.λong.address@1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444444444555555555.6666666666777777777788888888889999999999000000000.1111111111222222222233333333334444.info", FAIL), "me@bad-tld-1",
# ("me@bad-tld-1", FAIL), "me@bad.tld-2",
# ("me@bad.tld-2", FAIL), "me@xn--0.tld",
# ("me@xn--0.tld", FAIL), "me@yy--0.tld",
# ("me@yy--0.tld", FAIL), "me@yy0.tld",
# ("me@yy0.tld", FAIL), )
] ]
# fmt: on # fmt: on
@ -1087,7 +1106,7 @@ class TestWebPath(BaseTest):
("https://example.com/folder", "/https://example.com/folder") ("https://example.com/folder", "/https://example.com/folder")
], reason="Should fail or scheme+domain removed"), ], reason="Should fail or scheme+domain removed"),
# readonly # readonly
("/overwrite", "/value", {"readonly": True, "current_value": "/value"}), ("/overwrite", "/value", {"readonly": True, "default": "/value"}),
# FIXME should path have forbidden_chars? # FIXME should path have forbidden_chars?
] ]
# fmt: on # fmt: on
@ -1111,21 +1130,17 @@ class TestUrl(BaseTest):
*nones(None, "", output=""), *nones(None, "", output=""),
("http://some.org/folder/file.txt", "http://some.org/folder/file.txt"), ("http://some.org/folder/file.txt", "http://some.org/folder/file.txt"),
(' https://www.example.com \n', 'https://www.example.com'),
# readonly # readonly
("https://overwrite.org", "https://example.org", {"readonly": True, "current_value": "https://example.org"}), ("https://overwrite.org", "https://example.org", {"readonly": True, "default": "https://example.org"}),
# rest is taken from https://github.com/pydantic/pydantic/blob/main/tests/test_networks.py # rest is taken from https://github.com/pydantic/pydantic/blob/main/tests/test_networks.py
# valid # valid
*unchanged( *unchanged(
# Those are valid but not sure how they will output with pydantic # Those are valid but not sure how they will output with pydantic
'http://example.org', 'http://example.org',
'http://test',
'http://localhost',
'https://example.org/whatever/next/', 'https://example.org/whatever/next/',
'https://example.org', 'https://example.org',
'http://localhost',
'http://localhost/',
'http://localhost:8000',
'http://localhost:8000/',
'https://foo_bar.example.com/', 'https://foo_bar.example.com/',
'http://example.co.jp', 'http://example.co.jp',
'http://www.example.com/a%C2%B1b', 'http://www.example.com/a%C2%B1b',
@ -1149,29 +1164,31 @@ class TestUrl(BaseTest):
'http://twitter.com/@handle/', 'http://twitter.com/@handle/',
'http://11.11.11.11.example.com/action', 'http://11.11.11.11.example.com/action',
'http://abc.11.11.11.11.example.com/action', 'http://abc.11.11.11.11.example.com/action',
'http://example#',
'http://example/#',
'http://example/#fragment',
'http://example/?#',
'http://example.org/path#', 'http://example.org/path#',
'http://example.org/path#fragment', 'http://example.org/path#fragment',
'http://example.org/path?query#', 'http://example.org/path?query#',
'http://example.org/path?query#fragment', 'http://example.org/path?query#fragment',
'https://foo_bar.example.com/',
'https://exam_ple.com/',
'HTTP://EXAMPLE.ORG',
'https://example.org',
'https://example.org?a=1&b=2',
'https://example.org#a=3;b=3',
'https://example.xn--p1ai',
'https://example.xn--vermgensberatung-pwb',
'https://example.xn--zfr164b',
), ),
# Pydantic default parsing add a final `/`
('https://foo_bar.example.com/', 'https://foo_bar.example.com/'),
('https://exam_ple.com/', 'https://exam_ple.com/'),
*xfail(scenarios=[ *xfail(scenarios=[
(' https://www.example.com \n', 'https://www.example.com/'), ('http://test', 'http://test'),
('HTTP://EXAMPLE.ORG', 'http://example.org/'), ('http://localhost', 'http://localhost'),
('https://example.org', 'https://example.org/'), ('http://localhost/', 'http://localhost/'),
('https://example.org?a=1&b=2', 'https://example.org/?a=1&b=2'), ('http://localhost:8000', 'http://localhost:8000'),
('https://example.org#a=3;b=3', 'https://example.org/#a=3;b=3'), ('http://localhost:8000/', 'http://localhost:8000/'),
('https://example.xn--p1ai', 'https://example.xn--p1ai/'), ('http://example#', 'http://example#'),
('https://example.xn--vermgensberatung-pwb', 'https://example.xn--vermgensberatung-pwb/'), ('http://example/#', 'http://example/#'),
('https://example.xn--zfr164b', 'https://example.xn--zfr164b/'), ('http://example/#fragment', 'http://example/#fragment'),
], reason="pydantic default behavior would append a final `/`"), ('http://example/?#', 'http://example/?#'),
], reason="Should this be valid?"),
# invalid # invalid
*all_fails( *all_fails(
'ftp://example.com/', 'ftp://example.com/',
@ -1182,15 +1199,13 @@ class TestUrl(BaseTest):
"/", "/",
"+http://example.com/", "+http://example.com/",
"ht*tp://example.com/", "ht*tp://example.com/",
"http:///",
"http://??",
"https://example.org more",
"http://2001:db8::ff00:42:8329",
"http://[192.168.1.1]:8329",
"http://example.com:99999",
), ),
*xpass(scenarios=[
("http:///", "http:///"),
("http://??", "http://??"),
("https://example.org more", "https://example.org more"),
("http://2001:db8::ff00:42:8329", "http://2001:db8::ff00:42:8329"),
("http://[192.168.1.1]:8329", "http://[192.168.1.1]:8329"),
("http://example.com:99999", "http://example.com:99999"),
], reason="Should fail"),
] ]
# fmt: on # fmt: on
@ -1361,7 +1376,6 @@ class TestSelect(BaseTest):
# [-1, 0, 1] # [-1, 0, 1]
"raw_options": [ "raw_options": [
{"choices": [-1, 0, 1, 10]}, {"choices": [-1, 0, 1, 10]},
{"choices": {-1: "verbose -one", 0: "verbose zero", 1: "verbose one", 10: "verbose ten"}},
], ],
"scenarios": [ "scenarios": [
*nones(None, "", output=""), *nones(None, "", output=""),
@ -1375,6 +1389,18 @@ class TestSelect(BaseTest):
*all_fails("100", 100), *all_fails("100", 100),
] ]
}, },
{
"raw_options": [
{"choices": {-1: "verbose -one", 0: "verbose zero", 1: "verbose one", 10: "verbose ten"}},
{"choices": {"-1": "verbose -one", "0": "verbose zero", "1": "verbose one", "10": "verbose ten"}},
],
"scenarios": [
*nones(None, "", output=""),
*all_fails(-1, 0, 1, 10), # Should pass? converted to str?
*unchanged("-1", "0", "1", "10"),
*all_fails("100", 100),
]
},
# [True, False, None] # [True, False, None]
*unchanged(True, False, raw_option={"choices": [True, False, None]}), # FIXME we should probably forbid None in choices *unchanged(True, False, raw_option={"choices": [True, False, None]}), # FIXME we should probably forbid None in choices
(None, FAIL, {"choices": [True, False, None]}), (None, FAIL, {"choices": [True, False, None]}),
@ -1402,7 +1428,7 @@ class TestSelect(BaseTest):
] ]
}, },
# readonly # readonly
("one", "two", {"readonly": True, "choices": ["one", "two"], "current_value": "two"}), ("one", "two", {"readonly": True, "choices": ["one", "two"], "default": "two"}),
] ]
# fmt: on # fmt: on
@ -1411,7 +1437,7 @@ class TestSelect(BaseTest):
# │ TAGS │ # │ TAGS │
# ╰───────────────────────────────────────────────────────╯ # ╰───────────────────────────────────────────────────────╯
# [], ["one"], {}
class TestTags(BaseTest): class TestTags(BaseTest):
raw_option = {"type": "tags", "id": "tags_id"} raw_option = {"type": "tags", "id": "tags_id"}
prefill = { prefill = {
@ -1420,12 +1446,7 @@ class TestTags(BaseTest):
} }
# fmt: off # fmt: off
scenarios = [ scenarios = [
*nones(None, [], "", output=""), *nones(None, [], "", ",", output=""),
# FIXME `","` could be considered a none value which kinda already is since it fail when required
(",", FAIL),
*xpass(scenarios=[
(",", ",", {"optional": True})
], reason="Should output as `''`? ie: None"),
{ {
"raw_options": [ "raw_options": [
{}, {},
@ -1450,7 +1471,7 @@ class TestTags(BaseTest):
*all_fails(*([t] for t in [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]), raw_option={"choices": [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]}), *all_fails(*([t] for t in [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]), raw_option={"choices": [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]}),
*all_fails(*([str(t)] for t in [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]), raw_option={"choices": [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]}), *all_fails(*([str(t)] for t in [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]), raw_option={"choices": [False, True, -1, 0, 1, 1337, 13.37, [], ["one"], {}]}),
# readonly # readonly
("one", "one,two", {"readonly": True, "choices": ["one", "two"], "current_value": "one,two"}), ("one", "one,two", {"readonly": True, "choices": ["one", "two"], "default": "one,two"}),
] ]
# fmt: on # fmt: on
@ -1566,8 +1587,7 @@ class TestApp(BaseTest):
], ],
"scenarios": [ "scenarios": [
# FIXME there are currently 3 different nones (`None`, `""` and `_none`), choose one? # FIXME there are currently 3 different nones (`None`, `""` and `_none`), choose one?
*nones(None, output=None), # FIXME Should return chosen none? *nones(None, "", output=""), # FIXME Should return chosen none?
*nones("", output=""), # FIXME Should return chosen none?
*xpass(scenarios=[ *xpass(scenarios=[
("_none", "_none"), ("_none", "_none"),
("_none", "_none", {"default": "_none"}), ("_none", "_none", {"default": "_none"}),
@ -1590,7 +1610,7 @@ class TestApp(BaseTest):
(installed_webapp["id"], installed_webapp["id"], {"filter": "is_webapp"}), (installed_webapp["id"], installed_webapp["id"], {"filter": "is_webapp"}),
(installed_webapp["id"], FAIL, {"filter": "is_webapp == false"}), (installed_webapp["id"], FAIL, {"filter": "is_webapp == false"}),
(installed_webapp["id"], FAIL, {"filter": "id != 'my_webapp'"}), (installed_webapp["id"], FAIL, {"filter": "id != 'my_webapp'"}),
(None, None, {"filter": "id == 'fake_app'", "optional": True}), (None, "", {"filter": "id == 'fake_app'", "optional": True}),
] ]
}, },
{ {
@ -1800,7 +1820,7 @@ class TestGroup(BaseTest):
("", "custom_group", {"default": "custom_group"}), ("", "custom_group", {"default": "custom_group"}),
], reason="Should throw 'default must be in (None, 'all_users', 'visitors', 'admins')"), ], reason="Should throw 'default must be in (None, 'all_users', 'visitors', 'admins')"),
# readonly # readonly
("admins", YunohostError, {"readonly": True}), # readonly is forbidden ("admins", "all_users", {"readonly": True}), # readonly is forbidden (default is "all_users")
] ]
}, },
] ]
@ -1880,12 +1900,12 @@ def test_options_query_string():
"string_id": "string", "string_id": "string",
"text_id": "text\ntext", "text_id": "text\ntext",
"password_id": "sUpRSCRT", "password_id": "sUpRSCRT",
"color_id": "#ffff00", "color_id": "#ff0",
"number_id": 10, "number_id": 10,
"boolean_id": 1, "boolean_id": 1,
"date_id": "2030-03-06", "date_id": "2030-03-06",
"time_id": "20:55", "time_id": "20:55",
"email_id": "coucou@ynh.local", "email_id": "coucou@ynh.org",
"path_id": "/ynh-dev", "path_id": "/ynh-dev",
"url_id": "https://yunohost.org", "url_id": "https://yunohost.org",
"file_id": file_content1, "file_id": file_content1,
@ -1908,7 +1928,7 @@ def test_options_query_string():
"&boolean_id=y" "&boolean_id=y"
"&date_id=2030-03-06" "&date_id=2030-03-06"
"&time_id=20:55" "&time_id=20:55"
"&email_id=coucou@ynh.local" "&email_id=coucou@ynh.org"
"&path_id=ynh-dev/" "&path_id=ynh-dev/"
"&url_id=https://yunohost.org" "&url_id=https://yunohost.org"
f"&file_id={file_repr}" f"&file_id={file_repr}"