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) } } } }