test: replace real service tags and brands with generic placeholders

This commit is contained in:
imSp4rky
2026-10-18 12:17:08 -06:00
parent 768e35d905
commit 6fdfeb9cae
12 changed files with 48 additions and 45 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ from unshackle.core.api.handlers import load_client_cookies
COOKIE_FILE = "\n".join( COOKIE_FILE = "\n".join(
[ [
"# Netscape HTTP Cookie File", "# Netscape HTTP Cookie File",
"\t".join([".primevideo.com", "TRUE", "/", "TRUE", "2000000000", "session-id", "123-456"]), "\t".join([".example.com", "TRUE", "/", "TRUE", "2000000000", "session-id", "123-456"]),
"", "",
] ]
) )
+2 -2
View File
@@ -15,7 +15,7 @@ dl:
a_lang: [yes] a_lang: [yes]
require_subs: no require_subs: no
require_audio: [no] require_audio: [no]
AMZN: EXAMPLE:
lang: no lang: no
muxing: muxing:
default_language: default_language:
@@ -43,7 +43,7 @@ def test_bool_shaped_tags_survive_every_language_key(tmp_path: Path) -> None:
assert config.dl["a_lang"] == ["yes"] assert config.dl["a_lang"] == ["yes"]
assert config.dl["require_subs"] == "no" assert config.dl["require_subs"] == "no"
assert config.dl["require_audio"] == ["no"] assert config.dl["require_audio"] == ["no"]
assert config.dl["AMZN"]["lang"] == "no" assert config.dl["EXAMPLE"]["lang"] == "no"
assert config.muxing["default_language"]["audio"] == "no" assert config.muxing["default_language"]["audio"] == "no"
assert config.audio["language_priority"] == ["no", "da"] assert config.audio["language_priority"] == ["no", "da"]
+1 -1
View File
@@ -38,7 +38,7 @@ def test_safe_cache_key_accepts_plain_filename():
def test_safe_cache_key_accepts_nested_key_and_normalises_separators(): def test_safe_cache_key_accepts_nested_key_and_normalises_separators():
# A Cacher key with a subdirectory, as NF uses ("session_web/<sha1>"), keeps its # A Cacher key with a subdirectory ("session_web/<sha1>") keeps its
# posix form; a backslash from a Windows peer names the same nested file. # posix form; a backslash from a Windows peer names the same nested file.
assert safe_cache_key("session_web/abc") == "session_web/abc" assert safe_cache_key("session_web/abc") == "session_web/abc"
assert safe_cache_key("MSL/a/b") == "MSL/a/b" assert safe_cache_key("MSL/a/b") == "MSL/a/b"
+2 -2
View File
@@ -228,14 +228,14 @@ def test_refresh_reports_discarded_local_edits(monkeypatch, tmp_path):
def run(args, **kw): def run(args, **kw):
if "diff" in args and "--name-only" in args: if "diff" in args and "--name-only" in args:
return subprocess.CompletedProcess(args, 0, b"CR/__init__.py\n", b"") # local edit present return subprocess.CompletedProcess(args, 0, b"EXAMPLE/__init__.py\n", b"") # local edit present
if "rev-parse" in args: if "rev-parse" in args:
return subprocess.CompletedProcess(args, 0, b"samehead\n", b"") # upstream did not advance return subprocess.CompletedProcess(args, 0, b"samehead\n", b"") # upstream did not advance
return subprocess.CompletedProcess(args, 0, b"", b"") # fetch/reset/diff return subprocess.CompletedProcess(args, 0, b"", b"") # fetch/reset/diff
monkeypatch.setattr(service_repo.subprocess, "run", run) monkeypatch.setattr(service_repo.subprocess, "run", run)
_, changes = service_repo.refresh_repo("a/b") _, changes = service_repo.refresh_repo("a/b")
assert changes == ["!CR"] assert changes == ["!EXAMPLE"]
def test_changed_services_up_to_date(monkeypatch): def test_changed_services_up_to_date(monkeypatch):
+3 -4
View File
@@ -43,8 +43,7 @@ def sent_timeout(adapter: HTTPAdapter) -> Any:
def test_subclass_adapter_keeps_its_class_and_tls_context() -> None: def test_subclass_adapter_keeps_its_class_and_tls_context() -> None:
"""A service-mounted SSLCiphers must survive the growth with its TLS context. """A service-mounted SSLCiphers must survive the growth with its TLS context.
CBS, friDay, iP, MY5 and PMTP all mount SSLCiphers on the service session and none Services mount SSLCiphers on the service session without overriding get_session(). Replacing it with a plain adapter drops DEFAULT:@SECLEVEL=2 and
override get_session(). Replacing it with a plain adapter drops DEFAULT:@SECLEVEL=2 and
check_hostname=False, so metadata succeeds and every segment then fails at TLS or 403. check_hostname=False, so metadata succeeds and every segment then fails at TLS or 403.
""" """
session = requests.Session() session = requests.Session()
@@ -75,13 +74,13 @@ def test_a_distinct_http_adapter_is_grown_not_overwritten() -> None:
narrow = SSLCiphers(security_level=2) narrow = SSLCiphers(security_level=2)
plain = TimeoutHTTPAdapter(pool_connections=10, pool_maxsize=10) plain = TimeoutHTTPAdapter(pool_connections=10, pool_maxsize=10)
session.mount("https://", secure) session.mount("https://", secure)
session.mount("https://cbs.example/", narrow) session.mount("https://svc.example/", narrow)
session.mount("http://", plain) session.mount("http://", plain)
grow_session_pool(session, 64) grow_session_pool(session, 64)
assert session.get_adapter("https://x") is secure assert session.get_adapter("https://x") is secure
assert session.get_adapter("https://cbs.example/a") is narrow assert session.get_adapter("https://svc.example/a") is narrow
assert session.get_adapter("http://x") is plain assert session.get_adapter("http://x") is plain
assert session.get_adapter("http://x") is not session.get_adapter("https://x") assert session.get_adapter("http://x") is not session.get_adapter("https://x")
for adapter in (secure, narrow, plain): for adapter in (secure, narrow, plain):
+1 -1
View File
@@ -390,7 +390,7 @@ def test_only_the_kid_whose_window_fails_is_flagged(tmp_path: Path, monkeypatch:
lead = UUID(int=7) lead = UUID(int=7)
local = SQLite("local", tmp_path / "local.db") local = SQLite("local", tmp_path / "local.db")
poisoned = Remote("poisoned", tmp_path / "poisoned.db") poisoned = Remote("poisoned", tmp_path / "poisoned.db")
poisoned.add_key("SVC", lead, BAD) # the same wrong key under both KIDs, as the NF title had poisoned.add_key("SVC", lead, BAD) # the same wrong key under both KIDs
poisoned.add_key("SVC", KID, BAD) poisoned.add_key("SVC", KID, BAD)
other = Remote("other", tmp_path / "other.db") other = Remote("other", tmp_path / "other.db")
other.add_key("SVC", KID, GOOD) other.add_key("SVC", KID, GOOD)
@@ -48,8 +48,7 @@ DISTINCT_PAIRS = [
] ]
# A full real-world audio language set for one title, the kind of mix a streaming manifest ships, # A full real-world audio language set for one title, the kind of mix a streaming manifest ships,
# which must resolve 1:1 with no collision or loss. Tags are drawn from published streaming # which must resolve 1:1 with no collision or loss. Tags include regional and script variants.
# language references (Warner Bros. Discovery / Netflix) and include regional and script variants.
REAL_WORLD_AUDIO_LANGS = [ REAL_WORLD_AUDIO_LANGS = [
"ar", "ar",
"ar-EG", "ar-EG",
+2 -2
View File
@@ -136,12 +136,12 @@ def test_ring_masks_host_paths_and_secrets_in_records() -> None:
logger = logging.getLogger("test.ring.redact") logger = logging.getLogger("test.ring.redact")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
logger.addHandler(ring) logger.addHandler(ring)
logger.error(f'File "{root}/unshackle/services/HMAX/__init__.py" 404: https://api.x/y?token=abc') logger.error(f'File "{root}/unshackle/services/EXAMPLE/__init__.py" 404: https://api.x/y?token=abc')
logger.removeHandler(ring) logger.removeHandler(ring)
msg = ring.records[-1]["msg"] msg = ring.records[-1]["msg"]
assert root not in msg assert root not in msg
assert "<unshackle>/unshackle/services/HMAX" in msg assert "<unshackle>/unshackle/services/EXAMPLE" in msg
assert "token=abc" not in msg assert "token=abc" not in msg
assert "https://api.x/y" in msg assert "https://api.x/y" in msg
assert ring.since(0, logger="test") and not ring.since(0, logger="other") assert ring.since(0, logger="test") and not ring.since(0, logger="other")
+21 -21
View File
@@ -11,8 +11,8 @@ from unshackle.core.config import config
SERVICES = [ SERVICES = [
{ {
"tag": "AMZN", "tag": "EXAMPLE",
"aliases": ["amazon"], "aliases": ["sample"],
"cli_params": [{"kind": "option", "name": "extras"}, {"kind": "argument", "name": "title"}], "cli_params": [{"kind": "option", "name": "extras"}, {"kind": "argument", "name": "title"}],
} }
] ]
@@ -20,7 +20,7 @@ INFO = SERVICES[0]
class FakeRemote: class FakeRemote:
service_tag = "AMZN" service_tag = "EXAMPLE"
def __init__(self) -> None: def __init__(self) -> None:
self.closed = False self.closed = False
@@ -47,7 +47,7 @@ def lease(data, **extra):
return handlers.lease_remote_service({**data, **extra}, "srv", INFO, None, "show") return handlers.lease_remote_service({**data, **extra}, "srv", INFO, None, "show")
DATA = {"service": "AMZN", "title_id": "show", "remote": "srv"} DATA = {"service": "EXAMPLE", "title_id": "show", "remote": "srv"}
async def test_concurrent_leases_share_one_session(pool): async def test_concurrent_leases_share_one_session(pool):
@@ -89,17 +89,17 @@ def remote_config(monkeypatch):
def test_remote_server_resolves_case_and_aliases(remote_config): def test_remote_server_resolves_case_and_aliases(remote_config):
assert handlers.remote_server({"remote": "srv", "service": "amzn"}) == ("srv", INFO) assert handlers.remote_server({"remote": "srv", "service": "example"}) == ("srv", INFO)
assert handlers.remote_server({"remote": "srv", "service": "Amazon"}) == ("srv", INFO) assert handlers.remote_server({"remote": "srv", "service": "Sample"}) == ("srv", INFO)
assert handlers.remote_server({"service": "AMZN"}) is None assert handlers.remote_server({"service": "EXAMPLE"}) is None
@pytest.mark.parametrize( @pytest.mark.parametrize(
"data, code", "data, code",
[ [
({"remote": "nope", "service": "AMZN"}, APIErrorCode.INVALID_PARAMETERS), ({"remote": "nope", "service": "EXAMPLE"}, APIErrorCode.INVALID_PARAMETERS),
({"remote": ["srv"], "service": "AMZN"}, APIErrorCode.INVALID_PARAMETERS), ({"remote": ["srv"], "service": "EXAMPLE"}, APIErrorCode.INVALID_PARAMETERS),
({"remote": "srv", "service": "NF"}, APIErrorCode.INVALID_SERVICE), ({"remote": "srv", "service": "OTHER"}, APIErrorCode.INVALID_SERVICE),
], ],
) )
def test_remote_server_rejects(remote_config, data, code): def test_remote_server_rejects(remote_config, data, code):
@@ -109,31 +109,31 @@ def test_remote_server_rejects(remote_config, data, code):
def test_remote_server_applies_the_allowlist_and_reports_an_unreachable_server(remote_config, monkeypatch): def test_remote_server_applies_the_allowlist_and_reports_an_unreachable_server(remote_config, monkeypatch):
monkeypatch.setattr(handlers, "get_allowed_services", lambda request=None: ["NF"]) monkeypatch.setattr(handlers, "get_allowed_services", lambda request=None: ["OTHER"])
with pytest.raises(APIError) as e: with pytest.raises(APIError) as e:
handlers.remote_server({"remote": "srv", "service": "AMZN"}) handlers.remote_server({"remote": "srv", "service": "EXAMPLE"})
assert e.value.error_code == APIErrorCode.INVALID_SERVICE assert e.value.error_code == APIErrorCode.INVALID_SERVICE
monkeypatch.setattr(remote_service, "remote_service_list", lambda name: None) monkeypatch.setattr(remote_service, "remote_service_list", lambda name: None)
with pytest.raises(APIError) as e: with pytest.raises(APIError) as e:
handlers.remote_server({"remote": "srv", "service": "AMZN"}) handlers.remote_server({"remote": "srv", "service": "EXAMPLE"})
assert e.value.error_code == APIErrorCode.SERVICE_UNAVAILABLE assert e.value.error_code == APIErrorCode.SERVICE_UNAVAILABLE
def test_make_remote_service_passes_only_cli_options(remote_config, monkeypatch): def test_make_remote_service_passes_only_cli_options(remote_config, monkeypatch):
built = {} built = {}
monkeypatch.setattr(remote_service, "resolve_server", lambda name: ("https://srv", "k", {"AMZN": {"x": 1}})) monkeypatch.setattr(remote_service, "resolve_server", lambda name: ("https://srv", "k", {"EXAMPLE": {"x": 1}}))
monkeypatch.setattr(remote_service, "RemoteService", lambda *a, **kw: built.update(args=a, **kw)) monkeypatch.setattr(remote_service, "RemoteService", lambda *a, **kw: built.update(args=a, **kw))
remote_service.make_remote_service( remote_service.make_remote_service(
None, "srv", "AMZN", "id", {"service_params": {"extras": "y", "service": "DSNP", "proxy": "x"}}, False None, "srv", "EXAMPLE", "id", {"service_params": {"extras": "y", "service": "OTHER", "proxy": "x"}}, False
) )
assert built["service_params"] == {"extras": "y"} assert built["service_params"] == {"extras": "y"}
assert "AMZN" not in built["args"][5] # apply_overrides=False keeps the remote's overrides out assert "EXAMPLE" not in built["args"][5] # apply_overrides=False keeps the remote's overrides out
remote_service.make_remote_service(None, "srv", "AMZN", "id", {"extras": "z", "service": "DSNP", "quality": 1}) remote_service.make_remote_service(None, "srv", "EXAMPLE", "id", {"extras": "z", "service": "OTHER", "quality": 1})
assert built["service_params"] == {"extras": "z"} assert built["service_params"] == {"extras": "z"}
assert built["args"][5]["AMZN"] == {"x": 1} assert built["args"][5]["EXAMPLE"] == {"x": 1}
def test_remote_error_carries_its_reason_and_exits_a_cli_run(): def test_remote_error_carries_its_reason_and_exits_a_cli_run():
@@ -145,8 +145,8 @@ def test_remote_error_carries_its_reason_and_exits_a_cli_run():
def test_a_geofenced_remote_service_gets_its_first_allowed_region_as_proxy(monkeypatch): def test_a_geofenced_remote_service_gets_its_first_allowed_region_as_proxy(monkeypatch):
monkeypatch.setattr(handlers, "server_proxy_allowed", lambda request=None: True) monkeypatch.setattr(handlers, "server_proxy_allowed", lambda request=None: True)
fenced = {"tag": "PCOK", "geofence": ["US", "ca"], "geoblock": ["us"]} fenced = {"tag": "EXAMPLE", "geofence": ["US", "ca"], "geoblock": ["us"]}
assert handlers.geofence_proxy({}, {"tag": "PCOK", "geofence": ["US", "ca"]}) == {"proxy": "us"} assert handlers.geofence_proxy({}, {"tag": "EXAMPLE", "geofence": ["US", "ca"]}) == {"proxy": "us"}
assert handlers.geofence_proxy({}, fenced) == {"proxy": "ca"} assert handlers.geofence_proxy({}, fenced) == {"proxy": "ca"}
assert handlers.geofence_proxy({"proxy": "gb"}, fenced) == {} assert handlers.geofence_proxy({"proxy": "gb"}, fenced) == {}
assert handlers.geofence_proxy({"no_proxy": True}, fenced) == {} assert handlers.geofence_proxy({"no_proxy": True}, fenced) == {}
@@ -194,7 +194,7 @@ async def test_a_remote_login_prompt_waits_for_its_owners_answer(monkeypatch):
class Remote: class Remote:
auth_notice = None auth_notice = None
login = handlers.RemoteLogin("owner", "srv", "DSNP", "show", Remote()) login = handlers.RemoteLogin("owner", "srv", "OTHER", "show", Remote())
monkeypatch.setattr(handlers, "_remote_logins", {login.id: login}) monkeypatch.setattr(handlers, "_remote_logins", {login.id: login})
monkeypatch.setattr(handlers, "is_admin", lambda request: False) monkeypatch.setattr(handlers, "is_admin", lambda request: False)
caller = {"key": "owner"} caller = {"key": "owner"}
@@ -320,7 +320,7 @@ def test_load_cache_files_wiring(tmp_path, monkeypatch) -> None:
def test_load_cache_files_forwards_nested_keys(tmp_path, monkeypatch) -> None: def test_load_cache_files_forwards_nested_keys(tmp_path, monkeypatch) -> None:
"""A service that keeps its cache in subfolders (NF: session_web/<sha1>.json) must have """A service that keeps its cache in subfolders (session_web/<sha1>.json) must have
those files forwarded under the Cacher key, and the credential filter must still apply those files forwarded under the Cacher key, and the credential filter must still apply
to the basename of a nested file.""" to the basename of a nested file."""
import base64 import base64
@@ -69,14 +69,14 @@ def _wv_drm():
def _session(track): def _session(track):
# handle_single_server_cdm looks config.cdm up by the service class name, so the fake needs the tag as its name # handle_single_server_cdm looks config.cdm up by the service class name, so the fake needs the tag as its name
service = type("PCOK", (SimpleNamespace,), {})( service = type("EXAMPLE", (SimpleNamespace,), {})(
get_widevine_service_certificate=lambda **k: None, get_widevine_service_certificate=lambda **k: None,
get_widevine_license=lambda **k: b"", get_widevine_license=lambda **k: b"",
get_playready_license=lambda **k: b"", get_playready_license=lambda **k: b"",
) )
return SimpleNamespace( return SimpleNamespace(
lock=asyncio.Lock(), lock=asyncio.Lock(),
service_tag="PCOK", service_tag="EXAMPLE",
service_instance=service, service_instance=service,
served_keys={}, served_keys={},
tracks=SimpleNamespace(get={"vid": track}.get), tracks=SimpleNamespace(get={"vid": track}.get),
@@ -170,7 +170,10 @@ async def test_quality_keyed_mapping_resolves_to_the_device_it_loads(env, monkey
monkeypatch.setattr( monkeypatch.setattr(
handlers.config, handlers.config,
"cdm", "cdm",
{"PCOK": {"<=1080": "wv_tier", ">1080": {"widevine": "wv_tier", "playready": "pr_tier"}}, "default": "pr_dev"}, {
"EXAMPLE": {"<=1080": "wv_tier", ">1080": {"widevine": "wv_tier", "playready": "pr_tier"}},
"default": "pr_dev",
},
raising=False, raising=False,
) )
@@ -183,7 +186,9 @@ async def test_quality_keyed_mapping_resolves_to_the_device_it_loads(env, monkey
async def test_both_systems_mapped_follow_the_client(env, monkeypatch): async def test_both_systems_mapped_follow_the_client(env, monkeypatch):
"""A mapping with a device per system settles nothing, so the client's choice goes first.""" """A mapping with a device per system settles nothing, so the client's choice goes first."""
monkeypatch.setattr(handlers.config, "cdm", {"PCOK": {"widevine": "wv_dev", "playready": "pr_dev"}}, raising=False) monkeypatch.setattr(
handlers.config, "cdm", {"EXAMPLE": {"widevine": "wv_dev", "playready": "pr_dev"}}, raising=False
)
track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()]) track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()])
body = await _license(monkeypatch, track, {"drm_type": "widevine"}) body = await _license(monkeypatch, track, {"drm_type": "widevine"})
@@ -192,7 +197,7 @@ async def test_both_systems_mapped_follow_the_client(env, monkeypatch):
async def test_no_candidate_header_is_a_client_error(env, monkeypatch): async def test_no_candidate_header_is_a_client_error(env, monkeypatch):
monkeypatch.setattr(handlers.config, "cdm", {"PCOK": "pr_dev"}, raising=False) monkeypatch.setattr(handlers.config, "cdm", {"EXAMPLE": "pr_dev"}, raising=False)
track = SimpleNamespace(id="vid", drm=[_wv_drm()]) track = SimpleNamespace(id="vid", drm=[_wv_drm()])
with pytest.raises(APIError, match="No PSSH on track"): with pytest.raises(APIError, match="No PSSH on track"):
@@ -201,7 +206,7 @@ async def test_no_candidate_header_is_a_client_error(env, monkeypatch):
async def test_track_preference_goes_first_when_the_server_has_that_device(env, monkeypatch): async def test_track_preference_goes_first_when_the_server_has_that_device(env, monkeypatch):
"""A track that prefers PlayReady licenses with PlayReady even on a Widevine-mapped server.""" """A track that prefers PlayReady licenses with PlayReady even on a Widevine-mapped server."""
monkeypatch.setattr(handlers.config, "cdm", {"PCOK": {"widevine": "wv_dev", "default": "pr_dev"}}, raising=False) monkeypatch.setattr(handlers.config, "cdm", {"EXAMPLE": {"widevine": "wv_dev", "default": "pr_dev"}}, raising=False)
track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()], drm_preference="pr") track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()], drm_preference="pr")
body = await _license(monkeypatch, track, {"drm_type": "widevine"}) body = await _license(monkeypatch, track, {"drm_type": "widevine"})
@@ -212,7 +217,7 @@ async def test_track_preference_goes_first_when_the_server_has_that_device(env,
async def test_track_preference_falls_back_with_a_warning(env, monkeypatch, caplog): async def test_track_preference_falls_back_with_a_warning(env, monkeypatch, caplog):
"""With no PlayReady device the preference cannot be met: Widevine licenses and the client hears why.""" """With no PlayReady device the preference cannot be met: Widevine licenses and the client hears why."""
monkeypatch.setattr(handlers.config, "cdm", {"PCOK": "wv_dev"}, raising=False) monkeypatch.setattr(handlers.config, "cdm", {"EXAMPLE": "wv_dev"}, raising=False)
track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()], drm_preference="pr") track = SimpleNamespace(id="vid", drm=[_pr_drm(), _wv_drm()], drm_preference="pr")
with caplog.at_level("WARNING", logger=handlers.log.name): with caplog.at_level("WARNING", logger=handlers.log.name):
+1 -1
View File
@@ -310,7 +310,7 @@ def test_sdh_stripping_ass_without_subtitleedit_content_matches_codec(tmp_path,
assert "door creaks" not in text # bracketed effect removed assert "door creaks" not in text # bracketed effect removed
# Segmented fVTT (wvtt) / fTTML (stpp) ship from DASH/HLS as fragmented MP4 (e.g. HBO Max). The # Segmented fVTT (wvtt) / fTTML (stpp) ship from DASH/HLS as fragmented MP4. The
# downloader concatenates init + media segments into one file; parse() reads the MP4 boxes directly. # downloader concatenates init + media segments into one file; parse() reads the MP4 boxes directly.
FIXTURES = pathlib.Path(__file__).parent / "fixtures" FIXTURES = pathlib.Path(__file__).parent / "fixtures"