fix(api): progress checkpoints past the int4 column are a 422, and idle HLS sessions are reaped on a timer
Deep-review round 3 of bug hunt 2026-09. - The YouTube and Plex progress bodies are bounded by POSITION_MAX_S (the int4 position columns): a finite position of 2^31 or more was a DataError, i.e. a 500 — the sibling of round 2's non-finite fix. - plex/stream.py promised "a periodic reaper", but reap_idle ran only inside the NEXT session request: after the last playback an abandoned session (its ffmpeg still working, its directory a whole segmented copy of the film) lived until someone pressed play again. A lifespan task reaps every 60 s. - Tests for what round 2 left untested: the 422's body keeps the list shape the UI localizes, and stream_session refuses non-finite start/aoff.
This commit is contained in:
@@ -32,3 +32,32 @@ def test_a_non_finite_position_is_refused(client, path, raw):
|
||||
headers={"content-type": "application/json"},
|
||||
)
|
||||
assert r.status_code == 422, r.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["/api/videos/v1/progress", "/api/plex/item/98614/progress"])
|
||||
@pytest.mark.parametrize("field", ["position_seconds", "duration_seconds"])
|
||||
def test_a_position_past_the_int4_column_is_refused_not_a_500(client, path, field):
|
||||
"""Finite but past the int4 column: the flush was a DataError, i.e. a 500 with a traceback."""
|
||||
body = {"position_seconds": 10, "duration_seconds": 3000, field: 2**31}
|
||||
assert client.post(path, json=body).status_code == 422
|
||||
|
||||
|
||||
def test_the_refusal_keeps_the_shape_the_ui_reads(client):
|
||||
"""The handler replaces FastAPI's own for every validation error in the app, and the UI turns
|
||||
exactly this shape — a LIST of errors — into its localized "check your input" sentence. The
|
||||
refused value is echoed by its name, because JSON cannot carry NaN."""
|
||||
r = client.post(
|
||||
"/api/videos/v1/progress",
|
||||
content='{"position_seconds": NaN, "duration_seconds": 3000}',
|
||||
headers={"content-type": "application/json"},
|
||||
)
|
||||
detail = r.json()["detail"]
|
||||
assert isinstance(detail, list) and detail[0]["loc"] == ["body", "position_seconds"]
|
||||
assert detail[0]["input"] == "nan"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("q", ["start=inf", "start=nan", "aoff=inf", "aoff=-inf", "aoff=nan"])
|
||||
def test_a_session_at_an_impossible_offset_is_refused(client, q):
|
||||
"""`start=inf` reached `int()` in the session builder (a 500); `aoff=inf` spawned an ffmpeg
|
||||
with a non-finite offset."""
|
||||
assert client.post(f"/api/plex/stream/98614/session?{q}").status_code == 422
|
||||
|
||||
Reference in New Issue
Block a user