mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-12 20:15:10 +00:00
Renames 314 defs and 41 module/class-level constants. Keeps 11 names underscored where the stripped form collides in scope, plus 5.5.0 shims for the three symbols external service repos call.
158 lines
4.9 KiB
Python
158 lines
4.9 KiB
Python
"""Unit tests for module-level helpers in unshackle.core.remote_service."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
|
|
import pytest
|
|
|
|
from unshackle.core.remote_service import (
|
|
build_title,
|
|
build_tracks,
|
|
deserialize_audio,
|
|
deserialize_subtitle,
|
|
deserialize_video,
|
|
enum_get,
|
|
match_track,
|
|
reconstruct_drm,
|
|
)
|
|
from unshackle.core.titles.episode import Episode
|
|
from unshackle.core.titles.movie import Movie
|
|
from unshackle.core.tracks import Audio, Subtitle, Video
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
class _Color(Enum):
|
|
RED = 1
|
|
BLUE = 2
|
|
|
|
|
|
def test_enum_get_known() -> None:
|
|
assert enum_get(_Color, "RED") is _Color.RED
|
|
|
|
|
|
def test_enum_get_unknown_returns_default() -> None:
|
|
assert enum_get(_Color, "PURPLE", default=_Color.BLUE) is _Color.BLUE
|
|
|
|
|
|
def test_enum_get_none_returns_default() -> None:
|
|
assert enum_get(_Color, None, default=_Color.RED) is _Color.RED
|
|
|
|
|
|
def test_deserialize_video_minimal() -> None:
|
|
v = deserialize_video({"id": "video-1", "codec": "AVC", "width": 1920, "height": 1080, "bitrate": 5000})
|
|
assert isinstance(v, Video)
|
|
assert v.id == "video-1"
|
|
assert v.codec is Video.Codec.AVC
|
|
assert v.bitrate == 5_000_000 # kbps -> bps
|
|
assert v.width == 1920
|
|
assert v.height == 1080
|
|
assert v.range is Video.Range.SDR
|
|
|
|
|
|
def test_deserialize_video_unknown_codec_falls_back_to_none() -> None:
|
|
v = deserialize_video({"id": "v2", "codec": "MADE_UP", "width": 0, "height": 0})
|
|
assert v.codec is None
|
|
|
|
|
|
def test_deserialize_audio_atmos_flag_sets_joc() -> None:
|
|
a = deserialize_audio({"id": "a1", "codec": "AAC", "atmos": True, "channels": 6, "bitrate": 256})
|
|
assert isinstance(a, Audio)
|
|
assert a.joc == 1
|
|
assert a.channels == 6
|
|
assert a.bitrate == 256_000
|
|
|
|
|
|
def test_deserialize_audio_no_atmos() -> None:
|
|
a = deserialize_audio({"id": "a2", "codec": "AAC", "channels": 2})
|
|
assert a.joc == 0
|
|
|
|
|
|
def test_deserialize_subtitle_forced_flag() -> None:
|
|
s = deserialize_subtitle({"id": "s1", "codec": "WebVTT", "language": "en", "forced": True})
|
|
assert isinstance(s, Subtitle)
|
|
assert s.forced is True
|
|
assert s.sdh is False
|
|
|
|
|
|
def test_deserialize_subtitle_sdh_flag() -> None:
|
|
s = deserialize_subtitle({"id": "s2", "codec": "WebVTT", "language": "en", "sdh": True})
|
|
assert s.sdh is True
|
|
assert s.forced is False
|
|
|
|
|
|
def test_reconstruct_drm_empty() -> None:
|
|
assert reconstruct_drm(None) == []
|
|
assert reconstruct_drm([]) == []
|
|
|
|
|
|
def test_reconstruct_drm_skips_entries_without_pssh() -> None:
|
|
assert reconstruct_drm([{"type": "widevine"}]) == []
|
|
|
|
|
|
def test_reconstruct_drm_invalid_pssh_silently_dropped() -> None:
|
|
assert reconstruct_drm([{"type": "widevine", "pssh": "not-real-pssh"}]) == []
|
|
|
|
|
|
def test_build_tracks_aggregates() -> None:
|
|
data = {
|
|
"video": [{"id": "v", "codec": "AVC", "width": 1280, "height": 720, "bitrate": 2500}],
|
|
"audio": [{"id": "a", "codec": "AAC", "channels": 2, "bitrate": 128}],
|
|
"subtitles": [{"id": "s", "codec": "WebVTT", "language": "en"}],
|
|
"attachments": [],
|
|
}
|
|
t = build_tracks(data)
|
|
assert len(t.videos) == 1
|
|
assert len(t.audio) == 1
|
|
assert len(t.subtitles) == 1
|
|
|
|
|
|
def test_match_track_by_id() -> None:
|
|
a = deserialize_video({"id": "v1", "codec": "AVC", "width": 1920, "height": 1080})
|
|
b = deserialize_video({"id": "v2", "codec": "AVC", "width": 1280, "height": 720})
|
|
remote = deserialize_video({"id": "v2", "codec": "AVC", "width": 1280, "height": 720})
|
|
assert match_track(remote, [a, b]) is b
|
|
|
|
|
|
def test_match_track_by_attributes_when_id_missing() -> None:
|
|
local = deserialize_video({"id": "X", "codec": "AVC", "width": 1920, "height": 1080, "language": "en"})
|
|
remote = deserialize_video({"id": "Y", "codec": "AVC", "width": 1920, "height": 1080, "language": "en"})
|
|
assert match_track(remote, [local]) is local
|
|
|
|
|
|
def test_match_track_no_candidates_returns_none() -> None:
|
|
remote = deserialize_video({"id": "X", "codec": "AVC", "width": 1, "height": 1})
|
|
assert match_track(remote, []) is None
|
|
|
|
|
|
def test_build_title_movie() -> None:
|
|
info = {"type": "movie", "id": "movie-0001", "name": "Foo", "year": 2024, "language": "en"}
|
|
title = build_title(info, "ATV", "fallback")
|
|
assert isinstance(title, Movie)
|
|
assert title.id == "movie-0001"
|
|
assert title.name == "Foo"
|
|
|
|
|
|
def test_build_title_episode() -> None:
|
|
info = {
|
|
"type": "episode",
|
|
"id": "ep-00001",
|
|
"series_title": "Show",
|
|
"season": 1,
|
|
"number": 2,
|
|
"name": "Pilot",
|
|
"year": 2024,
|
|
"language": "en",
|
|
}
|
|
title = build_title(info, "ATV", "fallback")
|
|
assert isinstance(title, Episode)
|
|
assert title.season == 1
|
|
assert title.number == 2
|
|
assert title.name == "Pilot"
|
|
|
|
|
|
def test_build_title_falls_back_to_id_when_missing() -> None:
|
|
title = build_title({"type": "movie", "name": "x"}, "ATV", "fallback-id")
|
|
assert title.id == "fallback-id"
|