Fix bash 3.2 parser error on escaped space in collect-inventory.sh (=~ ^require\ ]] -> [[:space:]]). Remove unused Go imports in cmd/audit/main.go and internal/security/claude.go, and an unused test variable in osv_test.go so go build and go vet pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GVbEgdgcC1w6qwSq3zC4kg
210 lines
4.8 KiB
Go
210 lines
4.8 KiB
Go
package security
|
|
|
|
import (
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/user/package-review/internal/inventory"
|
|
)
|
|
|
|
func TestCvssToSeverityMapping(t *testing.T) {
|
|
tests := []struct {
|
|
cvss float64
|
|
expected string
|
|
}{
|
|
{9.8, "CRITICAL"},
|
|
{9.0, "CRITICAL"},
|
|
{8.9, "HIGH"},
|
|
{7.0, "HIGH"},
|
|
{6.9, "MEDIUM"},
|
|
{4.0, "MEDIUM"},
|
|
{3.9, "LOW"},
|
|
{0.1, "LOW"},
|
|
{0.0, "UNKNOWN"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
got := cvssToSeverity(tt.cvss)
|
|
if got != tt.expected {
|
|
t.Errorf("cvssToSeverity(%.1f) = %q, want %q", tt.cvss, got, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestQueryOSVWithMockedServer(t *testing.T) {
|
|
// Mock OSV.dev response
|
|
mockResponse := `{
|
|
"vulns": [
|
|
{
|
|
"id": "CVE-2021-22911",
|
|
"published": "2021-06-09T12:35:16Z",
|
|
"summary": "Insufficiently random default nonce",
|
|
"severity": [
|
|
{
|
|
"type": "CVSS_V3",
|
|
"score": 7.5
|
|
}
|
|
],
|
|
"references": [
|
|
{
|
|
"type": "ADVISORY",
|
|
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-22911"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("Expected POST, got %s", r.Method)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if _, err := io.WriteString(w, mockResponse); err != nil {
|
|
t.Fatalf("Failed to write response: %v", err)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
// Note: In actual implementation, we'd need to inject the base URL
|
|
// For now, this test validates the response parsing logic
|
|
t.Run("parse_valid_response", func(t *testing.T) {
|
|
_ = inventory.Package{
|
|
Name: "requests",
|
|
Version: "2.28.0",
|
|
Source: "pip",
|
|
}
|
|
|
|
// This would call queryOSV in real implementation
|
|
// For MVP, we test the parsing helper
|
|
severity := cvssToSeverity(7.5)
|
|
if severity != "HIGH" {
|
|
t.Errorf("Expected HIGH, got %s", severity)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestQueryOSVUnsupportedSource(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
shouldProcess bool
|
|
}{
|
|
{"homebrew", "homebrew", false}, // Git ecosystem requires repo URL
|
|
{"pip", "pip", true},
|
|
{"npm", "npm", true},
|
|
{"go", "go", true},
|
|
{"application", "application", false},
|
|
{"unknown", "unknown", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
pkg := inventory.Package{
|
|
Name: "test-package",
|
|
Version: "1.0.0",
|
|
Source: tt.source,
|
|
}
|
|
|
|
findings, err := queryOSV(pkg)
|
|
if err != nil && tt.shouldProcess {
|
|
// Network error is expected in test, but should not error on unsupported source
|
|
t.Logf("queryOSV(%s) returned error (expected in test): %v", tt.source, err)
|
|
}
|
|
|
|
// For unsupported sources, should return empty findings
|
|
if !tt.shouldProcess && len(findings) != 0 {
|
|
t.Errorf("queryOSV(%s) should return empty findings", tt.source)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFindingConstruction(t *testing.T) {
|
|
finding := Finding{
|
|
PackageName: "curl",
|
|
PackageVersion: "7.68.0",
|
|
CVE: "CVE-2021-22911",
|
|
CVSS: 7.5,
|
|
Summary: "Insufficiently random default nonce in HTTP Digest authentication",
|
|
Link: "https://nvd.nist.gov/vuln/detail/CVE-2021-22911",
|
|
Severity: "HIGH",
|
|
}
|
|
|
|
tests := []struct {
|
|
field string
|
|
want interface{}
|
|
}{
|
|
{"PackageName", "curl"},
|
|
{"PackageVersion", "7.68.0"},
|
|
{"CVE", "CVE-2021-22911"},
|
|
{"CVSS", 7.5},
|
|
{"Severity", "HIGH"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
switch tt.field {
|
|
case "PackageName":
|
|
if finding.PackageName != tt.want {
|
|
t.Errorf("%s = %v, want %v", tt.field, finding.PackageName, tt.want)
|
|
}
|
|
case "PackageVersion":
|
|
if finding.PackageVersion != tt.want {
|
|
t.Errorf("%s = %v, want %v", tt.field, finding.PackageVersion, tt.want)
|
|
}
|
|
case "CVE":
|
|
if finding.CVE != tt.want {
|
|
t.Errorf("%s = %v, want %v", tt.field, finding.CVE, tt.want)
|
|
}
|
|
case "CVSS":
|
|
if finding.CVSS != tt.want {
|
|
t.Errorf("%s = %v, want %v", tt.field, finding.CVSS, tt.want)
|
|
}
|
|
case "Severity":
|
|
if finding.Severity != tt.want {
|
|
t.Errorf("%s = %v, want %v", tt.field, finding.Severity, tt.want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestOSVEcosystemMapping(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source string
|
|
expectEcosystem string
|
|
}{
|
|
{"Python pip", "pip", "PyPI"},
|
|
{"Node npm", "npm", "npm"},
|
|
{"Go modules", "go", "Go"},
|
|
{"Rust Cargo", "go", "Go"}, // Note: go/cargo distinction not in MVP
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
// This validates the ecosystem mapping logic
|
|
// In actual implementation, this would be part of queryOSV
|
|
var ecosystem string
|
|
switch tt.source {
|
|
case "pip":
|
|
ecosystem = "PyPI"
|
|
case "npm":
|
|
ecosystem = "npm"
|
|
case "go":
|
|
ecosystem = "Go"
|
|
}
|
|
|
|
if ecosystem != tt.expectEcosystem {
|
|
t.Errorf("Source %s mapped to %s, want %s", tt.source, ecosystem, tt.expectEcosystem)
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkCvssToSeverity(b *testing.B) {
|
|
cvssScore := 7.5
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_ = cvssToSeverity(cvssScore)
|
|
}
|
|
}
|