fdaf547b06
Registry materialized the generic CORE security objectives (#5b, Modell C), so the two broad NIST controls now point at their canonical parents instead of the domain-scoped matches: SI-7 -> software_integrity_protection (CORE, Annex I (2)(f)) CM-7 -> attack_surface_minimization (CORE, Annex I (2)(j)) Non-breaking: the domain-scoped obligations stay valid and specialize the CORE. SI-7 evidence = sbom + config_export (SBOM evidences component/supply-chain integrity; config = signing/secure-boot). Export proposed_obligation_id + handler test (2 CORE cases) updated. go test green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
98 lines
3.4 KiB
Go
98 lines
3.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func newComplianceGraphTestRouter(t *testing.T) *gin.Engine {
|
|
t.Helper()
|
|
gin.SetMode(gin.TestMode)
|
|
h := NewComplianceGraphHandlers()
|
|
if err := h.LoadError(); err != nil {
|
|
t.Fatalf("compliance graph failed to load (candidate paths): %v", err)
|
|
}
|
|
r := gin.New()
|
|
h.RegisterRoutes(r.Group("/sdk/v1"))
|
|
return r
|
|
}
|
|
|
|
func getObligationStatus(t *testing.T, r *gin.Engine, query string) (int, cgStatusResponse) {
|
|
t.Helper()
|
|
w := httptest.NewRecorder()
|
|
req, _ := http.NewRequest(http.MethodGet, "/sdk/v1/compliance/obligation-status"+query, nil)
|
|
r.ServeHTTP(w, req)
|
|
var resp cgStatusResponse
|
|
if w.Code == http.StatusOK {
|
|
if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
|
|
t.Fatalf("decode body %q: %v", w.Body.String(), err)
|
|
}
|
|
}
|
|
return w.Code, resp
|
|
}
|
|
|
|
func TestObligationStatus(t *testing.T) {
|
|
r := newComplianceGraphTestRouter(t)
|
|
|
|
tests := []struct {
|
|
name string
|
|
query string
|
|
wantHTTP int
|
|
wantOverall string
|
|
wantControls bool // expect >=1 control
|
|
}{
|
|
{"missing param -> 400", "", http.StatusBadRequest, "", false},
|
|
{"unknown id -> unknown_obligation", "?obligation_id=does_not_exist", http.StatusOK, "unknown_obligation", false},
|
|
{"mapped (OWASP V6) -> not_assessed", "?obligation_id=user_authentication_required", http.StatusOK, "not_assessed", true},
|
|
{"NIST adopted (SI-2) -> not_assessed", "?obligation_id=provide_security_updates", http.StatusOK, "not_assessed", true},
|
|
{"CORE attack_surface_minimization -> CM-7", "?obligation_id=attack_surface_minimization", http.StatusOK, "not_assessed", true},
|
|
{"CORE software_integrity_protection -> SI-7", "?obligation_id=software_integrity_protection", http.StatusOK, "not_assessed", true},
|
|
{"in registry, no control -> unmapped", "?obligation_id=sbom_creation", http.StatusOK, "unmapped", false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
code, resp := getObligationStatus(t, r, tt.query)
|
|
if code != tt.wantHTTP {
|
|
t.Fatalf("http %d, want %d", code, tt.wantHTTP)
|
|
}
|
|
if tt.wantHTTP != http.StatusOK {
|
|
return
|
|
}
|
|
if resp.OverallStatus != tt.wantOverall {
|
|
t.Errorf("overall_status=%q, want %q", resp.OverallStatus, tt.wantOverall)
|
|
}
|
|
if tt.wantControls && len(resp.Controls) == 0 {
|
|
t.Error("expected >=1 control")
|
|
}
|
|
if !tt.wantControls && len(resp.Controls) != 0 {
|
|
t.Errorf("expected 0 controls, got %d", len(resp.Controls))
|
|
}
|
|
if resp.CitationSpans != "pending" {
|
|
t.Errorf("citation_spans=%q, want pending", resp.CitationSpans)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The MVP must NEVER auto-assert fulfillment: with no evidence collection wired, every required
|
|
// evidence is "missing" and the overall status stays "not_assessed".
|
|
func TestObligationStatus_NoFulfillmentClaim(t *testing.T) {
|
|
r := newComplianceGraphTestRouter(t)
|
|
code, resp := getObligationStatus(t, r, "?obligation_id=user_authentication_required")
|
|
if code != http.StatusOK {
|
|
t.Fatalf("http %d", code)
|
|
}
|
|
if resp.OverallStatus == "met" || resp.OverallStatus == "erfuellt" {
|
|
t.Fatalf("MVP must not assert fulfillment, got overall_status=%q", resp.OverallStatus)
|
|
}
|
|
for _, ctl := range resp.Controls {
|
|
if len(ctl.EvidenceRequired) > 0 && ctl.EvidenceStatus != "missing" {
|
|
t.Errorf("control %s/%s evidence_status=%q, want missing (no collection wired)", ctl.Framework, ctl.Control, ctl.EvidenceStatus)
|
|
}
|
|
}
|
|
}
|