| 1 | #!/usr/bin/env python3 |
| 2 | """Regression tests for scripts/check-coauthor-trailers.py.""" |
| 3 | |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | import importlib.util |
| 7 | import sys |
| 8 | import unittest |
| 9 | from pathlib import Path |
| 10 | |
| 11 | ROOT = Path(__file__).resolve().parents[1] |
| 12 | SCRIPT = ROOT / "scripts" / "check-coauthor-trailers.py" |
| 13 | AUTHOR_MAP = ROOT / ".github" / "AUTHOR_MAP" |
| 14 | FIXTURES = Path(__file__).resolve().parent / "fixtures" / "coauthor-trailers" |
| 15 | |
| 16 | SPEC = importlib.util.spec_from_file_location("check_coauthor_trailers", SCRIPT) |
| 17 | assert SPEC and SPEC.loader |
| 18 | mod = importlib.util.module_from_spec(SPEC) |
| 19 | sys.modules[SPEC.name] = mod |
| 20 | SPEC.loader.exec_module(mod) |
| 21 | |
| 22 | |
| 23 | def commit(subject: str, body: str, *, harvested: bool = False) -> mod.Commit: |
| 24 | if harvested and "Harvested from PR" not in body: |
| 25 | body = f"{body}\n\nHarvested from PR #1 by @contributor" |
| 26 | return mod.Commit( |
| 27 | sha="deadbeef" * 5, |
| 28 | parents="", |
| 29 | author_name="Maintainer", |
| 30 | author_email="1+maintainer@users.noreply.github.com", |
| 31 | subject=subject, |
| 32 | body=body, |
| 33 | ) |
| 34 | |
| 35 | |
| 36 | class CheckCoauthorTrailersTests(unittest.TestCase): |
| 37 | @classmethod |
| 38 | def setUpClass(cls) -> None: |
| 39 | cls.aliases = mod.load_author_map(AUTHOR_MAP) |
| 40 | |
| 41 | def test_rejects_cursor_trailer_on_non_harvested_commit(self) -> None: |
| 42 | body = (FIXTURES / "cursor-non-harvested.txt").read_text(encoding="utf-8") |
| 43 | errors = mod.validate([commit("direct change", body)], self.aliases, False) |
| 44 | self.assertTrue(errors) |
| 45 | self.assertIn("cursoragent@cursor.com", errors[0]) |
| 46 | |
| 47 | def test_rejects_cursor_trailer_on_harvested_commit(self) -> None: |
| 48 | body = (FIXTURES / "cursor-harvested.txt").read_text(encoding="utf-8") |
| 49 | errors = mod.validate([commit("harvested change", body, harvested=True)], self.aliases, False) |
| 50 | self.assertTrue(errors) |
| 51 | |
| 52 | def test_allows_human_canonical_trailer(self) -> None: |
| 53 | body = (FIXTURES / "human-canonical.txt").read_text(encoding="utf-8") |
| 54 | errors = mod.validate([commit("human credit", body)], self.aliases, False) |
| 55 | self.assertEqual(errors, []) |
| 56 | |
| 57 | def test_allows_merge_commit_with_bot_trailer(self) -> None: |
| 58 | merge = mod.Commit( |
| 59 | sha="cafebabe" * 5, |
| 60 | parents="aaa bbb", |
| 61 | author_name="Maintainer", |
| 62 | author_email="1+maintainer@users.noreply.github.com", |
| 63 | subject="Merge branch", |
| 64 | body="Co-authored-by: Cursor <cursoragent@cursor.com>", |
| 65 | ) |
| 66 | errors = mod.validate([merge], self.aliases, False) |
| 67 | self.assertEqual(errors, []) |
| 68 | |
| 69 | def test_allows_only_the_exact_immutable_automation_trailer(self) -> None: |
| 70 | legacy = mod.Commit( |
| 71 | sha="9a74825cd182a62465943bcbbcbcf591d1ce99ee", |
| 72 | parents="parent", |
| 73 | author_name="Maintainer", |
| 74 | author_email="1+maintainer@users.noreply.github.com", |
| 75 | subject="legacy automation commit", |
| 76 | body="Co-authored-by: CodeWhale Agent <codewhale-agent@hmbown.local>", |
| 77 | ) |
| 78 | errors = mod.validate([legacy], self.aliases, False) |
| 79 | self.assertEqual(errors, []) |
| 80 | |
| 81 | other_commit = mod.Commit( |
| 82 | sha="deadbeef" * 5, |
| 83 | parents=legacy.parents, |
| 84 | author_name=legacy.author_name, |
| 85 | author_email=legacy.author_email, |
| 86 | subject=legacy.subject, |
| 87 | body=legacy.body, |
| 88 | ) |
| 89 | errors = mod.validate([other_commit], self.aliases, False) |
| 90 | self.assertTrue(errors) |
| 91 | self.assertIn("codewhale-agent@hmbown.local", errors[0]) |
| 92 | |
| 93 | def test_legacy_exception_does_not_hide_another_bad_trailer(self) -> None: |
| 94 | legacy_with_extra = mod.Commit( |
| 95 | sha="9a74825cd182a62465943bcbbcbcf591d1ce99ee", |
| 96 | parents="parent", |
| 97 | author_name="Maintainer", |
| 98 | author_email="1+maintainer@users.noreply.github.com", |
| 99 | subject="legacy automation commit", |
| 100 | body=( |
| 101 | "Co-authored-by: CodeWhale Agent <codewhale-agent@hmbown.local>\n" |
| 102 | "Co-authored-by: Unknown Person <unknown@example.com>" |
| 103 | ), |
| 104 | ) |
| 105 | errors = mod.validate([legacy_with_extra], self.aliases, False) |
| 106 | self.assertEqual(len(errors), 1) |
| 107 | self.assertIn("unknown@example.com", errors[0]) |
| 108 | |
| 109 | def test_preserved_hunter_credit_resolves_only_for_exact_commit(self) -> None: |
| 110 | preserved = mod.Commit( |
| 111 | sha="5087269606fc8847487b0a8b51ef6adffa8eb2ca", |
| 112 | parents="parent", |
| 113 | author_name="Hunter B", |
| 114 | author_email="hmbown@gmail.com", |
| 115 | subject="docs(public): refresh v0.9.1 product truth", |
| 116 | body=( |
| 117 | "Harvested from PR #4508 by @Hmbown.\n\n" |
| 118 | "Co-authored-by: Hunter Bown <hmbown@gmail.com>" |
| 119 | ), |
| 120 | ) |
| 121 | errors = mod.validate([preserved], self.aliases, True) |
| 122 | self.assertEqual(errors, []) |
| 123 | |
| 124 | rewritten = mod.Commit( |
| 125 | sha="deadbeef" * 5, |
| 126 | parents=preserved.parents, |
| 127 | author_name=preserved.author_name, |
| 128 | author_email=preserved.author_email, |
| 129 | subject=preserved.subject, |
| 130 | body=preserved.body, |
| 131 | ) |
| 132 | errors = mod.validate([rewritten], self.aliases, True) |
| 133 | self.assertTrue(errors) |
| 134 | self.assertTrue(any("not GitHub-mappable" in error for error in errors)) |
| 135 | |
| 136 | def test_preserved_web_credit_resolves_without_rewriting_commit(self) -> None: |
| 137 | preserved = mod.Commit( |
| 138 | sha="e37df06caeb3064b2bb9263c1c98a903738f3a0a", |
| 139 | parents="parent", |
| 140 | author_name="Hunter B", |
| 141 | author_email="hmbown@gmail.com", |
| 142 | subject="docs(web): make the homepage product first", |
| 143 | body="Co-authored-by: Hunter Bown <hmbown@gmail.com>", |
| 144 | ) |
| 145 | errors = mod.validate([preserved], self.aliases, True) |
| 146 | self.assertEqual(errors, []) |
| 147 | |
| 148 | def test_preserved_fleitz_credit_resolves_only_for_exact_commit(self) -> None: |
| 149 | preserved = mod.Commit( |
| 150 | sha="6d0ebc881a8bd2469c45b25f2a606fa63681e112", |
| 151 | parents="parent", |
| 152 | author_name="Fred Leitz", |
| 153 | author_email="fred.leitz@gmail.com", |
| 154 | subject="fix(shell): default no-cwd shell commands to context.workspace", |
| 155 | body="Co-authored-by: fleitz <fleitzo@gmail.com>", |
| 156 | ) |
| 157 | errors = mod.validate([preserved], self.aliases, True) |
| 158 | self.assertEqual(errors, []) |
| 159 | |
| 160 | changed_identity = mod.Commit( |
| 161 | sha=preserved.sha, |
| 162 | parents=preserved.parents, |
| 163 | author_name=preserved.author_name, |
| 164 | author_email=preserved.author_email, |
| 165 | subject=preserved.subject, |
| 166 | body="Co-authored-by: fleitz <different@example.com>", |
| 167 | ) |
| 168 | errors = mod.validate([changed_identity], self.aliases, True) |
| 169 | self.assertTrue(errors) |
| 170 | self.assertTrue(any("different@example.com" in error for error in errors)) |
| 171 | |
| 172 | def test_preserved_telecom_harvest_author_resolves_only_for_exact_commit(self) -> None: |
| 173 | preserved = mod.Commit( |
| 174 | sha="338138eb546bcf8917b27395325f59af0d2e4f52", |
| 175 | parents="parent", |
| 176 | author_name="Hunter B", |
| 177 | author_email="hmbown@gmail.com", |
| 178 | subject="feat(provider): add TelecomJS live catalog", |
| 179 | body=( |
| 180 | "Harvested from PR #4370 by @baendlorel.\n\n" |
| 181 | "Co-authored-by: baendlorel " |
| 182 | "<50111870+baendlorel@users.noreply.github.com>" |
| 183 | ), |
| 184 | ) |
| 185 | errors = mod.validate([preserved], self.aliases, True) |
| 186 | self.assertEqual(errors, []) |
| 187 | |
| 188 | rewritten = mod.Commit( |
| 189 | sha="deadbeef" * 5, |
| 190 | parents=preserved.parents, |
| 191 | author_name=preserved.author_name, |
| 192 | author_email=preserved.author_email, |
| 193 | subject=preserved.subject, |
| 194 | body=preserved.body, |
| 195 | ) |
| 196 | errors = mod.validate([rewritten], self.aliases, True) |
| 197 | self.assertTrue(errors) |
| 198 | self.assertTrue(any("not canonical" in error for error in errors)) |
| 199 | |
| 200 | |
| 201 | if __name__ == "__main__": |
| 202 | raise SystemExit(unittest.main()) |
| 203 |