utils/repo_contract/tests/test_agent_ready_gate.py
Krzysztof kuhy Rudnicki e74aa7b0ea
Some checks failed
dependency freshness / dependencies on newest stable (push) Waiting to run
earned_time pre-commit / pre-commit (push) Waiting to run
earned_time tests / test (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
crdt-sync pre-commit / pre-commit (push) Has been cancelled
crdt-sync tests / test (push) Has been cancelled
freedays pre-commit / pre-commit (push) Has been cancelled
freedays tests / test (push) Has been cancelled
gatelock pre-commit / pre-commit (push) Has been cancelled
gatelock tests / test (push) Has been cancelled
music_theory tests / test (push) Has been cancelled
sickdays pre-commit / pre-commit (push) Has been cancelled
sickdays tests / test (push) Has been cancelled
ts_config tests / test (push) Has been cancelled
ts_core tests / test (push) Has been cancelled
web_ui tests / test (push) Has been cancelled
test(agent_ready_gate): clear GITHUB_ACTIONS in the real-checker test
In CI the gate defers to the workflow and returns 0, so the real-checker
test saw 0 instead of 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBScELfMUiieK9ManFbS3Y
2026-10-22 21:51:50 +02:00

179 lines
6.3 KiB
Python

"""The agent-ready gate: build, then run the shared AgentReady checker."""
from __future__ import annotations
import subprocess
from pathlib import Path
import pytest
from repo_contract import agent_ready_gate as gate
from .conftest import write
from .test_web import config, vite_repo
@pytest.fixture
def fake_checker(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
"""A stand-in check.sh that logs its argv and exits with $FAKE_RC."""
log = tmp_path / "checker.log"
script = write(
tmp_path,
"fake/check.sh",
f'#!/bin/bash\necho "$@" >> {log}\nexit "${{FAKE_RC:-0}}"\n',
0o755,
)
monkeypatch.setattr(gate, "CHECKER", script)
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
return log
def site(tmp_path: Path, body: str) -> Path:
root = tmp_path / "site"
root.mkdir()
config(root, body)
return root
def test_dist_builds_in_a_copy_then_checks(tmp_path: Path, fake_checker: Path) -> None:
root = site(
tmp_path,
'[agent_ready]\ndist = "out"\nbuild = "mkdir -p out && touch out/built"\n'
'install = "touch installed"\nact = true\n',
)
assert gate.main(["--repo", str(root)]) == 0
assert not (root / "out").exists()
assert not (root / "installed").exists()
mode, checked, act = fake_checker.read_text().split("\n")[0].split(" ")
assert (mode, Path(checked).name, act) == ("dist", "out", "--act")
assert root not in Path(checked).parents
assert gate.main(["--repo", str(root), "--install"]) == 0
assert (root / "installed").exists()
LIVE_BUILD = (
'[agent_ready]\ndist = "dist"\n'
'build = "rm -rf dist && mkdir dist && echo built > dist/index.html"\n'
)
def _live_site(root: Path) -> Path:
config(root, LIVE_BUILD)
write(root, "dist/live.html", "served by caddy\n")
write(root, "src/app.ts", "x\n")
write(root, "node_modules/dep/index.js", "module.exports = 1\n")
return root
def _assert_live_untouched(root: Path, log: Path) -> None:
assert (root / "dist" / "live.html").read_text() == "served by caddy\n"
assert not (root / "dist" / "index.html").exists()
assert "index.html" in log.read_text() # the copy WAS built and checked
@pytest.fixture
def listing_checker(fake_checker: Path) -> Path:
"""The fake checker also logs what the checked build dir contains."""
script = gate.CHECKER
script.write_text(script.read_text().replace('echo "$@"', 'echo "$@"; ls "$2"'))
return fake_checker
def test_live_dist_untouched_without_git(tmp_path: Path, listing_checker: Path) -> None:
root = _live_site(tmp_path / "site")
assert gate.main(["--repo", str(root)]) == 0
_assert_live_untouched(root, listing_checker)
def test_live_dist_untouched_in_git_repo(tmp_path: Path, listing_checker: Path) -> None:
root = _live_site(tmp_path / "site")
write(root, ".gitignore", "dist/\nnode_modules/\n")
subprocess.run(["git", "init", "-q", str(root)], check=True)
(root / "src" / "gone.ts").write_text("x\n")
subprocess.run(["git", "-C", str(root), "add", "-A"], check=True)
(root / "src" / "gone.ts").unlink() # tracked but deleted in the worktree
config(root, LIVE_BUILD.replace("echo built", "ls node_modules/dep src"))
assert gate.main(["--repo", str(root)]) == 0
_assert_live_untouched(root, listing_checker)
def test_failed_build_fails_without_checking(
tmp_path: Path, fake_checker: Path, capsys: pytest.CaptureFixture[str]
) -> None:
root = site(tmp_path, '[agent_ready]\ndist = "d"\nbuild = "echo boom; exit 7"\n')
assert gate.main(["--repo", str(root)]) == 1
assert "boom" in capsys.readouterr().err
assert not fake_checker.exists()
def test_failed_install_fails(tmp_path: Path, fake_checker: Path) -> None:
root = site(tmp_path, '[agent_ready]\ndist = "d"\ninstall = "exit 3"\n')
assert gate.main(["--repo", str(root), "--install"]) == 1
assert not fake_checker.exists()
def test_checker_failure_propagates(
tmp_path: Path, fake_checker: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
root = site(tmp_path, '[agent_ready]\ndist = "d"\n')
monkeypatch.setenv("FAKE_RC", "1")
assert gate.main(["--repo", str(root)]) == 1
def test_url_mode_degrades_offline_unless_strict(
tmp_path: Path,
fake_checker: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
root = site(tmp_path, '[agent_ready]\nurl = "https://x.example"\n')
monkeypatch.setenv("FAKE_RC", "3")
assert gate.main(["--repo", str(root)]) == 0
assert "DEGRADED" in capsys.readouterr().err
assert gate.main(["--repo", str(root), "--strict"]) == 3
assert fake_checker.read_text().splitlines()[0] == "url https://x.example"
def test_skip_and_no_site(
tmp_path: Path, fake_checker: Path, capsys: pytest.CaptureFixture[str]
) -> None:
root = site(tmp_path, '[agent_ready]\nskip = "auth-gated"\n')
assert gate.main(["--repo", str(root)]) == 0
assert "auth-gated" in capsys.readouterr().out
assert gate.main(["--repo", str(tmp_path / "fake")]) == 0
assert not fake_checker.exists()
def test_unconfigured_web_repo_fails(tmp_path: Path, fake_checker: Path) -> None:
root = vite_repo(tmp_path / "v")
assert gate.main(["--repo", str(root)]) == 1
def test_bad_config_is_usage_error(tmp_path: Path, fake_checker: Path) -> None:
root = site(tmp_path, "[agent_ready]\n")
assert gate.main(["--repo", str(root)]) == 2
def test_pre_commit_in_ci_defers_to_the_workflow(
tmp_path: Path,
fake_checker: Path,
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
root = site(tmp_path, '[agent_ready]\ndist = "d"\nbuild = "exit 1"\n')
monkeypatch.setenv("GITHUB_ACTIONS", "true")
assert gate.main(["--repo", str(root)]) == 0
assert "agent-ready workflow" in capsys.readouterr().out
assert gate.main(["--repo", str(root), "--strict"]) == 1
@pytest.mark.skipif(not gate.CHECKER.exists(), reason="agent_ready not checked out")
def test_real_checker_fails_a_bare_build(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
# In CI the gate defers to the workflow (returns 0); force the local path.
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
root = site(tmp_path, '[agent_ready]\ndist = "dist"\n')
write(root, "dist/index.html", "<!doctype html><title>x</title><p>hi</p>")
assert gate.main(["--repo", str(root)]) == 1