from pathlib import Path import pytest from fastapi.testclient import TestClient from sqlmodel import SQLModel, create_engine @pytest.fixture def api_client(tmp_path, monkeypatch): db_path = tmp_path / "media_pipeline_test.db" monkeypatch.setenv("APPOPS_DB_PATH", str(db_path)) monkeypatch.setenv("APP_OPS_VOICE_TOKEN", "test-voice-token") from backend.app.db import session from backend.app.db.init_db import init_db from backend.app.main import app engine = create_engine(f"sqlite:///{Path(db_path)}", connect_args={"check_same_thread": False}) session.engine = engine SQLModel.metadata.drop_all(engine) SQLModel.metadata.create_all(engine) init_db() with TestClient(app) as client: yield client def _healthy_payloads(): return { "sonarr_health": {"success": True, "status": "connected"}, "sonarr_wanted": {"success": True, "status": "connected", "total": 3, "items": [{"title": "The Boys"}]}, "sonarr_queue": {"success": True, "status": "connected", "total": 1, "downloading": 1, "queued": 0, "failed": 0, "stalled": 0, "items": [{"title": "The Boys", "status": "downloading"}]}, "prowlarr_health": {"success": True, "status": "connected"}, "prowlarr_indexers": {"success": True, "status": "ok", "total": 4, "enabled": 4, "failing": 0, "items": []}, "prowlarr_apps": {"success": True, "status": "ok", "total": 2, "items": [{"name": "Sonarr", "status": "healthy"}, {"name": "Radarr", "status": "healthy"}]}, "prowlarr_sync": {"success": True, "status": "ok", "applicationsConfigured": 2, "indexersConfigured": 4, "indexersEnabled": 4, "indexersFailing": 0, "healthWarnings": 0, "healthErrors": 0}, "deluge_health": {"success": True, "status": "connected"}, "deluge_session": {"success": True, "status": "ok", "connected": True, "downloadRate": 1_200_000, "uploadRate": 200_000, "numTorrents": 2, "active": 1, "paused": 1, "stalled": 0, "errors": 0}, "deluge_active": {"success": True, "status": "ok", "total": 1, "items": [{"id": "active-1", "name": "The Boys", "state": "Downloading", "progress": 55}]}, "deluge_stalled": {"success": True, "status": "ok", "total": 0, "items": []}, "deluge_paused": {"success": True, "status": "ok", "total": 1, "items": [{"id": "paused-1", "name": "Paused", "state": "Paused"}]}, "deluge_speeds": {"success": True, "status": "ok", "downloadRate": 1_200_000, "uploadRate": 200_000, "active": 1, "paused": 1, "stalled": 0, "errors": 0}, "plex_health": {"success": True, "status": "connected"}, "plex_libraries": {"success": True, "status": "ok", "total": 2, "libraries": [{"id": "1", "title": "Series", "type": "show", "itemCount": 200}]}, "plex_sessions": {"success": True, "status": "ok", "total": 1, "sessions": [{"id": "plex-1", "title": "The Boys", "user": "Jens", "videoDecision": "directplay"}]}, "plex_recent": {"success": True, "status": "ok", "total": 1, "items": [{"title": "The Boys", "library": "Series", "type": "episode"}]}, "plex_streams": {"success": True, "status": "ok", "total": 1, "directPlay": 1, "directStream": 0, "transcoding": 0, "bandwidth": 4000, "users": 1}, "tautulli_health": {"success": True, "status": "connected"}, "tautulli_activity": {"success": True, "status": "ok", "streamCount": 1, "sessions": [{"sessionKey": "t1", "title": "The Boys", "user": "Jens", "ipAddress": "192.168.10.x", "transcodeDecision": "directplay"}]}, "tautulli_history": {"success": True, "status": "ok", "total": 1, "items": [{"rowId": "h1", "title": "The Boys", "user": "Jens", "duration": 3600}]}, "tautulli_stats": {"success": True, "status": "ok", "totalPlays": 10, "totalDuration": 36000, "topUsers": [{"name": "Jens", "plays": 10, "duration": 36000}], "topLibraries": [], "topPlatforms": [], "playsByDay": []}, "tautulli_streams": {"success": True, "status": "ok", "activeStreams": 1, "directPlay": 1, "directStream": 0, "transcoding": 0, "lanStreams": 1, "wanStreams": 0, "bandwidth": 4000}, "tautulli_libraries": {"success": True, "status": "ok", "total": 1, "items": [{"sectionId": "1", "name": "Series", "type": "show", "count": 200}]}, "widefrog_health": {"success": True, "status": "connected"}, "widefrog_queue": {"success": True, "status": "ok", "total": 1, "active": 1, "queued": 0, "completed": 0, "failed": 0, "items": [{"id": "wf-1", "title": "Process media"}]}, "widefrog_failed": {"success": True, "status": "ok", "failed": 0, "items": []}, "widefrog_activity": {"success": True, "status": "ok", "recent": [{"message": "Processed media"}], "last_event": {"message": "Processed media"}}, "widefrog_downloads": {"success": True, "status": "ok", "active": 1, "processing": 1, "items": [{"title": "Import"}]}, } def _patch_pipeline(monkeypatch, payloads=None): payloads = payloads or _healthy_payloads() monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.health", lambda self: payloads["sonarr_health"], raising=False) monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.wanted_missing", lambda self: payloads["sonarr_wanted"], raising=False) monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.queue_status", lambda self: payloads["sonarr_queue"], raising=False) monkeypatch.setattr("backend.app.connectors.prowlarr.ProwlarrConnector.health", lambda self: payloads["prowlarr_health"], raising=False) monkeypatch.setattr("backend.app.connectors.prowlarr.ProwlarrConnector.indexer_status", lambda self: payloads["prowlarr_indexers"], raising=False) monkeypatch.setattr("backend.app.connectors.prowlarr.ProwlarrConnector.application_status", lambda self: payloads["prowlarr_apps"], raising=False) monkeypatch.setattr("backend.app.connectors.prowlarr.ProwlarrConnector.sync_status", lambda self: payloads["prowlarr_sync"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.health", lambda self: payloads["deluge_health"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.session_status", lambda self: payloads["deluge_session"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.active_downloads", lambda self: payloads["deluge_active"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.stalled_downloads", lambda self: payloads["deluge_stalled"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.paused_downloads", lambda self: payloads["deluge_paused"], raising=False) monkeypatch.setattr("backend.app.connectors.deluge.DelugeConnector.speed_status", lambda self: payloads["deluge_speeds"], raising=False) monkeypatch.setattr("backend.app.connectors.plex.PlexConnector.health", lambda self: payloads["plex_health"], raising=False) monkeypatch.setattr("backend.app.connectors.plex.PlexConnector.library_status", lambda self: payloads["plex_libraries"], raising=False) monkeypatch.setattr("backend.app.connectors.plex.PlexConnector.active_sessions", lambda self: payloads["plex_sessions"], raising=False) monkeypatch.setattr("backend.app.connectors.plex.PlexConnector.recent_media", lambda self: payloads["plex_recent"], raising=False) monkeypatch.setattr("backend.app.connectors.plex.PlexConnector.stream_summary", lambda self: payloads["plex_streams"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.health", lambda self: payloads["tautulli_health"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.activity", lambda self: payloads["tautulli_activity"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.history", lambda self: payloads["tautulli_history"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.watch_stats", lambda self: payloads["tautulli_stats"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.stream_summary", lambda self: payloads["tautulli_streams"], raising=False) monkeypatch.setattr("backend.app.connectors.tautulli.TautulliConnector.libraries", lambda self: payloads["tautulli_libraries"], raising=False) monkeypatch.setattr("backend.app.connectors.widefrog.WidefrogConnector.health", lambda self: payloads["widefrog_health"], raising=False) monkeypatch.setattr("backend.app.connectors.widefrog.WidefrogConnector.queue_status", lambda self: payloads["widefrog_queue"], raising=False) monkeypatch.setattr("backend.app.connectors.widefrog.WidefrogConnector.failed_jobs", lambda self: payloads["widefrog_failed"], raising=False) monkeypatch.setattr("backend.app.connectors.widefrog.WidefrogConnector.activity", lambda self: payloads["widefrog_activity"], raising=False) monkeypatch.setattr("backend.app.connectors.widefrog.WidefrogConnector.download_status", lambda self: payloads["widefrog_downloads"], raising=False) return payloads def test_media_pipeline_summary_healthy(api_client, monkeypatch): _patch_pipeline(monkeypatch) payload = api_client.get("/api/media-pipeline/summary").json() assert payload["success"] is True assert payload["status"] == "healthy" assert payload["connectors"]["sonarr"]["status"] == "connected" assert payload["counts"]["wantedSeries"] == 3 assert payload["counts"]["activeDownloads"] == 1 assert payload["counts"]["plexLibraries"] == 2 assert payload["counts"]["activeStreams"] == 1 assert payload["counts"]["indexers"] == 4 assert payload["risk"]["level"] == "low" def test_media_pipeline_reuses_snapshot_for_burst_reads(api_client, monkeypatch): from backend.app.services import media_pipeline_service payloads = _patch_pipeline(monkeypatch) calls = {"sonarr_health": 0} def sonarr_health(_self): calls["sonarr_health"] += 1 return payloads["sonarr_health"] monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.health", sonarr_health, raising=False) media_pipeline_service._SNAPSHOT_CACHE.clear() summary_payload = api_client.get("/api/media-pipeline/summary").json() issues_payload = api_client.get("/api/media-pipeline/issues").json() assert summary_payload["success"] is True assert issues_payload["success"] is True assert calls["sonarr_health"] == 1 def test_media_pipeline_summary_reuses_persistent_cache_after_snapshot_loss(api_client, monkeypatch): from backend.app.services import media_pipeline_service _patch_pipeline(monkeypatch) media_pipeline_service._SNAPSHOT_CACHE.clear() first = api_client.get("/api/media-pipeline/summary").json() media_pipeline_service._SNAPSHOT_CACHE.clear() def fail_build_snapshot(): raise AssertionError("persistent media summary cache should avoid cold connector reads") monkeypatch.setattr(media_pipeline_service, "_build_snapshot", fail_build_snapshot) second = api_client.get("/api/media-pipeline/summary").json() assert second["status"] == first["status"] assert second["counts"] == first["counts"] assert second["connectors"] == first["connectors"] def test_media_pipeline_snapshot_cache_prevents_concurrent_stampede(api_client, monkeypatch): from concurrent.futures import ThreadPoolExecutor from time import sleep from backend.app.services import media_pipeline_service payloads = _patch_pipeline(monkeypatch) calls = {"sonarr_health": 0} def sonarr_health(_self): calls["sonarr_health"] += 1 sleep(0.05) return payloads["sonarr_health"] monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.health", sonarr_health, raising=False) media_pipeline_service._SNAPSHOT_CACHE.clear() with ThreadPoolExecutor(max_workers=4) as executor: results = list(executor.map(lambda _index: media_pipeline_service._snapshot_cached(), range(4))) assert all(result["sonarrHealth"]["success"] is True for result in results) assert calls["sonarr_health"] == 1 def test_media_pipeline_cold_snapshot_runs_connector_reads_in_parallel(api_client, monkeypatch): from threading import Barrier from backend.app.services import media_pipeline_service payloads = _patch_pipeline(monkeypatch) barrier = Barrier(3, timeout=1) def wait_and_return(key): def _inner(_self): barrier.wait() return payloads[key] return _inner monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.health", wait_and_return("sonarr_health"), raising=False) monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.wanted_missing", wait_and_return("sonarr_wanted"), raising=False) monkeypatch.setattr("backend.app.connectors.sonarr.SonarrConnector.queue_status", wait_and_return("sonarr_queue"), raising=False) media_pipeline_service._SNAPSHOT_CACHE.clear() payload = api_client.get("/api/media-pipeline/summary").json() assert payload["success"] is True assert payload["connectors"]["sonarr"]["status"] == "connected" assert payload["counts"]["wantedSeries"] == 3 def test_media_pipeline_summary_partial_when_connector_missing_config(api_client, monkeypatch): payloads = _healthy_payloads() payloads["prowlarr_health"] = {"success": False, "status": "missing_config", "message": "missing_config"} payloads["prowlarr_indexers"] = {"success": False, "status": "missing_config", "total": 0, "enabled": 0, "failing": 0, "items": []} payloads["prowlarr_sync"] = {"success": False, "status": "missing_config", "applicationsConfigured": 0, "indexersConfigured": 0, "indexersEnabled": 0, "indexersFailing": 0, "healthWarnings": 0, "healthErrors": 0} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/summary").json() assert payload["success"] is True assert payload["status"] in {"partial", "missing_config"} assert payload["connectors"]["prowlarr"]["status"] == "missing_config" assert "prowlarr" in " ".join(payload["risk"]["reasons"]).lower() def test_media_pipeline_flow_returns_nodes_and_edges(api_client, monkeypatch): _patch_pipeline(monkeypatch) payload = api_client.get("/api/media-pipeline/flow").json() node_ids = {node["id"] for node in payload["nodes"]} assert {"sonarr", "prowlarr", "deluge", "widefrog", "plex", "tautulli"}.issubset(node_ids) assert {"from": "sonarr", "to": "prowlarr", "status": "healthy", "label": "indexer sync"} in payload["edges"] assert not any(edge["from"] == "deluge" and edge["to"] == "widefrog" for edge in payload["edges"]) assert {"from": "widefrog", "to": "plex", "status": "healthy", "label": "media processing"} in payload["edges"] action_ids = [action["actionId"] for node in payload["nodes"] for action in node.get("actions", [])] assert not any("delete" in action or "remove" in action for action in action_ids) def test_media_pipeline_flow_has_no_healthy_edges_when_configuration_is_missing(api_client, monkeypatch): payloads = _healthy_payloads() for connector in ("sonarr", "prowlarr", "deluge", "widefrog", "plex", "tautulli"): payloads[f"{connector}_health"] = {"success": False, "status": "missing_config"} _patch_pipeline(monkeypatch, payloads) response = api_client.get("/api/media-pipeline/flow") assert response.status_code == 200 payload = response.json() assert len(payload["edges"]) == 4 assert all(edge["status"] == "unknown" for edge in payload["edges"]) assert all(node["status"] == "missing_config" for node in payload["nodes"]) @pytest.mark.parametrize("connector", ["sonarr", "prowlarr", "deluge", "widefrog", "plex", "tautulli"]) @pytest.mark.parametrize("health_status,expected", [ ("missing_config", "unknown"), ("unknown", "unknown"), ("unavailable", "unknown"), ("unsupported", "unknown"), ("unexpected_state", "unknown"), ("auth_failed", "broken"), ("unreachable", "broken"), ("error", "broken"), ]) def test_media_pipeline_flow_checks_each_endpoint_before_queue_warnings(api_client, monkeypatch, connector, health_status, expected): payloads = _healthy_payloads() payloads[f"{connector}_health"] = {"success": False, "status": health_status} # Nonzero counts must never turn unavailable or failed endpoints amber/green. payloads["prowlarr_indexers"]["failing"] = 2 payloads["widefrog_failed"]["failed"] = 3 _patch_pipeline(monkeypatch, payloads) response = api_client.get("/api/media-pipeline/flow") assert response.status_code == 200 edges = response.json()["edges"] unrelated_statuses = { ("sonarr", "prowlarr"): "healthy", ("prowlarr", "deluge"): "warning", ("widefrog", "plex"): "warning", ("plex", "tautulli"): "healthy", } for edge in edges: if connector in (edge["from"], edge["to"]): assert edge["status"] == expected else: assert edge["status"] == unrelated_statuses[(edge["from"], edge["to"])] def test_media_pipeline_flow_unknown_endpoint_precedes_failure_on_the_other_endpoint(api_client, monkeypatch): payloads = _healthy_payloads() payloads["prowlarr_health"] = {"success": False, "status": "unreachable"} payloads["deluge_health"] = {"success": False, "status": "missing_config"} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/flow").json() edge_statuses = {(edge["from"], edge["to"]): edge["status"] for edge in payload["edges"]} assert edge_statuses[("sonarr", "prowlarr")] == "broken" assert edge_statuses[("prowlarr", "deluge")] == "unknown" def test_media_pipeline_flow_applies_queue_warnings_only_with_connected_endpoints(api_client, monkeypatch): payloads = _healthy_payloads() payloads["prowlarr_indexers"]["failing"] = 2 payloads["widefrog_failed"]["failed"] = 3 _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/flow").json() assert {(edge["from"], edge["to"]): edge["status"] for edge in payload["edges"]} == { ("sonarr", "prowlarr"): "healthy", ("prowlarr", "deluge"): "warning", ("widefrog", "plex"): "warning", ("plex", "tautulli"): "healthy", } def test_media_pipeline_issues_detect_cross_connector_problems(api_client, monkeypatch): payloads = _healthy_payloads() payloads["prowlarr_indexers"] = {"success": True, "status": "ok", "total": 3, "enabled": 2, "failing": 1, "items": [{"name": "Indexer", "status": "error"}]} payloads["deluge_stalled"] = {"success": True, "status": "ok", "total": 1, "items": [{"id": "stalled-1", "name": "The Boys", "state": "Error"}]} payloads["widefrog_failed"] = {"success": True, "status": "ok", "failed": 2, "items": [{"id": "wf-fail", "title": "Import failed"}]} payloads["tautulli_health"] = {"success": False, "status": "missing_config", "message": "missing_config"} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/issues").json() titles = " ".join(issue["title"] for issue in payload["issues"]).lower() assert "indexer" in titles assert "stalled" in titles assert "widefrog" in titles assert "tautulli" in titles actions = [action for issue in payload["issues"] for action in issue["suggestedActions"]] assert actions assert all(action["policyDecision"] in {"allowed", "approval_required", "blocked"} for action in actions) assert all(action.get("link") for action in actions) assert not any(any(blocked in action["actionId"] for blocked in ["delete", "remove", "kill_session", "terminate"]) for action in actions) def test_deluge_stalled_issue_includes_source_and_timestamp(api_client, monkeypatch): payloads = _healthy_payloads() payloads["deluge_stalled"] = {"success": True, "status": "ok", "total": 1, "items": [{"id": "stalled-1", "name": "The Boys", "state": "Downloading"}]} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/issues").json() stalled = next(issue for issue in payload["issues"] if issue["id"] == "deluge-stalled") assert stalled["source"] == "deluge.stalled_downloads" assert stalled["observedAt"] assert any(str(item).startswith("source=deluge.stalled_downloads") for item in stalled["evidence"]) assert any(str(item).startswith("observedAt=") for item in stalled["evidence"]) def test_media_pipeline_issues_handles_review_apps_without_connector_type(api_client, monkeypatch): from sqlmodel import Session from backend.app.db.models import App from backend.app.db.session import engine _patch_pipeline(monkeypatch) with Session(engine) as session: session.add(App(id="media-review-app", name="Media Review App", category="media", needs_review=True)) session.commit() response = api_client.get("/api/media-pipeline/issues") assert response.status_code == 200 payload = response.json() review_issue = next(issue for issue in payload["issues"] if issue["id"] == "app-needs-review-media-review-app") assert review_issue["affectedConnectors"] == ["media-review-app"] assert review_issue["suggestedActions"][0]["target"] == "media-review-app" assert review_issue["suggestedActions"][0]["link"] == "/apps/media-review-app" def test_media_pipeline_downloads_combines_deluge_and_widefrog(api_client, monkeypatch): payloads = _healthy_payloads() payloads["deluge_stalled"] = {"success": True, "status": "ok", "total": 1, "items": [{"id": "stalled-1", "name": "The Boys", "state": "Error"}]} payloads["widefrog_failed"] = {"success": True, "status": "ok", "failed": 1, "items": [{"id": "wf-fail", "title": "Import failed"}]} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/downloads").json() assert payload["active"]["total"] == 1 assert payload["stalled"]["total"] == 1 assert payload["failedWidefrogJobs"]["failed"] == 1 action_ids = [action["actionId"] for action in payload["suggestedActions"]] assert "deluge.resume_torrent" in action_ids assert "widefrog.retry_failed" in action_ids assert {action["target"]: action["link"] for action in payload["suggestedActions"] if action["target"] in {"deluge", "widefrog"}} == { "deluge": "/apps/deluge", "widefrog": "/apps/widefrog", } assert not any("remove" in action or "delete" in action for action in action_ids) def test_media_pipeline_library_combines_plex_tautulli_widefrog(api_client, monkeypatch): _patch_pipeline(monkeypatch) payload = api_client.get("/api/media-pipeline/library").json() assert payload["plexLibraries"]["total"] == 2 assert payload["tautulliStats"]["totalPlays"] == 10 assert payload["widefrogQueue"]["total"] == 1 action_ids = [action["actionId"] for action in payload["suggestedActions"]] assert "plex.scan_library" in action_ids assert "plex.scan_all_libraries" in action_ids assert "widefrog.scan_library" in action_ids assert all(action.get("link") in {"/apps/plex", "/apps/widefrog"} for action in payload["suggestedActions"]) assert not any("empty_trash" in action or "kill_session" in action for action in action_ids) def test_media_pipeline_activity_marks_privacy_sensitive_data(api_client, monkeypatch): _patch_pipeline(monkeypatch) payload = api_client.get("/api/media-pipeline/activity").json() assert payload["privacy"]["sensitive"] is True assert "tautulli.history" in payload["privacy"]["actions"] assert payload["tautulliActivity"]["streamCount"] == 1 assert payload["plexSessions"]["total"] == 1 assert "192.168.10.x" in str(payload) def test_media_pipeline_recommendations_are_generated_from_issues(api_client, monkeypatch): payloads = _healthy_payloads() payloads["prowlarr_indexers"] = {"success": True, "status": "ok", "total": 3, "enabled": 2, "failing": 1, "items": []} payloads["plex_streams"] = {"success": True, "status": "ok", "total": 3, "directPlay": 0, "directStream": 0, "transcoding": 3, "bandwidth": 12000, "users": 3} _patch_pipeline(monkeypatch, payloads) payload = api_client.get("/api/media-pipeline/recommendations").json() assert payload["success"] is True assert payload["recommendations"] assert all("confidence" in item for item in payload["recommendations"]) assert any("transcode" in item["title"].lower() or "indexer" in item["title"].lower() for item in payload["recommendations"]) def test_media_pipeline_check_is_read_only_and_audited(api_client, monkeypatch): _patch_pipeline(monkeypatch) payload = api_client.post("/api/media-pipeline/check").json() assert payload["success"] is True assert payload["summary"]["status"] == "healthy" assert payload["auditId"].startswith("audit-") assert payload["jobId"].startswith("job-") def test_media_pipeline_diagnose_returns_evidence_without_execution(api_client, monkeypatch): payloads = _healthy_payloads() payloads["sonarr_wanted"] = {"success": True, "status": "connected", "total": 1, "items": [{"title": "The Boys"}]} payloads["deluge_active"] = {"success": True, "status": "ok", "total": 0, "items": []} payloads["prowlarr_indexers"] = {"success": True, "status": "ok", "total": 3, "enabled": 2, "failing": 1, "items": []} _patch_pipeline(monkeypatch, payloads) payload = api_client.post("/api/media-pipeline/diagnose", json={"query": "waarom downloadt The Boys niet?", "scope": "downloads"}).json() assert payload["success"] is True assert payload["diagnosis"]["likelyCauses"] assert payload["diagnosis"]["evidence"] assert payload["diagnosis"]["executedActions"] == [] assert not any("delete" in action["actionId"] or "remove" in action["actionId"] for action in payload["diagnosis"]["suggestedActions"]) def test_media_pipeline_diagnose_policy_is_read_only_allowed(api_client): payload = api_client.post( "/api/policies/evaluate", json={"action": "media_pipeline.diagnose", "target": "media-pipeline", "source": "manual"}, ).json() assert payload["decision"] == "allowed" assert payload["risk"] == "low"