package main import ( "strings" "testing" ) // A cut of govulncheck v1.8.0's JSON stream: GO-2026-0001 is called, // GO-2026-0002 only imported. const stream = `{"config": {"scanner_version": "v1.8.0", "go_version": "go1.26.8"}} {"osv": {"id": "GO-2026-0001", "summary": "Called in net/http"}} {"osv": {"id": "GO-2026-0002", "summary": "Imported only"}} {"finding": {"osv": "GO-2026-0001", "fixed_version": "v1.26.9", "trace": [{"module": "stdlib", "version": "v1.26.8"}]}} {"finding": {"osv": "GO-2026-0001", "fixed_version": "v1.26.9", "trace": [{"module": "stdlib", "version": "v1.26.8", "package": "net/http", "function": "Serve"}, {"module": "werkdock", "function": "main"}]}} {"finding": {"osv": "GO-2026-0002", "fixed_version": "v1.26.9", "trace": [{"module": "stdlib", "package": "mime"}]}} ` func TestParseMarksOnlyCalledVulnerabilities(t *testing.T) { vulns, err := parse(strings.NewReader(stream)) if err != nil { t.Fatal(err) } if v := vulns["GO-2026-0001"]; !v.Calls["stdlib@v1.26.8 net/http.Serve"] || len(v.Calls) != 1 || v.Fixed != "v1.26.9" || v.Summary != "Called in net/http" { t.Errorf("GO-2026-0001 = %+v, want the call of net/http.Serve in v1.26.8, fixed in v1.26.9, with its summary", v) } if v := vulns["GO-2026-0002"]; len(v.Calls) != 0 { t.Errorf("GO-2026-0002 = %+v, want not called", v) } } func called(id string, calls ...string) vuln { v := vuln{ID: id, Calls: map[string]bool{}} for _, c := range calls { v.Calls[c] = true } return v } func TestVerdictInFullFailsOnEveryCalledVulnerability(t *testing.T) { head := map[string]vuln{"A": called("A", "m@v1 p.F"), "B": {ID: "B"}} if got := ids(verdict(head, nil, nil)); got != "A" { t.Errorf("verdict = %q, want A", got) } } func TestVerdictWithBaselineFailsOnlyOnWhatTheBranchBrings(t *testing.T) { head := map[string]vuln{"OLD": called("OLD", "m@v1 p.F"), "NEW": called("NEW", "m@v1 p.G"), "NOWCALLED": called("NOWCALLED", "m@v1 p.H")} base := map[string]vuln{"OLD": called("OLD", "m@v1 p.F"), "NOWCALLED": {ID: "NOWCALLED"}} if got := ids(verdict(head, base, nil)); got != "NEW NOWCALLED" { t.Errorf("verdict = %q, want NEW NOWCALLED", got) } } func TestVerdictWithBaselineFailsOnAnotherCallOfTheSameAdvisory(t *testing.T) { base := map[string]vuln{"A": called("A", "m@v1 p.F")} for name, head := range map[string]vuln{ "another symbol": called("A", "m@v1 p.F", "m@v1 p.G"), "another affected version": called("A", "m@v2 p.F"), } { if got := ids(verdict(map[string]vuln{"A": head}, base, nil)); got != "A" { t.Errorf("%s: verdict = %q, want A", name, got) } } } func TestVerdictSkipsExceptions(t *testing.T) { head := map[string]vuln{"A": called("A", "m@v1 p.F")} if got := ids(verdict(head, nil, map[string]string{"A": "not reachable from a sandbox"})); got != "" { t.Errorf("verdict = %q, want none", got) } } func TestExceptionNeedsAReason(t *testing.T) { if _, err := parseExceptions(strings.NewReader("# comment\n\nGO-2026-0001\n")); err == nil { t.Error("an exception without a reason was accepted") } got, err := parseExceptions(strings.NewReader("GO-2026-0001 werkdock never parses multipart forms\n")) if err != nil || got["GO-2026-0001"] != "werkdock never parses multipart forms" { t.Errorf("parseExceptions = %v, %v", got, err) } } func ids(vs []vuln) string { var s []string for _, v := range vs { s = append(s, v.ID) } return strings.Join(s, " ") }