Completely rewrite how subscription validation is performed. Optimizes it quite a bit while also adding backend support for the upcoming `dissect` sub-command, where you can resolve any subscription into its 'raw' form for easier debugging when making scripting changes.
319 lines
11 KiB
Python
319 lines
11 KiB
Python
import os.path
|
|
import re
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from ytdl_sub.config.preset import Preset
|
|
from ytdl_sub.plugins.nfo_tags import NfoTagsOptions
|
|
from ytdl_sub.utils.exceptions import ValidationException
|
|
|
|
|
|
class TestPreset:
|
|
@pytest.mark.parametrize(
|
|
"download_value",
|
|
[
|
|
{"url": "youtube.com/watch?v=123abc"}, # url download strategy
|
|
{"urls": [{"url": "youtube.com/watch?v=123abc"}]}, # multi-url download strategy
|
|
"youtube.com/watch?v=123abc", # single string
|
|
["youtube.com/watch?v=123abc", "youtube.com/watch?v=123xyz"], # list of strings
|
|
[{"url": "youtube.com/watch?v=123abc"}, "youtube.com/watch?v=123abc"], # dict and str
|
|
],
|
|
)
|
|
def test_bare_minimum_preset(self, config_file, output_options, download_value):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={"download": download_value, "output_options": output_options},
|
|
)
|
|
|
|
def test_preset_with_override_variable(self, config_file, output_options, youtube_video):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": {"output_directory": "dir", "file_name": "{dne_var}"},
|
|
"overrides": {"dne_var": "not dne"},
|
|
},
|
|
)
|
|
|
|
def test_preset_with_output_directory_tilda(self, config_file, output_options, youtube_video):
|
|
out = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": {"output_directory": "~/output/dir", "file_name": "{dne_var}"},
|
|
"overrides": {"dne_var": "not dne"},
|
|
},
|
|
)
|
|
assert out.overrides.apply_formatter(out.output_options.output_directory) == str(
|
|
Path(os.path.expanduser("~")) / "output" / "dir"
|
|
)
|
|
|
|
def test_preset_parent(self, config_file, output_options, youtube_video):
|
|
preset = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"preset": "parent_preset_1",
|
|
"download": youtube_video,
|
|
"output_options": output_options,
|
|
"nfo_tags": {"tags": {"key-2": "this-preset"}},
|
|
},
|
|
)
|
|
|
|
nfo_options: NfoTagsOptions = preset.plugins.get(NfoTagsOptions)
|
|
tags_string_dict = {
|
|
key: formatter[0].format_string
|
|
for key, formatter in nfo_options.tags.string_tags.items()
|
|
}
|
|
|
|
assert tags_string_dict == {"key-1": "preset_0", "key-2": "this-preset"}
|
|
|
|
@pytest.mark.parametrize(
|
|
"preset_value", [["parent_preset_1", "parent_preset_2"], "parent_preset_3"]
|
|
)
|
|
def test_preset_multiple_parents(
|
|
self, config_file, output_options, youtube_video, preset_value
|
|
):
|
|
preset = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"preset": preset_value,
|
|
"download": youtube_video,
|
|
"output_options": output_options,
|
|
"nfo_tags": {"tags": {"key-3": "this-preset"}},
|
|
},
|
|
)
|
|
|
|
nfo_options: NfoTagsOptions = preset.plugins.get(NfoTagsOptions)
|
|
tags_string_dict = {
|
|
key: formatter[0].format_string
|
|
for key, formatter in nfo_options.tags.string_tags.items()
|
|
}
|
|
|
|
assert tags_string_dict == {
|
|
"key-1": "preset_0",
|
|
"key-2": "preset_2",
|
|
"key-3": "this-preset",
|
|
}
|
|
|
|
def test_preset_datetime_with_override(self, config_file, youtube_video, output_options):
|
|
preset = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": dict(
|
|
output_options,
|
|
**{"maintain_download_archive": True, "keep_files_after": "today-{ttl}"},
|
|
),
|
|
"overrides": {"ttl": "2months"},
|
|
},
|
|
)
|
|
assert (
|
|
preset.overrides.apply_formatter(formatter=preset.output_options.keep_files_after)
|
|
== "today-2months"
|
|
)
|
|
|
|
def test_preset_preserve_mtime_option(self, config_file, youtube_video, output_options):
|
|
# Test preserve_mtime defaults to False
|
|
preset_default = Preset(
|
|
config=config_file,
|
|
name="test_default",
|
|
value={"download": youtube_video, "output_options": output_options},
|
|
)
|
|
assert preset_default.output_options.preserve_mtime is False
|
|
|
|
# Test preserve_mtime can be set to True
|
|
preset_enabled = Preset(
|
|
config=config_file,
|
|
name="test_enabled",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": dict(output_options, **{"preserve_mtime": True}),
|
|
},
|
|
)
|
|
assert preset_enabled.output_options.preserve_mtime is True
|
|
|
|
@pytest.mark.parametrize(
|
|
"parent_preset", ["preset_self_loop", "preset_loop_0", "preset_loop_1"]
|
|
)
|
|
def test_preset_error__parent_loop(
|
|
self, config_file, output_options, youtube_video, parent_preset
|
|
):
|
|
with pytest.raises(ValidationException, match="preset loop detected"):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"preset": parent_preset,
|
|
"download": youtube_video,
|
|
"output_options": output_options,
|
|
},
|
|
)
|
|
|
|
def test_preset_with_multi_url__contains_empty_url(self, config_file, output_options):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": [{"url": "non-empty url"}, {"url": ""}], # empty url
|
|
"output_options": output_options,
|
|
},
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"entry_variable_name",
|
|
[
|
|
"title",
|
|
"playlist_uid",
|
|
"source_title",
|
|
"playlist_max_upload_year",
|
|
],
|
|
)
|
|
def test_preset_error_override_variable_collides_with_entry_variable(
|
|
self, config_file, output_options, youtube_video, entry_variable_name: str
|
|
):
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
f"Override variable with name {entry_variable_name} cannot be used since"
|
|
" it is a built-in ytdl-sub entry variable name."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": {"output_directory": "dir", "file_name": "{dne_var}"},
|
|
"overrides": {entry_variable_name: "fail"},
|
|
},
|
|
)
|
|
|
|
def test_preset_error_override_variable_collides_added_variable(
|
|
self, config_file, output_options, youtube_video
|
|
):
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
f"Override variable with name subtitles_ext cannot be used since"
|
|
" it is added by a plugin."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": {"output_directory": "dir", "file_name": "ack"},
|
|
"subtitles": {
|
|
"embed_subtitles": True,
|
|
},
|
|
"overrides": {"subtitles_ext": "collide"},
|
|
},
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"function_name",
|
|
[
|
|
"%extract_field_from_siblings",
|
|
"%sanitize",
|
|
"%array",
|
|
],
|
|
)
|
|
def test_preset_error_override_variable_collides_with_custom_function(
|
|
self, config_file, output_options, youtube_video, function_name: str
|
|
):
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
f"Override function definition with name {function_name} cannot be used since"
|
|
" it is a built-in ytdl-sub function name."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": {"output_directory": "dir", "file_name": "{dne_var}"},
|
|
"overrides": {function_name: "fail"},
|
|
},
|
|
)
|
|
|
|
def test_preset_error_override_added_variable_collides_with_built_in(
|
|
self, config_file, output_options
|
|
):
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
"Cannot use the variable name title because it exists as a "
|
|
"built-in ytdl-sub variable name."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": {
|
|
"url": "youtube.com/watch?v=123abc",
|
|
"variables": {"title": "nope"},
|
|
},
|
|
"output_options": {"output_directory": "dir", "file_name": "acjk"},
|
|
},
|
|
)
|
|
|
|
def test_preset_error_override_added_variable_collides_with_override(
|
|
self, config_file, output_options
|
|
):
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
"Override variable with name the_bad_one cannot be used since "
|
|
"it is added by a plugin."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": {
|
|
"url": "youtube.com/watch?v=123abc",
|
|
"variables": {"the_bad_one": "should error"},
|
|
},
|
|
"output_options": {"output_directory": "dir", "file_name": "acjk"},
|
|
"overrides": {"the_bad_one": "ack"},
|
|
},
|
|
)
|
|
|
|
@pytest.mark.parametrize(
|
|
"name", ["!ack", "*asfsaf", "1234352", "--234asdf", "___asdf", "1asdfasdfasd"]
|
|
)
|
|
@pytest.mark.parametrize("is_function", [True, False])
|
|
def test_preset_error_overrides_invalid_variable_name(
|
|
self, config_file, youtube_video, output_options, name: str, is_function: bool
|
|
):
|
|
name_type = "function" if is_function else "variable"
|
|
name = f"%{name}" if is_function else name
|
|
|
|
with pytest.raises(
|
|
ValidationException,
|
|
match=re.escape(
|
|
f"Override {name_type} with name {name} is invalid."
|
|
" Names must be lower_snake_cased and begin with a letter."
|
|
),
|
|
):
|
|
_ = Preset(
|
|
config=config_file,
|
|
name="test",
|
|
value={
|
|
"download": youtube_video,
|
|
"output_options": output_options,
|
|
"overrides": {name: "ack"},
|
|
},
|
|
)
|