4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
328 lines
11 KiB
Python
328 lines
11 KiB
Python
"""Tests for /rca history and /rca show."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import io
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from rich.console import Console
|
|
|
|
from core.agent_harness.session import JsonlSessionStorage
|
|
from surfaces.interactive_shell.command_registry import dispatch_slash
|
|
from surfaces.interactive_shell.session import Session
|
|
|
|
SessionStore = JsonlSessionStorage()
|
|
|
|
|
|
def _capture() -> tuple[Console, io.StringIO]:
|
|
buf = io.StringIO()
|
|
return Console(file=buf, force_terminal=False, highlight=False), buf
|
|
|
|
|
|
def test_rca_history_lists_persisted_reports(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "redis timeout", "problem_md": "cache unavailable"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca history", Session(), console) is True
|
|
output = buf.getvalue()
|
|
assert "RCA history" in output
|
|
assert "redis timeout" in output
|
|
assert "/investigate generic" in output
|
|
|
|
|
|
def test_rca_show_renders_full_report(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "bad deploy", "problem_md": "## Report\nRollback required"},
|
|
trigger="/investigate grafana",
|
|
)
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca show {inv_id[:4]}", Session(), console) is True
|
|
output = buf.getvalue()
|
|
assert "bad deploy" in output
|
|
assert "Rollback required" in output
|
|
|
|
|
|
def test_bare_rca_defaults_to_history(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca", Session(), console) is True
|
|
assert "no persisted RCA reports yet" in buf.getvalue()
|
|
|
|
|
|
def test_tty_rca_menu_latest_shows_report(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from surfaces.interactive_shell.command_registry import rca_cmds
|
|
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "connection pool exhausted", "problem_md": "## Report\nPool at max"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
monkeypatch.setattr(rca_cmds, "repl_tty_interactive", lambda: True)
|
|
monkeypatch.setattr(rca_cmds, "repl_choose_one", lambda **_: rca_cmds._RCA_LATEST)
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca", session, console) is True
|
|
output = buf.getvalue()
|
|
assert "connection pool exhausted" in output
|
|
assert "Pool at max" in output
|
|
assert inv_id[:8] in output
|
|
|
|
|
|
def test_tty_rca_history_menu_picks_report_directly(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from surfaces.interactive_shell.command_registry import rca_cmds
|
|
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
older_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "older issue", "problem_md": "older report"},
|
|
trigger="/investigate grafana",
|
|
)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "newer issue", "problem_md": "newer report"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
monkeypatch.setattr(rca_cmds, "repl_tty_interactive", lambda: True)
|
|
monkeypatch.setattr(rca_cmds, "repl_choose_one", lambda **_: older_id)
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca history", session, console) is True
|
|
output = buf.getvalue()
|
|
assert "older issue" in output
|
|
assert "older report" in output
|
|
assert "newer issue" not in output
|
|
|
|
|
|
def test_tty_rca_root_menu_history_picks_report(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from surfaces.interactive_shell.command_registry import rca_cmds
|
|
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
older_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "older issue", "problem_md": "older report"},
|
|
trigger="/investigate grafana",
|
|
)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "newer issue", "problem_md": "newer report"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
picks = iter([rca_cmds._RCA_HISTORY, older_id])
|
|
monkeypatch.setattr(rca_cmds, "repl_tty_interactive", lambda: True)
|
|
monkeypatch.setattr(rca_cmds, "repl_choose_one", lambda **_: next(picks))
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca", session, console) is True
|
|
output = buf.getvalue()
|
|
assert "older issue" in output
|
|
assert "older report" in output
|
|
assert "newer issue" not in output
|
|
|
|
|
|
def test_rca_save_writes_markdown(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "redis timeout", "problem_md": "cache unavailable"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
dest = tmp_path / "report.md"
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save {dest}", Session(), console) is True
|
|
assert (
|
|
dest.read_text(encoding="utf-8")
|
|
== "## Root Cause\n\nredis timeout\n\n## Report\n\ncache unavailable\n"
|
|
)
|
|
assert "saved:" in buf.getvalue()
|
|
|
|
|
|
def test_rca_save_by_id_writes_json(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "bad deploy", "problem_md": "rollback required"},
|
|
trigger="/investigate grafana",
|
|
)
|
|
|
|
dest = tmp_path / "report.json"
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save {inv_id[:4]} {dest}", Session(), console) is True
|
|
payload = json.loads(dest.read_text(encoding="utf-8"))
|
|
assert payload["root_cause"] == "bad deploy"
|
|
assert payload["problem_md"] == "rollback required"
|
|
assert payload["investigation_id"] == inv_id
|
|
assert "saved:" in buf.getvalue()
|
|
|
|
|
|
def test_rca_save_strips_quoted_path(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "quoted path", "problem_md": "saved body"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
dest = tmp_path / "quoted.md"
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save {inv_id[:4]} '{dest}'", Session(), console) is True
|
|
assert dest.read_text(encoding="utf-8").startswith("## Root Cause")
|
|
assert "saved:" in buf.getvalue()
|
|
|
|
|
|
def test_rca_save_to_new_folder_adds_default_filename(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "folder save", "problem_md": "in subfolder"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
folder = tmp_path / "rca_reports"
|
|
folder.mkdir()
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save {inv_id[:4]} {folder}/", Session(), console) is True
|
|
saved = folder / f"rca-{inv_id[:8]}.md"
|
|
assert saved.exists()
|
|
assert "folder save" in saved.read_text(encoding="utf-8")
|
|
assert "saved:" in buf.getvalue()
|
|
|
|
|
|
def test_rca_save_to_new_folder_trailing_slash_creates_subdirectory(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
inv_id = SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "nested save", "problem_md": "inside new folder"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
folder = tmp_path / "new_rca_folder"
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save {inv_id[:4]} {folder}/", Session(), console) is True
|
|
saved = folder / f"rca-{inv_id[:8]}.md"
|
|
assert saved.exists()
|
|
assert "nested save" in saved.read_text(encoding="utf-8")
|
|
assert "saved:" in buf.getvalue()
|
|
|
|
|
|
def test_rca_save_unknown_id_reports_not_found(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "existing issue", "problem_md": "existing report"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
dest = tmp_path / "report.md"
|
|
console, buf = _capture()
|
|
assert dispatch_slash(f"/rca save badid {dest}", Session(), console) is True
|
|
output = buf.getvalue()
|
|
assert "RCA report not found" in output
|
|
assert "no persisted RCA reports yet" not in output
|
|
assert not dest.exists()
|
|
|
|
|
|
def test_normalize_rca_save_path_strips_quotes() -> None:
|
|
from surfaces.interactive_shell.command_registry import rca_cmds
|
|
|
|
dest = rca_cmds._normalize_rca_save_path("'/tmp/report.md'", investigation_id="abcd1234")
|
|
assert dest == Path("/tmp/report.md")
|
|
|
|
|
|
def test_tty_rca_save_menu_picks_latest_and_prompts_path(
|
|
tmp_path: Path,
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from surfaces.interactive_shell.command_registry import rca_cmds
|
|
|
|
monkeypatch.setattr("config.constants.OPENSRE_HOME_DIR", tmp_path)
|
|
session = Session()
|
|
SessionStore.open_session(session)
|
|
SessionStore.append_investigation_result(
|
|
session.session_id,
|
|
{"root_cause": "latest root cause", "problem_md": "latest body"},
|
|
trigger="/investigate generic",
|
|
)
|
|
|
|
dest = tmp_path / "picked.md"
|
|
monkeypatch.setattr(rca_cmds, "repl_tty_interactive", lambda: True)
|
|
monkeypatch.setattr(rca_cmds, "repl_choose_one", lambda **_: rca_cmds._RCA_LATEST)
|
|
monkeypatch.setattr(rca_cmds, "_prompt_rca_save_path", lambda _console: str(dest))
|
|
|
|
console, buf = _capture()
|
|
assert dispatch_slash("/rca save", session, console) is True
|
|
assert "latest root cause" in dest.read_text(encoding="utf-8")
|
|
assert "saved:" in buf.getvalue()
|