From 4bdbb27b633e821908694182cd417a9ff4761d4d Mon Sep 17 00:00:00 2001 From: Jesse Bannon Date: Tue, 25 Oct 2022 22:33:18 -0700 Subject: [PATCH] good state of things --- src/ytdl_sub/config/preset.py | 2 +- src/ytdl_sub/plugins/file_convert.py | 10 ++ src/ytdl_sub/plugins/music_tags.py | 10 ++ src/ytdl_sub/plugins/regex.py | 10 ++ src/ytdl_sub/plugins/split_by_chapters.py | 7 ++ src/ytdl_sub/plugins/video_tags.py | 10 ++ tests/unit/config/test_config_file.py | 131 ++++++++++++++++++++++ tests/unit/config/test_preset.py | 97 ---------------- 8 files changed, 179 insertions(+), 98 deletions(-) create mode 100644 tests/unit/config/test_config_file.py diff --git a/src/ytdl_sub/config/preset.py b/src/ytdl_sub/config/preset.py index 9dba29a8..b190ace0 100644 --- a/src/ytdl_sub/config/preset.py +++ b/src/ytdl_sub/config/preset.py @@ -146,7 +146,7 @@ class Preset(_PresetShell): if len(sources) > 1: raise validation_exception( name=name, - error_message=f"Contains the sources {', '.join(sources)}' but can only have one", + error_message=f"Contains the sources {', '.join(sources)} but can only have one", ) # If no sources, nothing more to validate diff --git a/src/ytdl_sub/plugins/file_convert.py b/src/ytdl_sub/plugins/file_convert.py index bdc1b07d..f1428dd5 100644 --- a/src/ytdl_sub/plugins/file_convert.py +++ b/src/ytdl_sub/plugins/file_convert.py @@ -1,4 +1,5 @@ import os +from typing import Any from typing import Dict from typing import Optional @@ -27,6 +28,15 @@ class FileConvertOptions(PluginOptions): _required_keys = {"convert_to"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + """ + Partially validate file_convert + """ + if isinstance(value, dict): + value["convert_to"] = value.get("convert_to", "mp3") + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) self._convert_to = self._validate_key(key="convert_to", validator=FileTypeValidator).value diff --git a/src/ytdl_sub/plugins/music_tags.py b/src/ytdl_sub/plugins/music_tags.py index 8b4b3480..0f85c55b 100644 --- a/src/ytdl_sub/plugins/music_tags.py +++ b/src/ytdl_sub/plugins/music_tags.py @@ -1,3 +1,4 @@ +from typing import Any from typing import Dict import mediafile @@ -34,6 +35,15 @@ class MusicTagsOptions(PluginOptions): _required_keys = {"tags"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + """ + Partially validate music tags + """ + if isinstance(value, dict): + value["tags"] = value.get("tags", {}) + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) diff --git a/src/ytdl_sub/plugins/regex.py b/src/ytdl_sub/plugins/regex.py index b818e9da..9f20b560 100644 --- a/src/ytdl_sub/plugins/regex.py +++ b/src/ytdl_sub/plugins/regex.py @@ -1,3 +1,4 @@ +from typing import Any from typing import Dict from typing import List from typing import Optional @@ -149,6 +150,15 @@ class RegexOptions(PluginOptions): _required_keys = {"from"} _optional_keys = {"skip_if_match_fails"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + """ + Partially validate regex + """ + if isinstance(value, dict): + value["from"] = value.get("from", {}) + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) self._from = self._validate_key(key="from", validator=FromSourceVariablesRegex) diff --git a/src/ytdl_sub/plugins/split_by_chapters.py b/src/ytdl_sub/plugins/split_by_chapters.py index 1dfbceb8..c7fa4b50 100644 --- a/src/ytdl_sub/plugins/split_by_chapters.py +++ b/src/ytdl_sub/plugins/split_by_chapters.py @@ -1,6 +1,7 @@ import copy import os.path from pathlib import Path +from typing import Any from typing import List from typing import Optional from typing import Tuple @@ -67,6 +68,12 @@ class SplitByChaptersOptions(PluginOptions): _required_keys = {"when_no_chapters"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + if isinstance(value, dict): + value["when_no_chapters"] = value.get("when_no_chapters", "pass") + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) self._when_no_chapters = self._validate_key( diff --git a/src/ytdl_sub/plugins/video_tags.py b/src/ytdl_sub/plugins/video_tags.py index 61d89a78..e1fb85a4 100644 --- a/src/ytdl_sub/plugins/video_tags.py +++ b/src/ytdl_sub/plugins/video_tags.py @@ -1,3 +1,4 @@ +from typing import Any from typing import Dict from ytdl_sub.entries.entry import Entry @@ -27,6 +28,15 @@ class VideoTagsOptions(PluginOptions): _required_keys = {"tags"} + @classmethod + def partial_validate(cls, name: str, value: Any) -> None: + """ + Partially validate video tags + """ + if isinstance(value, dict): + value["tags"] = value.get("tags", {}) + _ = cls(name, value) + def __init__(self, name, value): super().__init__(name, value) self._tags = self._validate_key(key="tags", validator=DictFormatterValidator) diff --git a/tests/unit/config/test_config_file.py b/tests/unit/config/test_config_file.py new file mode 100644 index 00000000..733d5f2f --- /dev/null +++ b/tests/unit/config/test_config_file.py @@ -0,0 +1,131 @@ +import re +from typing import Dict +from typing import Optional + +import pytest + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.config.preset import PRESET_KEYS +from ytdl_sub.config.preset_class_mappings import PluginMapping +from ytdl_sub.utils.exceptions import ValidationException + + +class TestConfigFilePartiallyValidatesPresets: + @classmethod + def _partial_validate( + cls, preset_dict: Dict, expected_error_message: Optional[str] = None + ) -> None: + def _config_create() -> None: + _ = ConfigFile( + name="test_partial_validate", + value={ + "configuration": {"working_directory": "."}, + "presets": {"partial_preset": preset_dict}, + }, + ) + + if expected_error_message: + with pytest.raises(ValidationException, match=re.escape(expected_error_message)): + _config_create() + else: + _config_create() + + @pytest.mark.parametrize( + "preset_dict", + [ + {"nfo_tags": {"tags": {"key-1": "preset_0"}}}, + {"output_directory_nfo_tags": {"nfo_root": "test"}}, + {"output_options": {"file_name": "test"}}, + {"output_options": {"keep_files_after": "today", "maintain_download_archive": True}}, + {"ytdl_options": {"format": "best"}}, + {"overrides": {"a": "b"}}, + ], + ) + def test_success(self, preset_dict: Dict): + self._partial_validate(preset_dict) + + @pytest.mark.parametrize("plugin", PluginMapping.plugins()) + def test_success__empty_plugins(self, plugin: str): + self._partial_validate({plugin: {}}) + + def test_error__bad_preset_section(self): + self._partial_validate( + preset_dict={"does_not_exist": "lol"}, + expected_error_message="Validation error in partial_preset: " + "'partial_preset' contains the field 'does_not_exist' which is not allowed. " + f"Allowed fields: {', '.join(sorted(PRESET_KEYS))}", + ) + + def test_error__multiple_sources(self): + self._partial_validate( + preset_dict={"youtube": {}, "generic": {}}, + expected_error_message="Validation error in partial_preset: " + "Contains the sources generic, youtube but can only have one", + ) + + def test_error__no_download_strategy(self): + self._partial_validate( + preset_dict={"generic": {}}, + expected_error_message="Validation error in partial_preset.generic: " + "missing the required field 'download_strategy'", + ) + + def test_error__bad_download_strategy(self): + self._partial_validate( + preset_dict={"generic": {"download_strategy": "fail"}}, + expected_error_message="Validation error in partial_preset.generic: " + "Tried to use download strategy 'fail' with source 'generic', " + "which does not exist. Available download strategies: collection, source", + ) + + def test_error__bad_download_strategy_args(self): + self._partial_validate( + preset_dict={"generic": {"download_strategy": "collection", "bad_key": "nope"}}, + expected_error_message="Validation error in partial_preset.generic: " + "'partial_preset.generic' contains the field 'bad_key' which is not allowed. " + "Allowed fields: urls", + ) + + @pytest.mark.parametrize( + "preset_dict", + [ + {"nfo_tags": {"tags": {"key-1": {"attributes": {"test": "2"}}}}}, + {"generic": {"urls": [{"variables_to_set": {"name": "value"}}]}}, + ], + ) + def test_partial_validate__incomplete_list_item(self, preset_dict): + with pytest.raises(ValidationException): + _ = ConfigFile( + name="test_partial_validate", + value={ + "configuration": {"working_directory": "."}, + "presets": {"partial_preset": preset_dict}, + }, + ) + + @pytest.mark.parametrize( + "preset_dict", + [ + {"overrides": "not a dict"}, + {"overrides": {"nested": {"dict": "value"}}}, + {"overrides": ["list"]}, + ], + ) + def test_error__bad_overrides(self, preset_dict): + self._partial_validate( + preset_dict=preset_dict, + expected_error_message="Validation error in partial_preset.overrides", + ) + + @pytest.mark.parametrize( + "preset_dict", + [ + {"ytdl_options": "not a dict"}, + {"ytdl_options": ["list"]}, + ], + ) + def test_error__bad_ytdl_options(self, preset_dict): + self._partial_validate( + preset_dict=preset_dict, + expected_error_message="Validation error in partial_preset.ytdl_options", + ) diff --git a/tests/unit/config/test_preset.py b/tests/unit/config/test_preset.py index 279bfc61..d924c82c 100644 --- a/tests/unit/config/test_preset.py +++ b/tests/unit/config/test_preset.py @@ -1,11 +1,5 @@ -import re -from typing import Dict -from typing import Optional - import pytest -from ytdl_sub.config.config_file import ConfigFile -from ytdl_sub.config.preset import PRESET_KEYS from ytdl_sub.config.preset import Preset from ytdl_sub.plugins.nfo_tags import NfoTagsOptions from ytdl_sub.utils.exceptions import StringFormattingVariableNotFoundException @@ -206,94 +200,3 @@ class TestPreset: }, }, ) - - -class TestPresetPartialValidate: - @classmethod - def _partial_validate( - cls, preset_dict: Dict, expected_error_message: Optional[str] = None - ) -> None: - def _config_create() -> None: - _ = ConfigFile( - name="test_partial_validate", - value={ - "configuration": {"working_directory": "."}, - "presets": {"partial_preset": preset_dict}, - }, - ) - - if expected_error_message: - with pytest.raises(ValidationException, match=re.escape(expected_error_message)): - _config_create() - else: - _config_create() - - @pytest.mark.parametrize( - "preset_dict", - [ - {"nfo_tags": {"tags": {"key-1": "preset_0"}}}, - {"output_directory_nfo_tags": {"nfo_root": "test"}}, - {"output_directory": {"file_name": "test"}}, - {"output_directory": {"keep_files_after": "today"}}, - {"ytdl_options": {"format": "best"}}, - ], - ) - def test_success(self, preset_dict: Dict): - self._partial_validate( - preset_dict, - ) - - def test_error__bad_preset_section(self): - self._partial_validate( - preset_dict={"does_not_exist": "lol"}, - expected_error_message="Validation error in partial_preset: " - "'partial_preset' contains the field 'does_not_exist' which is not allowed. " - f"Allowed fields: {', '.join(sorted(PRESET_KEYS))}", - ) - - def test_error__multiple_sources(self): - self._partial_validate( - preset_dict={"youtube": {}, "generic": {}}, - expected_error_message="Validation error in partial_preset: " - "Contains the sources generic, youtube but can only have one", - ) - - def test_error__no_download_strategy(self): - self._partial_validate( - preset_dict={"generic": {}}, - expected_error_message="Validation error in partial_preset.generic: " - "missing the required field 'download_strategy'", - ) - - def test_error__bad_download_strategy(self): - self._partial_validate( - preset_dict={"generic": {"download_strategy": "fail"}}, - expected_error_message="Validation error in partial_preset.generic: " - "Tried to use download strategy 'fail' with source 'generic', " - "which does not exist. Available download strategies: collection, source", - ) - - def test_error__bad_download_strategy_args(self): - self._partial_validate( - preset_dict={"generic": {"download_strategy": "collection", "bad_key": "nope"}}, - expected_error_message="Validation error in partial_preset.generic: " - "'partial_preset.generic' contains the field 'bad_key' which is not allowed. " - "Allowed fields: urls", - ) - - @pytest.mark.parametrize( - "preset_dict", - [ - {"nfo_tags": {"tags": {"key-1": {"attributes": {"test": "2"}}}}}, - {"generic": {"urls": [{"variables_to_set": {"name": "value"}}]}}, - ], - ) - def test_partial_validate__incomplete_list_item(self, preset_dict): - with pytest.raises(ValidationException): - _ = ConfigFile( - name="test_partial_validate", - value={ - "configuration": {"working_directory": "."}, - "presets": {"partial_preset": preset_dict}, - }, - )