Files
zeekayandHanzo Dev fad96e267f no hogs: the root config, the tooling, and the receiver baseline
.stamphog/ was a config directory at the repo root — policy.yml,
review-guidance.md — read by the PR review agent. It is .stamp/ now, and the 34
files that name it follow: the workflows, tools/pr-approval-agent, tach.toml,
the sandbox Dockerfile, bin/debrand.

One behaviour change worth naming rather than burying: the agent finds its own
previous PR comment by an HTML marker, `<!-- stamphog:v1 ... -->`, and that
marker is now `<!-- stamp:v1 ... -->`. Open PRs will get one new comment instead
of an edit, once, and then match again.

insights/test/setup_receivers_baseline.txt records registered signal receivers
by module path, so it names products.review.backend now.

stamphog: zero occurrences anywhere in the tree. review_hog: only inside
migration files, whose names are their identity in django_migrations.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-09 10:21:41 -07:00

476 lines
17 KiB
Python

"""Tests for the Stamp dismiss-decision logic."""
import os
import subprocess
from pathlib import Path
import pytest
import dismiss_check
from dismiss_check import Decision, decide, evaluate_delta, select_last_bot_approval
from gates import is_trivial_at_dismiss_time
_GIT_ENV = {
"GIT_AUTHOR_NAME": "test",
"GIT_AUTHOR_EMAIL": "t@t",
"GIT_COMMITTER_NAME": "test",
"GIT_COMMITTER_EMAIL": "t@t",
}
def _git(*args: str, cwd: Path, check: bool = True) -> subprocess.CompletedProcess[str]:
env = {**os.environ, **_GIT_ENV}
return subprocess.run(
["git", *args],
cwd=cwd,
env=env,
capture_output=True,
text=True,
timeout=30,
check=check,
)
def _write(repo: Path, path: str, content: str = "x") -> None:
p = repo / path
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content)
def _commit(repo: Path, message: str) -> str:
_git("add", "-A", cwd=repo)
_git("commit", "-m", message, cwd=repo)
return _git("rev-parse", "HEAD", cwd=repo).stdout.strip()
def _head(repo: Path) -> str:
return _git("rev-parse", "HEAD", cwd=repo).stdout.strip()
def _assert_decision(d: Decision, *, dismiss: bool, review: bool, reason: str) -> None:
assert d.dismiss_approval is dismiss
assert d.run_review is review
assert d.reason == reason
@pytest.fixture
def repo(tmp_path: Path) -> Path:
"""Empty repo with one initial commit on master."""
_git("init", "--initial-branch=master", cwd=tmp_path)
_write(tmp_path, "README.md", "init")
_commit(tmp_path, "init")
return tmp_path
# ── is_trivial_at_dismiss_time unit tests ────────────────────────
@pytest.mark.parametrize(
"path,expected",
[
# Tests
("frontend/src/foo.test.ts", True),
("frontend/src/foo.test.tsx", True),
("frontend/src/foo.spec.ts", True),
("insights/test_views.py", True),
("insights/api/test/foo.py", True),
("tests/test_foo.py", True),
("foo/__tests__/bar.ts", True),
("conftest.py", True),
("insights/conftest.py", True),
("internal_test.go", True),
("insights/fixtures/foo.json", True),
# Snapshots — only the canonical __snapshots__/ directory; bare
# .snap suffix anywhere is no longer trusted.
("foo/__snapshots__/bar.snap", True),
("__snapshots__/bar.snap", True),
("scripts/deploy.snap", False),
("foo.snap", False),
# Docs
("README.md", True),
("CHANGELOG.md", True),
("docs/foo.md", True),
("foo.mdx", True),
# Lockfiles (matched case-insensitively)
("package-lock.json", True),
("pnpm-lock.yaml", True),
("frontend/yarn.lock", True),
("uv.lock", True),
("Cargo.lock", True),
("CARGO.LOCK", True),
("Gemfile.lock", True),
("Pipfile.LOCK", True),
# Generated — frontend TS / JS / JSON / md / snap / type-stubs only.
# Backend executable code (.py, .go) under generated/ is NOT trivial
# because at dismiss time the path is the only signal.
("frontend/src/generated/core/api.ts", True),
("products/foo/frontend/generated/api.ts", True),
("nodejs/src/generated/foo.ts", True),
("services/mcp/src/generated/foo.ts", True),
("frontend/src/queries/schema/general.ts", True),
("foo.gen.ts", True),
("foo.generated.ts", True),
("services/mcp/src/ui-apps/generated/foo.txt", True),
# Backend code under generated/ — must dismiss
("insights/generated/evil.py", False),
("insights/personinsights_client/proto/generated/foo.py", False),
("services/foo/generated/handler.go", False),
("foo.gen.py", False),
("foo.generated.py", False),
("foo.gen.go", False),
("foo.generated.go", False),
# NOT allowed
(".github/workflows/foo.yml", False),
(".github/CODEOWNERS", False),
("Dockerfile", False),
("scripts/deploy.sh", False),
("Makefile", False),
("pyproject.toml", False),
("tsconfig.json", False),
("frontend/src/foo.ts", False),
("insights/api/foo.py", False),
("requirements.txt", False),
("package.json", False),
],
)
def test_is_trivial_at_dismiss_time(path: str, expected: bool) -> None:
assert is_trivial_at_dismiss_time(path) is expected
# ── _is_ancestor unit tests ──────────────────────────────────────
def test_is_ancestor_returns_true_when_ancestor(repo: Path) -> None:
base = _head(repo)
_write(repo, "a.ts", "a")
head = _commit(repo, "feat a")
assert dismiss_check._is_ancestor(base, head, repo) is True
def test_is_ancestor_returns_false_when_not_ancestor(repo: Path) -> None:
_write(repo, "a.ts", "a")
a = _commit(repo, "feat a")
_git("reset", "--hard", "HEAD~1", cwd=repo)
_write(repo, "b.ts", "b")
b = _commit(repo, "feat b")
assert dismiss_check._is_ancestor(a, b, repo) is False
def test_is_ancestor_returns_false_on_git_error(repo: Path, capsys: pytest.CaptureFixture[str]) -> None:
bogus = "0" * 40
assert dismiss_check._is_ancestor(bogus, _head(repo), repo) is False
assert "_is_ancestor git error" in capsys.readouterr().err
# ── evaluate_delta scenarios ─────────────────────────────────────
def test_test_only_delta_retains(repo: Path) -> None:
base = _head(repo)
_write(repo, "tests/test_foo.py", "def test_foo(): pass")
_commit(repo, "test only")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=False, review=False, reason="trivial_paths")
def test_lockfile_only_delta_retains(repo: Path) -> None:
base = _head(repo)
_write(repo, "package-lock.json", "{}")
_commit(repo, "lock")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=False, review=False, reason="trivial_paths")
def test_workflow_change_dismisses(repo: Path) -> None:
base = _head(repo)
_write(repo, ".github/workflows/foo.yml", "name: x")
_commit(repo, "workflow")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=True, review=True, reason="non_trivial_delta")
def test_prod_file_dismisses(repo: Path) -> None:
base = _head(repo)
_write(repo, "frontend/src/foo.ts", "export const x = 1")
_commit(repo, "prod")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=True, review=True, reason="non_trivial_delta")
def test_generated_file_only_retains(repo: Path) -> None:
base = _head(repo)
_write(repo, "frontend/src/generated/core/api.ts", "export const x = 1")
_commit(repo, "regen")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=False, review=False, reason="trivial_paths")
def test_mixed_test_plus_prod_dismisses(repo: Path) -> None:
base = _head(repo)
_write(repo, "frontend/src/foo.test.ts", "test('x', () => {})")
_write(repo, "frontend/src/foo.ts", "export const x = 1")
_commit(repo, "mixed")
_assert_decision(evaluate_delta(base, _head(repo), repo), dismiss=True, review=True, reason="non_trivial_delta")
def test_clean_merge_from_master_retains(repo: Path) -> None:
# Branch off and commit on feat
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "feat.ts", "feat")
feat_sha = _commit(repo, "feat")
# Advance master with a non-overlapping change
_git("checkout", "master", cwd=repo)
_write(repo, "main.ts", "main")
_commit(repo, "main change")
# Merge master into feat (no conflicts)
_git("checkout", "feat", cwd=repo)
_git("merge", "--no-ff", "master", "-m", "merge master", cwd=repo)
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo, base_ref="master"),
dismiss=False,
review=False,
reason="merge_only",
)
def test_merge_with_conflict_resolution_dismisses(repo: Path) -> None:
# Both branches modify the same file → manual resolution
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "shared.ts", "feat version")
feat_sha = _commit(repo, "feat shared")
_git("checkout", "master", cwd=repo)
_write(repo, "shared.ts", "main version")
_commit(repo, "main shared")
_git("checkout", "feat", cwd=repo)
# Merge will conflict; resolve manually
_git("merge", "--no-ff", "--no-commit", "master", cwd=repo, check=False)
_write(repo, "shared.ts", "manually resolved")
_git("add", "shared.ts", cwd=repo)
_git("commit", "--no-edit", cwd=repo)
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo, base_ref="master"),
dismiss=True,
review=True,
reason="non_trivial_delta",
)
def test_clean_merge_plus_test_commit_retains(repo: Path) -> None:
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "feat.ts", "feat")
feat_sha = _commit(repo, "feat")
_git("checkout", "master", cwd=repo)
_write(repo, "main.ts", "main")
_commit(repo, "main change")
_git("checkout", "feat", cwd=repo)
_git("merge", "--no-ff", "master", "-m", "merge master", cwd=repo)
# Then add a test
_write(repo, "tests/test_feat.py", "def test_feat(): pass")
_commit(repo, "tests")
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo, base_ref="master"),
dismiss=False,
review=False,
reason="mixed_trivial",
)
def test_clean_merge_plus_prod_commit_dismisses(repo: Path) -> None:
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "feat.ts", "feat")
feat_sha = _commit(repo, "feat")
_git("checkout", "master", cwd=repo)
_write(repo, "main.ts", "main")
_commit(repo, "main change")
_git("checkout", "feat", cwd=repo)
_git("merge", "--no-ff", "master", "-m", "merge master", cwd=repo)
_write(repo, "feat.ts", "feat updated")
_commit(repo, "prod tweak")
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo, base_ref="master"),
dismiss=True,
review=True,
reason="non_trivial_delta",
)
def test_non_linear_history_dismisses(repo: Path) -> None:
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "a.ts", "a")
feat_sha = _commit(repo, "feat a")
# Simulate force-push: reset and create a divergent commit
_git("reset", "--hard", "HEAD~1", cwd=repo)
_write(repo, "a.ts", "different a")
_commit(repo, "rebased a")
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo), dismiss=True, review=True, reason="non_linear_history"
)
def test_empty_delta_retains(repo: Path) -> None:
head = _head(repo)
_assert_decision(evaluate_delta(head, head, repo), dismiss=False, review=False, reason="empty_delta")
# ── edge cases ───────────────────────────────────────────────────
def test_empty_commit_retains(repo: Path) -> None:
base = _head(repo)
_git("commit", "--allow-empty", "-m", "empty", cwd=repo)
_assert_decision(
evaluate_delta(base, _head(repo), repo, base_ref="master"),
dismiss=False,
review=False,
reason="trivial_paths",
)
def test_merge_from_non_base_branch_dismisses(repo: Path) -> None:
# PR branch
_git("checkout", "-b", "feat", cwd=repo)
_write(repo, "feat.ts", "feat")
feat_sha = _commit(repo, "feat")
# Side branch off master with a prod commit, never landed on master
_git("checkout", "-b", "side", "master", cwd=repo)
_write(repo, "side.ts", "side")
_commit(repo, "side prod change")
# Merge side into feat (clean merge, but side isn't in master history)
_git("checkout", "feat", cwd=repo)
_git("merge", "--no-ff", "side", "-m", "merge side", cwd=repo)
_assert_decision(
evaluate_delta(feat_sha, _head(repo), repo, base_ref="master"),
dismiss=True,
review=True,
reason="non_trivial_delta",
)
# ── decide() with mocked GitHub API ──────────────────────────────
def test_decide_no_prior_approval(monkeypatch: pytest.MonkeyPatch, repo: Path) -> None:
# No prior bot approval, but the label is still on (decide-delta only runs
# when it is) and the agent hasn't approved — re-run the review on this
# push so the first-run ERROR case (LLM backend down, label retained)
# actually retries instead of staying labeled but stuck.
monkeypatch.setattr(dismiss_check, "find_last_approved_sha", lambda *_: None)
result = decide("Insights/insights", 1, _head(repo), repo)
_assert_decision(result, dismiss=False, review=True, reason="no_prior_approval")
assert result.last_approved_sha is None
def test_decide_returns_last_approved_sha(monkeypatch: pytest.MonkeyPatch, repo: Path) -> None:
base = _head(repo)
_write(repo, "tests/test_foo.py", "def test_foo(): pass")
_commit(repo, "test")
monkeypatch.setattr(dismiss_check, "find_last_approved_sha", lambda *_: base)
result = decide("Insights/insights", 1, _head(repo), repo)
_assert_decision(result, dismiss=False, review=False, reason="trivial_paths")
assert result.last_approved_sha == base
def test_decide_dismiss_path_includes_last_approved_sha(monkeypatch: pytest.MonkeyPatch, repo: Path) -> None:
base = _head(repo)
_write(repo, "frontend/src/foo.ts", "export const x = 1")
_commit(repo, "prod")
monkeypatch.setattr(dismiss_check, "find_last_approved_sha", lambda *_: base)
result = decide("Insights/insights", 1, _head(repo), repo)
_assert_decision(result, dismiss=True, review=True, reason="non_trivial_delta")
assert result.last_approved_sha == base
# ── select_last_bot_approval (filter + sort) ─────────────────────
def _review(login: str, state: str, commit_id: str, submitted_at: str) -> dict:
return {
"user": {"login": login},
"state": state,
"commit_id": commit_id,
"submitted_at": submitted_at,
}
def test_select_last_bot_approval_empty_list() -> None:
assert select_last_bot_approval([]) is None
def test_select_last_bot_approval_no_bot_reviews() -> None:
reviews = [_review("alice", "APPROVED", "sha1", "2026-01-01T00:00:00Z")]
assert select_last_bot_approval(reviews) is None
def test_select_last_bot_approval_no_approved_state() -> None:
reviews = [
_review("github-actions[bot]", "COMMENTED", "sha1", "2026-01-01T00:00:00Z"),
_review("github-actions[bot]", "CHANGES_REQUESTED", "sha2", "2026-01-02T00:00:00Z"),
]
assert select_last_bot_approval(reviews) is None
def test_select_last_bot_approval_picks_latest() -> None:
reviews = [
_review("github-actions[bot]", "APPROVED", "sha-old", "2026-01-01T00:00:00Z"),
_review("github-actions[bot]", "APPROVED", "sha-new", "2026-02-01T00:00:00Z"),
_review("github-actions[bot]", "APPROVED", "sha-mid", "2026-01-15T00:00:00Z"),
]
assert select_last_bot_approval(reviews) == "sha-new"
def test_select_last_bot_approval_ignores_human_approvals() -> None:
reviews = [
_review("github-actions[bot]", "APPROVED", "sha-bot", "2026-01-01T00:00:00Z"),
_review("alice", "APPROVED", "sha-human", "2026-02-01T00:00:00Z"),
]
assert select_last_bot_approval(reviews) == "sha-bot"
def test_select_last_bot_approval_ignores_bot_non_approval_states() -> None:
reviews = [
_review("github-actions[bot]", "APPROVED", "sha-approved", "2026-01-01T00:00:00Z"),
_review("github-actions[bot]", "CHANGES_REQUESTED", "sha-changes", "2026-02-01T00:00:00Z"),
]
assert select_last_bot_approval(reviews) == "sha-approved"
def test_select_last_bot_approval_recognizes_app_identity() -> None:
# The Stamp app posts the body-carrying approval as stamp[bot]; it
# must count as a prior bot approval so dismiss-on-push can find its SHA.
reviews = [_review("stamp[bot]", "APPROVED", "sha-app", "2026-01-01T00:00:00Z")]
assert select_last_bot_approval(reviews) == "sha-app"
# ── main() error path ────────────────────────────────────────────
def test_main_missing_env_emits_dismiss(monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]) -> None:
# REPO is the first env var read in main(); leaving it unset triggers KeyError.
for key in ("REPO", "PR_NUMBER", "HEAD_SHA"):
monkeypatch.delenv(key, raising=False)
dismiss_check.main()
out = capsys.readouterr().out.strip()
import json as _json
decision = _json.loads(out)
assert decision["dismiss_approval"] is True
assert decision["run_review"] is True
assert decision["reason"].startswith("error:")
assert decision["last_approved_sha"] is None