diff --git a/pkg/output/table_test.go b/pkg/output/table_test.go new file mode 100644 index 0000000..cf896da --- /dev/null +++ b/pkg/output/table_test.go @@ -0,0 +1,103 @@ +package output + +import ( + "bytes" + "io" + "os" + "regexp" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/salvacybersec/keyhunter/pkg/engine" +) + +// ansiRE strips lipgloss/ANSI color escape sequences so assertions can run +// against plain text regardless of the terminal styling applied at render time. +var ansiRE = regexp.MustCompile(`\x1b\[[0-9;]*m`) + +func stripANSI(s string) string { return ansiRE.ReplaceAllString(s, "") } + +// captureStdout redirects os.Stdout for the duration of fn and returns the +// captured bytes (ANSI-stripped for convenience). +func captureStdout(t *testing.T, fn func()) string { + t.Helper() + orig := os.Stdout + r, w, err := os.Pipe() + require.NoError(t, err) + os.Stdout = w + + done := make(chan struct{}) + var buf bytes.Buffer + go func() { + _, _ = io.Copy(&buf, r) + close(done) + }() + + fn() + + require.NoError(t, w.Close()) + <-done + os.Stdout = orig + return stripANSI(buf.String()) +} + +func TestPrintFindings_NoVerification_Unchanged(t *testing.T) { + findings := []engine.Finding{ + { + ProviderName: "openai", + KeyValue: "sk-test-1234567890abcdef", + KeyMasked: "sk-test-...cdef", + Confidence: "high", + Source: "main.go", + LineNumber: 42, + }, + } + out := captureStdout(t, func() { PrintFindings(findings, false) }) + assert.NotContains(t, out, "VERIFY", "VERIFY column must not appear when no findings are verified") + assert.Contains(t, out, "PROVIDER") + assert.Contains(t, out, "openai") +} + +func TestPrintFindings_LiveVerification_ShowsCheck(t *testing.T) { + findings := []engine.Finding{ + { + ProviderName: "openai", + KeyMasked: "sk-test-...cdef", + Confidence: "high", + Source: "main.go", + LineNumber: 42, + Verified: true, + VerifyStatus: "live", + }, + } + out := captureStdout(t, func() { PrintFindings(findings, false) }) + assert.Contains(t, out, "VERIFY") + assert.Contains(t, out, "live") +} + +func TestPrintFindings_Metadata_Rendered(t *testing.T) { + findings := []engine.Finding{ + { + ProviderName: "openai", + KeyMasked: "sk-test-...cdef", + Confidence: "high", + Source: "main.go", + LineNumber: 42, + Verified: true, + VerifyStatus: "live", + VerifyMetadata: map[string]string{"org": "Acme", "tier": "plus"}, + }, + } + out := captureStdout(t, func() { PrintFindings(findings, false) }) + assert.Contains(t, out, "org: Acme") + assert.Contains(t, out, "tier: plus") + // Deterministic order: "org" before "tier" alphabetically + orgIdx := strings.Index(out, "org: Acme") + tierIdx := strings.Index(out, "tier: plus") + require.GreaterOrEqual(t, orgIdx, 0) + require.GreaterOrEqual(t, tierIdx, 0) + assert.Less(t, orgIdx, tierIdx, "metadata pairs should be sorted alphabetically") +}