"""P5 tests — coach research route + the `research` CLI verb.""" from __future__ import annotations from pathlib import Path from fastapi.testclient import TestClient from typer.testing import CliRunner from mindxtrain.cli.main import app from mindxtrain.operator.app import app as fastapi_app from mindxtrain.research import Ledger runner = CliRunner() def test_research_verb_in_help() -> None: result = runner.invoke(app, ["--help"]) assert result.exit_code == 0 assert "research" in result.output def test_research_anchor_errors_cleanly_without_registry(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] monkeypatch.delenv("MINDXTRAIN_AUTORESEARCH_REGISTRY_ADDR", raising=False) # A throwaway git repo so the search runs to a baseline, then --anchor fails # cleanly (no registry configured) — exit 1, not a crash or silent success. import subprocess def git(*a: str) -> None: subprocess.run(["git", "-C", str(tmp_path), *a], check=True, capture_output=True, text=True) git("init", "-b", "main") git("config", "user.email", "ci@example.com") git("config", "user.name", "ci") (tmp_path / ".gitignore").write_text("out/\n", encoding="utf-8") (tmp_path / "value.txt").write_text("1\n", encoding="utf-8") git("add", ".") git("commit", "-m", "baseline") (tmp_path / "out").mkdir() # gitignored → the contract file isn't a stray c = tmp_path / "out" / "c.toml" c.write_text( 'task="t"\ncommand="true"\neditable=["value.txt"]\nmetric="score"\n', encoding="utf-8" ) monkeypatch.chdir(tmp_path) result = runner.invoke(app, ["research", str(c), "--log-root", "out", "--anchor"]) assert result.exit_code == 1 assert "registry address" in result.output def test_research_attempts_route(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] import mindxtrain.operator.coach.api as api monkeypatch.setattr(api, "_RESEARCH_LOG_ROOT", tmp_path) led = Ledger(tmp_path) led.append(researcher="codephreak", commit="c1", status="completed", metric={"name": "score", "value": 0.5, "mode": "max"}) led.append(researcher="other", commit="c2", status="completed") client = TestClient(fastapi_app) res = client.get("/coach/api/research/codephreak/attempts") assert res.status_code == 200 body = res.json() assert len(body) == 1 # filtered to this researcher assert body[0]["commit"] == "c1" assert body[0]["metric"]["value"] == 0.5 def test_research_diff_route_path_guard(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] import mindxtrain.operator.coach.api as api monkeypatch.setattr(api, "_RESEARCH_LOG_ROOT", tmp_path) client = TestClient(fastapi_app) # run_dir outside the research root is rejected. res = client.get("/coach/api/research/diff", params={"run_dir": "/etc"}) assert res.status_code == 400 def test_research_diff_route_returns_diff(tmp_path: Path, monkeypatch) -> None: # type: ignore[no-untyped-def] import mindxtrain.operator.coach.api as api monkeypatch.setattr(api, "_RESEARCH_LOG_ROOT", tmp_path) run_dir = tmp_path / "codephreak" / "attempt-001" run_dir.mkdir(parents=True) (run_dir / "code.diff").write_text("--- a\n+++ b\n", encoding="utf-8") client = TestClient(fastapi_app) res = client.get("/coach/api/research/diff", params={"run_dir": str(run_dir)}) assert res.status_code == 200 assert "+++ b" in res.json()["diff"]