diff --git a/src/ytdl_sub/config/preset_options.py b/src/ytdl_sub/config/preset_options.py index 2f9e75df..93f36453 100644 --- a/src/ytdl_sub/config/preset_options.py +++ b/src/ytdl_sub/config/preset_options.py @@ -8,6 +8,9 @@ from ytdl_sub.config.overrides import Overrides from ytdl_sub.config.plugin.plugin_operation import PluginOperation from ytdl_sub.config.validators.options import OptionsDictValidator from ytdl_sub.entries.script.variable_definitions import VARIABLES as v +from ytdl_sub.utils.exceptions import SubscriptionPermissionError +from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.file_handler import FileHandler from ytdl_sub.validators.file_path_validators import OverridesStringFormatterFilePathValidator from ytdl_sub.validators.file_path_validators import StringFormatterFileNameValidator from ytdl_sub.validators.string_datetime import StringDatetimeValidator @@ -57,12 +60,24 @@ class YTDLOptions(UnstructuredOverridesDictFormatterValidator): def to_native_dict(self, overrides: Overrides) -> Dict: """ Materializes the entire ytdl-options dict from OverrideStringFormatters into - native python + native python. """ - return { + out = { key: overrides.apply_overrides_formatter_to_native(val) for key, val in self.dict.items() } + if "cookiefile" in out: + if not FileHandler.is_file_existent(out["cookiefile"]): + raise ValidationException( + f"Specified cookiefile {out['cookiefile']} but it does not exist as a file." + ) + + if not FileHandler.is_file_readable(out["cookiefile"]): + raise SubscriptionPermissionError( + f"Cannot read cookiefile {out['cookiefile']} due to permissions issue." + ) + + return out # Disable for proper docstring formatting diff --git a/src/ytdl_sub/utils/file_handler.py b/src/ytdl_sub/utils/file_handler.py index 002cac44..1db4bb6c 100644 --- a/src/ytdl_sub/utils/file_handler.py +++ b/src/ytdl_sub/utils/file_handler.py @@ -381,7 +381,7 @@ class FileHandler: @classmethod def is_path_writable(cls, src_file_path: Union[str, Path]) -> bool: - """" + """ Check whether a path is writable. If it does not exist, try to find the base directory and check permissions on that. """ @@ -395,6 +395,20 @@ class FileHandler: return os.access(path, os.W_OK) + @classmethod + def is_file_existent(cls, file_path: Union[str, Path]) -> bool: + """ + Check whether a file exists. + """ + return os.path.isfile(file_path) + + @classmethod + def is_file_readable(cls, file_path: Union[str, Path]) -> bool: + """ + Check whether a file exists and is readable. + """ + return cls.is_file_existent(file_path) and os.access(file_path, os.R_OK) + @classmethod def copy(cls, src_file_path: Union[str, Path], dst_file_path: Union[str, Path]): """ diff --git a/tests/unit/plugins/test_ytdl_options.py b/tests/unit/plugins/test_ytdl_options.py new file mode 100644 index 00000000..bfeb0ef1 --- /dev/null +++ b/tests/unit/plugins/test_ytdl_options.py @@ -0,0 +1,38 @@ +import re +from typing import Any +from typing import Dict + +import pytest +from conftest import get_match_filters + +from ytdl_sub.config.config_file import ConfigFile +from ytdl_sub.subscriptions.subscription import Subscription +from ytdl_sub.utils.exceptions import ValidationException + + +@pytest.fixture +def preset_dict(output_directory) -> Dict[str, Any]: + return { + "download": "https://your.name.here", + "output_options": {"output_directory": output_directory, "file_name": "will_error.mp4"}, + } + + +class TestYtdlOptions: + def test_cookiefile_does_not_exist( + self, + default_config: ConfigFile, + preset_dict: Dict[str, Any], + ): + preset_dict["ytdl_options"] = { + "cookiefile": "/path/to/nowhere", + } + + error_msg = "Specified cookiefile /path/to/nowhere but it does not exist as a file." + + with pytest.raises(ValidationException, match=re.escape(error_msg)): + Subscription.from_dict( + config=default_config, + preset_name="test_ytdl_options", + preset_dict=preset_dict, + ).download(dry_run=False)