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
297 lines
10 KiB
Python
297 lines
10 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, NoReturn
|
|
|
|
import pytest
|
|
|
|
from core.domain.stream import StreamEvent
|
|
from integrations.llm_cli.errors import CLIAuthenticationRequired
|
|
from surfaces.cli.error_mapping import reraise_cli_runtime_error
|
|
from surfaces.cli.investigation import run_investigation_cli, stream_investigation_cli
|
|
from surfaces.interactive_shell.utils.error_handling.errors import OpenSREError
|
|
from tools.investigation.capability import resolve_investigation_context
|
|
|
|
|
|
def test_resolve_investigation_context_prefers_cli_overrides() -> None:
|
|
alert_name, pipeline_name, severity = resolve_investigation_context(
|
|
raw_alert={
|
|
"alert_name": "PayloadAlert",
|
|
"pipeline_name": "payload_pipeline",
|
|
"severity": "warning",
|
|
},
|
|
alert_name="CliAlert",
|
|
pipeline_name="cli_pipeline",
|
|
severity="critical",
|
|
)
|
|
|
|
assert alert_name == "CliAlert"
|
|
assert pipeline_name == "cli_pipeline"
|
|
assert severity == "critical"
|
|
|
|
|
|
def test_resolve_investigation_context_uses_raw_alert_without_pipeline_default() -> None:
|
|
alert_name, pipeline_name, severity = resolve_investigation_context(
|
|
raw_alert={
|
|
"title": "CPU high",
|
|
"commonLabels": {"service": "checkout", "severity": "critical"},
|
|
},
|
|
alert_name=None,
|
|
pipeline_name=None,
|
|
severity=None,
|
|
)
|
|
|
|
assert alert_name == "CPU high"
|
|
assert pipeline_name == "checkout"
|
|
assert severity == "critical"
|
|
|
|
|
|
def test_run_investigation_cli_passes_investigation_metadata_to_runner(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
captured: dict[str, Any] = {}
|
|
|
|
def fake_call(**kwargs: Any) -> dict[str, Any]:
|
|
captured.update(kwargs)
|
|
return {
|
|
"report": "r",
|
|
"problem_md": "p",
|
|
"root_cause": "c",
|
|
"is_noise": False,
|
|
"validity_score": 0.0,
|
|
}
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.run_investigation_payload",
|
|
fake_call,
|
|
)
|
|
run_investigation_cli(
|
|
raw_alert={"description": "x"},
|
|
investigation_metadata=("A", "B", "high"),
|
|
)
|
|
assert captured == {
|
|
"raw_alert": {"description": "x"},
|
|
"opensre_evaluate": False,
|
|
"investigation_metadata": ("A", "B", "high"),
|
|
}
|
|
|
|
|
|
def test_run_investigation_cli_shapes_agent_state(monkeypatch) -> None:
|
|
captured: dict[str, object] = {}
|
|
|
|
def fake_run_investigation(
|
|
raw_alert: str | dict[str, object],
|
|
**kwargs: object,
|
|
) -> dict[str, object]:
|
|
captured["raw_alert"] = raw_alert
|
|
captured.update(kwargs)
|
|
return {
|
|
"slack_message": "report body",
|
|
"problem_md": "# problem",
|
|
"root_cause": "bad deploy",
|
|
}
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.run_investigation",
|
|
fake_run_investigation,
|
|
)
|
|
|
|
result = run_investigation_cli(
|
|
raw_alert={"alert_name": "PayloadAlert"},
|
|
)
|
|
|
|
assert captured == {
|
|
"raw_alert": {"alert_name": "PayloadAlert"},
|
|
"opensre_evaluate": False,
|
|
"investigation_metadata": None,
|
|
}
|
|
assert result == {
|
|
"report": "report body",
|
|
"problem_md": "# problem",
|
|
"root_cause": "bad deploy",
|
|
"is_noise": False,
|
|
"validity_score": 0.0,
|
|
}
|
|
|
|
|
|
def test_run_investigation_cli_evaluate_reports_skip_when_no_rubric(monkeypatch) -> None:
|
|
def fake_run(
|
|
raw_alert: str | dict[str, object],
|
|
**kwargs: object,
|
|
) -> dict[str, object]:
|
|
return {
|
|
"slack_message": "r",
|
|
"problem_md": "p",
|
|
"root_cause": "c",
|
|
"opensre_evaluate": True,
|
|
"opensre_eval_rubric": "",
|
|
"opensre_llm_eval": {},
|
|
}
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr("tools.investigation.capability.run_investigation", fake_run)
|
|
|
|
result = run_investigation_cli(
|
|
raw_alert={"alert_name": "A"},
|
|
opensre_evaluate=True,
|
|
)
|
|
assert result["opensre_llm_eval"]["skipped"] is True
|
|
assert "No scoring_points" in result["opensre_llm_eval"]["reason"]
|
|
|
|
|
|
def test_parse_args_evaluate_flag() -> None:
|
|
from surfaces.cli.args import parse_args
|
|
|
|
assert parse_args(["--input", "a.json"]).evaluate is False
|
|
assert parse_args(["--input", "a.json", "--evaluate"]).evaluate is True
|
|
|
|
|
|
def test_run_investigation_cli_fails_fast_for_missing_llm_auth(monkeypatch, tmp_path) -> None:
|
|
from config.llm_auth.credentials import CredentialStatus
|
|
from platform.common.errors import OpenSREError
|
|
|
|
monkeypatch.setenv("LLM_PROVIDER", "openai")
|
|
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
|
monkeypatch.setenv("OPENSRE_LLM_AUTH_METADATA_PATH", str(tmp_path / "llm-auth.json"))
|
|
monkeypatch.setattr(
|
|
"config.llm_auth.credentials.status",
|
|
lambda provider: CredentialStatus(
|
|
provider=provider,
|
|
configured=False,
|
|
source="none",
|
|
verified=False,
|
|
stale=False,
|
|
detail="OPENAI_API_KEY is not set",
|
|
),
|
|
)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.run_investigation_payload",
|
|
lambda *_args, **_kwargs: pytest.fail("investigation should not start"),
|
|
)
|
|
|
|
with pytest.raises(OpenSREError, match="credentials are missing"):
|
|
run_investigation_cli(raw_alert={"alert_name": "PayloadAlert"})
|
|
|
|
|
|
def test_stream_investigation_cli_raises_queued_exception_immediately(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
async def fake_astream_investigation(*args: object, **kwargs: object):
|
|
yield StreamEvent("metadata", data={"run_id": "run-123"})
|
|
raise RuntimeError("stream failed")
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.astream_investigation",
|
|
fake_astream_investigation,
|
|
)
|
|
|
|
events = stream_investigation_cli(raw_alert={"alert_name": "PayloadAlert"})
|
|
|
|
first = next(events)
|
|
assert first.event_type == "metadata"
|
|
with pytest.raises(RuntimeError, match="stream failed"):
|
|
next(events)
|
|
|
|
|
|
def test_stream_investigation_cli_closes_cleanly_on_generator_close(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
"""Closing the generator must not hang and must clean up the background thread."""
|
|
import asyncio
|
|
import time
|
|
|
|
async def fake_astream_investigation(*args: object, **kwargs: object):
|
|
yield StreamEvent("metadata", data={"run_id": "run-123"})
|
|
# Simulate a long-running stream
|
|
await asyncio.sleep(1000)
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.astream_investigation",
|
|
fake_astream_investigation,
|
|
)
|
|
|
|
events = stream_investigation_cli(raw_alert={"alert_name": "PayloadAlert"})
|
|
first = next(events)
|
|
assert first.event_type == "metadata"
|
|
|
|
# Without eager pump cancellation, thread.join() would block for the full timeout (~5s).
|
|
t0 = time.monotonic()
|
|
events.close()
|
|
assert time.monotonic() - t0 < 2.0
|
|
|
|
|
|
def test_run_investigation_cli_maps_cli_auth_to_opensre_error(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
def boom(raw_alert: str | dict[str, object], **_kwargs: object) -> NoReturn:
|
|
raise CLIAuthenticationRequired(
|
|
provider="cursor",
|
|
auth_hint="Run: agent login.",
|
|
detail="Not logged in.",
|
|
)
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr("tools.investigation.capability.run_investigation", boom)
|
|
|
|
with pytest.raises(OpenSREError, match="not authenticated") as exc_info:
|
|
run_investigation_cli(raw_alert={"alert_name": "PayloadAlert"})
|
|
assert exc_info.value.suggestion is not None
|
|
assert "agent login" in exc_info.value.suggestion
|
|
|
|
|
|
def test_stream_investigation_cli_maps_cli_auth_to_opensre_error(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
async def fake_astream_investigation(*args: object, **kwargs: object):
|
|
yield StreamEvent("metadata", data={"run_id": "run-123"})
|
|
raise CLIAuthenticationRequired(
|
|
provider="cursor",
|
|
auth_hint="Run: agent login.",
|
|
detail="Not logged in.",
|
|
)
|
|
|
|
monkeypatch.setattr("surfaces.cli.investigation.investigate.check_llm_settings", lambda: None)
|
|
monkeypatch.setattr(
|
|
"tools.investigation.capability.astream_investigation",
|
|
fake_astream_investigation,
|
|
)
|
|
|
|
events = stream_investigation_cli(raw_alert={"alert_name": "PayloadAlert"})
|
|
assert next(events).event_type == "metadata"
|
|
with pytest.raises(OpenSREError, match="not authenticated"):
|
|
next(events)
|
|
|
|
|
|
def test_reraise_cli_runtime_error_maps_cli_auth() -> None:
|
|
exc = CLIAuthenticationRequired(
|
|
provider="opencode",
|
|
auth_hint="Run: opencode auth login",
|
|
detail="not logged in",
|
|
)
|
|
|
|
with pytest.raises(OpenSREError) as raised:
|
|
reraise_cli_runtime_error(exc)
|
|
|
|
assert str(raised.value) == "opencode CLI is not authenticated."
|
|
assert raised.value.suggestion == "Run: opencode auth login (not logged in)"
|
|
|
|
|
|
def test_reraise_cli_runtime_error_maps_cli_not_found() -> None:
|
|
exc = RuntimeError("codex CLI not found on PATH")
|
|
|
|
with pytest.raises(OpenSREError) as raised:
|
|
reraise_cli_runtime_error(exc)
|
|
|
|
assert str(raised.value) == "CLI tool is not installed or not found."
|
|
assert raised.value.suggestion == "codex CLI not found on PATH"
|
|
|
|
|
|
def test_reraise_cli_runtime_error_reraises_unknown_runtime_error() -> None:
|
|
exc = RuntimeError("some unrelated runtime failure")
|
|
|
|
with pytest.raises(RuntimeError, match="some unrelated runtime failure"):
|
|
reraise_cli_runtime_error(exc)
|