package ucca import ( "os" "path/filepath" "testing" ) func TestEvidenceRequirement_Validate(t *testing.T) { valid := EvidenceRequirement{Framework: "OWASP ASVS", Control: "V6.3.1", EvidenceType: "config_export", EvidenceSource: "github", FreshnessRequirement: "per_release", Required: true} if err := valid.Validate(); err != nil { t.Fatalf("valid rejected: %v", err) } bad := []struct { name string e EvidenceRequirement }{ {"no control", EvidenceRequirement{Framework: "X", EvidenceType: "sbom", EvidenceSource: "ci", FreshnessRequirement: "per_release"}}, {"bad evidence_type", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "screenshot", EvidenceSource: "ci", FreshnessRequirement: "per_release"}}, {"bad evidence_source", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "sbom", EvidenceSource: "email", FreshnessRequirement: "per_release"}}, {"bad freshness", EvidenceRequirement{Framework: "X", Control: "Y", EvidenceType: "sbom", EvidenceSource: "ci", FreshnessRequirement: "weekly"}}, } for _, tt := range bad { if err := tt.e.Validate(); err == nil { t.Errorf("%s: expected rejection", tt.name) } } } func TestLoadEvidenceRequirements(t *testing.T) { dir := t.TempDir() content := `// header {"framework":"OWASP ASVS","control":"V6.3.1","evidence_type":"config_export","evidence_source":"github","freshness_requirement":"per_release","required":true,"version":"2026-06-25"} {"framework":"OWASP ASVS","control":"V6.3.1","evidence_type":"pentest","evidence_source":"manual_upload","freshness_requirement":"annually","required":false,"version":"2026-06-25"} ` if err := os.WriteFile(filepath.Join(dir, "e.jsonl"), []byte(content), 0o644); err != nil { t.Fatal(err) } set, err := LoadEvidenceRequirements(dir) if err != nil { t.Fatalf("load: %v", err) } if len(set.All) != 2 { t.Fatalf("want 2, got %d", len(set.All)) } if got := set.For("OWASP ASVS", "V6.3.1"); len(got) != 2 { t.Errorf("For: want 2, got %d", len(got)) } if got := set.RequiredFor("OWASP ASVS", "V6.3.1"); len(got) != 1 { t.Errorf("RequiredFor: want 1 (pentest is optional), got %d", len(got)) } } func TestEvidenceRequirements_SeedFileValid(t *testing.T) { set, err := LoadEvidenceRequirements("../../data/evidence_requirements") if err != nil { t.Fatalf("seed evidence_requirements failed to load: %v", err) } if len(set.All) == 0 { t.Fatal("seed evidence_requirements is empty") } } // TestGraph_AcceptedControlsHaveEvidence wires the two layers: every control an accepted // CRA->OWASP mapping points to must have >=1 required evidence — the Obligation -> Control -> // Evidence chain must be connected, no dangling control nodes. func TestGraph_AcceptedControlsHaveEvidence(t *testing.T) { maps, err := LoadControlMappings("../../data/control_mappings") if err != nil { t.Fatal(err) } ev, err := LoadEvidenceRequirements("../../data/evidence_requirements") if err != nil { t.Fatal(err) } for _, m := range maps.All { if !m.IsAccepted() { continue } if len(ev.RequiredFor(m.TargetFramework, m.TargetControl)) == 0 { t.Errorf("accepted control %s %s has no required evidence (dangling graph node)", m.TargetFramework, m.TargetControl) } } }