import json import pytest from fastapi.testclient import TestClient from knaster.api import create_app from .test_router import RECHNUNG GOOD = json.dumps({"nummer": "4711", "betrag": 99}) H = {"Authorization": "Bearer secret"} @pytest.fixture def client(make_system): sysm = make_system() sysm.chat.handlers.update({"tier1-general/rechnungen-v1": lambda m: '{"nummer": "1"}', "tier0-strong": lambda m: GOOD}) return TestClient(create_app(sysm)) def test_auth(client): assert client.post("/v1/route", json={"input": "x"}).status_code == 401 assert client.get("/health").status_code == 200 def test_route_and_run(client): r = client.post("/v1/route", json={"input": RECHNUNG}, headers=H).json() assert r["stage"] == 1 and r["adapter"] == "rechnungen-v1" r = client.post("/v1/run", json={"input": RECHNUNG}, headers=H).json() assert r["status"] == "ok" and r["stage"] == 0 and r["escalations"] == 1 assert r["attempts"][0]["verification"]["passed"] is False assert client.post("/v1/route", json={"input": "x", "skill": "gibtsnicht"}, headers=H).status_code == 404 def test_metrics(client): client.post("/v1/run", json={"input": RECHNUNG}, headers=H) text = client.get("/metrics").text assert 'knaster_runs_total{stage="0",status="ok"} 1.0' in text assert 'knaster_escalations_total{signal="verifier_failed"} 1.0' in text assert 'knaster_slot_available{slot="tier1-general"} 1.0' in text def test_registry_and_slot_state(client): reg = client.get("/v1/registry", headers=H).json() assert {s["name"] for s in reg["slots"]} >= {"tier1-general", "tier0-strong"} r = client.post("/v1/registry/slots/tier1-general/state", json={"status": "degraded", "reason": "x"}, headers=H) assert r.json()["status"] == "degraded" r = client.post("/v1/registry/slots/tier1-general/state", json={"status": "ok", "set_by": "n8n"}, headers=H) assert "proposal" in r.json() assert client.post("/v1/registry/slots/nope/state", json={"status": "ok"}, headers=H).status_code == 404 def test_maintenance_flow(client): r = client.post("/v1/maintenance/command", json={"text": "markiere tier1-flex als degraded", "user": "robert"}, headers=H).json() pid = r["proposals"][0]["id"] seq = r["proposals"][0]["seq"] open_ = client.get("/v1/maintenance/proposals", headers=H).json() assert [p["id"] for p in open_] == [pid] r = client.post(f"/v1/maintenance/proposals/{seq}/approve", json={"user": "robert"}, headers=H).json() assert r["state"] == "executed" assert client.post(f"/v1/maintenance/proposals/{seq}/approve", json={}, headers=H).status_code == 409 rep = client.post("/v1/maintenance/routine", headers=H).json() assert "Service-Routine" in rep["text"] def test_healer_endpoint(client): payload = {"version": "4", "status": "firing", "alerts": [ {"status": "firing", "labels": {"alertname": "GpuTemperatureHigh", "slot": "tier1-flex"}, "fingerprint": "abc"}]} r = client.post("/v1/healer/alertmanager", json=payload, headers=H).json() assert r["outcomes"][0]["status"] == "executed" reg = client.get("/v1/registry", headers=H).json() assert next(s for s in reg["slots"] if s["name"] == "tier1-flex")["status"] == "degraded" def test_candidate_via_api_needs_approval_then_reports(client): r = client.post("/v1/lifecycle/candidates", json={"kind": "adapter", "slot": "tier1-general", "target": {"cluster": "rechnungen"}}, headers=H).json() assert r["state"] == "open" client.post(f"/v1/maintenance/proposals/{r['seq']}/approve", json={}, headers=H) cands = client.get("/v1/lifecycle/candidates", headers=H).json() cid = cands[0]["id"] assert cands[0]["state"] == "building" r = client.post(f"/v1/lifecycle/candidates/{cid}/report", json={"step": "build", "payload": {"model": "rv2"}}, headers=H).json() assert r["state"] == "evaluating" assert client.post(f"/v1/lifecycle/candidates/{cid}/report", json={"step": "canary"}, headers=H).status_code == 409 def test_graduation_endpoint(client): app_sys = client.app.state.system app_sys.chat.handlers["tier1-general/rechnungen-v1"] = lambda m: GOOD variants = [f"{RECHNUNG} Variante {i}" for i in range(10)] r = client.post("/v1/skills/rechnung-extrahieren/graduate", json={"variants": variants}, headers=H).json() assert r["report"]["graduated"] and "proposal" in r cases = app_sys.store.list_cases() assert len(cases) == 10 and all(c["origin"] == "graduation" for c in cases) app_sys.chat.handlers["tier1-general/rechnungen-v1"] = lambda m: "kaputt" r = client.post("/v1/skills/rechnung-extrahieren/graduate", json={"variants": variants}, headers=H).json() assert not r["report"]["graduated"] and r["report"]["failures"] assert client.post("/v1/skills/nope/graduate", json={"variants": []}, headers=H).status_code == 404 def test_reload_skills(client): assert client.post("/v1/skills/reload", headers=H).json()["skills"] == 5