"""Tests del router /executions: invoke, get, list, approve, reject.""" from pathlib import Path import pytest from fastapi.testclient import TestClient from forja_core.api import deps from forja_core.main import create_app FIXTURES = Path(__file__).parent.parent / "fixtures" @pytest.fixture(autouse=True) def patch_settings(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("DATA_DIR", str(tmp_path)) monkeypatch.setenv("AGENTS_DIR", str(FIXTURES / "agents")) monkeypatch.setenv("POLICIES_DIR", str(FIXTURES / "policies")) monkeypatch.setenv("LLM_PROVIDER", "mock") for fn in ( deps.get_settings, deps.get_registry, deps.get_policy_store, deps.get_orchestrator, deps.get_llm_provider, deps.get_guardrail_engine, ): fn.cache_clear() @pytest.fixture def client() -> TestClient: return TestClient(create_app()) def test_invoke_devuelve_execution_completada(client: TestClient) -> None: r = client.post("/agents/incident_analyzer/invoke", json={"input": "degradación MOS pool SBC"}) assert r.status_code == 200 body = r.json() assert body["status"] == "completed" assert body["agent_name"] == "incident_analyzer" assert body["agent_version"] == "v1" assert body["needs_human_for"] is None def test_invoke_agente_inexistente_404(client: TestClient) -> None: r = client.post("/agents/inexistente/invoke", json={"input": "x"}) assert r.status_code == 404 def test_invoke_riesgo_alto_pausa_hitl(client: TestClient) -> None: r = client.post("/agents/incident_analyzer/invoke", json={"input": "caída registros sip"}) assert r.status_code == 200 body = r.json() assert body["status"] == "awaiting_approval" assert body["needs_human_for"] assert {a["id"] for a in body["needs_human_for"]} == {"act-1"} def test_get_execution_existe_tras_invoke(client: TestClient) -> None: trace_id = client.post( "/agents/incident_analyzer/invoke", json={"input": "degradación MOS pool SBC"} ).json()["trace_id"] r = client.get(f"/executions/{trace_id}") assert r.status_code == 200 assert r.json()["trace_id"] == trace_id def test_get_execution_inexistente_404(client: TestClient) -> None: r = client.get("/executions/00000000-0000-0000-0000-000000000000") assert r.status_code == 404 def test_list_executions_incluye_la_completada(client: TestClient) -> None: client.post("/agents/incident_analyzer/invoke", json={"input": "degradación MOS pool SBC"}) r = client.get("/executions") assert r.status_code == 200 rows = r.json() assert len(rows) == 1 assert rows[0]["status"] == "completed" assert rows[0]["n_proposed_actions"] == 1 def test_list_executions_incluye_la_awaiting(client: TestClient) -> None: """Una ejecución pausada en HITL no se escribe en el JSONL; aún así debe listarse (la página de Aprobaciones del dashboard depende de ello).""" trace_id = client.post( "/agents/incident_analyzer/invoke", json={"input": "caída registros sip"} ).json()["trace_id"] rows = client.get("/executions").json() awaiting = [r for r in rows if r["trace_id"] == trace_id] assert len(awaiting) == 1 assert awaiting[0]["status"] == "awaiting_approval" def test_approve_resume_completa_ejecucion(client: TestClient) -> None: invoked = client.post( "/agents/incident_analyzer/invoke", json={"input": "caída registros sip"} ).json() trace_id = invoked["trace_id"] action_ids = [a["id"] for a in invoked["needs_human_for"]] r = client.post( f"/executions/{trace_id}/approve", json={"approved_action_ids": action_ids, "comment": "OK adelante"}, ) assert r.status_code == 200 body = r.json() assert body["status"] == "completed" assert {a["id"] for a in body["final_output"]["approved_actions"]} == {"act-1"} def test_reject_marca_failed(client: TestClient) -> None: trace_id = client.post( "/agents/incident_analyzer/invoke", json={"input": "caída registros sip"} ).json()["trace_id"] r = client.post(f"/executions/{trace_id}/reject", json={"reason": "no procede ahora"}) assert r.status_code == 200 body = r.json() assert body["status"] == "failed" assert "rejected_by_human" in body["error"] def test_approve_sobre_estado_no_pausado_da_409(client: TestClient) -> None: trace_id = client.post( "/agents/incident_analyzer/invoke", json={"input": "degradación MOS pool SBC"} ).json()["trace_id"] r = client.post(f"/executions/{trace_id}/approve", json={"approved_action_ids": []}) assert r.status_code == 409 def test_approve_trace_id_desconocido_da_404(client: TestClient) -> None: r = client.post( "/executions/11111111-1111-1111-1111-111111111111/approve", json={"approved_action_ids": []}, ) assert r.status_code == 404