"""Unit tests for Widevine CDM hint-file seeding (cloakbrowser/widevine.py).""" import json import pytest from cloakbrowser import widevine from cloakbrowser.widevine import resolve_widevine_cdm_dir, seed_widevine_hint _HINT = "WidevineCdm/latest-component-updated-widevine-cdm" @pytest.fixture(autouse=True) def _force_linux(monkeypatch, tmp_path): """Run as if on Linux unless a test overrides it (seeding is Linux-only).""" monkeypatch.setattr(widevine.platform, "system", lambda: "Linux") monkeypatch.delenv("CLOAKBROWSER_WIDEVINE", raising=False) monkeypatch.delenv("CLOAKBROWSER_WIDEVINE_CDM", raising=False) # Isolate the cache-root fallback from any real ~/.cloakbrowser on the host. monkeypatch.setenv("CLOAKBROWSER_CACHE_DIR", str(tmp_path / "_isolated_cache")) def _make_cdm(dirpath): """Create a fake WidevineCdm dir with a manifest.json.""" dirpath.mkdir(parents=True, exist_ok=True) (dirpath / "manifest.json").write_text('{"version": "4.10.3050.0"}') return dirpath def _binary(tmp_path): """Return a fake chrome binary path inside its own dir.""" bdir = tmp_path / "bin" bdir.mkdir(parents=True, exist_ok=True) return bdir / "chrome" def test_seeds_hint_next_to_binary(tmp_path): """CDM in /WidevineCdm -> hint file written with abs Path.""" binary = _binary(tmp_path) cdm = _make_cdm(binary.parent / "WidevineCdm") profile = tmp_path / "profile" seed_widevine_hint(profile, binary) hint = profile / _HINT assert hint.is_file() assert json.loads(hint.read_text())["Path"] == str(cdm.resolve()) def test_seeds_hint_from_env_var(tmp_path, monkeypatch): """CLOAKBROWSER_WIDEVINE_CDM takes priority and is used as the Path.""" cdm = _make_cdm(tmp_path / "custom_cdm") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(cdm)) profile = tmp_path / "profile" seed_widevine_hint(profile, _binary(tmp_path)) assert json.loads((profile / _HINT).read_text())["Path"] == str(cdm.resolve()) def test_no_cdm_no_file(tmp_path): """No CDM present -> nothing written, no exception.""" profile = tmp_path / "profile" seed_widevine_hint(profile, _binary(tmp_path)) assert not (profile / _HINT).exists() def test_kill_switch_disables(tmp_path, monkeypatch): """CLOAKBROWSER_WIDEVINE=0 disables seeding even when a CDM exists.""" monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(_make_cdm(tmp_path / "custom_cdm"))) monkeypatch.setenv("CLOAKBROWSER_WIDEVINE", "0") profile = tmp_path / "profile" seed_widevine_hint(profile, _binary(tmp_path)) assert not (profile / _HINT).exists() def test_idempotent(tmp_path, monkeypatch): """Seeding twice leaves the same correct content and doesn't error.""" cdm = _make_cdm(tmp_path / "custom_cdm") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(cdm)) profile = tmp_path / "profile" binary = _binary(tmp_path) seed_widevine_hint(profile, binary) seed_widevine_hint(profile, binary) assert json.loads((profile / _HINT).read_text())["Path"] == str(cdm.resolve()) def test_noop_on_non_linux(tmp_path, monkeypatch): """On non-Linux, seeding is a no-op even with a CDM present.""" monkeypatch.setattr(widevine.platform, "system", lambda: "Windows") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(_make_cdm(tmp_path / "cdm"))) profile = tmp_path / "profile" seed_widevine_hint(profile, _binary(tmp_path)) assert not (profile / _HINT).exists() def test_resolve_requires_manifest(tmp_path, monkeypatch): """A WidevineCdm dir without manifest.json is not treated as a CDM.""" bogus = tmp_path / "custom_cdm" bogus.mkdir() monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(bogus)) assert resolve_widevine_cdm_dir(_binary(tmp_path)) is None def test_env_var_is_exclusive(tmp_path, monkeypatch): """An invalid CLOAKBROWSER_WIDEVINE_CDM skips seeding — no fallback to binary dir.""" binary = _binary(tmp_path) _make_cdm(binary.parent / "WidevineCdm") # valid CDM next to binary bogus = tmp_path / "bogus" bogus.mkdir() # set but no manifest.json monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(bogus)) assert resolve_widevine_cdm_dir(binary) is None def test_resolve_falls_back_to_cache_root(tmp_path, monkeypatch): """No CDM next to the binary -> auto-detect falls back to /WidevineCdm. Simulates the Pro case: a Pro binary sits in its own chromium--pro dir with no adjacent CDM, while the Docker auto-fetch left one at the cache root. """ cache = tmp_path / "cache" monkeypatch.setenv("CLOAKBROWSER_CACHE_DIR", str(cache)) cdm = _make_cdm(cache / "WidevineCdm") pro_binary = tmp_path / "chromium-148.0-pro" / "chrome" pro_binary.parent.mkdir(parents=True) # binary dir exists, but has no CDM assert resolve_widevine_cdm_dir(pro_binary) == cdm.resolve() def test_resolve_binary_dir_wins_over_cache_root(tmp_path, monkeypatch): """A manual sideload next to the binary takes precedence over the cache root.""" cache = tmp_path / "cache" monkeypatch.setenv("CLOAKBROWSER_CACHE_DIR", str(cache)) _make_cdm(cache / "WidevineCdm") # cache-root CDM present... binary = _binary(tmp_path) next_to = _make_cdm(binary.parent / "WidevineCdm") # ...but sideload wins assert resolve_widevine_cdm_dir(binary) == next_to.resolve() def test_seeds_hint_from_cache_root_fallback(tmp_path, monkeypatch): """End-to-end: a cache-root CDM seeds the hint for a binary with none adjacent.""" cache = tmp_path / "cache" monkeypatch.setenv("CLOAKBROWSER_CACHE_DIR", str(cache)) cdm = _make_cdm(cache / "WidevineCdm") profile = tmp_path / "profile" seed_widevine_hint(profile, _binary(tmp_path)) # binary has no adjacent CDM assert json.loads((profile / _HINT).read_text())["Path"] == str(cdm.resolve()) def test_empty_env_var_is_exclusive(tmp_path, monkeypatch): """An empty (but set) CLOAKBROWSER_WIDEVINE_CDM resolves to None — and must NOT pick up a stray manifest.json in the working directory (``Path("")`` -> ``.``).""" binary = _binary(tmp_path) _make_cdm(binary.parent / "WidevineCdm") # valid CDM next to binary monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", "") cwd = tmp_path / "cwd" cwd.mkdir() (cwd / "manifest.json").write_text("{}") # stray manifest in CWD must be ignored monkeypatch.chdir(cwd) assert resolve_widevine_cdm_dir(binary) is None def test_empty_user_data_dir_skips(tmp_path, monkeypatch): """Empty user_data_dir (ephemeral profile) -> no CWD pollution, no seeding.""" cdm = _make_cdm(tmp_path / "custom_cdm") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(cdm)) monkeypatch.chdir(tmp_path) seed_widevine_hint("", _binary(tmp_path)) assert not (tmp_path / "WidevineCdm").exists() def test_never_raises_on_write_failure(tmp_path, monkeypatch): """A write failure (hint dir path is a file) must not raise — launch must not break.""" cdm = _make_cdm(tmp_path / "custom_cdm") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(cdm)) profile = tmp_path / "profile" profile.mkdir() # Block mkdir of /WidevineCdm by occupying that path with a file. (profile / "WidevineCdm").write_text("not a dir") seed_widevine_hint(profile, _binary(tmp_path)) # must not raise def test_rewrites_corrupt_existing_hint(tmp_path, monkeypatch): """A non-UTF8 / mismatched existing hint is overwritten, without raising.""" cdm = _make_cdm(tmp_path / "custom_cdm") monkeypatch.setenv("CLOAKBROWSER_WIDEVINE_CDM", str(cdm)) profile = tmp_path / "profile" hint = profile / "WidevineCdm" / _HINT.split("/")[-1] hint.parent.mkdir(parents=True) hint.write_bytes(b"\xff\xfe not valid utf-8") seed_widevine_hint(profile, _binary(tmp_path)) # must not raise # corrupt content replaced with a valid hint pointing at the CDM assert json.loads(hint.read_text())["Path"] == str(cdm.resolve())