package workflow import ( "os" "path/filepath" "testing" ) // Trigger literals are assembled from fragments so this test source stays // self-clean for eeco's own attribution scan (Constraint 3), matching the // discipline in attribution.go. func coTrailer() string { return "Co-" + "Authored-" + "By: " + "A Real Person " } func genLine() string { return "Gen" + "erated " + "with our " + "Assistant" } func newGuardDetector(t *testing.T) *Detector { t.Helper() det, err := NewDetector(nil) if err != nil { t.Fatal(err) } return det } func TestScanCommitGuard_InlineMessageTrailer(t *testing.T) { det := newGuardDetector(t) cmd := `git commit -m "fix: thing" -m "` + coTrailer() + `"` res := ScanCommitGuard(det, cmd, t.TempDir()) if !res.IsCommit { t.Fatal("expected IsCommit true") } if len(res.Findings) == 0 { t.Errorf("expected a finding for the inline trailer, got none") } } func TestScanCommitGuard_CleanCommitNoFinding(t *testing.T) { det := newGuardDetector(t) res := ScanCommitGuard(det, `git commit -m "fix: a real change"`, t.TempDir()) if !res.IsCommit { t.Fatal("expected IsCommit true") } if len(res.Findings) != 0 { t.Errorf("clean commit produced findings: %+v", res.Findings) } } func TestScanCommitGuard_FileMessageTrailer(t *testing.T) { det := newGuardDetector(t) dir := t.TempDir() msgPath := filepath.Join(dir, "MSG.txt") body := "feat: x\n\n" + coTrailer() + "\n" if err := os.WriteFile(msgPath, []byte(body), 0o644); err != nil { t.Fatal(err) } res := ScanCommitGuard(det, `git commit -F MSG.txt`, dir) if !res.IsCommit || len(res.Findings) == 0 { t.Errorf("expected commit + finding from -F file, got IsCommit=%v findings=%d", res.IsCommit, len(res.Findings)) } } func TestScanCommitGuard_NonCommitSegments(t *testing.T) { det := newGuardDetector(t) for _, cmd := range []string{ `echo "git commit -m bad"`, `git status`, `git log --oneline`, `ls -la && pwd`, } { res := ScanCommitGuard(det, cmd, t.TempDir()) if res.IsCommit { t.Errorf("%q wrongly qualified as a commit", cmd) } if len(res.Findings) != 0 { t.Errorf("%q produced findings: %+v", cmd, res.Findings) } } } func TestScanCommitGuard_ChainedCommit(t *testing.T) { det := newGuardDetector(t) cmd := `git add . && git commit -m "subject" -m "` + coTrailer() + `"` res := ScanCommitGuard(det, cmd, t.TempDir()) if !res.IsCommit || len(res.Findings) == 0 { t.Errorf("chained commit: IsCommit=%v findings=%d, want true + >0", res.IsCommit, len(res.Findings)) } } func TestScanCommitGuard_GlobalOptionAndEnvPrefix(t *testing.T) { det := newGuardDetector(t) for _, cmd := range []string{ `git -C /tmp/repo commit -m "x" -m "` + coTrailer() + `"`, `GIT_AUTHOR_NAME=bot git commit -m "x" -m "` + coTrailer() + `"`, `git -c user.name=x commit -am "` + coTrailer() + `"`, } { res := ScanCommitGuard(det, cmd, t.TempDir()) if !res.IsCommit || len(res.Findings) == 0 { t.Errorf("%q: IsCommit=%v findings=%d, want true + >0", cmd, res.IsCommit, len(res.Findings)) } } } func TestScanCommitGuard_StagedDiffFinding(t *testing.T) { det := newGuardDetector(t) orig := stagedDiff defer func() { stagedDiff = orig }() diff := "diff --git a/x b/x\n+// " + genLine() + "\n" stagedDiff = func(string) string { return diff } // A clean message, but the staged diff carries a generated-by line. res := ScanCommitGuard(det, `git commit -m "fix: clean"`, t.TempDir()) if !res.IsCommit || len(res.Findings) == 0 { t.Errorf("expected a finding from the staged diff, got IsCommit=%v findings=%d", res.IsCommit, len(res.Findings)) } } func TestScanCommitGuard_DegradeOpenOnCommandSubstitution(t *testing.T) { det := newGuardDetector(t) orig := stagedDiff defer func() { stagedDiff = orig }() stagedDiff = func(string) string { return "" } // A $()-resolved message cannot be statically read: no finding (allow), // no panic. IsCommit is still true (the segment is a commit). res := ScanCommitGuard(det, `git commit -m "$(cat msg.txt)"`, t.TempDir()) if !res.IsCommit { t.Fatal("expected IsCommit true") } if len(res.Findings) != 0 { t.Errorf("command-substitution message must degrade open, got findings: %+v", res.Findings) } } func TestScanCommitGuard_QuotedSeparatorDoesNotSplit(t *testing.T) { det := newGuardDetector(t) // The ';' and '&&' live inside the quoted message and must not split // the segment, so the commit is still detected as one. res := ScanCommitGuard(det, `git commit -m "fix; really && done"`, t.TempDir()) if !res.IsCommit { t.Error("quoted separators wrongly split the commit segment") } }