//nolint:gosec package main import ( "encoding/json" "errors" "flag" "fmt" "os" "os/exec" "path/filepath" "strings" "testing" "github.com/Wikid82/charon/backend/internal/patchreport" ) func TestMainProcessHelper(t *testing.T) { t.Helper() if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { return } separatorIndex := -1 for index, arg := range os.Args { if arg == "--" { separatorIndex = index break } } if separatorIndex == -1 { os.Exit(2) } os.Args = append([]string{os.Args[0]}, os.Args[separatorIndex+1:]...) flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ExitOnError) main() os.Exit(0) } func TestMain_SuccessWritesReports(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "reports", "local-patch.json") mdOut := filepath.Join(repoRoot, "reports", "local-patch.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-backend-coverage", "backend/coverage.txt", "-frontend-coverage", "frontend/coverage/lcov.info", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success exit code 0, got %d, stderr=%s", result.exitCode, result.stderr) } if _, err := os.Stat(jsonOut); err != nil { t.Fatalf("expected json report to exist: %v", err) } if _, err := os.Stat(mdOut); err != nil { t.Fatalf("expected markdown report to exist: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. reportBytes, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json report: %v", err) } var report reportJSON if err := json.Unmarshal(reportBytes, &report); err != nil { t.Fatalf("unmarshal report: %v", err) } if report.Mode != "warn" { t.Fatalf("unexpected mode: %s", report.Mode) } if report.Artifacts.JSON == "" || report.Artifacts.Markdown == "" { t.Fatalf("expected artifacts to be populated: %+v", report.Artifacts) } if !strings.Contains(result.stdout, "Local patch report generated") { t.Fatalf("expected success output, got: %s", result.stdout) } } func TestMain_FailsWhenBackendCoverageIsMissing(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.Remove(filepath.Join(repoRoot, "backend", "coverage.txt")); err != nil { t.Fatalf("remove backend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for missing backend coverage") } if !strings.Contains(result.stderr, "missing backend coverage file") { t.Fatalf("expected missing backend coverage error, stderr=%s", result.stderr) } } func TestMain_FailsWhenGitBaselineIsInvalid(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "this-is-not-a-valid-revision", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for invalid baseline") } if !strings.Contains(result.stderr, "error generating git diff") { t.Fatalf("expected git diff error, stderr=%s", result.stderr) } } func TestMain_FailsWhenBackendCoverageParseErrors(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) backendCoverage := filepath.Join(repoRoot, "backend", "coverage.txt") tooLongLine := strings.Repeat("a", 3*1024*1024) if err := os.WriteFile(backendCoverage, []byte("mode: atomic\n"+tooLongLine+"\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for backend parse error") } if !strings.Contains(result.stderr, "error parsing backend coverage") { t.Fatalf("expected backend parse error, stderr=%s", result.stderr) } } func TestMain_FailsWhenFrontendCoverageParseErrors(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) frontendCoverage := filepath.Join(repoRoot, "frontend", "coverage", "lcov.info") tooLongLine := strings.Repeat("b", 3*1024*1024) if err := os.WriteFile(frontendCoverage, []byte("TN:\nSF:frontend/src/file.ts\nDA:1,1\n"+tooLongLine+"\n"), 0o600); err != nil { t.Fatalf("write frontend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for frontend parse error") } if !strings.Contains(result.stderr, "error parsing frontend coverage") { t.Fatalf("expected frontend parse error, stderr=%s", result.stderr) } } func TestMain_FailsWhenJSONOutputCannotBeWritten(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonDir := filepath.Join(repoRoot, "locked-json-dir") if err := os.MkdirAll(jsonDir, 0o750); err != nil { t.Fatalf("create json dir: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonDir, ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code when json output path is a directory") } if !strings.Contains(result.stderr, "error writing json report") { t.Fatalf("expected json write error, stderr=%s", result.stderr) } } func TestResolvePathAndRelOrAbs(t *testing.T) { repoRoot := t.TempDir() absolute := filepath.Join(repoRoot, "absolute.txt") if got := resolvePath(repoRoot, absolute); got != absolute { t.Fatalf("expected absolute path unchanged, got %s", got) } relative := "nested/file.txt" expected := filepath.Join(repoRoot, relative) if got := resolvePath(repoRoot, relative); got != expected { t.Fatalf("expected joined path %s, got %s", expected, got) } if got := relOrAbs(repoRoot, expected); got != "nested/file.txt" { t.Fatalf("expected repo-relative path, got %s", got) } } func TestAssertFileExists(t *testing.T) { tempDir := t.TempDir() filePath := filepath.Join(tempDir, "ok.txt") if err := os.WriteFile(filePath, []byte("ok"), 0o600); err != nil { t.Fatalf("write file: %v", err) } if err := assertFileExists(filePath, "test file"); err != nil { t.Fatalf("expected existing file to pass: %v", err) } err := assertFileExists(filepath.Join(tempDir, "missing.txt"), "missing file") if err == nil || !strings.Contains(err.Error(), "missing missing file") { t.Fatalf("expected missing file error, got: %v", err) } err = assertFileExists(tempDir, "directory input") if err == nil || !strings.Contains(err.Error(), "found directory") { t.Fatalf("expected directory error, got: %v", err) } } func TestGitDiffAndWriters(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) diffContent, err := gitDiff(repoRoot, "HEAD...HEAD") if err != nil { t.Fatalf("gitDiff should succeed for HEAD...HEAD: %v", err) } if diffContent != "" { t.Fatalf("expected empty diff for HEAD...HEAD, got: %q", diffContent) } _, err = gitDiff(repoRoot, "bad-baseline") if err == nil { t.Fatal("expected gitDiff failure for invalid baseline") } report := reportJSON{ Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn", Thresholds: thresholdJSON{Overall: 90, Backend: 85, Frontend: 85}, ThresholdSources: thresholdSourcesJSON{ Overall: "default", Backend: "default", Frontend: "default", }, Overall: patchreport.ScopeCoverage{ChangedLines: 10, CoveredLines: 5, PatchCoveragePct: 50, Status: "warn"}, Backend: patchreport.ScopeCoverage{ChangedLines: 6, CoveredLines: 2, PatchCoveragePct: 33.3, Status: "warn"}, Frontend: patchreport.ScopeCoverage{ChangedLines: 4, CoveredLines: 3, PatchCoveragePct: 75, Status: "warn"}, FilesNeedingCoverage: []patchreport.FileCoverageDetail{{ Path: "backend/cmd/localpatchreport/main.go", PatchCoveragePct: 0, UncoveredChangedLines: 2, UncoveredChangedLineRange: []string{"10-11"}, }}, Warnings: []string{"warning one"}, Artifacts: artifactsJSON{Markdown: "test-results/report.md", JSON: "test-results/report.json"}, } jsonPath := filepath.Join(t.TempDir(), "report.json") err = writeJSON(jsonPath, report) if err != nil { t.Fatalf("writeJSON should succeed: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. jsonBytes, err := os.ReadFile(jsonPath) if err != nil { t.Fatalf("read json file: %v", err) } if !strings.Contains(string(jsonBytes), "\"baseline\": \"origin/development...HEAD\"") { t.Fatalf("unexpected json content: %s", string(jsonBytes)) } markdownPath := filepath.Join(t.TempDir(), "report.md") err = writeMarkdown(markdownPath, report, "backend/coverage.txt", "frontend/coverage/lcov.info") if err != nil { t.Fatalf("writeMarkdown should succeed: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. markdownBytes, err := os.ReadFile(markdownPath) if err != nil { t.Fatalf("read markdown file: %v", err) } markdown := string(markdownBytes) if !strings.Contains(markdown, "## Files Needing Coverage") { t.Fatalf("expected files section in markdown: %s", markdown) } if !strings.Contains(markdown, "## Warnings") { t.Fatalf("expected warnings section in markdown: %s", markdown) } scope := patchreport.ScopeCoverage{ChangedLines: 3, CoveredLines: 2, PatchCoveragePct: 66.7, Status: "warn"} row := scopeRow("Backend", scope) if !strings.Contains(row, "| Backend | 3 | 2 | 66.7 | warn |") { t.Fatalf("unexpected scope row: %s", row) } } func runMainSubprocess(t *testing.T, args ...string) subprocessResult { t.Helper() commandArgs := append([]string{"-test.run=TestMainProcessHelper", "--"}, args...) // #nosec G204 -- Test helper subprocess invocation with controlled arguments. cmd := exec.Command(os.Args[0], commandArgs...) cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") stdout, err := cmd.Output() if err == nil { return subprocessResult{exitCode: 0, stdout: string(stdout), stderr: ""} } var exitError *exec.ExitError if errors.As(err, &exitError) { return subprocessResult{exitCode: exitError.ExitCode(), stdout: string(stdout), stderr: string(exitError.Stderr)} } t.Fatalf("unexpected subprocess failure: %v", err) return subprocessResult{} } type subprocessResult struct { exitCode int stdout string stderr string } func createGitRepoWithCoverageInputs(t *testing.T) string { t.Helper() repoRoot := t.TempDir() mustRunCommand(t, repoRoot, "git", "init") mustRunCommand(t, repoRoot, "git", "config", "user.email", "test@example.com") mustRunCommand(t, repoRoot, "git", "config", "user.name", "Test User") paths := []string{ filepath.Join(repoRoot, "backend", "internal"), filepath.Join(repoRoot, "frontend", "src"), filepath.Join(repoRoot, "frontend", "coverage"), filepath.Join(repoRoot, "backend"), } for _, path := range paths { if err := os.MkdirAll(path, 0o750); err != nil { t.Fatalf("mkdir %s: %v", path, err) } } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 1\n"), 0o600); err != nil { t.Fatalf("write backend sample: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "src", "sample.ts"), []byte("export const sample = 1;\n"), 0o600); err != nil { t.Fatalf("write frontend sample: %v", err) } backendCoverage := "mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 1\n" if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte(backendCoverage), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } frontendCoverage := "TN:\nSF:frontend/src/sample.ts\nDA:1,1\nend_of_record\n" if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), []byte(frontendCoverage), 0o600); err != nil { t.Fatalf("write frontend coverage: %v", err) } mustRunCommand(t, repoRoot, "git", "add", ".") mustRunCommand(t, repoRoot, "git", "commit", "-m", "initial commit") return repoRoot } func mustRunCommand(t *testing.T, dir string, name string, args ...string) { t.Helper() // #nosec G204 -- Test helper executes deterministic local commands. cmd := exec.Command(name, args...) cmd.Dir = dir output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("command %s %s failed: %v\n%s", name, strings.Join(args, " "), err, string(output)) } } func TestWriteJSONReturnsErrorWhenPathIsDirectory(t *testing.T) { dir := t.TempDir() report := reportJSON{Baseline: "x", GeneratedAt: "y", Mode: "warn"} if err := writeJSON(dir, report); err == nil { t.Fatal("expected writeJSON to fail when target is a directory") } } func TestWriteMarkdownReturnsErrorWhenPathIsDirectory(t *testing.T) { dir := t.TempDir() report := reportJSON{ Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn", Thresholds: thresholdJSON{Overall: 90, Backend: 85, Frontend: 85}, ThresholdSources: thresholdSourcesJSON{Overall: "default", Backend: "default", Frontend: "default"}, Overall: patchreport.ScopeCoverage{Status: "pass"}, Backend: patchreport.ScopeCoverage{Status: "pass"}, Frontend: patchreport.ScopeCoverage{Status: "pass"}, FilesNeedingCoverage: nil, Warnings: nil, Artifacts: artifactsJSON{Markdown: "a", JSON: "b"}, } if err := writeMarkdown(dir, report, "backend/coverage.txt", "frontend/coverage/lcov.info"); err == nil { t.Fatal("expected writeMarkdown to fail when target is a directory") } } func TestMain_FailsWhenMarkdownDirectoryCreationFails(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) lockedParent := filepath.Join(repoRoot, "md-root") if err := os.WriteFile(lockedParent, []byte("file-not-dir"), 0o600); err != nil { t.Fatalf("write locked parent file: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", filepath.Join(lockedParent, "report.md"), ) if result.exitCode == 0 { t.Fatalf("expected markdown directory creation failure") } if !strings.Contains(result.stderr, "error creating markdown output directory") { t.Fatalf("expected markdown mkdir error, stderr=%s", result.stderr) } } func TestMain_FailsWhenJSONDirectoryCreationFails(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) lockedParent := filepath.Join(repoRoot, "json-root") if err := os.WriteFile(lockedParent, []byte("file-not-dir"), 0o600); err != nil { t.Fatalf("write locked parent file: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", filepath.Join(lockedParent, "report.json"), ) if result.exitCode == 0 { t.Fatalf("expected json directory creation failure") } if !strings.Contains(result.stderr, "error creating json output directory") { t.Fatalf("expected json mkdir error, stderr=%s", result.stderr) } } func TestMain_PrintsWarningsWhenThresholdsNotMet(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 2\n"), 0o600); err != nil { t.Fatalf("update backend sample: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "src", "sample.ts"), []byte("export const sample = 2;\n"), 0o600); err != nil { t.Fatalf("update frontend sample: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 0\n"), 0o600); err != nil { t.Fatalf("write backend uncovered coverage: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), []byte("TN:\nSF:frontend/src/sample.ts\nDA:1,0\nend_of_record\n"), 0o600); err != nil { t.Fatalf("write frontend uncovered coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success with warnings, got exit=%d stderr=%s", result.exitCode, result.stderr) } if !strings.Contains(result.stdout, "WARN: Overall patch coverage") { t.Fatalf("expected WARN output, stdout=%s", result.stdout) } } func TestRelOrAbsConvertsSlashes(t *testing.T) { repoRoot := t.TempDir() targetPath := filepath.Join(repoRoot, "reports", "file.json") got := relOrAbs(repoRoot, targetPath) if got != "reports/file.json" { t.Fatalf("expected slash-normalized relative path, got %s", got) } } func TestHelperCommandFailureHasContext(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) _, err := gitDiff(repoRoot, "definitely-invalid") if err == nil { t.Fatal("expected gitDiff error") } if !strings.Contains(err.Error(), "git diff definitely-invalid failed") { t.Fatalf("expected contextual error message, got %v", err) } } func TestMain_FailsWhenMarkdownWriteFails(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdDir := filepath.Join(repoRoot, "md-as-dir") if err := os.MkdirAll(mdDir, 0o750); err != nil { t.Fatalf("create markdown dir: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdDir, ) if result.exitCode == 0 { t.Fatalf("expected markdown write failure") } if !strings.Contains(result.stderr, "error writing markdown report") { t.Fatalf("expected markdown write error, stderr=%s", result.stderr) } } func TestMain_FailsWhenFrontendCoverageIsMissing(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.Remove(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info")); err != nil { t.Fatalf("remove frontend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for missing frontend coverage") } if !strings.Contains(result.stderr, "missing frontend coverage file") { t.Fatalf("expected missing frontend coverage error, stderr=%s", result.stderr) } } func TestMain_FailsWhenRepoRootInvalid(t *testing.T) { nonexistentPath := filepath.Join(t.TempDir(), "missing", "repo") result := runMainSubprocess(t, "-repo-root", nonexistentPath, "-baseline", "HEAD...HEAD", "-backend-coverage", "backend/coverage.txt", "-frontend-coverage", "frontend/coverage/lcov.info", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit code for invalid repo root") } if !strings.Contains(result.stderr, "missing backend coverage file") { t.Fatalf("expected backend missing error for invalid repo root, stderr=%s", result.stderr) } } func TestMain_WarnsForInvalidThresholdEnv(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) commandArgs := []string{"-test.run=TestMainProcessHelper", "--", "-repo-root", repoRoot, "-baseline", "HEAD...HEAD"} // #nosec G204 -- Test helper subprocess invocation with controlled arguments. cmd := exec.Command(os.Args[0], commandArgs...) cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1", "CHARON_OVERALL_PATCH_COVERAGE_MIN=invalid") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("expected success with warning env, got err=%v output=%s", err, string(output)) } if !strings.Contains(string(output), "WARN: Ignoring invalid CHARON_OVERALL_PATCH_COVERAGE_MIN") { t.Fatalf("expected invalid-threshold warning, output=%s", string(output)) } } func TestWriteMarkdownIncludesArtifactsSection(t *testing.T) { report := reportJSON{ Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn", Thresholds: thresholdJSON{Overall: 90, Backend: 85, Frontend: 85}, ThresholdSources: thresholdSourcesJSON{Overall: "default", Backend: "default", Frontend: "default"}, Overall: patchreport.ScopeCoverage{ChangedLines: 1, CoveredLines: 1, PatchCoveragePct: 100, Status: "pass"}, Backend: patchreport.ScopeCoverage{ChangedLines: 1, CoveredLines: 1, PatchCoveragePct: 100, Status: "pass"}, Frontend: patchreport.ScopeCoverage{ChangedLines: 0, CoveredLines: 0, PatchCoveragePct: 100, Status: "pass"}, Artifacts: artifactsJSON{Markdown: "test-results/local-patch-report.md", JSON: "test-results/local-patch-report.json"}, } path := filepath.Join(t.TempDir(), "report.md") if err := writeMarkdown(path, report, "backend/coverage.txt", "frontend/coverage/lcov.info"); err != nil { t.Fatalf("writeMarkdown: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. body, err := os.ReadFile(path) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "## Artifacts") { t.Fatalf("expected artifacts section, got: %s", string(body)) } } func TestRunMainSubprocessReturnsExitCode(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "not-a-revision", ) if result.exitCode == 0 { t.Fatalf("expected non-zero exit for invalid baseline") } if result.stderr == "" { t.Fatal("expected stderr to be captured") } } func TestMustRunCommandHelper(t *testing.T) { temp := t.TempDir() mustRunCommand(t, temp, "git", "init") // #nosec G204 -- Test setup command with fixed arguments. configEmail := exec.Command("git", "-C", temp, "config", "user.email", "test@example.com") if output, err := configEmail.CombinedOutput(); err != nil { t.Fatalf("configure email failed: %v output=%s", err, string(output)) } // #nosec G204 -- Test setup command with fixed arguments. configName := exec.Command("git", "-C", temp, "config", "user.name", "Test User") if output, err := configName.CombinedOutput(); err != nil { t.Fatalf("configure name failed: %v output=%s", err, string(output)) } if err := os.WriteFile(filepath.Join(temp, "README.md"), []byte("content\n"), 0o600); err != nil { t.Fatalf("write file: %v", err) } mustRunCommand(t, temp, "git", "add", ".") mustRunCommand(t, temp, "git", "commit", "-m", "test") } func TestSubprocessHelperFailsWithoutSeparator(t *testing.T) { // #nosec G204 -- Test helper subprocess invocation with fixed arguments. cmd := exec.Command(os.Args[0], "-test.run=TestMainProcessHelper") cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") _, err := cmd.CombinedOutput() if err == nil { t.Fatal("expected helper process to fail without separator") } } func TestScopeRowFormatting(t *testing.T) { row := scopeRow("Overall", patchreport.ScopeCoverage{ChangedLines: 10, CoveredLines: 8, PatchCoveragePct: 80.0, Status: "warn"}) expected := "| Overall | 10 | 8 | 80.0 | warn |\n" if row != expected { t.Fatalf("unexpected row\nwant: %q\ngot: %q", expected, row) } } func TestMainProcessHelperNoopWhenEnvUnset(t *testing.T) { if os.Getenv("GO_WANT_HELPER_PROCESS") != "" { t.Skip("helper env is set by parent process") } } func TestRelOrAbsWithNestedPath(t *testing.T) { repoRoot := t.TempDir() nested := filepath.Join(repoRoot, "a", "b", "c", "report.json") if got := relOrAbs(repoRoot, nested); got != "a/b/c/report.json" { t.Fatalf("unexpected relative path: %s", got) } } func TestResolvePathWithAbsoluteInput(t *testing.T) { repoRoot := t.TempDir() abs := filepath.Join(repoRoot, "direct.txt") if resolvePath(repoRoot, abs) != abs { t.Fatal("resolvePath should return absolute input unchanged") } } func TestResolvePathWithRelativeInput(t *testing.T) { repoRoot := t.TempDir() got := resolvePath(repoRoot, "test-results/out.json") expected := filepath.Join(repoRoot, "test-results", "out.json") if got != expected { t.Fatalf("unexpected resolved path: %s", got) } } func TestAssertFileExistsErrorMessageIncludesLabel(t *testing.T) { err := assertFileExists(filepath.Join(t.TempDir(), "missing"), "backend coverage file") if err == nil { t.Fatal("expected error for missing file") } if !strings.Contains(err.Error(), "backend coverage file") { t.Fatalf("expected label in error, got: %v", err) } } func TestWriteJSONContentIncludesTrailingNewline(t *testing.T) { path := filepath.Join(t.TempDir(), "out.json") report := reportJSON{Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn"} if err := writeJSON(path, report); err != nil { t.Fatalf("writeJSON: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. body, err := os.ReadFile(path) if err != nil { t.Fatalf("read json: %v", err) } if len(body) == 0 || body[len(body)-1] != '\n' { t.Fatalf("expected trailing newline, got: %q", string(body)) } } func TestMainProducesRelArtifactPaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := "test-results/custom/report.json" mdOut := "test-results/custom/report.md" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: stderr=%s", result.stderr) } // #nosec G304 -- Test reads artifact path created by this test. content, err := os.ReadFile(filepath.Join(repoRoot, jsonOut)) if err != nil { t.Fatalf("read json report: %v", err) } var report reportJSON if err := json.Unmarshal(content, &report); err != nil { t.Fatalf("unmarshal report: %v", err) } if report.Artifacts.JSON != "test-results/custom/report.json" { t.Fatalf("unexpected json artifact path: %s", report.Artifacts.JSON) } if report.Artifacts.Markdown != "test-results/custom/report.md" { t.Fatalf("unexpected markdown artifact path: %s", report.Artifacts.Markdown) } } func TestMainWithExplicitInputPaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-backend-coverage", filepath.Join(repoRoot, "backend", "coverage.txt"), "-frontend-coverage", filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), ) if result.exitCode != 0 { t.Fatalf("expected success with explicit paths: stderr=%s", result.stderr) } } func TestMainOutputIncludesArtifactPaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := "test-results/a.json" mdOut := "test-results/a.md" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: stderr=%s", result.stderr) } if !strings.Contains(result.stdout, "JSON: test-results/a.json") { t.Fatalf("expected JSON output path in stdout: %s", result.stdout) } if !strings.Contains(result.stdout, "Markdown: test-results/a.md") { t.Fatalf("expected markdown output path in stdout: %s", result.stdout) } } func TestMainWithFileNeedingCoverageIncludesMarkdownTable(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) backendSource := filepath.Join(repoRoot, "backend", "internal", "sample.go") if err := os.WriteFile(backendSource, []byte("package internal\nvar Sample = 3\n"), 0o600); err != nil { t.Fatalf("update backend source: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 0\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } mdOut := filepath.Join(repoRoot, "test-results", "patch.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: stderr=%s", result.stderr) } // #nosec G304 -- Test reads artifact path created by this test. body, err := os.ReadFile(mdOut) if err != nil { t.Fatalf("read markdown report: %v", err) } if !strings.Contains(string(body), "| Path | Patch Coverage (%) | Uncovered Changed Lines | Uncovered Changed Line Ranges |") { t.Fatalf("expected files table in markdown, got: %s", string(body)) } } func TestMainStderrForMissingFrontendCoverage(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.Remove(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info")); err != nil { t.Fatalf("remove lcov: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected failure for missing lcov") } if !strings.Contains(result.stderr, "missing frontend coverage file") { t.Fatalf("unexpected stderr: %s", result.stderr) } } func TestWriteMarkdownWithoutWarningsOrFiles(t *testing.T) { report := reportJSON{ Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn", Thresholds: thresholdJSON{Overall: 90, Backend: 85, Frontend: 85}, ThresholdSources: thresholdSourcesJSON{Overall: "default", Backend: "default", Frontend: "default"}, Overall: patchreport.ScopeCoverage{ChangedLines: 0, CoveredLines: 0, PatchCoveragePct: 100, Status: "pass"}, Backend: patchreport.ScopeCoverage{ChangedLines: 0, CoveredLines: 0, PatchCoveragePct: 100, Status: "pass"}, Frontend: patchreport.ScopeCoverage{ChangedLines: 0, CoveredLines: 0, PatchCoveragePct: 100, Status: "pass"}, Artifacts: artifactsJSON{Markdown: "test-results/out.md", JSON: "test-results/out.json"}, } path := filepath.Join(t.TempDir(), "report.md") if err := writeMarkdown(path, report, "backend/coverage.txt", "frontend/coverage/lcov.info"); err != nil { t.Fatalf("writeMarkdown failed: %v", err) } // #nosec G304 -- Test reads artifact path created by this test. body, err := os.ReadFile(path) if err != nil { t.Fatalf("read markdown: %v", err) } text := string(body) if strings.Contains(text, "## Warnings") { t.Fatalf("did not expect warnings section: %s", text) } if strings.Contains(text, "## Files Needing Coverage") { t.Fatalf("did not expect files section: %s", text) } } func TestMainProducesExpectedJSONSchemaFields(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "schema.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: stderr=%s", result.stderr) } // #nosec G304 -- Test reads artifact path created by this test. body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } var raw map[string]any if err := json.Unmarshal(body, &raw); err != nil { t.Fatalf("unmarshal raw json: %v", err) } required := []string{"baseline", "generated_at", "mode", "thresholds", "threshold_sources", "overall", "backend", "frontend", "artifacts"} for _, key := range required { if _, ok := raw[key]; !ok { t.Fatalf("missing required key %q in report json", key) } } } func TestMainReturnsNonZeroWhenBackendCoveragePathIsDirectory(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.Remove(filepath.Join(repoRoot, "backend", "coverage.txt")); err != nil { t.Fatalf("remove backend coverage: %v", err) } if err := os.MkdirAll(filepath.Join(repoRoot, "backend", "coverage.txt"), 0o750); err != nil { t.Fatalf("create backend coverage dir: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected failure when backend coverage path is dir") } if !strings.Contains(result.stderr, "expected backend coverage file to be a file") { t.Fatalf("unexpected stderr: %s", result.stderr) } } func TestMainReturnsNonZeroWhenFrontendCoveragePathIsDirectory(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) lcovPath := filepath.Join(repoRoot, "frontend", "coverage", "lcov.info") if err := os.Remove(lcovPath); err != nil { t.Fatalf("remove lcov path: %v", err) } if err := os.MkdirAll(lcovPath, 0o750); err != nil { t.Fatalf("create lcov dir: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected failure when frontend coverage path is dir") } if !strings.Contains(result.stderr, "expected frontend coverage file to be a file") { t.Fatalf("unexpected stderr: %s", result.stderr) } } func TestMainHandlesAbsoluteOutputPaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(t.TempDir(), "absolute", "report.json") mdOut := filepath.Join(t.TempDir(), "absolute", "report.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success with absolute outputs: stderr=%s", result.stderr) } if _, err := os.Stat(jsonOut); err != nil { t.Fatalf("expected absolute json file to exist: %v", err) } if _, err := os.Stat(mdOut); err != nil { t.Fatalf("expected absolute markdown file to exist: %v", err) } } func TestMainWithNoChangedLinesStillPasses(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success when no lines changed, stderr=%s", result.stderr) } } func TestMain_UsageOfBaselineFlagAffectsGitDiff(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 5\n"), 0o600); err != nil { t.Fatalf("update backend source: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success for baseline HEAD, stderr=%s", result.stderr) } } func TestMainOutputsWarnLinesWhenAnyScopeWarns(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 7\n"), 0o600); err != nil { t.Fatalf("update backend file: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 0\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success with warnings: stderr=%s", result.stderr) } if !strings.Contains(result.stdout, "WARN:") { t.Fatalf("expected warning lines in stdout: %s", result.stdout) } } func TestMainProcessHelperWithMalformedArgsExitsNonZero(t *testing.T) { // #nosec G204 -- Test helper subprocess invocation with fixed arguments. cmd := exec.Command(os.Args[0], "-test.run=TestMainProcessHelper", "--", "-repo-root") cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") _, err := cmd.CombinedOutput() if err == nil { t.Fatal("expected helper process to fail for malformed args") } } func TestWriteMarkdownContainsSummaryTable(t *testing.T) { report := reportJSON{ Baseline: "origin/development...HEAD", GeneratedAt: "2026-02-17T00:00:00Z", Mode: "warn", Thresholds: thresholdJSON{Overall: 90, Backend: 85, Frontend: 85}, ThresholdSources: thresholdSourcesJSON{Overall: "default", Backend: "default", Frontend: "default"}, Overall: patchreport.ScopeCoverage{ChangedLines: 5, CoveredLines: 2, PatchCoveragePct: 40.0, Status: "warn"}, Backend: patchreport.ScopeCoverage{ChangedLines: 3, CoveredLines: 1, PatchCoveragePct: 33.3, Status: "warn"}, Frontend: patchreport.ScopeCoverage{ChangedLines: 2, CoveredLines: 1, PatchCoveragePct: 50.0, Status: "warn"}, Artifacts: artifactsJSON{Markdown: "test-results/report.md", JSON: "test-results/report.json"}, } path := filepath.Join(t.TempDir(), "summary.md") if err := writeMarkdown(path, report, "backend/coverage.txt", "frontend/coverage/lcov.info"); err != nil { t.Fatalf("write markdown: %v", err) } body, err := os.ReadFile(path) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "| Scope | Changed Lines | Covered Lines | Patch Coverage (%) | Status |") { t.Fatalf("expected summary table in markdown: %s", string(body)) } } func TestMainWithRepoRootDotFromSubprocess(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) commandArgs := []string{"-test.run=TestMainProcessHelper", "--", "-repo-root", ".", "-baseline", "HEAD...HEAD"} // #nosec G204 -- Test helper subprocess invocation with controlled arguments. cmd := exec.Command(os.Args[0], commandArgs...) cmd.Dir = repoRoot cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("expected success with repo-root dot: %v\n%s", err, string(output)) } } func TestMain_InvalidBackendCoverageFlagPath(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-backend-coverage", "backend/does-not-exist.txt", ) if result.exitCode == 0 { t.Fatalf("expected failure for invalid backend coverage flag path") } } func TestMain_InvalidFrontendCoverageFlagPath(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-frontend-coverage", "frontend/coverage/missing.info", ) if result.exitCode == 0 { t.Fatalf("expected failure for invalid frontend coverage flag path") } } func TestGitDiffReturnsContextualErrorOutput(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) _, err := gitDiff(repoRoot, "refs/heads/does-not-exist") if err == nil { t.Fatal("expected gitDiff to fail") } if !strings.Contains(err.Error(), "refs/heads/does-not-exist") { t.Fatalf("expected baseline in error: %v", err) } } func TestMain_EmitsWarningsInSortedOrderWithEnvWarning(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) // #nosec G204 -- Test helper subprocess invocation with controlled arguments. // #nosec G204 -- Test helper subprocess invocation with controlled arguments. cmd := exec.Command(os.Args[0], "-test.run=TestMainProcessHelper", "--", "-repo-root", repoRoot, "-baseline", "HEAD...HEAD") cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1", "CHARON_FRONTEND_PATCH_COVERAGE_MIN=bad") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("expected success with env warning: %v\n%s", err, string(output)) } if !strings.Contains(string(output), "WARN: Ignoring invalid CHARON_FRONTEND_PATCH_COVERAGE_MIN") { t.Fatalf("expected frontend env warning: %s", string(output)) } } func TestMain_FrontendParseErrorWithMissingSFDataStillSucceeds(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), []byte("TN:\nDA:1,1\nend_of_record\n"), 0o600); err != nil { t.Fatalf("write lcov: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success with lcov missing SF sections, stderr=%s", result.stderr) } } func TestMain_BackendCoverageWithInvalidRowsStillSucceeds(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nthis is not valid coverage row\nbackend/internal/sample.go:1.1,2.20 1 1\n"), 0o600); err != nil { t.Fatalf("write coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success with ignored invalid rows, stderr=%s", result.stderr) } } func TestMainOutputMentionsModeWarn(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } if !strings.Contains(result.stdout, "mode=warn") { t.Fatalf("expected mode in stdout: %s", result.stdout) } } func TestMain_GeneratesMarkdownAtConfiguredRelativePath(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdOut := "custom/out/report.md" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } if _, err := os.Stat(filepath.Join(repoRoot, mdOut)); err != nil { t.Fatalf("expected markdown output to exist: %v", err) } } func TestMain_GeneratesJSONAtConfiguredRelativePath(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := "custom/out/report.json" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } if _, err := os.Stat(filepath.Join(repoRoot, jsonOut)); err != nil { t.Fatalf("expected json output to exist: %v", err) } } func TestMainWarningsAppearWhenThresholdRaised(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) cmd := exec.Command(os.Args[0], "-test.run=TestMainProcessHelper", "--", "-repo-root", repoRoot, "-baseline", "HEAD...HEAD") cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1", "CHARON_OVERALL_PATCH_COVERAGE_MIN=101") output, err := cmd.CombinedOutput() if err != nil { t.Fatalf("expected success with invalid threshold env: %v\n%s", err, string(output)) } if !strings.Contains(string(output), "WARN: Ignoring invalid CHARON_OVERALL_PATCH_COVERAGE_MIN") { t.Fatalf("expected invalid threshold warning in output: %s", string(output)) } } func TestMain_BaselineFlagRoundTripIntoJSON(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "baseline.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } var report reportJSON if err := json.Unmarshal(body, &report); err != nil { t.Fatalf("unmarshal json: %v", err) } if report.Baseline != "HEAD...HEAD" { t.Fatalf("expected baseline to match flag, got %s", report.Baseline) } } func TestMain_WithChangedFilesProducesFilesNeedingCoverageInJSON(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 42\n"), 0o600); err != nil { t.Fatalf("update backend file: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 0\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } jsonOut := filepath.Join(repoRoot, "test-results", "coverage-gaps.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json output: %v", err) } var report reportJSON if err := json.Unmarshal(body, &report); err != nil { t.Fatalf("unmarshal json: %v", err) } if len(report.FilesNeedingCoverage) == 0 { t.Fatalf("expected files_needing_coverage to be non-empty") } } func TestMain_FailsWhenMarkdownPathParentIsDirectoryFileConflict(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) conflict := filepath.Join(repoRoot, "conflict") if err := os.WriteFile(conflict, []byte("x"), 0o600); err != nil { t.Fatalf("write conflict file: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", filepath.Join(conflict, "nested", "report.md"), ) if result.exitCode == 0 { t.Fatalf("expected failure due to markdown path parent conflict") } } func TestMain_FailsWhenJSONPathParentIsDirectoryFileConflict(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) conflict := filepath.Join(repoRoot, "json-conflict") if err := os.WriteFile(conflict, []byte("x"), 0o600); err != nil { t.Fatalf("write conflict file: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", filepath.Join(conflict, "nested", "report.json"), ) if result.exitCode == 0 { t.Fatalf("expected failure due to json path parent conflict") } } func TestMain_ReportContainsThresholdSources(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "threshold-sources.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } if !strings.Contains(string(body), "\"threshold_sources\"") { t.Fatalf("expected threshold_sources in json: %s", string(body)) } } func TestMain_ReportContainsCoverageScopes(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "scopes.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } for _, key := range []string{"\"overall\"", "\"backend\"", "\"frontend\""} { if !strings.Contains(string(body), key) { t.Fatalf("expected %s in json: %s", key, string(body)) } } } func TestMain_ReportIncludesGeneratedAt(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "generated-at.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } if !strings.Contains(string(body), "\"generated_at\"") { t.Fatalf("expected generated_at in json: %s", string(body)) } } func TestMain_ReportIncludesMode(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "mode.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } if !strings.Contains(string(body), "\"mode\": \"warn\"") { t.Fatalf("expected warn mode in json: %s", string(body)) } } func TestMain_ReportIncludesArtifactsPaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "artifacts.json") mdOut := filepath.Join(repoRoot, "test-results", "artifacts.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json: %v", err) } if !strings.Contains(string(body), "\"artifacts\"") { t.Fatalf("expected artifacts object in json: %s", string(body)) } } func TestMain_FailsWhenGitRepoNotInitialized(t *testing.T) { repoRoot := t.TempDir() if err := os.MkdirAll(filepath.Join(repoRoot, "backend"), 0o750); err != nil { t.Fatalf("mkdir backend: %v", err) } if err := os.MkdirAll(filepath.Join(repoRoot, "frontend", "coverage"), 0o750); err != nil { t.Fatalf("mkdir frontend: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,1.2 1 1\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), []byte("TN:\nSF:frontend/src/sample.ts\nDA:1,1\nend_of_record\n"), 0o600); err != nil { t.Fatalf("write frontend lcov: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected failure when repo is not initialized") } if !strings.Contains(result.stderr, "error generating git diff") { t.Fatalf("expected git diff error, got: %s", result.stderr) } } func TestMain_WritesWarningsToJSONWhenPresent(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "internal", "sample.go"), []byte("package internal\nvar Sample = 8\n"), 0o600); err != nil { t.Fatalf("update backend source: %v", err) } if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte("mode: atomic\nbackend/internal/sample.go:1.1,2.20 1 0\n"), 0o600); err != nil { t.Fatalf("write backend coverage: %v", err) } jsonOut := filepath.Join(repoRoot, "test-results", "warnings.json") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD", "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success with warnings: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read warnings json: %v", err) } if !strings.Contains(string(body), "\"warnings\"") { t.Fatalf("expected warnings array in json: %s", string(body)) } } func TestMain_CreatesOutputDirectoriesRecursively(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "nested", "json", "report.json") mdOut := filepath.Join(repoRoot, "nested", "md", "report.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-json-out", jsonOut, "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } if _, err := os.Stat(jsonOut); err != nil { t.Fatalf("expected json output to exist: %v", err) } if _, err := os.Stat(mdOut); err != nil { t.Fatalf("expected markdown output to exist: %v", err) } } func TestMain_ReportMarkdownIncludesInputs(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdOut := filepath.Join(repoRoot, "test-results", "inputs.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(mdOut) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "- Backend coverage:") || !strings.Contains(string(body), "- Frontend coverage:") { t.Fatalf("expected inputs section in markdown: %s", string(body)) } } func TestMain_ReportMarkdownIncludesThresholdTable(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdOut := filepath.Join(repoRoot, "test-results", "thresholds.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(mdOut) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "## Resolved Thresholds") { t.Fatalf("expected thresholds section in markdown: %s", string(body)) } } func TestMain_ReportMarkdownIncludesCoverageSummary(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdOut := filepath.Join(repoRoot, "test-results", "summary.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(mdOut) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "## Coverage Summary") { t.Fatalf("expected coverage summary section in markdown: %s", string(body)) } } func TestMain_ReportMarkdownIncludesArtifactsSection(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) mdOut := filepath.Join(repoRoot, "test-results", "artifacts.md") result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-md-out", mdOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(mdOut) if err != nil { t.Fatalf("read markdown: %v", err) } if !strings.Contains(string(body), "## Artifacts") { t.Fatalf("expected artifacts section in markdown: %s", string(body)) } } func TestMain_RepoRootAbsoluteAndRelativeCoveragePaths(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) absoluteBackend := filepath.Join(repoRoot, "backend", "coverage.txt") relativeFrontend := "frontend/coverage/lcov.info" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", "-backend-coverage", absoluteBackend, "-frontend-coverage", relativeFrontend, ) if result.exitCode != 0 { t.Fatalf("expected success with mixed path styles: %s", result.stderr) } } func TestMain_StderrContainsContextOnGitFailure(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "not-a-baseline", ) if result.exitCode == 0 { t.Fatalf("expected git failure") } if !strings.Contains(result.stderr, "error generating git diff") { t.Fatalf("expected context in stderr, got: %s", result.stderr) } } func TestMain_StderrContainsContextOnBackendParseFailure(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "backend", "coverage.txt"), []byte(strings.Repeat("x", 3*1024*1024)), 0o600); err != nil { t.Fatalf("write large backend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected backend parse failure") } if !strings.Contains(result.stderr, "error parsing backend coverage") { t.Fatalf("expected backend parse context, got: %s", result.stderr) } } func TestMain_StderrContainsContextOnFrontendParseFailure(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) if err := os.WriteFile(filepath.Join(repoRoot, "frontend", "coverage", "lcov.info"), []byte(strings.Repeat("y", 3*1024*1024)), 0o600); err != nil { t.Fatalf("write large frontend coverage: %v", err) } result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", "HEAD...HEAD", ) if result.exitCode == 0 { t.Fatalf("expected frontend parse failure") } if !strings.Contains(result.stderr, "error parsing frontend coverage") { t.Fatalf("expected frontend parse context, got: %s", result.stderr) } } func TestMain_UsesConfiguredBaselineInOutput(t *testing.T) { repoRoot := createGitRepoWithCoverageInputs(t) jsonOut := filepath.Join(repoRoot, "test-results", "baseline-output.json") baseline := "HEAD...HEAD" result := runMainSubprocess(t, "-repo-root", repoRoot, "-baseline", baseline, "-json-out", jsonOut, ) if result.exitCode != 0 { t.Fatalf("expected success: %s", result.stderr) } body, err := os.ReadFile(jsonOut) if err != nil { t.Fatalf("read json output: %v", err) } if !strings.Contains(string(body), fmt.Sprintf("\"baseline\": %q", baseline)) { t.Fatalf("expected baseline in output json, got: %s", string(body)) } }