mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-06-10 03:02:09 +00:00
test(remote): add unit + e2e suite for remote-services subsystem
Covers RemoteClient/RemoteService, REST routes, handlers, SessionStore, InputBridge, DownloadQueueManager, errors, compression, and serve CLI. E2e tier opts in via --live and can auto-spawn its own serve.
This commit is contained in:
273
tests/remote/unit/test_handlers_serialize.py
Normal file
273
tests/remote/unit/test_handlers_serialize.py
Normal file
@@ -0,0 +1,273 @@
|
||||
"""Unit tests for unshackle.core.api.handlers serializers + validators."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from langcodes import Language
|
||||
|
||||
from unshackle.core.api.handlers import (
|
||||
sanitize_log,
|
||||
serialize_audio_track,
|
||||
serialize_drm,
|
||||
serialize_subtitle_track,
|
||||
serialize_title,
|
||||
serialize_video_track,
|
||||
validate_download_parameters,
|
||||
validate_service,
|
||||
)
|
||||
from unshackle.core.titles.episode import Episode
|
||||
from unshackle.core.titles.movie import Movie
|
||||
from unshackle.core.tracks import Audio, Subtitle, Video
|
||||
from unshackle.core.tracks.track import Track
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
class _FakeSvc:
|
||||
pass
|
||||
|
||||
|
||||
def _video(**overrides) -> Video:
|
||||
base = dict(
|
||||
url="https://example.com/v.mpd",
|
||||
language=Language.get("en"),
|
||||
descriptor=Track.Descriptor.URL,
|
||||
codec=Video.Codec.AVC,
|
||||
range_=Video.Range.SDR,
|
||||
bitrate=5_000_000,
|
||||
width=1920,
|
||||
height=1080,
|
||||
fps=24,
|
||||
id_="video-001",
|
||||
)
|
||||
base.update(overrides)
|
||||
return Video(**base)
|
||||
|
||||
|
||||
def _audio(**overrides) -> Audio:
|
||||
base = dict(
|
||||
url="https://example.com/a.mpd",
|
||||
language=Language.get("en"),
|
||||
descriptor=Track.Descriptor.URL,
|
||||
codec=Audio.Codec.AAC,
|
||||
bitrate=128_000,
|
||||
channels=2,
|
||||
joc=0,
|
||||
descriptive=False,
|
||||
id_="audio-001",
|
||||
)
|
||||
base.update(overrides)
|
||||
return Audio(**base)
|
||||
|
||||
|
||||
def _subtitle(**overrides) -> Subtitle:
|
||||
base = dict(
|
||||
url="https://example.com/s.vtt",
|
||||
language=Language.get("en"),
|
||||
descriptor=Track.Descriptor.URL,
|
||||
codec=Subtitle.Codec.WebVTT,
|
||||
cc=False,
|
||||
sdh=False,
|
||||
forced=False,
|
||||
id_="sub-001",
|
||||
)
|
||||
base.update(overrides)
|
||||
return Subtitle(**base)
|
||||
|
||||
|
||||
# ---------- sanitize_log ----------
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"raw, expected",
|
||||
[
|
||||
("hello\nworld", "helloworld"),
|
||||
("a\r\nb\x00c", "abc"),
|
||||
("clean", "clean"),
|
||||
(12345, "12345"),
|
||||
],
|
||||
)
|
||||
def test_sanitize_log(raw, expected: str) -> None:
|
||||
assert sanitize_log(raw) == expected
|
||||
|
||||
|
||||
# ---------- serialize_title ----------
|
||||
|
||||
|
||||
def test_serialize_title_movie() -> None:
|
||||
movie = Movie(id_="movie-0001", service=_FakeSvc, name="Title X", year=2024, language=Language.get("en"))
|
||||
d = serialize_title(movie)
|
||||
assert d["type"] == "movie"
|
||||
assert d["name"] == "Title X"
|
||||
assert d["year"] == 2024
|
||||
assert d["id"] == "movie-0001"
|
||||
assert d["language"] == "en"
|
||||
|
||||
|
||||
def test_serialize_title_episode_named() -> None:
|
||||
ep = Episode(
|
||||
id_="ep-00001",
|
||||
service=_FakeSvc,
|
||||
title="My Show",
|
||||
season=2,
|
||||
number=3,
|
||||
name="Pilot",
|
||||
year=2024,
|
||||
language=Language.get("en"),
|
||||
)
|
||||
d = serialize_title(ep)
|
||||
assert d["type"] == "episode"
|
||||
assert d["series_title"] == "My Show"
|
||||
assert d["season"] == 2
|
||||
assert d["number"] == 3
|
||||
assert d["name"] == "Pilot"
|
||||
|
||||
|
||||
def test_serialize_title_episode_unnamed_falls_back_to_number() -> None:
|
||||
ep = Episode(
|
||||
id_="ep-00002",
|
||||
service=_FakeSvc,
|
||||
title="Show",
|
||||
season=1,
|
||||
number=5,
|
||||
name=None,
|
||||
year=None,
|
||||
language=Language.get("en"),
|
||||
)
|
||||
d = serialize_title(ep)
|
||||
assert d["name"] == "Episode 05"
|
||||
|
||||
|
||||
# ---------- serialize_video/audio/subtitle ----------
|
||||
|
||||
|
||||
def test_serialize_video_track_basic() -> None:
|
||||
d = serialize_video_track(_video())
|
||||
assert d["id"] == "video-001"
|
||||
assert d["codec"] == "AVC"
|
||||
assert d["bitrate"] == 5000 # kbps
|
||||
assert d["resolution"] == "1920x1080"
|
||||
assert d["fps"] == 24
|
||||
assert d["range"] == "SDR"
|
||||
assert d["language"] == "en"
|
||||
assert d["drm"] is None
|
||||
assert "url" not in d
|
||||
|
||||
|
||||
def test_serialize_video_track_include_url() -> None:
|
||||
d = serialize_video_track(_video(), include_url=True)
|
||||
assert d["url"] == "https://example.com/v.mpd"
|
||||
|
||||
|
||||
def test_serialize_audio_track_basic() -> None:
|
||||
d = serialize_audio_track(_audio())
|
||||
assert d["id"] == "audio-001"
|
||||
assert d["codec"] == "AAC"
|
||||
assert d["bitrate"] == 128
|
||||
assert d["channels"] == 2
|
||||
assert d["descriptive"] is False
|
||||
|
||||
|
||||
def test_serialize_subtitle_track_basic() -> None:
|
||||
d = serialize_subtitle_track(_subtitle(forced=True))
|
||||
assert d["id"] == "sub-001"
|
||||
assert d["codec"] == "WebVTT"
|
||||
assert d["forced"] is True
|
||||
assert d["sdh"] is False
|
||||
assert d["cc"] is False
|
||||
|
||||
|
||||
# ---------- serialize_drm ----------
|
||||
|
||||
|
||||
def test_serialize_drm_none_returns_none() -> None:
|
||||
assert serialize_drm(None) is None
|
||||
assert serialize_drm([]) is None
|
||||
|
||||
|
||||
def test_serialize_drm_widevine_minimal() -> None:
|
||||
class _PSSH:
|
||||
def dumps(self) -> str:
|
||||
return "BASE64PSSH=="
|
||||
|
||||
class _Widevine:
|
||||
def __init__(self) -> None:
|
||||
self._pssh = _PSSH()
|
||||
self.kids = ["00112233445566778899aabbccddeeff"]
|
||||
self.license_url = "https://lic.example.com/wv"
|
||||
|
||||
out = serialize_drm(_Widevine())
|
||||
assert isinstance(out, list)
|
||||
assert len(out) == 1
|
||||
info = out[0]
|
||||
assert info["type"] == "_widevine" # class name lowercased
|
||||
assert info["pssh"] == "BASE64PSSH=="
|
||||
assert info["kids"] == ["00112233445566778899aabbccddeeff"]
|
||||
assert info["license_url"] == "https://lic.example.com/wv"
|
||||
|
||||
|
||||
# ---------- validate_service ----------
|
||||
|
||||
|
||||
def test_validate_service_unknown_returns_none() -> None:
|
||||
assert validate_service("NOPE_THIS_IS_NOT_REAL_") is None
|
||||
|
||||
|
||||
# ---------- validate_download_parameters ----------
|
||||
|
||||
|
||||
def test_validate_download_params_accepts_defaults() -> None:
|
||||
assert validate_download_parameters({}) is None
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"data, fragment",
|
||||
[
|
||||
({"vcodec": "WUT"}, "Invalid vcodec"),
|
||||
({"vcodec": 123}, "vcodec must be a string or list"),
|
||||
({"acodec": "MP9"}, "Invalid acodec"),
|
||||
({"sub_format": "doc"}, "Invalid sub_format"),
|
||||
({"vbitrate": -1}, "vbitrate"),
|
||||
({"abitrate": "no"}, "abitrate"),
|
||||
({"vbitrate_range": "no-dash-but-letters"}, None),
|
||||
({"vbitrate_range": "nope"}, "MIN-MAX"),
|
||||
({"channels": -3}, "channels"),
|
||||
({"workers": 0}, "workers"),
|
||||
({"downloads": 0}, "downloads"),
|
||||
({"video_only": True, "audio_only": True}, "exclusive"),
|
||||
({"no_subs": True, "subs_only": True}, "no_subs and subs_only"),
|
||||
({"no_audio": True, "audio_only": True}, "no_audio and audio_only"),
|
||||
({"s_lang": ["en"], "require_subs": ["en"]}, "s_lang and require_subs"),
|
||||
({"range": "UHD"}, "Invalid range"),
|
||||
({"range": ["SDR", "UHD"]}, "Invalid range value"),
|
||||
],
|
||||
)
|
||||
def test_validate_download_params_errors(data: dict, fragment) -> None:
|
||||
result = validate_download_parameters(data)
|
||||
if fragment is None:
|
||||
# A dash-containing string is valid syntactically per current rule
|
||||
assert result is None
|
||||
else:
|
||||
assert result is not None
|
||||
assert fragment in result
|
||||
|
||||
|
||||
def test_validate_download_params_accepts_valid_values() -> None:
|
||||
assert (
|
||||
validate_download_parameters(
|
||||
{
|
||||
"vcodec": "H264,H265",
|
||||
"acodec": ["AAC", "EAC3"],
|
||||
"sub_format": "VTT",
|
||||
"vbitrate": 6000,
|
||||
"abitrate": 128,
|
||||
"vbitrate_range": "6000-7000",
|
||||
"abitrate_range": "96-192",
|
||||
"channels": 5.1,
|
||||
"workers": 8,
|
||||
"downloads": 2,
|
||||
"range": ["SDR", "HDR10"],
|
||||
}
|
||||
)
|
||||
is None
|
||||
)
|
||||
Reference in New Issue
Block a user