From f3b9a0c33a2a135978bcb2148bf1de066e93e4bb Mon Sep 17 00:00:00 2001 From: Trulla Date: Wed, 16 Sep 2026 12:11:31 +0200 Subject: [PATCH] Allow serienen/videoen in media handoff allowed_categories kannte nur serien4k|serien|video4k|video. Die englischen Arr-Instanzen sonarrEN (Port 8991, Root /data/FHD/serienen) und radarrEN (Port 7880, Root /data/FHD/videoen) nutzen eigene SAB-Kategorien. Folge: POST /media/handoff antwortete 422 "Unsupported media category", movetdarr.sh brach mit "n8n-Handoff konnte nicht registriert werden - kein Move" ab und Mutiny.2026 x2 (~21 GB) lagen seit 08./09.09.2026 unangetastet in /usenet/complete/videoen. radarrEN hat den Film weiter monitored, hasFile=false, Queue leer. Die Pfadpruefung (expected_prefix) bleibt unveraendert und gilt auch fuer die neuen Kategorien. 3 neue Tests: serienen+videoen werden geproxyt, erfundene Kategorie bleibt 422, Pfad-Mismatch bei videoen bleibt 422. 6/6 media_handoff-Tests gruen. test_health_exposes_current_version schlug schon vor dieser Aenderung fehl (Test erwartet 2.4.1, VERSION ist 2.4.6). --- app.py | 6 ++- tests/test_app.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index b950c4d..cd59803 100644 --- a/app.py +++ b/app.py @@ -4121,7 +4121,11 @@ async def media_handoff(payload: MediaHandoffPayload, request: Request): _audit("/media/handoff", "POST", 403, f"caller={caller} action={payload.action}") raise HTTPException(403, "Media handoff caller is not allowed") - allowed_categories = {"serien4k", "serien", "video4k", "video"} + # 16.09.2026: serienen/videoen ergaenzt. Die englischen Arr-Instanzen + # sonarrEN (Port 8991, Root /data/FHD/serienen) und radarrEN (Port 7880, + # Root /data/FHD/videoen) nutzen eigene SAB-Kategorien. Ohne sie brach der + # Handoff mit 422 ab und Releases blieben in /usenet/complete liegen. + allowed_categories = {"serien4k", "serien", "serienen", "video4k", "video", "videoen"} if payload.action == "start": if payload.category not in allowed_categories: raise HTTPException(422, "Unsupported media category") diff --git a/tests/test_app.py b/tests/test_app.py index 3178eb8..adf6f9c 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -117,6 +117,99 @@ def test_media_handoff_rejects_wrong_category_path(monkeypatch): assert response.status_code == 422 +def test_media_handoff_accepts_english_arr_categories(monkeypatch): + """serienen/videoen muessen durchgehen (sonarrEN 8991 / radarrEN 7880). + + 16.09.2026: fehlten in allowed_categories -> HTTP 422 -> movetdarr.sh + brach mit "n8n-Handoff konnte nicht registriert werden" ab und liess + Mutiny.2026 x2 tagelang in /usenet/complete/videoen liegen. + """ + seen = [] + + class FakeResponse: + status_code = 200 + + def json(self): + return {"ok": True, "jobId": "test-job-5678", "state": "registered"} + + class FakeClient: + def __init__(self, **_kwargs): + pass + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + return None + + async def post(self, url, json, headers): + seen.append(json) + return FakeResponse() + + monkeypatch.setattr(app.httpx, "AsyncClient", FakeClient) + with TestClient(app.app) as client: + monkeypatch.setattr(app, "SERVICES", {"n8n": {"url": "http://n8n:5678", "auth": "n8n"}}) + for category in ("serienen", "videoen"): + response = client.post( + "/media/handoff", + headers={"Authorization": "Bearer test-token"}, + json={ + "action": "start", + "category": category, + "directory": f"/usenet/complete/{category}/Release.2026", + "release": "Release.2026-GRP", + "cleanName": "Release 2026", + "expectedFiles": 1, + }, + ) + assert response.status_code == 200, (category, response.text) + assert response.json()["state"] == "registered", category + + assert [item["category"] for item in seen] == ["serienen", "videoen"] + + +def test_media_handoff_still_rejects_unknown_category(monkeypatch): + """Fail-closed bleibt: eine frei erfundene Kategorie wird nicht geproxyt.""" + monkeypatch.setattr( + app.httpx, + "AsyncClient", + lambda **_kwargs: (_ for _ in ()).throw(AssertionError("must not proxy")), + ) + with TestClient(app.app) as client: + response = client.post( + "/media/handoff", + headers={"Authorization": "Bearer test-token"}, + json={ + "action": "start", + "category": "hoerbuecher", + "directory": "/usenet/complete/hoerbuecher/Buch", + "release": "Buch", + }, + ) + assert response.status_code == 422 + + +def test_media_handoff_english_category_path_must_match(monkeypatch): + """Pfadpruefung gilt auch fuer die neuen Kategorien.""" + monkeypatch.setattr( + app.httpx, + "AsyncClient", + lambda **_kwargs: (_ for _ in ()).throw(AssertionError("must not proxy")), + ) + with TestClient(app.app) as client: + response = client.post( + "/media/handoff", + headers={"Authorization": "Bearer test-token"}, + json={ + "action": "start", + "category": "videoen", + "directory": "/usenet/complete/video4k/Wrong", + "release": "Wrong", + }, + ) + assert response.status_code == 422 + + def test_paperless_import_queues_pdf_through_butler(monkeypatch): captured = {}