from __future__ import annotations import json from ariadne.services import testing_triage class DummyStorage: def __init__(self) -> None: self.events: list[tuple[str, dict]] = [] def latest_cluster_state(self): # type: ignore[no-untyped-def] return { "collected_at": "2026-05-20T00:00:00+00:00", "summary": { "health_bullets": ["Pods pending: 1"], "attention_ranked": [{"kind": "pod_pending"}], }, "nodes_summary": {"total": 3, "ready": 3, "not_ready": 0}, "flux": {"items": [{"namespace": "flux-system", "name": "monitoring"}]}, "pod_issues": { "items": [{"namespace": "jenkins", "pod": "agent-1", "phase": "Pending"}], "pending_oldest": [{"namespace": "jenkins", "pod": "agent-1"}], }, "jobs": {"failing": [], "active_oldest": []}, "events": {"warnings_recent": []}, } def record_event(self, event_type: str, detail: dict) -> None: self.events.append((event_type, detail)) def list_events(self, limit: int = 1, event_type: str | None = None): # type: ignore[no-untyped-def] matching = [ {"detail": detail} for stored_type, detail in self.events if event_type is None or stored_type == event_type ] return matching[-limit:][::-1] class SettingsStub: def __init__(self, **overrides) -> None: # type: ignore[no-untyped-def] self.vm_url = "" self.cluster_state_vm_timeout_sec = 1.0 self.jenkins_base_url = "" self.jenkins_api_user = "" self.jenkins_api_token = "" self.jenkins_api_timeout_sec = 1.0 for key, value in overrides.items(): setattr(self, key, value) def test_collect_testing_triage_builds_bundle(monkeypatch) -> None: storage = DummyStorage() monkeypatch.setattr( testing_triage, "_vm_items", lambda query, errors: [{"labels": {"suite": "ariadne"}, "value": 1.0}] if "platform_quality_gate_runs_total" in query else [], ) monkeypatch.setattr(testing_triage, "_jenkins_signals", lambda errors: {"failed_builds": []}) bundle = testing_triage.collect_testing_triage(storage) assert bundle["kind"] == "testing_triage_bundle" assert bundle["summary"]["status"] == "needs_attention" assert bundle["summary"]["failed_suites"] == ["ariadne"] assert "Testing Triage Evidence" in bundle["markdown"] assert bundle["openclaw"]["ariadne_latest_url"].endswith("/api/internal/testing/triage/latest") def test_run_testing_triage_stores_latest(monkeypatch) -> None: storage = DummyStorage() monkeypatch.setattr( testing_triage, "collect_testing_triage", lambda _storage: { "summary": {"status": "ok", "problem_count": 0, "failed_suites": []}, }, ) summary = testing_triage.run_testing_triage(storage) latest = testing_triage.latest_testing_triage_bundle(storage) assert summary.status == "ok" assert storage.events[0][0] == testing_triage.TRIAGE_EVENT_TYPE assert latest["summary"]["status"] == "ok" def test_latest_testing_triage_bundle_handles_json_strings() -> None: class JsonStorage: def list_events(self, limit: int = 1, event_type: str | None = None): # type: ignore[no-untyped-def] assert limit == 1 assert event_type == testing_triage.TRIAGE_EVENT_TYPE return [{"detail": json.dumps({"summary": {"status": "ok"}})}] latest = testing_triage.latest_testing_triage_bundle(JsonStorage()) # type: ignore[arg-type] assert latest == {"summary": {"status": "ok"}} def test_latest_testing_triage_bundle_ignores_bad_payloads() -> None: class BadStorage: def __init__(self, detail) -> None: # type: ignore[no-untyped-def] self.detail = detail def list_events(self, limit: int = 1, event_type: str | None = None): # type: ignore[no-untyped-def] return [{"detail": self.detail}] assert testing_triage.latest_testing_triage_bundle(BadStorage("{")) is None # type: ignore[arg-type] assert testing_triage.latest_testing_triage_bundle(BadStorage(["nope"])) is None # type: ignore[arg-type] assert testing_triage.latest_testing_triage_bundle(BadStorage(None)) is None # type: ignore[arg-type] class EmptyStorage: def list_events(self, limit: int = 1, event_type: str | None = None): # type: ignore[no-untyped-def] return [] assert testing_triage.latest_testing_triage_bundle(EmptyStorage()) is None # type: ignore[arg-type] def test_latest_cluster_snapshot_falls_back_to_live_collect(monkeypatch) -> None: class BrokenStorage: def latest_cluster_state(self): # type: ignore[no-untyped-def] raise RuntimeError("storage down") monkeypatch.setattr( testing_triage, "collect_cluster_state", lambda: ({"collected_at": "live"}, {"status": "ok"}), ) errors: list[str] = [] snapshot = testing_triage._latest_cluster_snapshot(BrokenStorage(), errors) # noqa: SLF001 assert snapshot == {"collected_at": "live"} assert errors == ["cluster_state_latest: storage down"] def test_latest_cluster_snapshot_records_collect_error(monkeypatch) -> None: monkeypatch.setattr( testing_triage, "collect_cluster_state", lambda: (_ for _ in ()).throw(RuntimeError("api down")), ) errors: list[str] = [] snapshot = testing_triage._latest_cluster_snapshot(None, errors) # noqa: SLF001 assert snapshot == {} assert errors == ["cluster_state_collect: api down"] def test_cluster_evidence_limits_and_defaults() -> None: snapshot = { "summary": { "health_bullets": list(range(20)), "attention_ranked": [{"item": i} for i in range(20)], }, "nodes_summary": {"total": 2, "ready": 1, "not_ready": 1, "not_ready_names": ["titan-06"]}, "flux": {"items": [{"name": str(i)} for i in range(20)]}, "pod_issues": { "items": [{"pod": str(i)} for i in range(20)], "pending_oldest": [{"pod": "p"}], }, "jobs": {"failing": [{"job": "j"}], "active_oldest": [{"job": "old"}]}, "events": {"warnings_recent": [{"message": "warn"}]}, } evidence = testing_triage._cluster_evidence(snapshot) # noqa: SLF001 assert len(evidence["health_bullets"]) == testing_triage._MAX_EVIDENCE_ITEMS # noqa: SLF001 assert evidence["nodes"]["not_ready_names"] == ["titan-06"] assert evidence["jobs_failing"] == [{"job": "j"}] def test_vm_items_handles_success_failure_and_bad_values(monkeypatch) -> None: class FakeResponse: def __init__(self, payload) -> None: # type: ignore[no-untyped-def] self.payload = payload def raise_for_status(self) -> None: return None def json(self): # type: ignore[no-untyped-def] return self.payload class FakeClient: def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] self.payload = kwargs.pop("payload", None) def __enter__(self): return self def __exit__(self, *args) -> None: # type: ignore[no-untyped-def] return None def get(self, url, params=None): # type: ignore[no-untyped-def] assert url.endswith("/api/v1/query") assert params == {"query": "up"} return FakeResponse( { "status": "success", "data": { "result": [ {"metric": {"suite": "ariadne", "__name__": "ignored"}, "value": [1, "2.5"]}, {"metric": {"suite": "metis"}, "value": [1, "bad"]}, ] }, } ) monkeypatch.setattr(testing_triage, "settings", SettingsStub(vm_url="http://vm")) monkeypatch.setattr(testing_triage.httpx, "Client", FakeClient) errors: list[str] = [] items = testing_triage._vm_items("up", errors) # noqa: SLF001 assert items == [ {"labels": {"suite": "ariadne"}, "value": 2.5}, {"labels": {"suite": "metis"}, "value": 0.0}, ] assert errors == [] def test_vm_items_records_errors(monkeypatch) -> None: class BrokenClient: def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] return None def __enter__(self): raise RuntimeError("network down") def __exit__(self, *args) -> None: # type: ignore[no-untyped-def] return None monkeypatch.setattr(testing_triage, "settings", SettingsStub(vm_url="http://vm")) monkeypatch.setattr(testing_triage.httpx, "Client", BrokenClient) errors: list[str] = [] assert testing_triage._vm_items("up", errors) == [] # noqa: SLF001 assert errors == ["victoria_metrics: network down"] def test_vm_items_handles_disabled_and_query_failure(monkeypatch) -> None: class FailedResponse: def raise_for_status(self) -> None: return None def json(self): # type: ignore[no-untyped-def] return {"status": "error"} class FailedClient: def __init__(self, *args, **kwargs) -> None: # type: ignore[no-untyped-def] return None def __enter__(self): return self def __exit__(self, *args) -> None: # type: ignore[no-untyped-def] return None def get(self, url, params=None): # type: ignore[no-untyped-def] return FailedResponse() errors: list[str] = [] monkeypatch.setattr(testing_triage, "settings", SettingsStub(vm_url="")) assert testing_triage._vm_items("up", errors) == [] # noqa: SLF001 assert errors == [] monkeypatch.setattr(testing_triage, "settings", SettingsStub(vm_url="http://vm")) monkeypatch.setattr(testing_triage.httpx, "Client", FailedClient) assert testing_triage._vm_items("up", errors) == [] # noqa: SLF001 assert errors == ["victoria_metrics: query failed"] def test_jenkins_flatten_status_and_log_tail(monkeypatch) -> None: rows = [ "skip", {"name": "", "lastBuild": {}}, { "name": "folder", "jobs": [ { "name": "child", "url": "http://jenkins/job/folder/job/child/", "color": "red", "lastBuild": { "number": 7, "result": "FAILURE", "timestamp": 2000, "duration": 3000, "url": "http://jenkins/build/7/", }, } ], }, {"name": "empty", "jobs": []}, ] flattened = testing_triage._flatten_jobs(rows) # noqa: SLF001 job = testing_triage._jenkins_job(flattened[0]) # noqa: SLF001 assert flattened[0]["name"] == "folder/child" assert job is not None assert job["status"] == "failure" assert job["last_run_ts"] == 2.0 assert job["last_duration_seconds"] == 3.0 assert job["console_url"] == "http://jenkins/build/7/consoleText" assert testing_triage._jenkins_status({"color": "blue_anime"}, "") == "running" # noqa: SLF001 assert testing_triage._jenkins_status({}, "SUCCESS") == "success" # noqa: SLF001 assert testing_triage._jenkins_status({"color": "green"}, "") == "success" # noqa: SLF001 assert testing_triage._jenkins_status({"color": "yellow"}, "") == "failure" # noqa: SLF001 assert testing_triage._jenkins_status({"color": "grey"}, "") == "unknown" # noqa: SLF001 assert testing_triage._jenkins_job({"name": 1, "url": "u"}) is None # noqa: SLF001 long_tail = testing_triage._tail_text("x" * (testing_triage._MAX_JENKINS_LOG_CHARS + 10)) # noqa: SLF001 assert len(long_tail) == testing_triage._MAX_JENKINS_LOG_CHARS # noqa: SLF001 assert testing_triage._tail_text("\n".join(str(i) for i in range(100))).startswith("20\n") # noqa: SLF001 def test_attach_jenkins_log_tail_ignores_missing_url_and_records_errors(monkeypatch) -> None: class BrokenClient: def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] return None def __enter__(self): return self def __exit__(self, *args) -> None: # type: ignore[no-untyped-def] return None def get(self, url): # type: ignore[no-untyped-def] raise RuntimeError("log gone") errors: list[str] = [] testing_triage._attach_jenkins_log_tail({"job": "missing"}, errors) # noqa: SLF001 assert errors == [] monkeypatch.setattr(testing_triage, "settings", SettingsStub(jenkins_api_timeout_sec=1)) monkeypatch.setattr(testing_triage.httpx, "Client", BrokenClient) testing_triage._attach_jenkins_log_tail({"job": "ariadne", "console_url": "http://jenkins/log"}, errors) # noqa: SLF001 assert errors == ["jenkins_log:ariadne: log gone"] def test_fetch_jenkins_jobs_and_log_tail(monkeypatch) -> None: class FakeResponse: def __init__(self, payload=None, text="") -> None: # type: ignore[no-untyped-def] self.payload = payload self.text = text def raise_for_status(self) -> None: return None def json(self): # type: ignore[no-untyped-def] return self.payload class FakeClient: def __init__(self, **kwargs) -> None: # type: ignore[no-untyped-def] assert kwargs["auth"] == ("jenkins", "token") def __enter__(self): return self def __exit__(self, *args) -> None: # type: ignore[no-untyped-def] return None def get(self, url, params=None): # type: ignore[no-untyped-def] if url.endswith("/api/json"): assert "tree" in params return FakeResponse( { "jobs": [ { "name": "ariadne", "url": "http://jenkins/job/ariadne/", "color": "red", "lastBuild": {"number": 8, "result": "FAILURE", "timestamp": 1000, "duration": 1000}, } ] } ) return FakeResponse(text="line1\nline2") monkeypatch.setattr( testing_triage, "settings", SettingsStub(jenkins_api_user="jenkins", jenkins_api_token="token", jenkins_api_timeout_sec=3), ) monkeypatch.setattr(testing_triage.httpx, "Client", FakeClient) jobs = testing_triage._fetch_jenkins_jobs("http://jenkins") # noqa: SLF001 errors: list[str] = [] testing_triage._attach_jenkins_log_tail(jobs[0], errors) # noqa: SLF001 assert jobs[0]["job"] == "ariadne" assert jobs[0]["status"] == "failure" assert jobs[0]["log_tail"] == "line1\nline2" assert errors == [] def test_jenkins_signals_handles_disabled_and_failures(monkeypatch) -> None: monkeypatch.setattr(testing_triage, "settings", SettingsStub(jenkins_base_url="")) assert testing_triage._jenkins_signals([]) == {"failed_builds": []} # noqa: SLF001 monkeypatch.setattr(testing_triage, "settings", SettingsStub(jenkins_base_url="http://jenkins")) monkeypatch.setattr( testing_triage, "_fetch_jenkins_jobs", lambda base_url: (_ for _ in ()).throw(RuntimeError("boom")), ) errors: list[str] = [] assert testing_triage._jenkins_signals(errors) == {"failed_builds": []} # noqa: SLF001 assert errors == ["jenkins: boom"] def test_jenkins_signals_attaches_recent_failed_builds(monkeypatch) -> None: jobs = [ {"job": "old", "status": "failure", "last_run_ts": 1}, {"job": "ok", "status": "success", "last_run_ts": 5}, {"job": "running", "status": "running", "last_run_ts": 10}, {"job": "unknown", "status": "unknown", "last_run_ts": 3}, ] attached: list[str] = [] monkeypatch.setattr(testing_triage, "settings", SettingsStub(jenkins_base_url="http://jenkins")) monkeypatch.setattr(testing_triage, "_fetch_jenkins_jobs", lambda base_url: jobs) monkeypatch.setattr(testing_triage, "_attach_jenkins_log_tail", lambda job, errors: attached.append(job["job"])) signals = testing_triage._jenkins_signals([]) # noqa: SLF001 assert [item["job"] for item in signals["failed_builds"]] == ["running", "unknown", "old"] assert attached == ["running", "unknown", "old"] def test_summary_and_markdown_helpers() -> None: quality = { "failed_runs_24h": {"items": [{"labels": {"suite": "ariadne"}, "value": 2}]}, "empty": {"items": []}, "bad": "skip", } cluster = { "flux_not_ready": [{"name": "monitoring"}], "pod_issues": [], "jobs_failing": [{"job": "job"}], "collected_at": "now", } jenkins = {"failed_builds": [{"job": "ariadne"}]} summary = testing_triage._summary(cluster, quality, jenkins, []) # noqa: SLF001 markdown = testing_triage._render_markdown( # noqa: SLF001 { "generated_at": "now", "summary": summary, "evidence": {"cluster": cluster, "quality": quality, "jenkins": jenkins}, "unknowns": ["missing vm"], } ) assert summary["status"] == "needs_attention" assert summary["failed_suites"] == ["ariadne"] assert "- Flux: monitoring" in markdown assert "- failed_runs_24h: {'suite': 'ariadne'} value=2" in markdown assert "- missing vm" in markdown assert testing_triage._markdown_named_items("Pods", ["bad"], "pod") == ["- Pods: none"] # noqa: SLF001 assert testing_triage._jenkins_auth() is None # noqa: SLF001