From 14d8440ee7fc9636aa142beb57af2a674770da95 Mon Sep 17 00:00:00 2001 From: jbannon Date: Tue, 5 Apr 2022 07:37:34 +0000 Subject: [PATCH] tests, docstrings --- tests/unit/validators/test_dict_validator.py | 99 +++++++++++++++++++ .../unit/validators/test_string_validator.py | 8 +- .../base/string_formatter_validator.py | 7 +- .../base/string_select_validator.py | 3 - ytdl_subscribe/validators/base/validators.py | 31 +++++- 5 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 tests/unit/validators/test_dict_validator.py diff --git a/tests/unit/validators/test_dict_validator.py b/tests/unit/validators/test_dict_validator.py new file mode 100644 index 00000000..0033b272 --- /dev/null +++ b/tests/unit/validators/test_dict_validator.py @@ -0,0 +1,99 @@ +import pytest + +from ytdl_subscribe.validators.base.validators import BoolValidator +from ytdl_subscribe.validators.base.validators import DictValidator +from ytdl_subscribe.validators.base.validators import StringValidator +from ytdl_subscribe.validators.exceptions import ValidationException + + +@pytest.mark.parametrize( + "value", [{}, {"key": "value"}, {"b": {}, "a": "keys_out_of_order"}] +) +def test_dict_validator(value): + dict_validator = DictValidator(name="good_dict_validator", value=value) + assert dict_validator._name == "good_dict_validator" + assert dict_validator._value == value + assert dict_validator._dict == value + assert dict_validator._keys == sorted(list(value.keys())) + + +@pytest.mark.parametrize("value", [None, "a", {"set"}, 0]) +def test_dict_validator_fails_bad_value(value): + with pytest.raises( + ValidationException, match="Validation error in fail: should be of type object" + ): + _ = DictValidator(name="fail", value=value) + + +@pytest.mark.parametrize( + "value, validator_class", [("string", StringValidator), (False, BoolValidator)] +) +def test_dict_validator_validate_key(value, validator_class): + dict_validator = DictValidator(name="validate_key", value={"key_name": value}) + validated_key = dict_validator._validate_key( + key="key_name", validator=validator_class + ) + + assert isinstance(validated_key, validator_class) + assert validated_key.value == value + + +def test_dict_validator_validate_key_errors_missing(): + dict_validator = DictValidator(name="validate_key", value={"key": None}) + with pytest.raises( + ValidationException, + match="Validation error in validate_key: key_name is missing when it should be present.", + ): + _ = dict_validator._validate_key(key="key_name", validator=StringValidator) + + +@pytest.mark.parametrize("bad_value", [True, None, {}]) +def test_dict_validator_validate_key_errors_bad_validation(bad_value): + dict_validator = DictValidator(name="parent", value={"child": bad_value}) + with pytest.raises( + ValidationException, + match="Validation error in parent.child: should be of type string", + ): + _ = dict_validator._validate_key(key="child", validator=StringValidator) + + +@pytest.mark.parametrize( + "value, validator_class", [("string", StringValidator), (False, BoolValidator)] +) +def test_dict_validator_validate_key_if_present(value, validator_class): + dict_validator = DictValidator(name="validate_key", value={"key_name": value}) + validated_key = dict_validator._validate_key_if_present( + key="key_name", validator=validator_class + ) + + assert isinstance(validated_key, validator_class) + assert validated_key.value == value + + +def test_dict_validator_validate_key_if_present_is_missing(): + dict_validator = DictValidator(name="validate_key", value={"key_name": None}) + out = dict_validator._validate_key_if_present( + key="non_existent_key", validator=StringValidator + ) + + assert out is None + + +def test_dict_validator_validate_key_if_present_is_missing_with_default(): + dict_validator = DictValidator(name="validate_key", value={"key_name": None}) + out = dict_validator._validate_key_if_present( + key="non_existent_key", validator=StringValidator, default="default" + ) + + assert isinstance(out, StringValidator) + assert out.value == "default" + + +@pytest.mark.parametrize("bad_value", [True, None, {}]) +def test_dict_validator_validate_key_errors_none_bad_validation(bad_value): + dict_validator = DictValidator(name="parent", value={"child": bad_value}) + with pytest.raises( + ValidationException, + match="Validation error in parent.child: should be of type string", + ): + _ = dict_validator._validate_key(key="child", validator=StringValidator) diff --git a/tests/unit/validators/test_string_validator.py b/tests/unit/validators/test_string_validator.py index 6b25c231..18906ccd 100644 --- a/tests/unit/validators/test_string_validator.py +++ b/tests/unit/validators/test_string_validator.py @@ -6,10 +6,10 @@ from ytdl_subscribe.validators.exceptions import ValidationException @pytest.mark.parametrize("value", ["a", "unicode 💩", ""]) def test_string_validator(value): - bool_validator = StringValidator(name="good_str_validator", value=value) - assert bool_validator._name == "good_str_validator" - assert bool_validator._value == value - assert bool_validator.value == value + string_validator = StringValidator(name="good_str_validator", value=value) + assert string_validator._name == "good_str_validator" + assert string_validator._value == value + assert string_validator.value == value @pytest.mark.parametrize("value", [None, {}, True, 0]) diff --git a/ytdl_subscribe/validators/base/string_formatter_validator.py b/ytdl_subscribe/validators/base/string_formatter_validator.py index c4c9854c..ff14a868 100644 --- a/ytdl_subscribe/validators/base/string_formatter_validator.py +++ b/ytdl_subscribe/validators/base/string_formatter_validator.py @@ -1,16 +1,18 @@ import re from keyword import iskeyword from typing import List +from typing import final from ytdl_subscribe.validators.base.validators import LiteralDictValidator -from ytdl_subscribe.validators.base.validators import StringValidator +from ytdl_subscribe.validators.base.validators import Validator -class StringFormatterValidator(StringValidator): +class StringFormatterValidator(Validator): """ Ensures user-created formatter strings are valid """ + _expected_value_type = str _expected_value_type_name = "format string" __fields_validator = re.compile(r"{([a-z_]+?)}") @@ -58,6 +60,7 @@ class StringFormatterValidator(StringValidator): super().__init__(name=name, value=value) self.format_variables = self.__validate_and_get_format_variables() + @final @property def format_string(self) -> str: """ diff --git a/ytdl_subscribe/validators/base/string_select_validator.py b/ytdl_subscribe/validators/base/string_select_validator.py index 75346b8f..8e88c3e3 100644 --- a/ytdl_subscribe/validators/base/string_select_validator.py +++ b/ytdl_subscribe/validators/base/string_select_validator.py @@ -1,6 +1,3 @@ -import re -from keyword import iskeyword -from typing import List from typing import Set from ytdl_subscribe.validators.base.validators import StringValidator diff --git a/ytdl_subscribe/validators/base/validators.py b/ytdl_subscribe/validators/base/validators.py index 6ee38a59..fb88ee94 100644 --- a/ytdl_subscribe/validators/base/validators.py +++ b/ytdl_subscribe/validators/base/validators.py @@ -5,6 +5,7 @@ from typing import List from typing import Optional from typing import Type from typing import TypeVar +from typing import final from ytdl_subscribe.validators.exceptions import ValidationException @@ -97,6 +98,7 @@ class DictValidator(Validator): _expected_value_type = dict _expected_value_type_name = "object" + @final @property def _dict(self) -> dict: """ @@ -106,6 +108,7 @@ class DictValidator(Validator): """ return self._value + @final @property def _keys(self) -> List[str]: """ @@ -115,6 +118,7 @@ class DictValidator(Validator): """ return sorted(list(self._dict.keys())) + @final def _validate_key( self, key: str, @@ -129,30 +133,47 @@ class DictValidator(Validator): validator The validator to use for the key's value default - If the key's value is None, use this as the default + If the key's value does not exist, use this value, unless it is None. Returns ------- An instance of the specified validator """ - value = self._dict.get(key, default) - if value is None: + if key not in self._dict and default is None: raise self._validation_exception( f"{key} is missing when it should be present." ) return validator( name=f"{self._name}.{key}", - value=value, + value=self._dict.get(key, default), ) + @final def _validate_key_if_present( self, key: str, validator: Type[T], default: Optional[Any] = None, ) -> Optional[T]: - if key not in self._dict: + """ + If the key does not exist in the dict, and no default is provided, return None. + Otherwise, validate the key. + + Parameters + ---------- + key + Name of they key in the dict to validate + validator + The validator to use for the key's value + default + If the key's value does not exist, use this value. + + Returns + ------- + An instance of the specified validator + """ + if key not in self._dict and default is None: return None return self._validate_key(key=key, validator=validator, default=default)