Files
unshackle/tests/remote/unit/test_session_reporting.py

146 lines
5.0 KiB
Python

"""The dashboard's view of a remote session: the live title, and the client's redacted command line."""
import asyncio
import shlex
from types import SimpleNamespace
from typing import Any
import pytest
from unshackle.core.api import session_store as store_mod
from unshackle.core.api.handlers import find_title_for_track, session_tracks_handler
from unshackle.core.api.session_store import SessionEntry
from unshackle.core.tracks import Tracks
from unshackle.core.utils.redact import redact_text
class FakeTitle:
def __init__(self, name: str) -> None:
self.name = name
def __str__(self) -> str:
return self.name
def make_entry() -> SessionEntry:
entry = SessionEntry(session_id="s1", service_tag="EXAMPLE", service_instance=None)
entry.title_map = {"t1": FakeTitle("Example Show 2024 S01E01"), "t3": FakeTitle("Example Show 2024 S01E03")}
return entry
def test_summary_falls_back_to_the_first_title() -> None:
summary = make_entry().summary()
assert summary["title_id"] == "t1"
assert summary["title"] == "Example Show 2024 S01E01"
def test_summary_reports_the_title_the_client_asked_for() -> None:
entry = make_entry()
entry.current_title_id = "t3"
summary = entry.summary()
assert summary["title_id"] == "t3"
assert summary["title"] == "Example Show 2024 S01E03"
def test_summary_ignores_a_title_id_that_left_the_map() -> None:
entry = make_entry()
entry.current_title_id = "gone"
assert entry.summary()["title_id"] == "t1"
def test_unknown_track_resolves_to_the_live_title() -> None:
entry = make_entry()
entry.current_title_id = "t3"
assert find_title_for_track("nope", entry) is entry.title_map["t3"]
def argv_line(argv: list[str], credentials: dict[str, str] | None = None) -> str:
"""Mirror the argv capture in RemoteService.authenticate."""
secrets = [v for v in (credentials or {}).values() if isinstance(v, str) and len(v) >= 4]
return redact_text(shlex.join(argv), secrets) or ""
def test_argv_keeps_the_flags_and_drops_the_proxy_password() -> None:
line = argv_line(
[
"dl",
"-w",
"s1e1-s1e4",
"-r",
"HDR10",
"-v",
"H.264,H.265",
"--list",
"--proxy",
"http://bob:hunter2@proxy.example.com:8080",
"--remote",
"EXAMPLE",
"https://example.com/title/1",
]
)
assert "hunter2" not in line
assert "bob" not in line
assert "***@proxy.example.com:8080" in line
assert "-w s1e1-s1e4" in line
assert "-r HDR10" in line
assert "--list" in line
def test_argv_drops_credential_values_and_url_tokens() -> None:
line = argv_line(
["dl", "--remote", "EXAMPLE", "https://example.com/t?token=abc123"],
credentials={"username": "bob", "password": "hunter2", "extra": "totp-seed"},
)
assert "hunter2" not in line
assert "totp-seed" not in line
assert "abc123" not in line
assert "token=***" in line
def test_argv_does_not_mask_a_short_credential_value() -> None:
line = argv_line(
["dl", "-w", "s1e1", "--remote", "EXAMPLE", "t1"], credentials={"username": "s1", "password": "e1"}
)
assert "-w s1e1" in line
@pytest.mark.parametrize("param", ["access_token", "auth_token", "client_secret", "api-key", "X-Api-Key"])
def test_argv_masks_affixed_secret_query_params(param: str) -> None:
line = argv_line(["dl", "--remote", "EXAMPLE", f"https://example.com/t?{param}=abc123&kind=movie"])
assert "abc123" not in line
assert f"{param}=***" in line
assert "kind=movie" in line
def test_tracks_handler_marks_the_title_live_before_it_fetches(monkeypatch: pytest.MonkeyPatch) -> None:
"""The dashboard should see the new title while get_tracks runs, not a title later."""
published: list[tuple[str, dict[str, Any]]] = []
seen_during_fetch: list[str | None] = []
monkeypatch.setattr(store_mod.bus, "publish", lambda topic, data: published.append((topic, data)))
class StubService:
session = SimpleNamespace(headers={}, cookies=[])
def get_tracks(self, title: Any) -> Tracks:
seen_during_fetch.append(published[-1][1]["title"] if published else None)
return Tracks()
def get_chapters(self, title: Any) -> None:
raise NotImplementedError
async def run() -> None:
store = store_mod.get_session_store()
entry = await store.create(
service_tag="EXAMPLE", service_instance=StubService(), session_id="live", owner_key=None
)
entry.title_map = {"t1": FakeTitle("Example Show 2024 S01E01"), "t3": FakeTitle("Example Show 2024 S01E03")}
published.clear()
resp = await session_tracks_handler({"title_id": "t3"}, "live")
assert resp.status == 200, resp.text
assert seen_during_fetch == ["Example Show 2024 S01E03"]
assert entry.current_title_id == "t3"
assert entry.summary()["title"] == "Example Show 2024 S01E03"
await store.delete("live")
asyncio.run(run())