From: Aleš Mrázek Date: Fri, 18 Mar 2022 12:50:58 +0000 (+0100) Subject: manager: tests: modelling: tests parametrization X-Git-Tag: v6.0.0a1~37^2~7 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e7f5a2090645c5e89c2eeaef2eb5aef4b11cf21f;p=thirdparty%2Fknot-resolver.git manager: tests: modelling: tests parametrization - utils/modelling: strip() lines in schema class docstring - general json_schema tests moved to test_modelling.py --- diff --git a/manager/knot_resolver_manager/utils/modelling.py b/manager/knot_resolver_manager/utils/modelling.py index 6de14933f..2d7ab5926 100644 --- a/manager/knot_resolver_manager/utils/modelling.py +++ b/manager/knot_resolver_manager/utils/modelling.py @@ -78,7 +78,7 @@ def _split_docstring(docstring: str) -> Tuple[str, Optional[str]]: """ if "---" not in docstring: - return (docstring, None) + return ("\n".join([s.strip() for s in docstring.splitlines()]).strip(), None) doc, attrs_doc = docstring.split("---", maxsplit=1) return ( diff --git a/manager/tests/unit/datamodel/test_config_schema.py b/manager/tests/unit/datamodel/test_config_schema.py index b1430d848..d8ad83a1e 100644 --- a/manager/tests/unit/datamodel/test_config_schema.py +++ b/manager/tests/unit/datamodel/test_config_schema.py @@ -41,7 +41,7 @@ def test_dns64_prefix_default(): assert str(KresConfig({"server": {"id": "test"}, "dns64": True}).dns64.prefix) == "64:ff9b::/96" -def test_json_schema(): +def test_config_json_schema(): dct = KresConfig.json_schema() def recser(obj: Any, path: str = "") -> None: @@ -57,61 +57,3 @@ def test_json_schema(): raise Exception(f"failed to serialize '{path}'") from e recser(dct) - - -def test_attribute_parsing(): - class TestClass(SchemaNode): - """ - This is an awesome test class - - --- - field: This field does nothing interesting - value: Neither does this - """ - - field: str - value: int - - schema = TestClass.json_schema() - assert schema["properties"]["field"]["description"] == "This field does nothing interesting" - assert schema["properties"]["value"]["description"] == "Neither does this" - - class AdditionalItem(SchemaNode): - """ - This class is wrong - - --- - field: nope - nothing: really nothing - """ - - nothing: str - - with raises(SchemaException): - _ = AdditionalItem.json_schema() - - class WrongDescription(SchemaNode): - """ - This class is wrong - - --- - other: description - """ - - nothing: str - - with raises(SchemaException): - _ = WrongDescription.json_schema() - - class NoDescription(SchemaNode): - nothing: str - - _ = NoDescription.json_schema() - - class NormalDescription(SchemaNode): - """ - Does nothing special - Really - """ - - _ = NormalDescription.json_schema() diff --git a/manager/tests/unit/utils/test_modeling.py b/manager/tests/unit/utils/test_modeling.py deleted file mode 100644 index 253b7c307..000000000 --- a/manager/tests/unit/utils/test_modeling.py +++ /dev/null @@ -1,216 +0,0 @@ -from typing import Any, Dict, List, Optional, Tuple, Union - -from pytest import raises -from typing_extensions import Literal - -from knot_resolver_manager.exceptions import SchemaException -from knot_resolver_manager.utils import SchemaNode -from knot_resolver_manager.utils.parsing import parse_json, parse_yaml - - -def test_primitive(): - class TestSchema(SchemaNode): - i: int - s: str - b: bool - - yaml = """ -i: 5 -s: test -b: false -""" - - o = TestSchema(parse_yaml(yaml)) - assert o.i == 5 - assert o.s == "test" - assert o.b == False - - -def test_parsing_primitive_exceptions(): - class TestStr(SchemaNode): - s: str - - # int and float are allowed inputs for string - with raises(SchemaException): - TestStr(parse_yaml("s: false")) # bool - - class TestInt(SchemaNode): - i: int - - with raises(SchemaException): - TestInt(parse_yaml("i: false")) # bool - with raises(SchemaException): - TestInt(parse_yaml('i: "5"')) # str - with raises(SchemaException): - TestInt(parse_yaml("i: 5.5")) # float - - class TestBool(SchemaNode): - b: bool - - with raises(SchemaException): - TestBool(parse_yaml("b: 5")) # int - with raises(SchemaException): - TestBool(parse_yaml('b: "5"')) # str - with raises(SchemaException): - TestBool(parse_yaml("b: 5.5")) # float - - -def test_nested(): - class LowerSchema(SchemaNode): - i: int - - class UpperSchema(SchemaNode): - l: LowerSchema - - yaml = """ -l: - i: 5 -""" - - o = UpperSchema(parse_yaml(yaml)) - assert o.l.i == 5 - - -def test_simple_compount_types(): - class TestSchema(SchemaNode): - l: List[int] - d: Dict[str, str] - t: Tuple[str, int] - o: Optional[int] - - yaml = """ -l: - - 1 - - 2 - - 3 - - 4 - - 5 -d: - something: else - w: all -t: - - test - - 5 -""" - - o = TestSchema(parse_yaml(yaml)) - assert o.l == [1, 2, 3, 4, 5] - assert o.d == {"something": "else", "w": "all"} - assert o.t == ("test", 5) - assert o.o is None - - -def test_nested_compound_types(): - class TestSchema(SchemaNode): - o: Optional[Dict[str, str]] - - yaml = """ -o: - key: val -""" - - o = TestSchema(parse_yaml(yaml)) - assert o.o == {"key": "val"} - - -def test_dash_conversion(): - class TestSchema(SchemaNode): - awesome_field: Dict[str, str] - - yaml = """ -awesome-field: - awesome-key: awesome-value -""" - - o = TestSchema(parse_yaml(yaml)) - assert o.awesome_field["awesome-key"] == "awesome-value" - - -def test_nested_compount_types2(): - class TestSchema(SchemaNode): - i: int - o: Optional[Dict[str, str]] - - yaml = "i: 5" - - o = TestSchema(parse_yaml(yaml)) - assert o.i == 5 - assert o.o is None - - -def test_partial_mutations(): - class InnerSchema(SchemaNode): - size: int = 5 - - class ConfPreviousSchema(SchemaNode): - workers: Union[Literal["auto"], int] = 1 - lua_config: Optional[str] = None - inner: InnerSchema = InnerSchema() - - class ConfSchema(SchemaNode): - _PREVIOUS_SCHEMA = ConfPreviousSchema - - workers: int - lua_config: Optional[str] - inner: InnerSchema - - def _workers(self, obj: Any) -> Any: - if "workers" in obj and obj["workers"] == "auto": - return 8 - return obj["workers"] - - def _validate(self) -> None: - if self.workers < 0: - raise ValueError("Number of workers must be non-negative") - - yaml = """ - workers: auto - lua-config: something - """ - - d = parse_yaml(yaml) - o = ConfSchema(d) - assert o.lua_config == "something" - assert o.inner.size == 5 - assert o.workers == 8 - - # replacement of 'lua-config' attribute - upd = d.update("/lua-config", parse_json('"new_value"')) - o = ConfSchema(upd) - assert o.lua_config == "new_value" - assert o.inner.size == 5 - assert o.workers == 8 - - # replacement of the whole tree - o = ConfSchema(d.update("/", parse_json('{"inner": {"size": 55}}'))) - assert o.lua_config is None - assert o.workers == 1 - assert o.inner.size == 55 - - # replacement of 'inner' subtree - o = ConfSchema(d.update("/inner", parse_json('{"size": 33}'))) - assert o.lua_config == "something" - assert o.workers == 8 - assert o.inner.size == 33 - - # raise validation SchemaException - with raises(SchemaException): - o = ConfSchema(d.update("/", parse_json('{"workers": -5}'))) - - -def test_eq(): - class A(SchemaNode): - field: int - - class B(SchemaNode): - a: A - field: str - - b1 = B({"a": {"field": 6}, "field": "val"}) - b2 = B({"a": {"field": 6}, "field": "val"}) - b_diff = B({"a": {"field": 7}, "field": "val"}) - - assert b1 == b2 - assert b2 != b_diff - assert b1 != b_diff - assert b_diff == b_diff diff --git a/manager/tests/unit/utils/test_modelling.py b/manager/tests/unit/utils/test_modelling.py new file mode 100644 index 000000000..627ef39c6 --- /dev/null +++ b/manager/tests/unit/utils/test_modelling.py @@ -0,0 +1,266 @@ +from typing import Any, Dict, List, Optional, Tuple, Type, Union + +import pytest +from pytest import raises +from typing_extensions import Literal + +from knot_resolver_manager.exceptions import SchemaException +from knot_resolver_manager.utils import SchemaNode +from knot_resolver_manager.utils.parsing import parse_json, parse_yaml + + +class _TestBool(SchemaNode): + v: bool + + +class _TestInt(SchemaNode): + v: int + + +class _TestStr(SchemaNode): + v: str + + +@pytest.mark.parametrize("val,exp", [("false", False), ("true", True), ("False", False), ("True", True)]) +def test_parsing_bool_valid(val: str, exp: bool): + assert _TestBool(parse_yaml(f"v: {val}")).v == exp + + +@pytest.mark.parametrize("val", ["0", "1", "5", "'true'", "'false'", "5.5"]) # int, str, float +def test_parsing_bool_invalid(val: str): + with raises(SchemaException): + _TestBool(parse_yaml(f"v: {val}")) + + +@pytest.mark.parametrize("val,exp", [("0", 0), ("5353", 5353), ("-5001", -5001)]) +def test_parsing_int_valid(val: str, exp: int): + assert _TestInt(parse_yaml(f"v: {val}")).v == exp + + +@pytest.mark.parametrize("val", ["false", "'5'", "5.5"]) # bool, str, float +def test_parsing_int_invalid(val: str): + with raises(SchemaException): + _TestInt(parse_yaml(f"v: {val}")) + + +# int and float are allowed inputs for string +@pytest.mark.parametrize("val,exp", [("test", "test"), (65, "65"), (5.5, "5.5")]) +def test_parsing_str_valid(val: Any, exp: str): + assert _TestStr(parse_yaml(f"v: {val}")).v == exp + + +def test_parsing_str_invalid(): + with raises(SchemaException): + _TestStr(parse_yaml("v: false")) # bool + + +@pytest.mark.parametrize("typ,val", [(_TestInt, 5), (_TestBool, False), (_TestStr, "test")]) +def test_parsing_nested(typ: Type[SchemaNode], val: Any): + class UpperSchema(SchemaNode): + l: typ + + yaml = f""" +l: + v: {val} +""" + + o = UpperSchema(parse_yaml(yaml)) + assert o.l.v == val + + +def test_parsing_simple_compound_types(): + class TestSchema(SchemaNode): + l: List[int] + d: Dict[str, str] + t: Tuple[str, int] + o: Optional[int] + + yaml = """ +l: + - 1 + - 2 + - 3 + - 4 + - 5 +d: + something: else + w: all +t: + - test + - 5 +""" + + o = TestSchema(parse_yaml(yaml)) + assert o.l == [1, 2, 3, 4, 5] + assert o.d == {"something": "else", "w": "all"} + assert o.t == ("test", 5) + assert o.o is None + + +def test_parsing_nested_compound_types(): + class TestSchema(SchemaNode): + i: int + o: Optional[Dict[str, str]] + + yaml1 = "i: 5" + yaml2 = f""" +{yaml1} +o: + key1: str1 + key2: str2 + """ + + o = TestSchema(parse_yaml(yaml1)) + assert o.i == 5 + assert o.o is None + + o = TestSchema(parse_yaml(yaml2)) + assert o.i == 5 + assert o.o == {"key1": "str1", "key2": "str2"} + + +def test_partial_mutations(): + class InnerSchema(SchemaNode): + size: int = 5 + + class ConfPreviousSchema(SchemaNode): + workers: Union[Literal["auto"], int] = 1 + lua_config: Optional[str] = None + inner: InnerSchema = InnerSchema() + + class ConfSchema(SchemaNode): + _PREVIOUS_SCHEMA = ConfPreviousSchema + + workers: int + lua_config: Optional[str] + inner: InnerSchema + + def _workers(self, obj: Any) -> Any: + if "workers" in obj and obj["workers"] == "auto": + return 8 + return obj["workers"] + + def _validate(self) -> None: + if self.workers < 0: + raise ValueError("Number of workers must be non-negative") + + yaml = """ + workers: auto + lua-config: something + """ + + d = parse_yaml(yaml) + o = ConfSchema(d) + assert o.lua_config == "something" + assert o.inner.size == 5 + assert o.workers == 8 + + # replacement of 'lua-config' attribute + upd = d.update("/lua-config", parse_json('"new_value"')) + o = ConfSchema(upd) + assert o.lua_config == "new_value" + assert o.inner.size == 5 + assert o.workers == 8 + + # replacement of the whole tree + o = ConfSchema(d.update("/", parse_json('{"inner": {"size": 55}}'))) + assert o.lua_config is None + assert o.workers == 1 + assert o.inner.size == 55 + + # replacement of 'inner' subtree + o = ConfSchema(d.update("/inner", parse_json('{"size": 33}'))) + assert o.lua_config == "something" + assert o.workers == 8 + assert o.inner.size == 33 + + # raise validation SchemaException + with raises(SchemaException): + o = ConfSchema(d.update("/", parse_json('{"workers": -5}'))) + + +def test_dash_conversion(): + class TestSchema(SchemaNode): + awesome_field: Dict[str, str] + + yaml = """ +awesome-field: + awesome-key: awesome-value +""" + + o = TestSchema(parse_yaml(yaml)) + assert o.awesome_field["awesome-key"] == "awesome-value" + + +def test_eq(): + class B(SchemaNode): + a: _TestInt + field: str + + b1 = B({"a": {"v": 6}, "field": "val"}) + b2 = B({"a": {"v": 6}, "field": "val"}) + b_diff = B({"a": {"v": 7}, "field": "val"}) + + assert b1 == b2 + assert b2 != b_diff + assert b1 != b_diff + assert b_diff == b_diff + + +def test_docstring_parsing_valid(): + class NormalDescription(SchemaNode): + """ + Does nothing special + Really + """ + + desc = NormalDescription.json_schema() + assert desc["description"] == "Does nothing special\nReally" + + class FieldsDescription(SchemaNode): + """ + This is an awesome test class + --- + field: This field does nothing interesting + value: Neither does this + """ + + field: str + value: int + + schema = FieldsDescription.json_schema() + assert schema["description"] == "This is an awesome test class" + assert schema["properties"]["field"]["description"] == "This field does nothing interesting" + assert schema["properties"]["value"]["description"] == "Neither does this" + + class NoDescription(SchemaNode): + nothing: str + + _ = NoDescription.json_schema() + + +def test_docstring_parsing_invalid(): + class AdditionalItem(SchemaNode): + """ + This class is wrong + --- + field: nope + nothing: really nothing + """ + + nothing: str + + with raises(SchemaException): + _ = AdditionalItem.json_schema() + + class WrongDescription(SchemaNode): + """ + This class is wrong + --- + other: description + """ + + nothing: str + + with raises(SchemaException): + _ = WrongDescription.json_schema()