Files
breakpilot-compliance/ai-compliance-sdk/internal/ucca/pattern_loader_test.go
Benjamin Admin 825e070ed9
Some checks failed
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Failing after 47s
CI/CD / test-python-backend-compliance (push) Successful in 33s
CI/CD / test-python-document-crawler (push) Successful in 24s
CI/CD / test-python-dsms-gateway (push) Successful in 18s
CI/CD / validate-canonical-controls (push) Successful in 11s
CI/CD / Deploy (push) Has been skipped
feat(multi-layer): complete Multi-Layer Control Architecture (Phases 1-8 + Pass 0)
Implements the full Multi-Layer Control Architecture for migrating ~25,000
Rich Controls into atomic, deduplicated Master Controls with full traceability.

Architecture: Legal Source → Obligation → Control Pattern → Master Control → Customer Instance

New services:
- ObligationExtractor: 3-tier extraction (exact → embedding → LLM)
- PatternMatcher: 2-tier matching (keyword + embedding + domain-bonus)
- ControlComposer: Pattern + Obligation → Master Control
- PipelineAdapter: Pipeline integration + Migration Passes 1-5
- DecompositionPass: Pass 0a/0b — Rich Control → atomic Controls
- CrosswalkRoutes: 15 API endpoints under /v1/canonical/

New DB schema:
- Migration 060: obligation_extractions, control_patterns, crosswalk_matrix
- Migration 061: obligation_candidates, parent_control_uuid tracking

Pattern Library: 50 YAML patterns (30 core + 20 IT-security)
Go SDK: Pattern loader with YAML validation and indexing
Documentation: MkDocs updated with full architecture overview

500 Python tests passing across all components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 09:00:37 +01:00

385 lines
9.3 KiB
Go

