Files
imSp4rky 22cc739e83 fix(downloader): correct truncation, ranged sizing, and session carry
Check a sequential download against the complete length whenever one is known, and settle the unknown-total case on the 416. An empty body of unknown length, a 206 with no complete length, and a 206 starting past the resume offset all passed the size checks before and were renamed into place. Four restart paths spent no attempt, so a host that always answered the same way cycled forever.
2026-09-06 20:03:08 -06:00

146 lines
5.3 KiB
Python

"""``dl.get_cdm`` builds each CDM once per run.
Regression: the callers ask for a CDM once per title, and the dedupe guard at the
call site compares object identity. No CDM class defines ``__eq__``, so the guard
was always true and every title paid a fresh build. A remote device costs an RSA
key pair and a blocking request to its host.
"""
from __future__ import annotations
import logging
from typing import Any, Optional
import pytest
import unshackle.core.cdm as cdm_mod
from unshackle.commands.dl import dl
from unshackle.core.cdm import loader
from unshackle.core.config import config
class StubCdm:
"""A distinct object per build, so a reused CDM is visible by identity."""
def __init__(self, cdm_name: str, service_name: str) -> None:
self.cdm_name = cdm_name
self.service_name = service_name
@pytest.fixture
def builds(monkeypatch) -> list[tuple[str, str]]:
"""Record every ``load_cdm`` call and hand back a fresh stub for each one."""
calls: list[tuple[str, str]] = []
def fake_load_cdm(cdm_name: str, *, service_name: str = "", vaults: Optional[Any] = None) -> StubCdm:
calls.append((cdm_name, service_name))
return StubCdm(cdm_name, service_name)
# The package resolves ``load_cdm`` lazily, so a ``setattr`` here would write the real
# function into the package dict on undo and shadow that lookup for the whole process.
monkeypatch.setitem(cdm_mod.__dict__, "load_cdm", fake_load_cdm)
return calls
def make_dl() -> dl:
# __new__ skips the CLI-driven __init__; get_cdm only needs these three.
instance = dl.__new__(dl)
instance.log = logging.getLogger("test_cdm_cache")
instance.cdm_override = None
instance.vaults = None
return instance
def test_repeated_calls_build_the_cdm_once(builds, monkeypatch) -> None:
monkeypatch.setattr(config, "cdm", {"EXAMPLE": "device_a"})
runner = make_dl()
first = runner.get_cdm("EXAMPLE", "default")
for _ in range(4):
assert runner.get_cdm("EXAMPLE", "default") is first
assert builds == [("device_a", "EXAMPLE")]
def test_the_title_loop_guard_sees_the_same_object(builds, monkeypatch) -> None:
"""The call site keeps its CDM when ``get_cdm`` returns what it already holds."""
monkeypatch.setattr(config, "cdm", {"EXAMPLE": "device_a"})
runner = make_dl()
held = runner.get_cdm("EXAMPLE", "default")
for _ in range(3):
quality_based_cdm = runner.get_cdm("EXAMPLE", "default", drm="widevine", quality=2160)
assert not (quality_based_cdm and quality_based_cdm != held)
assert len(builds) == 1
def test_a_different_service_gets_its_own_cdm(builds, monkeypatch) -> None:
"""A CDM is built with its ``service_name``, so two services must not share one."""
monkeypatch.setattr(config, "cdm", {"EXAMPLE": "device_a", "OTHER": "device_a"})
runner = make_dl()
example = runner.get_cdm("EXAMPLE", "default")
other = runner.get_cdm("OTHER", "default")
assert example is not other
assert builds == [("device_a", "EXAMPLE"), ("device_a", "OTHER")]
def test_a_different_profile_gets_its_own_cdm(builds, monkeypatch) -> None:
monkeypatch.setattr(config, "cdm", {"EXAMPLE": {"one": "device_one", "two": "device_two"}})
runner = make_dl()
one = runner.get_cdm("EXAMPLE", "one")
two = runner.get_cdm("EXAMPLE", "two")
assert one is not two
assert runner.get_cdm("EXAMPLE", "one") is one
assert builds == [("device_one", "EXAMPLE"), ("device_two", "EXAMPLE")]
def test_quality_selection_reuses_the_device_it_resolves_to(builds, monkeypatch) -> None:
"""Two spellings of the same device share one CDM; a different device does not."""
monkeypatch.setattr(config, "cdm", {"EXAMPLE": {">=1080": "device_hd", "<1080": "device_sd"}})
runner = make_dl()
hd = runner.get_cdm("EXAMPLE", "default", quality=2160)
assert runner.get_cdm("EXAMPLE", "default", quality=1080) is hd
sd = runner.get_cdm("EXAMPLE", "default", quality=720)
assert sd is not hd
assert builds == [("device_hd", "EXAMPLE"), ("device_sd", "EXAMPLE")]
def test_an_override_is_cached_under_the_name_it_resolves_to(builds, monkeypatch) -> None:
monkeypatch.setattr(config, "cdm", {"EXAMPLE": "device_a"})
runner = make_dl()
runner.cdm_override = "device_b"
override = runner.get_cdm("EXAMPLE", "default")
assert runner.get_cdm("EXAMPLE", "default") is override
assert builds == [("device_b", "EXAMPLE")]
@pytest.fixture
def leaked_shadow(monkeypatch) -> None:
"""Reproduce the residue that a ``setattr`` on the CDM package leaves behind.
``load_cdm`` reaches the package only through its lazy ``__getattr__``. A ``setattr`` on
the package writes the real function into the package dict when it undoes, which shadows
``__getattr__`` for the rest of the process. Two remote tests do exactly that.
"""
monkeypatch.setitem(cdm_mod.__dict__, "load_cdm", loader.load_cdm)
def test_a_shadowed_package_attribute_still_reaches_the_stub(leaked_shadow, builds, monkeypatch) -> None:
"""The stub intercepts ``get_cdm`` whatever an earlier test left on the CDM package."""
monkeypatch.setattr(config, "cdm", {"EXAMPLE": "device_a"})
runner = make_dl()
cdm = runner.get_cdm("EXAMPLE", "default")
assert isinstance(cdm, StubCdm)
assert builds == [("device_a", "EXAMPLE")]