utils/repo_contract/tests/test_hook_sync.py
Krzysztof kuhy Rudnicki d1ca1e3d5b
Some checks are pending
dependency freshness / dependencies on newest stable (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
repo_contract: launcher gate (icon ink radius, compliant label, hidden debug variants) + hook_sync
Flutter repos now fail the repo contract when a launcher icon's ink is not on
the shared 340px circle (png_ink), the launcher label breaks the naming rule
(capital, ASCII letters, <= 10 chars, <= 522 wide in GoogleSansFlex), or an
applicationIdSuffix variant still carries the LAUNCHER category. hook_sync
rewrites a repo's stale repo-contract `files:` line to the widened template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017k9j5dbjenpfZj7dh2faPN
2026-10-23 14:54:15 +02:00

119 lines
3.6 KiB
Python

"""The widened repo-contract hook `files:` and the sync that rolls it out."""
from __future__ import annotations
import re
from pathlib import Path
import pytest
import yaml
from repo_contract import hook_sync
from repo_contract.bootstrap import TEMPLATES
from .conftest import write
OLD = r" files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$"
CONFIG = """repos:
- repo: local
hooks:
- id: repo-contract
name: repo contract
entry: scripts/check_repo_contract.sh
language: system
pass_filenames: false
{files}
- id: other
entry: x
"""
def template_regex() -> re.Pattern[str]:
hooks = yaml.safe_load((TEMPLATES / "pre-commit-snippet.yaml").read_text())
return re.compile(hooks[0]["files"])
@pytest.mark.parametrize(
"path",
[
"CLAUDE.md",
"AGENTS.md",
"scripts/test_changed.sh",
"android/app/src/main/AndroidManifest.xml",
"app/android/app/src/sandbox/AndroidManifest.xml",
"android/app/build.gradle",
"android/app/build.gradle.kts",
"android/app/src/main/res/values/strings.xml",
"linux/todo.desktop",
"todo.desktop",
"assets/icon/icon_foreground.png",
"app/assets/icon/icon.png",
"repo-contract.toml",
"focus-owner/repo-contract.toml",
"pubspec.yaml",
],
)
def test_template_fires_on_launcher_files(path: str) -> None:
assert template_regex().search(path)
@pytest.mark.parametrize(
"path",
["lib/main.dart", "docs/CLAUDE.md", "assets/icon/sub/x.png", "x.desktop.bak"],
)
def test_template_ignores_other_files(path: str) -> None:
assert not template_regex().search(path)
def config(repo: Path, files: str = OLD) -> Path:
return write(repo, ".pre-commit-config.yaml", CONFIG.format(files=files))
def test_status(tmp_path: Path) -> None:
assert hook_sync.status(tmp_path) == "no hook"
config(tmp_path)
assert hook_sync.status(tmp_path) == "stale"
config(tmp_path, hook_sync.template_line())
assert hook_sync.status(tmp_path) == "current"
def test_dry_run_changes_nothing(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
before = config(tmp_path).read_text()
assert hook_sync.main([str(tmp_path)]) == 1
assert (tmp_path / ".pre-commit-config.yaml").read_text() == before
out = capsys.readouterr().out
assert f"would update: {tmp_path}" in out
assert "1 stale, 0 updated, 0 current, 0 without the hook" in out
def test_apply_rewrites_only_the_files_line(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
path = config(tmp_path)
assert hook_sync.main(["--apply", str(tmp_path)]) == 0
assert path.read_text() == CONFIG.format(files=hook_sync.template_line())
assert f"updated: {tmp_path}" in capsys.readouterr().out
assert hook_sync.main(["--apply", str(tmp_path)]) == 0
assert "0 stale, 0 updated, 1 current" in capsys.readouterr().out
def test_hook_without_files_line_gets_one(tmp_path: Path) -> None:
config(tmp_path, " always_run: false")
assert hook_sync.status(tmp_path) == "stale"
hook_sync.main(["--apply", str(tmp_path)])
assert hook_sync.status(tmp_path) == "current"
def test_default_roots_are_the_sweep_roots(
tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
(tmp_path / "a" / ".git").mkdir(parents=True)
(tmp_path / "b" / ".git").mkdir(parents=True)
config(tmp_path / "a", hook_sync.template_line())
assert hook_sync.main([], roots=[tmp_path]) == 0
out = capsys.readouterr().out
assert f"current: {tmp_path / 'a'}" in out
assert f"no hook: {tmp_path / 'b'}" in out