test(06-02): add failing tests for JSONFormatter
This commit is contained in:
149
pkg/output/json_test.go
Normal file
149
pkg/output/json_test.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package output
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/salvacybersec/keyhunter/pkg/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSONFormatter_EmptyFindings(t *testing.T) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := (JSONFormatter{}).Format(nil, &buf, Options{}); err != nil {
|
||||||
|
t.Fatalf("Format returned error: %v", err)
|
||||||
|
}
|
||||||
|
got := buf.String()
|
||||||
|
if got != "[]\n" {
|
||||||
|
t.Fatalf("empty findings: want %q, got %q", "[]\n", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFormatter_RegisteredUnderJSON(t *testing.T) {
|
||||||
|
f, err := Get("json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get(\"json\") error: %v", err)
|
||||||
|
}
|
||||||
|
if _, ok := f.(JSONFormatter); !ok {
|
||||||
|
t.Fatalf("Get(\"json\") returned %T, want JSONFormatter", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sampleFinding() engine.Finding {
|
||||||
|
return engine.Finding{
|
||||||
|
ProviderName: "openai",
|
||||||
|
KeyValue: "sk-proj-abcdef1234567890XYZW",
|
||||||
|
KeyMasked: "sk-proj-...XYZW",
|
||||||
|
Confidence: "high",
|
||||||
|
Source: "src/config.go",
|
||||||
|
SourceType: "file",
|
||||||
|
LineNumber: 42,
|
||||||
|
Offset: 1024,
|
||||||
|
DetectedAt: time.Date(2026, 4, 5, 12, 30, 0, 0, time.UTC),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFormatter_MaskedByDefault(t *testing.T) {
|
||||||
|
f := sampleFinding()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := (JSONFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
|
||||||
|
t.Fatalf("Format returned error: %v", err)
|
||||||
|
}
|
||||||
|
var out []map[string]any
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &out); err != nil {
|
||||||
|
t.Fatalf("invalid JSON: %v\n%s", err, buf.String())
|
||||||
|
}
|
||||||
|
if len(out) != 1 {
|
||||||
|
t.Fatalf("want 1 element, got %d", len(out))
|
||||||
|
}
|
||||||
|
if out[0]["key"] != f.KeyMasked {
|
||||||
|
t.Errorf("key: want %q (masked), got %v", f.KeyMasked, out[0]["key"])
|
||||||
|
}
|
||||||
|
if out[0]["key_masked"] != f.KeyMasked {
|
||||||
|
t.Errorf("key_masked: want %q, got %v", f.KeyMasked, out[0]["key_masked"])
|
||||||
|
}
|
||||||
|
if out[0]["provider"] != "openai" {
|
||||||
|
t.Errorf("provider: got %v", out[0]["provider"])
|
||||||
|
}
|
||||||
|
if out[0]["confidence"] != "high" {
|
||||||
|
t.Errorf("confidence: got %v", out[0]["confidence"])
|
||||||
|
}
|
||||||
|
if out[0]["line"].(float64) != 42 {
|
||||||
|
t.Errorf("line: got %v", out[0]["line"])
|
||||||
|
}
|
||||||
|
if out[0]["detected_at"] != "2026-04-05T12:30:00Z" {
|
||||||
|
t.Errorf("detected_at: got %v", out[0]["detected_at"])
|
||||||
|
}
|
||||||
|
if out[0]["verified"] != false {
|
||||||
|
t.Errorf("verified: got %v", out[0]["verified"])
|
||||||
|
}
|
||||||
|
// Unverified: omitempty fields should not appear.
|
||||||
|
if _, ok := out[0]["verify_status"]; ok {
|
||||||
|
t.Errorf("verify_status should be omitted when empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFormatter_UnmaskRevealsKey(t *testing.T) {
|
||||||
|
f := sampleFinding()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := (JSONFormatter{}).Format([]engine.Finding{f}, &buf, Options{Unmask: true}); err != nil {
|
||||||
|
t.Fatalf("Format returned error: %v", err)
|
||||||
|
}
|
||||||
|
var out []map[string]any
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &out); err != nil {
|
||||||
|
t.Fatalf("invalid JSON: %v", err)
|
||||||
|
}
|
||||||
|
if out[0]["key"] != f.KeyValue {
|
||||||
|
t.Errorf("key: want %q (unmasked), got %v", f.KeyValue, out[0]["key"])
|
||||||
|
}
|
||||||
|
if out[0]["key_masked"] != f.KeyMasked {
|
||||||
|
t.Errorf("key_masked should remain masked, got %v", out[0]["key_masked"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFormatter_VerifyFieldsPresent(t *testing.T) {
|
||||||
|
f := sampleFinding()
|
||||||
|
f.Verified = true
|
||||||
|
f.VerifyStatus = "live"
|
||||||
|
f.VerifyHTTPCode = 200
|
||||||
|
f.VerifyMetadata = map[string]string{"org": "acme"}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := (JSONFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
|
||||||
|
t.Fatalf("Format returned error: %v", err)
|
||||||
|
}
|
||||||
|
var out []map[string]any
|
||||||
|
if err := json.Unmarshal(buf.Bytes(), &out); err != nil {
|
||||||
|
t.Fatalf("invalid JSON: %v", err)
|
||||||
|
}
|
||||||
|
if out[0]["verified"] != true {
|
||||||
|
t.Errorf("verified: got %v", out[0]["verified"])
|
||||||
|
}
|
||||||
|
if out[0]["verify_status"] != "live" {
|
||||||
|
t.Errorf("verify_status: got %v", out[0]["verify_status"])
|
||||||
|
}
|
||||||
|
if out[0]["verify_http_code"].(float64) != 200 {
|
||||||
|
t.Errorf("verify_http_code: got %v", out[0]["verify_http_code"])
|
||||||
|
}
|
||||||
|
meta, ok := out[0]["verify_metadata"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("verify_metadata missing or wrong type: %v", out[0]["verify_metadata"])
|
||||||
|
}
|
||||||
|
if meta["org"] != "acme" {
|
||||||
|
t.Errorf("verify_metadata.org: got %v", meta["org"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFormatter_Indented(t *testing.T) {
|
||||||
|
f := sampleFinding()
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := (JSONFormatter{}).Format([]engine.Finding{f}, &buf, Options{}); err != nil {
|
||||||
|
t.Fatalf("Format returned error: %v", err)
|
||||||
|
}
|
||||||
|
// Expect 2-space indent on at least one property line.
|
||||||
|
if !strings.Contains(buf.String(), "\n \"provider\"") {
|
||||||
|
t.Errorf("output not indented with 2 spaces:\n%s", buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user