From 5aefd39b97e3683d1f8ef74dcbdd24603e957327 Mon Sep 17 00:00:00 2001 From: TJCurnutte <211682330+TJCurnutte@users.noreply.github.com> Date: Wed, 13 May 2026 19:24:35 -0400 Subject: [PATCH] test: add CodeBounty issue 100 fixture --- .../issue-100-duplicate-announcement.md | 32 +++++ scripts/validate-codebounty-issue-100.py | 131 ++++++++++++++++++ ...unty-issue-100-duplicate-announcement.json | 74 ++++++++++ 3 files changed, 237 insertions(+) create mode 100644 bounty-notes/issue-100-duplicate-announcement.md create mode 100644 scripts/validate-codebounty-issue-100.py create mode 100644 test-fixtures/codebounty-issue-100-duplicate-announcement.json diff --git a/bounty-notes/issue-100-duplicate-announcement.md b/bounty-notes/issue-100-duplicate-announcement.md new file mode 100644 index 0000000..cb50b2d --- /dev/null +++ b/bounty-notes/issue-100-duplicate-announcement.md @@ -0,0 +1,32 @@ +# CodeBounty issue #100 duplicate-announcement fixture + +Issue: https://github.com/CodeBountyOrg/BountyTestRepository/issues/100 + +## Snapshot + +- State: open +- Locked: false +- Assignees: none +- Labels: `bug`, `💰 Bounty Available` +- Visible CodeBounty amount: `$50 USD` +- Amount source: two CodeBounty bot announcement comments +- Required PR syntax from bot announcement: `fixes #100` +- Duplicate announcement evidence: two distinct local bounty links were posted for the same issue: + - `http://localhost:3000/bounty/6800af954e2040a18be603c7` + - `http://localhost:3000/bounty/6800af954e2040a18be603cc` +- Collision searches run immediately before this branch: `100`, `fixes #100`, `issue 100`, and `apr 17 235`. +- Exact same-scope open PR count: `0`. Three GitHub search hits were false positives from `$100` / issue `#103` contexts, not issue #100 linkage. + +## Payout boundary + +This is a submitted-visible CodeBounty action only. The bot announcement says a developer must submit an application through CodeBounty to be eligible, so this PR must not be treated as verified-payable or paid until platform/maintainer acceptance is confirmed. + +## Local validation + +```bash +python3 -m py_compile scripts/validate-codebounty-issue-100.py +python3 scripts/validate-codebounty-issue-100.py test-fixtures/codebounty-issue-100-duplicate-announcement.json +git diff --check +``` + +The PR body should include `fixes #100` to satisfy the CodeBounty linkage rule. diff --git a/scripts/validate-codebounty-issue-100.py b/scripts/validate-codebounty-issue-100.py new file mode 100644 index 0000000..b84122f --- /dev/null +++ b/scripts/validate-codebounty-issue-100.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python3 +"""Validate the CodeBounty issue #100 duplicate-announcement fixture. + +The fixture records the live GitHub + CodeBounty snapshot used for this PR. +It keeps the payout boundary explicit: the visible amount comes from bot +announcement comments and remains submitted-visible only until the required +CodeBounty application and maintainer/platform acceptance are verified. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + + +EXPECTED_BOUNTY_LINKS = { + "http://localhost:3000/bounty/6800af954e2040a18be603c7", + "http://localhost:3000/bounty/6800af954e2040a18be603cc", +} +EXPECTED_ANNOUNCEMENT_COMMENTS = { + "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100#issuecomment-2812057916", + "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100#issuecomment-2812058021", +} + + +def fail(message: str) -> None: + raise SystemExit(f"issue-100 fixture invalid: {message}") + + +def main() -> None: + if len(sys.argv) != 2: + fail("usage: validate-codebounty-issue-100.py ") + + path = Path(sys.argv[1]) + if not path.is_file(): + fail(f"missing fixture: {path}") + + data = json.loads(path.read_text(encoding="utf-8")) + issue = data.get("issue") or {} + bounty = data.get("bounty") or {} + duplicate = data.get("duplicate_announcement_snapshot") or {} + collision = data.get("collision_snapshot") or {} + expected_pr = data.get("expected_pr") or {} + + if issue.get("number") != 100: + fail("issue number must be 100") + if issue.get("state") != "open": + fail("issue must be open at snapshot time") + if issue.get("locked") is not False: + fail("locked must be false at snapshot time") + if issue.get("assignees") != []: + fail("issue must be unassigned at snapshot time") + labels = set(issue.get("labels", [])) + if "bug" not in labels: + fail("bug label missing from live snapshot") + if "💰 Bounty Available" not in labels: + fail("bounty-available label missing from live snapshot") + if "235 am" not in issue.get("body", ""): + fail("issue body snapshot should preserve the original 235 am marker") + + if bounty.get("platform") != "CodeBounty": + fail("platform must be CodeBounty") + if bounty.get("visible_amount_usd") != 50: + fail("visible bounty amount must be $50") + if bounty.get("visible_amount_source") != "bot_comments": + fail("visible amount source must stay scoped to bot comments") + if bounty.get("announcement_comment_count") != 2: + fail("expected two CodeBounty bot announcement comments") + if set(bounty.get("announcement_comments", [])) != EXPECTED_ANNOUNCEMENT_COMMENTS: + fail("announcement comment URLs must match the live snapshot") + if set(bounty.get("bounty_links", [])) != EXPECTED_BOUNTY_LINKS: + fail("bounty links should match both announcement comment links") + if bounty.get("required_pr_linkage", "").lower() != "fixes #100": + fail("required PR linkage must be `fixes #100`") + if bounty.get("developer_application_required") is not True: + fail("developer application requirement must stay explicit") + if bounty.get("verified_payable_at_snapshot") is not False: + fail("fixture must not claim verified-payable payout") + + if duplicate.get("duplicate_bot_announcements") is not True: + fail("duplicate announcement flag must be true") + if duplicate.get("unique_bounty_link_count") != 2: + fail("duplicate snapshot must preserve both distinct bounty links") + if duplicate.get("human_comment_present") is not True: + fail("human follow-up comment should be preserved in the snapshot") + + if collision.get("repo_archived") is not False: + fail("repo must be non-archived at snapshot time") + if collision.get("repo_visibility") != "PUBLIC": + fail("repo visibility must be PUBLIC") + if collision.get("same_scope_open_pr_count") != 0: + fail("same-scope open PR count must be zero at snapshot time") + terms = {term.lower() for term in collision.get("open_pr_search_terms", [])} + if {"100", "fixes #100", "issue 100", "apr 17 235"} - terms: + fail("collision-search terms are incomplete") + for hit in collision.get("false_positive_open_pr_hits", []): + if "#100" in (hit.get("match_reason", "")): + fail("false-positive reasons must not claim a same-scope #100 match") + + required_body = {item.lower() for item in expected_pr.get("body_must_include", [])} + for required in { + "fixes #100", + "codebounty", + "$50", + "developer_application_required", + "duplicate_bot_announcements", + "verified_payable=false", + }: + if required not in required_body: + fail(f"expected PR body requirement missing: {required}") + + print( + json.dumps( + { + "ok": True, + "issue": issue["number"], + "visible_amount_usd": bounty["visible_amount_usd"], + "visible_amount_source": bounty["visible_amount_source"], + "required_pr_linkage": bounty["required_pr_linkage"], + "duplicate_bot_announcements": duplicate["duplicate_bot_announcements"], + "verified_payable_at_snapshot": bounty["verified_payable_at_snapshot"], + "same_scope_open_pr_count": collision["same_scope_open_pr_count"], + }, + sort_keys=True, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/test-fixtures/codebounty-issue-100-duplicate-announcement.json b/test-fixtures/codebounty-issue-100-duplicate-announcement.json new file mode 100644 index 0000000..50c74ac --- /dev/null +++ b/test-fixtures/codebounty-issue-100-duplicate-announcement.json @@ -0,0 +1,74 @@ +{ + "issue": { + "number": 100, + "url": "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100", + "title": "issue apr 17 235", + "state": "open", + "body": "235 am", + "labels": ["bug", "💰 Bounty Available"], + "assignees": [], + "locked": false + }, + "bounty": { + "platform": "CodeBounty", + "visible_amount_usd": 50, + "visible_amount_source": "bot_comments", + "status_at_snapshot": "announced_by_two_bot_comments_bounty_label_present", + "announcement_comment_count": 2, + "announcement_comments": [ + "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100#issuecomment-2812057916", + "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100#issuecomment-2812058021" + ], + "bounty_links": [ + "http://localhost:3000/bounty/6800af954e2040a18be603c7", + "http://localhost:3000/bounty/6800af954e2040a18be603cc" + ], + "required_pr_linkage": "fixes #100", + "developer_application_required": true, + "verified_payable_at_snapshot": false, + "blocker": "The CodeBounty announcement requires a platform application; a GitHub PR is submitted-visible only until CodeBounty/maintainer acceptance and payout eligibility are verified." + }, + "duplicate_announcement_snapshot": { + "duplicate_bot_announcements": true, + "unique_bounty_link_count": 2, + "human_comment_present": true, + "human_comment_url": "https://github.com/CodeBountyOrg/BountyTestRepository/issues/100#issuecomment-2840500225", + "note": "Issue #100 has two CodeBounty bot announcement comments for the same $50 bounty. This fixture preserves both distinct local bounty links and the maintainer/user follow-up comment so downstream validation can distinguish duplicate announcement evidence from payout verification." + }, + "collision_snapshot": { + "checked_at": "2026-05-13T23:23:01Z", + "repo_archived": false, + "repo_visibility": "PUBLIC", + "open_pr_search_terms": ["100", "fixes #100", "issue 100", "apr 17 235"], + "same_scope_open_pr_count": 0, + "raw_open_pr_search_hits": 3, + "false_positive_open_pr_hits": [ + { + "pr": "https://github.com/CodeBountyOrg/BountyTestRepository/pull/115", + "title": "docs: add issue #103 bounty note", + "match_reason": "GitHub search hit from issue #103 / $100 context; no exact issue-100 linkage." + }, + { + "pr": "https://github.com/CodeBountyOrg/BountyTestRepository/pull/113", + "title": "docs: add issue #8 bounty note", + "match_reason": "GitHub search hit from $100 bounty context; no exact issue-100 linkage." + }, + { + "pr": "https://github.com/CodeBountyOrg/BountyTestRepository/pull/119", + "title": "test: add issue 12 functionality smoke fixture", + "match_reason": "GitHub search hit from $100 bounty context; no exact issue-100 linkage." + } + ] + }, + "expected_pr": { + "title": "test: add CodeBounty issue 100 duplicate-announcement fixture", + "body_must_include": [ + "fixes #100", + "CodeBounty", + "$50", + "developer_application_required", + "duplicate_bot_announcements", + "verified_payable=false" + ] + } +}