mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-06-11 03:32:10 +00:00
fix(dl): apply per-service dl config overrides for all options
services.<TAG>.dl values only applied when the key was also set in the global dl: section (equality check against config.dl missed Click's declared defaults). Gate on Click's ParameterSource instead: CLI/env > service dl > global dl > defaults, converted via each option's own type. - record parameter sources on serve's hand-built context so client values are never clobbered - accept range/list as natural keys for range_/list_ - harden QualityList (YAML int) and SlowDelayRange (YAML bool) converts
This commit is contained in:
@@ -6,7 +6,7 @@ from __future__ import annotations
|
||||
import pytest
|
||||
|
||||
from unshackle.core.tracks.subtitle import Subtitle
|
||||
from unshackle.core.utils.click_types import SubtitleCodecChoice
|
||||
from unshackle.core.utils.click_types import QUALITY_LIST, SLOW_DELAY_RANGE, SubtitleCodecChoice
|
||||
|
||||
choice = SubtitleCodecChoice(Subtitle.Codec)
|
||||
|
||||
@@ -31,3 +31,29 @@ def test_codecs_still_map(value, expected):
|
||||
|
||||
def test_empty_is_none():
|
||||
assert choice.convert(None) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
(1080, [1080]),
|
||||
([720, 1080], [1080, 720]),
|
||||
("1080p", [1080]),
|
||||
("720,1080", [1080, 720]),
|
||||
],
|
||||
)
|
||||
def test_quality_list_accepts_yaml_native_values(value, expected):
|
||||
assert QUALITY_LIST.convert(value) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
(True, (60, 120)),
|
||||
(False, None),
|
||||
("20-40", (20, 40)),
|
||||
((25, 30), (25, 30)),
|
||||
],
|
||||
)
|
||||
def test_slow_delay_range_accepts_bool(value, expected):
|
||||
assert SLOW_DELAY_RANGE.convert(value, None, None) == expected
|
||||
|
||||
154
tests/core/test_service_dl_overrides.py
Normal file
154
tests/core/test_service_dl_overrides.py
Normal file
@@ -0,0 +1,154 @@
|
||||
"""Tests for ``apply_service_dl_overrides`` — precedence: CLI/env > service dl > global dl > defaults."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Optional
|
||||
|
||||
import click
|
||||
import pytest
|
||||
from click.core import ParameterSource
|
||||
|
||||
from unshackle.commands.dl import apply_service_dl_overrides, normalize_dl_config
|
||||
from unshackle.core.tracks import Video
|
||||
from unshackle.core.utils.click_types import LANGUAGE_RANGE, MultipleChoice
|
||||
|
||||
log = logging.getLogger("test_service_dl_overrides")
|
||||
|
||||
|
||||
def make_ctx(args: Optional[list[str]] = None, default_map: Optional[dict[str, Any]] = None) -> click.Context:
|
||||
"""Build a parsed context for a mirror of the dl group's option shapes."""
|
||||
|
||||
@click.group("dl", invoke_without_command=True)
|
||||
@click.option("-vl", "--v-lang", type=LANGUAGE_RANGE, default=[])
|
||||
@click.option(
|
||||
"-r", "--range", "range_", type=MultipleChoice(Video.Range, case_sensitive=False), default=[Video.Range.SDR]
|
||||
)
|
||||
@click.option("--repack", is_flag=True, default=False)
|
||||
@click.option("--workers", type=int, default=None, envvar="TEST_DL_WORKERS")
|
||||
def cmd(**__: Any) -> None:
|
||||
pass
|
||||
|
||||
return cmd.make_context("dl", args or [], default_map=default_map)
|
||||
|
||||
|
||||
def test_service_only_key_applies():
|
||||
"""The reported bug: key set only under services.<TAG>.dl, not under global dl:."""
|
||||
ctx = make_ctx()
|
||||
assert ctx.params["v_lang"] == []
|
||||
apply_service_dl_overrides(ctx, {"v_lang": ["en"]}, log)
|
||||
assert ctx.params["v_lang"] == ["en"]
|
||||
|
||||
|
||||
def test_cli_value_beats_service_config():
|
||||
ctx = make_ctx(["-vl", "fr"])
|
||||
apply_service_dl_overrides(ctx, {"v_lang": ["en"]}, log)
|
||||
assert ctx.params["v_lang"] == ["fr"]
|
||||
|
||||
|
||||
def test_env_value_beats_service_config(monkeypatch):
|
||||
monkeypatch.setenv("TEST_DL_WORKERS", "5")
|
||||
ctx = make_ctx()
|
||||
apply_service_dl_overrides(ctx, {"workers": 9}, log)
|
||||
assert ctx.params["workers"] == 5
|
||||
|
||||
|
||||
def test_service_config_beats_global_default_map():
|
||||
ctx = make_ctx(default_map={"v_lang": "de"})
|
||||
assert ctx.params["v_lang"] == ["de"]
|
||||
apply_service_dl_overrides(ctx, {"v_lang": ["en"]}, log)
|
||||
assert ctx.params["v_lang"] == ["en"]
|
||||
|
||||
|
||||
def test_flag_global_true_service_false():
|
||||
ctx = make_ctx(default_map={"repack": True})
|
||||
assert ctx.params["repack"] is True
|
||||
apply_service_dl_overrides(ctx, {"repack": False}, log)
|
||||
assert ctx.params["repack"] is False
|
||||
|
||||
|
||||
def test_range_scalar_converts_to_enum_list():
|
||||
ctx = make_ctx()
|
||||
apply_service_dl_overrides(ctx, {"range_": "HDR10"}, log)
|
||||
assert ctx.params["range_"] == [Video.Range.HDR10]
|
||||
|
||||
|
||||
def test_range_alias_applies():
|
||||
"""`range` in config is an accepted alias for the `range_` parameter."""
|
||||
ctx = make_ctx()
|
||||
apply_service_dl_overrides(ctx, {"range": "HDR10"}, log)
|
||||
assert ctx.params["range_"] == [Video.Range.HDR10]
|
||||
|
||||
|
||||
def test_normalize_dl_config_maps_aliases():
|
||||
assert normalize_dl_config({"range": "SDR", "list": True, "v_lang": ["en"]}) == {
|
||||
"range_": "SDR",
|
||||
"list_": True,
|
||||
"v_lang": ["en"],
|
||||
}
|
||||
|
||||
|
||||
def test_unknown_key_warns(caplog):
|
||||
ctx = make_ctx()
|
||||
with caplog.at_level(logging.WARNING, logger=log.name):
|
||||
apply_service_dl_overrides(ctx, {"bogus": 1}, log)
|
||||
assert any("unknown dl option 'bogus'" in r.message for r in caplog.records)
|
||||
|
||||
|
||||
def test_none_value_is_skipped():
|
||||
ctx = make_ctx()
|
||||
apply_service_dl_overrides(ctx, {"v_lang": None}, log)
|
||||
assert ctx.params["v_lang"] == []
|
||||
|
||||
|
||||
def test_invalid_value_warns_and_keeps_current(caplog):
|
||||
ctx = make_ctx()
|
||||
with caplog.at_level(logging.WARNING, logger=log.name):
|
||||
apply_service_dl_overrides(ctx, {"workers": "abc"}, log)
|
||||
assert ctx.params["workers"] is None
|
||||
assert any("Failed to apply service dl override 'workers'" in r.message for r in caplog.records)
|
||||
|
||||
|
||||
def hand_built_ctx(body: dict[str, Any]) -> click.Context:
|
||||
"""Mirror the serve path: ctx.params set directly, sources recorded manually."""
|
||||
parsed = make_ctx()
|
||||
ctx = click.Context(parsed.command)
|
||||
ctx.params = {"repack": body.get("repack", False)}
|
||||
for name in ctx.params:
|
||||
ctx.set_parameter_source(
|
||||
name, ParameterSource.COMMANDLINE if name in body else ParameterSource.DEFAULT
|
||||
)
|
||||
return ctx
|
||||
|
||||
|
||||
def test_hand_built_ctx_default_gets_override():
|
||||
ctx = hand_built_ctx({})
|
||||
apply_service_dl_overrides(ctx, {"repack": True}, log)
|
||||
assert ctx.params["repack"] is True
|
||||
|
||||
|
||||
def test_hand_built_ctx_client_value_wins():
|
||||
ctx = hand_built_ctx({"repack": False})
|
||||
apply_service_dl_overrides(ctx, {"repack": True}, log)
|
||||
assert ctx.params["repack"] is False
|
||||
|
||||
|
||||
def test_hand_built_ctx_missing_param_is_skipped():
|
||||
"""Known dl option absent from a hand-built context must not raise or be injected."""
|
||||
ctx = hand_built_ctx({})
|
||||
apply_service_dl_overrides(ctx, {"v_lang": ["en"]}, log)
|
||||
assert "v_lang" not in ctx.params
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,expected",
|
||||
[
|
||||
(["en", "de"], ["en", "de"]),
|
||||
("en,de", ["en", "de"]),
|
||||
("en", ["en"]),
|
||||
],
|
||||
)
|
||||
def test_language_range_yaml_shapes(value, expected):
|
||||
ctx = make_ctx()
|
||||
apply_service_dl_overrides(ctx, {"v_lang": value}, log)
|
||||
assert ctx.params["v_lang"] == expected
|
||||
Reference in New Issue
Block a user