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

349 lines
13 KiB
Python

"""Tests for GitHub review normalization used by the PR approval agent."""
import re
import pytest
import github
from github import (
CommitProvenance,
_normalize_discussion_for_prompt,
_normalize_reviews_for_prompt,
_reaction_emoji,
_trusted_reactor_predicate,
is_bot_author,
parse_provenance_trailers,
)
def test_normalize_reviews_marks_current_head_and_preserves_stale_reviews() -> None:
head_sha = "072cdd75592bfd0bf0c016209385f20f85a45201"
current_review = {
"user": {"login": "copilot-pull-request-reviewer", "type": "Bot"},
"state": "COMMENTED",
"body": "Current head concern",
"commit_id": head_sha,
"submitted_at": "2026-04-07T20:14:03Z",
"author_association": "BOT",
}
stale_review = {
"user": {"login": "greptile-apps", "type": "Bot"},
"state": "COMMENTED",
"body": "Older concern",
"commit_id": "3c51bb8de4c73929c5266986118a14b966cb6831",
"submitted_at": "2026-04-07T20:02:32Z",
"author_association": "BOT",
}
normalized = _normalize_reviews_for_prompt([current_review, stale_review], head_sha)
assert normalized == [
{
"user": "copilot-pull-request-reviewer",
"state": "COMMENTED",
"body": "Current head concern",
"commit_id": head_sha,
"is_current_head": True,
"submitted_at": "2026-04-07T20:14:03Z",
},
{
"user": "greptile-apps",
"state": "COMMENTED",
"body": "Older concern",
"commit_id": "3c51bb8de4c73929c5266986118a14b966cb6831",
"is_current_head": False,
"submitted_at": "2026-04-07T20:02:32Z",
},
]
@pytest.mark.parametrize(
"author_association,user_type,expected_count",
[
pytest.param("MEMBER", "User", 1, id="member-reviewer"),
pytest.param("OWNER", "User", 1, id="owner-reviewer"),
pytest.param("COLLABORATOR", "User", 1, id="collaborator-reviewer"),
pytest.param("BOT", "User", 1, id="bot-association"),
pytest.param("NONE", "Bot", 1, id="bot-user-type"),
pytest.param("NONE", "User", 0, id="untrusted-reviewer"),
],
)
def test_normalize_reviews_filters_by_trust_source(
author_association: str, user_type: str, expected_count: int
) -> None:
normalized = _normalize_reviews_for_prompt(
[
{
"user": {"login": "reviewer", "type": user_type},
"state": "COMMENTED",
"body": "Review body",
"commit_id": "abc123",
"submitted_at": "2026-04-07T20:14:03Z",
"author_association": author_association,
}
],
"abc123",
)
assert len(normalized) == expected_count
@pytest.mark.parametrize(
"login,expected_count",
[
pytest.param("stamp[bot]", 0, id="own-refuse-comment-review"),
pytest.param("github-actions[bot]", 0, id="own-approve-review"),
pytest.param("greptile-apps[bot]", 1, id="other-bot-kept"),
],
)
def test_normalize_reviews_excludes_stamps_own_prior_reviews(login: str, expected_count: int) -> None:
# Feeding stamp's own stale reviews back into the prompt makes the next
# run read them as third-party claims about state that no longer matches —
# it then suspects tampering and refuses forever.
normalized = _normalize_reviews_for_prompt(
[
{
"user": {"login": login, "type": "Bot"},
"state": "COMMENTED",
"body": "Refusing: reviews in flight",
"commit_id": "abc123",
"submitted_at": "2026-04-07T20:14:03Z",
"author_association": "NONE",
}
],
"abc123",
)
assert len(normalized) == expected_count
@pytest.mark.parametrize(
"login,user_type,author_association,expected_count",
[
pytest.param("stamp[bot]", "Bot", "NONE", 0, id="own-refuse-comment-excluded"),
pytest.param("github-actions[bot]", "Bot", "NONE", 0, id="own-approve-identity-excluded"),
pytest.param("greptile-apps[bot]", "Bot", "NONE", 1, id="other-bot-kept"),
pytest.param("alice", "User", "MEMBER", 1, id="trusted-member-kept"),
pytest.param("owner", "User", "OWNER", 1, id="trusted-owner-kept"),
# A drive-by external commenter must not reach the prompt: the
# maintainer-hold norm makes an untrusted "please hold" both griefable
# and forgeable, so discussion gets the same trust gate as reviews.
pytest.param("outsider", "User", "NONE", 0, id="untrusted-external-excluded"),
],
)
def test_normalize_discussion_filters_by_trust_and_own_comments(
login: str, user_type: str, author_association: str, expected_count: int
) -> None:
normalized = _normalize_discussion_for_prompt(
[
{
"user": {"login": login, "type": user_type},
"author_association": author_association,
"body": "a comment",
"created_at": "2026-04-07T20:14:03Z",
}
]
)
assert len(normalized) == expected_count
@pytest.mark.parametrize(
"content,expected",
[
pytest.param("+1", "👍", id="rest-thumbs-up"),
pytest.param("THUMBS_UP", "👍", id="graphql-thumbs-up"),
pytest.param("-1", "👎", id="rest-thumbs-down"),
pytest.param("EYES", "👀", id="graphql-eyes"),
pytest.param("sparkle", "sparkle", id="unknown-passthrough"),
],
)
def test_reaction_emoji_normalizes_rest_and_graphql(content: str, expected: str) -> None:
assert _reaction_emoji(content) == expected
@pytest.mark.parametrize(
"login,expected",
[
pytest.param("prauthor", False, id="author-self-reaction"),
pytest.param("ghost", False, id="deleted-user"),
pytest.param("", False, id="empty-login"),
pytest.param("greptile-apps[bot]", True, id="allowlisted-bot"),
pytest.param("inkeep[bot]", False, id="unlisted-bot"),
pytest.param("teammate", True, id="org-member"),
pytest.param("outsider", False, id="external-non-member"),
],
)
def test_trusted_reactor_predicate_gates_untrusted_and_author(
monkeypatch: pytest.MonkeyPatch, login: str, expected: bool
) -> None:
monkeypatch.setattr(github, "_is_org_member", lambda org, member: member == "teammate")
is_trusted = _trusted_reactor_predicate("Insights/insights", author="prauthor")
assert is_trusted(login) is expected
@pytest.mark.parametrize(
"user,expected",
[
pytest.param({"login": "mendral-app[bot]", "type": "Bot"}, True, id="github-app-type"),
pytest.param({"login": "dependabot[bot]", "type": "Bot"}, True, id="dependabot"),
pytest.param({"login": "some-tool[bot]", "type": "User"}, True, id="bot-suffix-misreported-type"),
pytest.param({"login": "insights-bot", "type": "User"}, True, id="machine-user"),
pytest.param({"login": "INSIGHTS-BOT", "type": "User"}, True, id="machine-user-case-insensitive"),
pytest.param({"login": "alice", "type": "User"}, False, id="human"),
pytest.param({}, False, id="missing-user"),
],
)
def test_is_bot_author(user: dict, expected: bool) -> None:
assert is_bot_author(user) is expected
@pytest.mark.parametrize(
"log_output,expected",
[
pytest.param(
"fix: resolve login redirect loop\n\n"
"Generated-By: Insights Desktop\n"
"Task-Id: a95947d1-6e11-4565-9922-1f857cc6f6fe\n\x1e\n",
CommitProvenance(
commit_count=1,
agent_commit_count=1,
generated_by=("Insights Desktop",),
task_ids=("a95947d1-6e11-4565-9922-1f857cc6f6fe",),
),
id="single-agent-commit",
),
pytest.param(
"fix: handle empty cohort\n\x1e\nchore: bump deps\n\x1e\n",
CommitProvenance(commit_count=2, agent_commit_count=0, generated_by=(), task_ids=()),
id="human-only-commits",
),
pytest.param(
"feat: add export\n\nGenerated-By: Insights Desktop\nTask-Id: task-1\n\x1e\n"
"fix: review feedback\n\x1e\n"
"fix: more feedback\n\nGenerated-By: Insights Desktop\nTask-Id: task-2\n\x1e\n",
CommitProvenance(
commit_count=3,
agent_commit_count=2,
generated_by=("Insights Desktop",),
task_ids=("task-1", "task-2"),
),
id="mixed-commits-dedupe-generator-collect-task-ids",
),
pytest.param(
"feat(insights): retention export (#71452)\n\n"
"* feat: first pass\n\n"
"Generated-By: Insights Desktop\n"
"Task-Id: task-1\n\n"
"* fix: address review\n\x1e\n",
CommitProvenance(
commit_count=1, agent_commit_count=1, generated_by=("Insights Desktop",), task_ids=("task-1",)
),
id="squash-style-message-with-embedded-trailers",
),
pytest.param(
"chore: retry task\n\ngenerated-by: insights code\ntask-id: task-9\n\x1e\n",
CommitProvenance(
commit_count=1, agent_commit_count=1, generated_by=("insights code",), task_ids=("task-9",)
),
id="case-insensitive-trailers",
),
pytest.param(
"",
CommitProvenance(commit_count=0, agent_commit_count=0, generated_by=(), task_ids=()),
id="empty-log-output",
),
],
)
def test_parse_provenance_trailers(log_output: str, expected: CommitProvenance) -> None:
# The provenance dimension (agent-authored % of PRs) is derived entirely
# from these trailers — a parsing regression zeroes it silently rather
# than failing anything, so the shapes git actually emits are locked here.
assert parse_provenance_trailers(log_output) == expected
def _worst_case_node_count(query: str) -> int:
# Mirrors GitHub's pre-execution node-limit check: each connection requests
# `first` nodes multiplied by the `first` of every ancestor connection.
total = 0
multipliers = [1]
pending = 1
for match in re.finditer(r"first:\s*(\d+)|[{}]", query):
if match.group(1):
pending = int(match.group(1))
total += multipliers[-1] * pending
elif match.group(0) == "{":
multipliers.append(multipliers[-1] * pending)
pending = 1
else:
multipliers.pop()
return total
def test_review_threads_query_stays_under_github_node_limit() -> None:
# GitHub rejects any GraphQL query whose worst-case node count exceeds
# 500,000 before executing it, which hard-fails the review on every PR.
assert _worst_case_node_count(github._REVIEW_THREADS_QUERY) < 500_000
@pytest.mark.parametrize(
"login,expected_users",
[
pytest.param("stamp[bot]", ["greptile-apps[bot]"], id="own-inline-comment-excluded"),
pytest.param("github-actions[bot]", ["greptile-apps[bot]"], id="own-approve-identity-excluded"),
pytest.param(
"copilot-pull-request-reviewer[bot]",
["copilot-pull-request-reviewer[bot]", "greptile-apps[bot]"],
id="other-bot-kept",
),
],
)
def test_fetch_threads_excludes_stamps_own_inline_comments(
monkeypatch: pytest.MonkeyPatch, login: str, expected_users: list[str]
) -> None:
# Stamp's earlier verdicts fed back through inline comments read as
# third-party claims about a stale snapshot — later runs then suspect
# impersonation and refuse forever, exactly like stale top-level reviews.
def fake_graphql(query: str, variables: dict | None = None) -> dict:
def comment(user: str) -> dict:
return {
"author": {"login": user, "__typename": "Bot"},
"authorAssociation": "NONE",
"body": f"comment from {user}",
"databaseId": 1,
"replyTo": None,
"reactions": {"nodes": []},
}
return {
"data": {
"repository": {
"pullRequest": {
"reactions": {"nodes": []},
"reviewThreads": {
"pageInfo": {"hasNextPage": False, "endCursor": None},
"nodes": [
{
"isResolved": False,
"isOutdated": False,
"path": "insights/api/insight.py",
"line": 42,
"comments": {
"pageInfo": {"hasNextPage": False},
"nodes": [comment(login), comment("greptile-apps[bot]")],
},
}
],
},
}
}
}
}
monkeypatch.setattr(github, "_gh_graphql", fake_graphql)
monkeypatch.setattr(github, "_is_org_member", lambda org, member: False)
comments, _ = github._fetch_threads_and_reactions("Insights/insights", 1, author="alice")
assert [c["user"] for c in comments] == expected_users