strix/tests/test_run_summaries.py
Ahmed Allam e954301a09 feat(cli): pick a prior run interactively when --resume has no name
strix --resume with no run name now lists the runs in ./strix_runs inline
in the terminal (started, target, status, findings, run name), newest
first, with arrow-key selection, type-to-search and esc to cancel.
Picking a run continues through the same path as --resume <name>.
Headless or non-TTY launches error with the run list instead.
2026-10-04 20:07:47 +03:00

124 lines
3.8 KiB
Python

"""Tests for the run listing behind the resume picker."""
from __future__ import annotations
import json
import os
from typing import TYPE_CHECKING
from strix.report.runs import list_run_summaries
if TYPE_CHECKING:
from pathlib import Path
def _write_run(
base: Path,
name: str,
record: object,
*,
modified: float,
state: bool = True,
findings: int | None = None,
) -> None:
run_dir = base / name
run_dir.mkdir(parents=True)
path = run_dir / "run.json"
path.write_text(record if isinstance(record, str) else json.dumps(record), encoding="utf-8")
if state:
(run_dir / ".state").mkdir()
(run_dir / ".state" / "agents.json").write_text("{}", encoding="utf-8")
if findings is not None:
(run_dir / "vulnerabilities.json").write_text(
json.dumps([{"id": index} for index in range(findings)]), encoding="utf-8"
)
os.utime(path, (modified, modified))
def test_lists_runs_newest_first_with_target_status_and_findings(tmp_path: Path) -> None:
base = tmp_path / "strix_runs"
_write_run(
base,
"example-com_1111",
{
"status": "completed",
"start_time": "2026-10-04T10:00:00+00:00",
"end_time": "2026-10-04T10:05:00+00:00",
"targets_info": [{"type": "web_application", "original": "https://example.com"}],
},
modified=1_000,
findings=2,
)
_write_run(
base,
"example-com_2222",
{
"status": "interrupted",
"start_time": "2026-10-04T11:00:00+00:00",
"targets_info": [
{"type": "web_application", "original": "https://example.com"},
{"type": "web_application", "original": "https://api.example.com"},
],
},
modified=3_000,
)
(base / "not-a-run").mkdir()
runs = list_run_summaries(cwd=tmp_path)
assert [run.run_name for run in runs] == ["example-com_2222", "example-com_1111"]
newest, oldest = runs
assert newest.target == "https://example.com +1 more"
assert newest.status == "interrupted"
assert newest.started_at == "2026-10-04T11:00:00+00:00"
assert newest.ended_at == ""
assert newest.findings == 0
assert newest.resumable is True
assert oldest.target == "https://example.com"
assert oldest.findings == 2
assert oldest.ended_at == "2026-10-04T10:05:00+00:00"
def test_target_less_runs_describe_their_workspace_or_instruction(tmp_path: Path) -> None:
base = tmp_path / "strix_runs"
_write_run(
base,
"pentest_aaaa",
{"status": "stopped", "targets_info": [], "workspace_mount": "/home/me/app"},
modified=2_000,
)
_write_run(
base,
"pentest_bbbb",
{"status": "failed", "targets_info": [], "user_instruction": "audit the login\nflow"},
modified=1_000,
)
runs = {run.run_name: run for run in list_run_summaries(cwd=tmp_path)}
assert runs["pentest_aaaa"].target == "/home/me/app (workspace)"
assert runs["pentest_bbbb"].target == "audit the login"
def test_runs_without_agent_state_or_with_a_broken_record_are_listed_not_resumable(
tmp_path: Path,
) -> None:
base = tmp_path / "strix_runs"
_write_run(base, "no-state_cccc", {"status": "failed"}, modified=2_000, state=False)
_write_run(base, "broken_dddd", "{not json", modified=1_000)
_write_run(base, "list_eeee", "[]", modified=500)
runs = list_run_summaries(cwd=tmp_path)
assert [(run.run_name, run.resumable) for run in runs] == [
("no-state_cccc", False),
("broken_dddd", True),
("list_eeee", True),
]
assert runs[1].status == "unknown"
assert runs[1].target == ""
def test_missing_runs_dir_lists_nothing(tmp_path: Path) -> None:
assert list_run_summaries(cwd=tmp_path) == []