"""A player that no longer wants its HLS session stops it, instead of leaving ffmpeg to run on. Left to the idle reaper, a transcode ran to the end of the film after the viewer had gone (measured on localdev: ~4 cores for a minute after leaving a resumed 480p film). The player names the session it played; the server stops it only while it is still that viewer's registered session for the item, so a stop that arrives after a seek-restart (or from a second tab) cannot end the newer playback. No `db` fixture: the route reads nothing from the database (`current_user` is overridden wholesale). """ import time import pytest from fastapi.testclient import TestClient import app.state as state from app.auth import current_user from app.main import app from app.models import User from app.plex import stream as plex_stream VIEWER = 41 class _Proc: def __init__(self): self.terminated = False def poll(self): return None def terminate(self): self.terminated = True def wait(self, timeout=None): return 0 class _Session: def __init__(self, session_id: str, directory): self.id = session_id self.key = "m1" self.dir = directory self.proc = _Proc() self.last_access = time.time() @pytest.fixture def sessions(monkeypatch): live: dict = {} monkeypatch.setattr(plex_stream, "_sessions", live) return live @pytest.fixture def client(monkeypatch, override_dep): monkeypatch.setattr(state, "_configured_cache", True) user = User(id=VIEWER, email="stop@example.com") override_dep(current_user, lambda: user) return TestClient(app) def _session(tmp_path, sid: str) -> _Session: d = tmp_path / sid d.mkdir() return _Session(sid, d) def test_the_named_session_is_stopped_and_its_directory_goes(client, tmp_path, sessions): s = _session(tmp_path, "sid-a") sessions[(VIEWER, "m1")] = s r = client.post("/api/plex/stream/m1/session/sid-a/stop", json={}) assert r.status_code == 200, r.text assert r.json() == {"ok": True, "stopped": True} assert s.proc.terminated assert sessions == {} assert not s.dir.exists() def test_a_session_that_replaced_the_named_one_plays_on(client, tmp_path, sessions): """The stop of the session a seek-restart replaced arrives after the new one registered.""" newer = _session(tmp_path, "sid-b") sessions[(VIEWER, "m1")] = newer r = client.post("/api/plex/stream/m1/session/sid-a/stop", json={}) assert r.json() == {"ok": True, "stopped": False} assert not newer.proc.terminated assert sessions == {(VIEWER, "m1"): newer} def test_another_viewers_session_is_not_this_viewers_to_stop(client, tmp_path, sessions): theirs = _session(tmp_path, "sid-a") sessions[(7, "m1")] = theirs r = client.post("/api/plex/stream/m1/session/sid-a/stop", json={}) assert r.json() == {"ok": True, "stopped": False} assert not theirs.proc.terminated def test_a_stop_with_no_session_left_is_not_an_error(client, sessions): """The reaper or a cap may have dropped it first; the beacon must not fail loudly for that.""" r = client.post("/api/plex/stream/m1/session/sid-a/stop", json={}) assert r.status_code == 200, r.text assert r.json() == {"ok": True, "stopped": False}