ajhahn.de
← eeco
Go 145 lines
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 <[email protected]>"
}

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")
	}
}