File size: 3,542 Bytes
c27193b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
"""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"]