package ucca
import (
"strings"
"testing"
)
func TestLoadControlPatterns_ValidFiles(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Expected no error, got %v", err)
}
if idx == nil {
t.Fatal("Expected non-nil index")
}
if len(idx.All) != 50 {
t.Errorf("Expected 50 patterns, got %d", len(idx.All))
}
}
func TestLoadControlPatterns_NoDuplicateIDs(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
seen := make(map[string]bool)
for _, p := range idx.All {
if seen[p.ID] {
t.Errorf("Duplicate pattern ID: %s", p.ID)
}
seen[p.ID] = true
}
}
func TestControlPatternIndex_GetPattern(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
tests := []struct {
name string
id string
expected bool
}{
{"existing pattern CP-AUTH-001", "CP-AUTH-001", true},
{"existing pattern CP-CRYP-001", "CP-CRYP-001", true},
{"lowercase lookup", "cp-auth-001", true},
{"non-existing pattern", "CP-FAKE-999", false},
{"empty id", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p, ok := idx.GetPattern(tt.id)
if ok != tt.expected {
t.Errorf("GetPattern(%q): expected found=%v, got found=%v", tt.id, tt.expected, ok)
}
if ok && p.ID == "" {
t.Error("Pattern found but has empty ID")
}
})
}
}
func TestControlPatternIndex_GetPatternsByDomain(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
tests := []struct {
domain string
minCount int
}{
{"AUTH", 3},
{"CRYP", 3},
{"DATA", 5},
{"SEC", 3},
{"COMP", 5},
{"LOG", 2},
{"INC", 3},
{"AI", 2},
}
for _, tt := range tests {
t.Run(tt.domain, func(t *testing.T) {
patterns := idx.GetPatternsByDomain(tt.domain)
if len(patterns) < tt.minCount {
t.Errorf("Domain %s: expected at least %d patterns, got %d",
tt.domain, tt.minCount, len(patterns))
}
})
}
emptyPatterns := idx.GetPatternsByDomain("NOPE")
if len(emptyPatterns) != 0 {
t.Errorf("Expected 0 patterns for unknown domain, got %d", len(emptyPatterns))
}
}
func TestControlPatternIndex_GetPatternsByCategory(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
authPatterns := idx.GetPatternsByCategory("authentication")
if len(authPatterns) < 3 {
t.Errorf("Expected at least 3 authentication patterns, got %d", len(authPatterns))
}
encPatterns := idx.GetPatternsByCategory("encryption")
if len(encPatterns) < 3 {
t.Errorf("Expected at least 3 encryption patterns, got %d", len(encPatterns))
}
}
func TestControlPatternIndex_GetPatternsByTag(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
dpPatterns := idx.GetPatternsByTag("data_protection")
if len(dpPatterns) < 3 {
t.Errorf("Expected at least 3 data_protection tagged patterns, got %d", len(dpPatterns))
}
secPatterns := idx.GetPatternsByTag("security")
if len(secPatterns) >= 1 {
// At least 1 pattern tagged with "security" — good
}
}
func TestControlPatternIndex_MatchByKeywords(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
tests := []struct {
name string
text string
expectPatternID string
}{
{
"password related text",
"Die Passwortrichtlinie muss sicherstellen, dass Anmeldedaten geschuetzt sind",
"CP-AUTH-001",
},
{
"encryption text",
"Verschluesselung ruhender Daten muss mit AES-256 erfolgen",
"CP-CRYP-001",
},
{
"incident response text",
"Ein Vorfall-Reaktionsplan muss fuer Sicherheitsvorfaelle bereitstehen",
"CP-INC-001",
},
{
"DSGVO consent text",
"Die Einwilligung der betroffenen Person muss freiwillig erfolgen",
"CP-DATA-004",
},
{
"AI risk text",
"KI-Systeme mit hohem Risiko muessen einer Konformitaetsbewertung unterzogen werden",
"CP-AI-001",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
matches := idx.MatchByKeywords(tt.text)
if len(matches) == 0 {
t.Fatalf("Expected at least 1 match for text: %s", tt.text[:50])
}
// Check if the expected pattern is in top 3 matches
found := false
for i, m := range matches {
if i >= 3 {
break
}
if m.Pattern.ID == tt.expectPatternID {
found = true
break
}
}
if !found {
topIDs := make([]string, 0, 3)
for i, m := range matches {
if i >= 3 {
break
}
topIDs = append(topIDs, m.Pattern.ID)
}
t.Errorf("Expected %s in top 3, got %v", tt.expectPatternID, topIDs)
}
})
}
}
func TestControlPatternIndex_MatchByKeywords_NoMatch(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
matches := idx.MatchByKeywords("xyzzy foobar baz completely unrelated text")
if len(matches) != 0 {
t.Errorf("Expected 0 matches for unrelated text, got %d", len(matches))
}
}
func TestPatternMatch_Score(t *testing.T) {
match := PatternMatch{
KeywordHits: 3,
TotalKeywords: 7,
}
score := match.Score()
expected := 3.0 / 7.0
if score < expected-0.01 || score > expected+0.01 {
t.Errorf("Expected score ~%.3f, got %.3f", expected, score)
}
zeroMatch := PatternMatch{
KeywordHits: 0,
TotalKeywords: 0,
}
if zeroMatch.Score() != 0 {
t.Errorf("Expected 0 score for zero keywords, got %f", zeroMatch.Score())
}
}
func TestControlPatternIndex_ValidatePatternID(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
if !idx.ValidatePatternID("CP-AUTH-001") {
t.Error("Expected CP-AUTH-001 to be valid")
}
if idx.ValidatePatternID("CP-FAKE-999") {
t.Error("Expected CP-FAKE-999 to be invalid")
}
}
func TestControlPatternIndex_Domains(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
domains := idx.Domains()
if len(domains) < 5 {
t.Errorf("Expected at least 5 domains, got %d: %v", len(domains), domains)
}
// Check critical domains are present
domainSet := make(map[string]bool)
for _, d := range domains {
domainSet[d] = true
}
for _, required := range []string{"AUTH", "CRYP", "DATA", "SEC", "COMP"} {
if !domainSet[required] {
t.Errorf("Expected domain %s to be present", required)
}
}
}
func TestControlPattern_FieldsNotEmpty(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
for _, p := range idx.All {
t.Run(p.ID, func(t *testing.T) {
if p.ID == "" {
t.Error("Empty ID")
}
if p.Name == "" {
t.Error("Empty Name")
}
if p.NameDE == "" {
t.Error("Empty NameDE")
}
if p.Domain == "" {
t.Error("Empty Domain")
}
if p.Category == "" {
t.Error("Empty Category")
}
if len(p.Description) < 20 {
t.Errorf("Description too short: %d chars", len(p.Description))
}
if len(p.ObjectiveTemplate) < 20 {
t.Errorf("ObjectiveTemplate too short: %d chars", len(p.ObjectiveTemplate))
}
if len(p.RationaleTemplate) < 20 {
t.Errorf("RationaleTemplate too short: %d chars", len(p.RationaleTemplate))
}
if len(p.RequirementsTemplate) < 2 {
t.Errorf("Not enough requirements: %d", len(p.RequirementsTemplate))
}
if len(p.TestProcedureTemplate) < 1 {
t.Errorf("Not enough test procedures: %d", len(p.TestProcedureTemplate))
}
if len(p.EvidenceTemplate) < 1 {
t.Errorf("Not enough evidence items: %d", len(p.EvidenceTemplate))
}
if len(p.ObligationMatchKeywords) < 3 {
t.Errorf("Not enough keywords: %d", len(p.ObligationMatchKeywords))
}
if len(p.Tags) < 1 {
t.Errorf("Not enough tags: %d", len(p.Tags))
}
validSeverities := map[string]bool{"low": true, "medium": true, "high": true, "critical": true}
if !validSeverities[p.SeverityDefault] {
t.Errorf("Invalid severity: %s", p.SeverityDefault)
}
})
}
}
func TestControlPattern_IDDomainConsistency(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
for _, p := range idx.All {
parts := strings.Split(p.ID, "-")
if len(parts) != 3 {
t.Errorf("Pattern %s: expected 3 parts in ID, got %d", p.ID, len(parts))
continue
}
idDomain := parts[1]
if idDomain != p.Domain {
t.Errorf("Pattern %s: ID domain '%s' != field domain '%s'", p.ID, idDomain, p.Domain)
}
}
}
func TestControlPattern_ComposableWithValid(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
for _, p := range idx.All {
for _, ref := range p.ComposableWith {
if _, ok := idx.ByID[ref]; !ok {
t.Errorf("Pattern %s: composable_with ref '%s' does not exist", p.ID, ref)
}
if ref == p.ID {
t.Errorf("Pattern %s: composable_with contains self-reference", p.ID)
}
}
}
}
func TestControlPattern_KeywordsLowercase(t *testing.T) {
idx, err := LoadControlPatterns()
if err != nil {
t.Fatalf("Failed to load patterns: %v", err)
}
for _, p := range idx.All {
for _, kw := range p.ObligationMatchKeywords {
if kw != strings.ToLower(kw) {
t.Errorf("Pattern %s: keyword should be lowercase: '%s'", p.ID, kw)
}
}
}
}