Files
unshackle/tests/lang_selection/test_required_langs_gate.py
imSp4rky 3477d09cc2 feat(dl): add --audio-description-only and --forced-subs-only
Each keeps one kind of track inside its track type. A run of more than one title skips a title that has no such track. The REST API rejects a non-boolean value for a boolean dl param, and the DASH parser reads audio description from Role elements.
2026-10-06 12:45:52 -06:00

74 lines
3.4 KiB
Python

"""The dl.result call site of missing_required_langs, and inputs the unit tests skip.
The gate in dl.result is not extractable without executing the whole download loop, so
its wiring is pinned the same way test_required_langs pins download_manager: against the
source. The value inputs cover what the helper's own tests leave out — real tracks carry
langcodes.Language objects, not strings.
"""
from __future__ import annotations
import inspect
from langcodes import Language
from unshackle.commands.dl import dl
from unshackle.core.utilities import missing_required_langs
def test_language_objects_satisfy_a_requirement():
# track.language is a langcodes.Language, never a str, at the real call site
assert missing_required_langs(["en"], [Language.get("en")]) == []
assert missing_required_langs(["en"], [Language.get("en-US")]) == []
assert missing_required_langs(["en"], [Language.get("fr")]) == ["en"]
def test_orig_accepts_a_language_object_title_language():
# title.language is a langcodes.Language at the call site, never a str
assert missing_required_langs(["orig"], ["ja"], Language.get("ja")) == []
assert missing_required_langs(["orig"], ["en"], Language.get("ja")) == ["ja"]
def test_requirement_checks_the_catalogue_not_the_selection():
# -l best,-fr --require-audio en: the gate answers "does en exist", so the
# exclusion of fr from the *selection* must not make en count as missing
assert missing_required_langs(["en"], ["en", "ko", "ja", "fr"]) == []
# requiring a language the selection excludes is still satisfied by the catalogue
assert missing_required_langs(["fr"], ["en", "ko", "ja", "fr"]) == []
class TestGateWiring:
"""Pin the dl.result gate's shape; a silent rewire is otherwise invisible to tests."""
source = inspect.getsource(dl.result)
def test_gate_is_disabled_with_the_track_kind(self):
# --no-audio / -V must not fail on a require_audio the download cannot honour
assert "require_audio if keep_audio else []" in self.source
assert "require_video if keep_videos else []" in self.source
def test_gate_counts_audio_embedded_in_kept_video(self):
assert "embedded_audio_langs(title.tracks.videos, keep_videos and not audio_description_only)" in self.source
def test_gate_honours_exact_lang(self):
assert "missing_required_langs(required, available, title.language, exact=exact_lang)" in self.source
def test_gate_reads_tracks_before_the_keep_flags_empty_them(self):
# the availability lists must be built before `if not keep_videos:` clears
# title.tracks.videos, or embedded audio could never satisfy a requirement
gate = self.source.index("missing_required_langs(required")
reset = self.source.index("if not keep_videos:")
assert gate < reset
def test_require_and_exclude_clash_is_rejected_per_flag(self):
# --require-audio en with -l all,-en can never be satisfied as the user
# meant it, so it must die at parse time, against each flag's own
# exclusion list (-vl/-al cascade from -l only when unset)
assert "cannot be required and excluded at the same time" in self.source
for pair in (
'("--require-audio", require_audio, audio_excl)',
'("--require-video", require_video, video_excl)',
'("--require-subs", require_subs, s_lang_excl)',
):
assert pair in self.source