Files
sif/internal/finding/json_test.go
T
TigahandGitHub f570dceadc feat(report): add json findings report with -json (#313)
sarif and markdown were the only structured exports and both drop the
per-item detail the normalized finding model already holds. add a -json
flag that writes the run's findings as a json array, one object per
finding with its target, module, severity, key, title and evidence.

carry detection confidence onto the finding model too so framework
detections report their score. it rides into the json report and is
omitted for scanners that carry none. the -silent line format and the
snapshot on-disk shape stay unchanged, since the report uses a dedicated
json view rather than marshalling the finding struct directly.
2026-07-22 12:50:08 -07:00

64 lines
2.5 KiB
Go

/*
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
: :
: █▀ █ █▀▀ · Blazing-fast pentesting suite :
: ▄█ █ █▀ · BSD 3-Clause License :
: :
: (c) 2022-2026 vmfunc, xyzeva, :
: lunchcat alumni & contributors :
: :
·━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━·
*/
package finding
import (
"encoding/json"
"testing"
)
func TestJSONReportEmptyIsArray(t *testing.T) {
data, err := JSONReport(nil)
if err != nil {
t.Fatalf("JSONReport: %v", err)
}
if string(data) != "[]" {
t.Errorf("empty report = %q, want %q", string(data), "[]")
}
}
func TestJSONReportShapeAndFields(t *testing.T) {
findings := []Finding{
{Target: "https://x", Module: "framework", Severity: SeverityHigh, Key: "framework:Laravel", Title: "Laravel detected", Raw: "Laravel 9.0", Confidence: 0.82},
{Target: "https://x", Module: "headers", Severity: SeverityInfo, Key: "headers:Server", Title: "Server", Raw: "nginx"},
}
data, err := JSONReport(findings)
if err != nil {
t.Fatalf("JSONReport: %v", err)
}
var got []map[string]any
if err := json.Unmarshal(data, &got); err != nil {
t.Fatalf("report is not valid json: %v", err)
}
if len(got) != 2 {
t.Fatalf("got %d entries, want 2", len(got))
}
// framework finding: severity rendered as its string name, confidence present.
if got[0]["severity"] != "high" {
t.Errorf("severity = %v, want \"high\"", got[0]["severity"])
}
if got[0]["confidence"] != 0.82 {
t.Errorf("confidence = %v, want 0.82", got[0]["confidence"])
}
// recon finding: zero confidence is omitted rather than serialized as 0.
if _, ok := got[1]["confidence"]; ok {
t.Errorf("zero confidence should be omitted, got %v", got[1]["confidence"])
}
if got[1]["severity"] != "info" {
t.Errorf("severity = %v, want \"info\"", got[1]["severity"])
}
}