ab3cb86b1c
CI / detect-changes (push) Successful in 5s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / secret-scan (push) Has been skipped
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / build-sha-integrity (push) Successful in 7s
CI / validate-canonical-controls (push) Successful in 5s
CI / loc-budget (push) Successful in 20s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Has been skipped
CI / test-go (push) Successful in 1m5s
CI / iace-gt-coverage (push) Successful in 17s
CI / test-python-backend (push) Has been skipped
CI / test-python-document-crawler (push) Has been skipped
CI / test-python-dsms-gateway (push) Has been skipped
The last edge of the compliance graph: what concrete, fresh evidence proves a framework control is met (config_export/test_report/sbom/audit_log/pentest/... from github/ci/scanner/manual_upload, with a freshness requirement). Seeded for all 7 accepted CRA->OWASP controls (Auth/Crypto/Logging). A graph test enforces connectivity: every accepted control must carry >=1 required evidence — no dangling node in Obligation -> Control -> Evidence. This is what will let the Advisor state "the CRA requirement is fulfilled" from present evidence, not from the mere existence of a document. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
85 lines
3.2 KiB
Go
85 lines
3.2 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|