package gates import ( "os" "os/exec" "path/filepath" "strings" "testing" ) // initRepo creates a fresh git repo in t.TempDir() with one seed commit // so HEAD~N..HEAD ranges resolve. Returns the workdir path. func initRepo(t *testing.T) string { t.Helper() dir := t.TempDir() runOrFail(t, dir, "git", "init", "-q", "-b", "main") runOrFail(t, dir, "git", "config", "user.email", "t@t.test") runOrFail(t, dir, "git", "config", "user.name", "test") if err := os.WriteFile(filepath.Join(dir, "README.md"), []byte("# seed\n"), 0o644); err != nil { t.Fatal(err) } runOrFail(t, dir, "git", "add", "README.md") runOrFail(t, dir, "git", "commit", "-q", "-m", "seed") return dir } func runOrFail(t *testing.T, dir, name string, args ...string) { t.Helper() cmd := exec.Command(name, args...) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("%s %s: %v\n%s", name, strings.Join(args, " "), err, out) } } func writeAndCommit(t *testing.T, dir, rel, body, msg string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, rel), []byte(body), 0o644); err != nil { t.Fatal(err) } runOrFail(t, dir, "git", "add", rel) runOrFail(t, dir, "git", "commit", "-q", "-m", msg) } func TestCheckAttribution_CleanTree(t *testing.T) { dir := initRepo(t) res, err := CheckAttribution(dir, Options{ScanFiles: true, ScanCommits: true, Range: "HEAD~0..HEAD"}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) != 0 { t.Errorf("clean tree has findings: %+v", res.Findings) } } func TestCheckAttribution_FileHitDetectedByDetector(t *testing.T) { dir := initRepo(t) // A simulated leak in a tracked file: a Co-Authored-By line. leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever \n" writeAndCommit(t, dir, "NOTES.md", leaked, "add notes") res, err := CheckAttribution(dir, Options{ScanFiles: true}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) == 0 { t.Fatal("expected a file hit for the seeded trailer") } hit := res.Findings[0] if hit.Path != "NOTES.md" { t.Errorf("hit path = %q, want NOTES.md", hit.Path) } if hit.Commit != "" { t.Errorf("file hit must not carry a commit SHA, got %q", hit.Commit) } } func TestCheckAttribution_BodyHitOnCommitMessage(t *testing.T) { dir := initRepo(t) // Author a commit whose MESSAGE carries a Co-Authored-By:Claude // trailer; the working-tree change itself is innocuous. if err := os.WriteFile(filepath.Join(dir, "x.md"), []byte("x\n"), 0o644); err != nil { t.Fatal(err) } runOrFail(t, dir, "git", "add", "x.md") runOrFail(t, dir, "git", "commit", "-q", "-m", "feat: x\n\n"+"Co-"+"Authored-"+"By: Claude ") res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) == 0 { t.Fatal("expected a commit-body hit") } hit := res.Findings[0] if hit.Commit == "" { t.Errorf("body hit must carry a commit SHA, got empty") } if !strings.Contains(strings.ToLower(hit.Excerpt), "claude") { t.Errorf("hit excerpt should name claude, got %q", hit.Excerpt) } } func TestCheckAttribution_BodyScanIgnoresPolicyDiscussion(t *testing.T) { dir := initRepo(t) // A docs commit whose SUBJECT and BODY mention the forbidden tokens // in prose — not as an actual trailer. Strict trailer-anchored // patterns must let this pass. if err := os.WriteFile(filepath.Join(dir, "docs.md"), []byte("policy\n"), 0o644); err != nil { t.Fatal(err) } runOrFail(t, dir, "git", "add", "docs.md") runOrFail(t, dir, "git", "commit", "-q", "-m", "docs: discuss the Co-"+"Authored-"+"By trailer policy\n\n"+ "The claude and anthropic tokens used to leak via the trailer; "+ "the noreply@anthropic email is also blocked.") res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) != 0 { t.Errorf("body scan flagged a policy-discussion commit: %+v", res.Findings) } } func TestCheckAttribution_RangeFallbackEmitsNotice(t *testing.T) { // Fresh repo has no origin/main remote — exercise the fallback. dir := initRepo(t) res, err := CheckAttribution(dir, Options{ScanCommits: true}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Notices) == 0 { t.Fatal("expected a fallback notice") } if !strings.Contains(res.Notices[0], "HEAD~10..HEAD") { t.Errorf("notice = %q, want HEAD~10..HEAD mention", res.Notices[0]) } } func TestCheckAttribution_NoCommitsSkipsBodyScan(t *testing.T) { dir := initRepo(t) // Stage a body-leak commit; ScanCommits=false should skip it. runOrFail(t, dir, "git", "commit", "--allow-empty", "-q", "-m", "feat: x\n\n"+"Co-"+"Authored-"+"By: Claude ") res, err := CheckAttribution(dir, Options{ScanFiles: true, ScanCommits: false}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } for _, f := range res.Findings { if f.Commit != "" { t.Errorf("body finding leaked through ScanCommits=false: %+v", f) } } } func TestCheckAttribution_PathsOverrideLimitsScope(t *testing.T) { dir := initRepo(t) // Seed a file with a real trailer; the override paths point at a // different (clean) file so the scan returns nothing. leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever \n" writeAndCommit(t, dir, "DIRTY.md", leaked, "add dirty") writeAndCommit(t, dir, "CLEAN.md", "just text\n", "add clean") res, err := CheckAttribution(dir, Options{ ScanFiles: true, Paths: []string{"CLEAN.md"}, }) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) != 0 { t.Errorf("override scope leaked DIRTY.md hit: %+v", res.Findings) } } func TestCheckAttribution_ExcludeSkipsPath(t *testing.T) { dir := initRepo(t) leaked := "feat\n\n" + "Co-" + "Authored-" + "By: Whoever \n" writeAndCommit(t, dir, "DIRTY.md", leaked, "add dirty") res, err := CheckAttribution(dir, Options{ ScanFiles: true, Excludes: []string{"DIRTY.md"}, }) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) != 0 { t.Errorf("exclude failed to skip DIRTY.md: %+v", res.Findings) } } func TestCheckAttribution_RobotEmojiInBody(t *testing.T) { dir := initRepo(t) robot := string([]rune{0x1F916}) body := "feat: thing\n\n" + robot + " " + "Generated" + " with the assistant\n" if err := os.WriteFile(filepath.Join(dir, "x.md"), []byte("x\n"), 0o644); err != nil { t.Fatal(err) } runOrFail(t, dir, "git", "add", "x.md") runOrFail(t, dir, "git", "commit", "-q", "-m", body) res, err := CheckAttribution(dir, Options{ScanCommits: true, Range: "HEAD~1..HEAD"}) if err != nil { t.Fatalf("CheckAttribution: %v", err) } if len(res.Findings) == 0 { t.Fatal("expected a body hit for robot-emoji Generated signature") } }