- GitleaksImporter parses native JSON array output to []engine.Finding - GitleaksCSVImporter parses CSV with header-based column resolution - normalizeGitleaksRuleID strips suffixes (-api-key, -access-token, ...) - Shared buildGitleaksFinding helper keeps JSON/CSV paths in lockstep - Test fixtures + 8 tests covering happy path, empty, invalid, symlink fallback
160 lines
5.1 KiB
Go
160 lines
5.1 KiB
Go
package importer
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func loadFixture(t *testing.T, name string) []byte {
|
|
t.Helper()
|
|
data, err := os.ReadFile("testdata/" + name)
|
|
if err != nil {
|
|
t.Fatalf("read fixture %s: %v", name, err)
|
|
}
|
|
return data
|
|
}
|
|
|
|
func TestGitleaksImporter_Name(t *testing.T) {
|
|
if (GitleaksImporter{}).Name() != "gitleaks" {
|
|
t.Errorf("GitleaksImporter.Name() = %q, want %q", (GitleaksImporter{}).Name(), "gitleaks")
|
|
}
|
|
if (GitleaksCSVImporter{}).Name() != "gitleaks-csv" {
|
|
t.Errorf("GitleaksCSVImporter.Name() = %q, want %q", (GitleaksCSVImporter{}).Name(), "gitleaks-csv")
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_JSON(t *testing.T) {
|
|
data := loadFixture(t, "gitleaks-sample.json")
|
|
findings, err := (GitleaksImporter{}).Import(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("Import: %v", err)
|
|
}
|
|
if len(findings) != 3 {
|
|
t.Fatalf("expected 3 findings, got %d", len(findings))
|
|
}
|
|
if findings[0].ProviderName != "openai" {
|
|
t.Errorf("findings[0].ProviderName = %q, want %q", findings[0].ProviderName, "openai")
|
|
}
|
|
if findings[0].KeyValue != "sk-proj-1234567890abcdef1234" {
|
|
t.Errorf("findings[0].KeyValue mismatch: %q", findings[0].KeyValue)
|
|
}
|
|
if findings[0].Source != "config/app.yml" {
|
|
t.Errorf("findings[0].Source = %q", findings[0].Source)
|
|
}
|
|
if findings[0].LineNumber != 12 {
|
|
t.Errorf("findings[0].LineNumber = %d, want 12", findings[0].LineNumber)
|
|
}
|
|
if findings[0].SourceType != "import:gitleaks" {
|
|
t.Errorf("findings[0].SourceType = %q", findings[0].SourceType)
|
|
}
|
|
if findings[0].Confidence != "medium" {
|
|
t.Errorf("findings[0].Confidence = %q, want medium", findings[0].Confidence)
|
|
}
|
|
if findings[0].VerifyStatus != "unverified" {
|
|
t.Errorf("findings[0].VerifyStatus = %q, want unverified", findings[0].VerifyStatus)
|
|
}
|
|
if findings[0].Verified {
|
|
t.Errorf("findings[0].Verified should be false")
|
|
}
|
|
if findings[0].KeyMasked == "" {
|
|
t.Errorf("findings[0].KeyMasked should be set")
|
|
}
|
|
if findings[1].ProviderName != "aws" {
|
|
t.Errorf("findings[1].ProviderName = %q, want aws", findings[1].ProviderName)
|
|
}
|
|
if findings[1].LineNumber != 55 {
|
|
t.Errorf("findings[1].LineNumber = %d, want 55", findings[1].LineNumber)
|
|
}
|
|
if findings[2].ProviderName != "generic" {
|
|
t.Errorf("findings[2].ProviderName = %q, want generic", findings[2].ProviderName)
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_CSV(t *testing.T) {
|
|
data := loadFixture(t, "gitleaks-sample.csv")
|
|
findings, err := (GitleaksCSVImporter{}).Import(bytes.NewReader(data))
|
|
if err != nil {
|
|
t.Fatalf("Import: %v", err)
|
|
}
|
|
if len(findings) != 3 {
|
|
t.Fatalf("expected 3 findings, got %d", len(findings))
|
|
}
|
|
if findings[0].ProviderName != "openai" {
|
|
t.Errorf("findings[0].ProviderName = %q, want openai", findings[0].ProviderName)
|
|
}
|
|
if findings[0].KeyValue != "sk-proj-1234567890abcdef1234" {
|
|
t.Errorf("findings[0].KeyValue = %q", findings[0].KeyValue)
|
|
}
|
|
if findings[0].Source != "config/app.yml" {
|
|
t.Errorf("findings[0].Source = %q", findings[0].Source)
|
|
}
|
|
if findings[0].LineNumber != 12 {
|
|
t.Errorf("findings[0].LineNumber = %d, want 12", findings[0].LineNumber)
|
|
}
|
|
if findings[1].ProviderName != "aws" {
|
|
t.Errorf("findings[1].ProviderName = %q, want aws", findings[1].ProviderName)
|
|
}
|
|
if findings[2].ProviderName != "generic" {
|
|
t.Errorf("findings[2].ProviderName = %q, want generic", findings[2].ProviderName)
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_NormalizeRuleID(t *testing.T) {
|
|
cases := []struct{ in, out string }{
|
|
{"openai-api-key", "openai"},
|
|
{"aws-access-token", "aws"},
|
|
{"anthropic-api-key", "anthropic"},
|
|
{"generic-api-key", "generic"},
|
|
{"github-pat", "github-pat"},
|
|
{"Some-Secret", "some"},
|
|
{"AWS-Access-Token", "aws"},
|
|
}
|
|
for _, c := range cases {
|
|
got := normalizeGitleaksRuleID(c.in)
|
|
if got != c.out {
|
|
t.Errorf("normalizeGitleaksRuleID(%q) = %q, want %q", c.in, got, c.out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_EmptyArray(t *testing.T) {
|
|
findings, err := (GitleaksImporter{}).Import(strings.NewReader("[]"))
|
|
if err != nil {
|
|
t.Fatalf("Import: %v", err)
|
|
}
|
|
if len(findings) != 0 {
|
|
t.Errorf("expected 0 findings, got %d", len(findings))
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_EmptyCSV(t *testing.T) {
|
|
header := "RuleID,Commit,File,SymlinkFile,Secret,Match,StartLine,EndLine,StartColumn,EndColumn,Author,Message,Date,Email,Fingerprint,Tags\n"
|
|
findings, err := (GitleaksCSVImporter{}).Import(strings.NewReader(header))
|
|
if err != nil {
|
|
t.Fatalf("Import: %v", err)
|
|
}
|
|
if len(findings) != 0 {
|
|
t.Errorf("expected 0 findings, got %d", len(findings))
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_InvalidJSON(t *testing.T) {
|
|
_, err := (GitleaksImporter{}).Import(strings.NewReader("{not json"))
|
|
if err == nil {
|
|
t.Errorf("expected error for invalid JSON")
|
|
}
|
|
}
|
|
|
|
func TestGitleaksImporter_SymlinkFallback(t *testing.T) {
|
|
jsonInput := `[{"RuleID":"openai-api-key","Secret":"sk-proj-1234567890abcdef1234","File":"","SymlinkFile":"link/config.yml","StartLine":1}]`
|
|
findings, err := (GitleaksImporter{}).Import(strings.NewReader(jsonInput))
|
|
if err != nil {
|
|
t.Fatalf("Import: %v", err)
|
|
}
|
|
if len(findings) != 1 || findings[0].Source != "link/config.yml" {
|
|
t.Errorf("expected symlink fallback source, got %+v", findings)
|
|
}
|
|
}
|