From 197208c57186d10054ae282d42b10686fb51c8f3 Mon Sep 17 00:00:00 2001 From: sascha Date: Sat, 5 Sep 2026 07:59:53 +0200 Subject: [PATCH] feat: add restricted media handoff bridge --- tests/test_app.py | 71 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/tests/test_app.py b/tests/test_app.py index 161ded6..ef136db 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -43,7 +43,76 @@ def test_health_exposes_current_version(): with TestClient(app.app) as client: response = client.get("/health") assert response.status_code == 200 - assert response.json()["version"] == app.VERSION == "2.3.5" + assert response.json()["version"] == app.VERSION == "2.3.6" + + +def test_media_handoff_proxies_strict_category_contract(monkeypatch): + captured = {} + + class FakeResponse: + status_code = 200 + + def json(self): + return {"ok": True, "jobId": "test-job-1234", "state": "registered"} + + class FakeClient: + def __init__(self, **kwargs): + captured["client"] = kwargs + + async def __aenter__(self): + return self + + async def __aexit__(self, *_args): + return None + + async def post(self, url, json, headers): + captured.update(url=url, json=json, headers=headers) + 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"}}) + response = client.post( + "/media/handoff", + headers={"Authorization": "Bearer test-token"}, + json={ + "action": "start", + "category": "serien4k", + "directory": "/usenet/complete/serien4k/Show.S01E01", + "release": "Show.S01E01-GRP", + "cleanName": "Show S01E01", + }, + ) + assert response.status_code == 200 + assert response.json()["state"] == "registered" + assert captured["url"] == "http://n8n:5678/webhook/media-handoff" + assert captured["json"]["category"] == "serien4k" + + +def test_media_handoff_rejects_untrusted_caller_before_proxy(monkeypatch): + 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", + json={"action": "status", "jobId": "test-job-1234"}, + ) + assert response.status_code == 403 + + +def test_media_handoff_rejects_wrong_category_path(monkeypatch): + 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": "video4k", + "directory": "/usenet/complete/serien4k/Wrong", + "release": "Wrong", + }, + ) + assert response.status_code == 422 def test_paperless_import_queues_pdf_through_butler(monkeypatch):