utils/repo_contract/tests/test_install.py
Krzysztof kuhy Rudnicki 6d96c58f01 repo_contract: idempotent installer for new repos
teatr-pw and teatr-pw-pytania (created ad hoc 2026-10-09) had no shim, hook or
'## Commands' section: the gate was only ever rolled out by hand. The
installer writes shim + pre-commit hook + CI workflow and bootstraps the
Commands section and scripts/test_changed.sh, exiting 1 while <FILL IN>
remains.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TjyHmRrjZssaxXDWcGuCeQ
2026-10-10 12:46:04 +02:00

115 lines
3.6 KiB
Python

"""The installer writes the shim, hook, workflow and Commands section once."""
from __future__ import annotations
import stat
from pathlib import Path
import pytest
from repo_contract import install as inst
from repo_contract.check import violations
from .conftest import good_repo, write
def python_repo(root: Path) -> Path:
"""A python repo with tests and no contract pieces at all."""
write(root, "pyproject.toml", "[project]\nname='x'\n")
write(root, "tests/test_a.py", "def test_a():\n pass\n")
return root
def test_plan_lists_everything_for_a_bare_repo(repo: Path) -> None:
assert inst.plan(repo) == [
"scripts/check_repo_contract.sh",
".github/workflows/repo-contract.yml",
".pre-commit-config.yaml (repo-contract hook)",
]
def test_install_writes_pieces_and_is_idempotent(repo: Path) -> None:
python_repo(repo)
done = inst.install(repo)
assert len(done) == 3
shim = repo / inst.SHIM
assert shim.stat().st_mode & stat.S_IXUSR
assert "id: repo-contract" in (repo / inst.PRECOMMIT).read_text(encoding="utf-8")
assert inst.install(repo) == []
assert inst.plan(repo) == []
def test_marker_skips_workflow(repo: Path) -> None:
write(repo, ".dep-freshness-no-workflow")
inst.install(repo)
assert not (repo / inst.WORKFLOW).exists()
def test_utils_itself_is_not_given_a_shim(repo: Path) -> None:
write(repo, "repo_contract/check.py")
assert inst.install(repo) != []
assert not (repo / inst.SHIM).exists()
def test_hook_lands_in_the_last_local_block(repo: Path) -> None:
write(
repo,
inst.PRECOMMIT.as_posix(),
"repos:\n - repo: local\n hooks:\n - id: a\n"
" - repo: https://example.com/x\n hooks:\n - id: b",
)
inst.install(repo)
lines = (repo / inst.PRECOMMIT).read_text(encoding="utf-8").splitlines()
assert lines.index(" - id: repo-contract") < lines.index(
" - repo: https://example.com/x"
)
def test_hook_needs_a_local_block(repo: Path) -> None:
write(repo, inst.PRECOMMIT.as_posix(), "repos:\n - repo: https://example.com/x\n")
with pytest.raises(ValueError, match="no ` - repo: local` block"):
inst.install(repo)
def test_bootstrap_commands_creates_section_and_script(repo: Path) -> None:
python_repo(repo)
actions = inst.bootstrap_commands(repo)
assert any("test_changed.sh" in a for a in actions)
assert "## Commands" in (repo / "CLAUDE.md").read_text(encoding="utf-8")
assert inst.bootstrap_commands(repo) == []
def test_main_check_changes_nothing(
repo: Path, capsys: pytest.CaptureFixture[str]
) -> None:
assert inst.main([str(repo), "--check"]) == 1
out = capsys.readouterr().out
assert "would write: scripts/check_repo_contract.sh" in out
assert not (repo / inst.SHIM).exists()
def test_main_installs_and_passes(
repo: Path, capsys: pytest.CaptureFixture[str]
) -> None:
good_repo(repo)
assert inst.main([str(repo)]) == 0
assert "wrote: scripts/check_repo_contract.sh" in capsys.readouterr().out
assert violations(repo)[0] == []
def test_main_no_bootstrap_leaves_violation(repo: Path) -> None:
assert inst.main([str(repo), "--no-bootstrap"]) == 1
assert not (repo / "CLAUDE.md").exists()
def test_main_rejects_non_repo(tmp_path: Path) -> None:
assert inst.main([str(tmp_path)]) == 2
def test_main_reports_bootstrap_actions(
repo: Path, capsys: pytest.CaptureFixture[str]
) -> None:
python_repo(repo)
inst.main([str(repo)])
assert "bootstrap: created scripts/test_changed.sh" in capsys.readouterr().out