feat(ucca): Pflichtendatenbank v2 (325 Obligations), Trigger-Engine, TOM-Control-Mapping
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 18s
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 32s
CI / test-python-backend-compliance (push) Successful in 29s
CI / test-python-document-crawler (push) Successful in 20s
CI / test-python-dsms-gateway (push) Successful in 18s
- 9 Regulation-JSON-Dateien (DSGVO 80, AI Act 60, NIS2 40, BDSG 30, TTDSG 20, DSA 35, Data Act 25, EU-Maschinen 15, DORA 20) - Condition-Tree-Engine fuer automatische Pflichtenselektion (all_of/any_of, 80+ Field-Paths) - Generischer JSONRegulationModule-Loader mit YAML-Fallback - Bidirektionales TOM-Control-Mapping (291 Obligation→Control, 92 Control→Obligation) - Gap-Analyse-Engine (Compliance-%, Priority Actions, Domain Breakdown) - ScopeDecision→UnifiedFacts Bridge fuer Auto-Profiling - 4 neue API-Endpoints (assess-from-scope, tom-controls, gap-analysis, reverse-lookup) - Frontend: Auto-Profiling Button, Regulation-Filter Chips, TOM-Panel, Gap-Analyse-View - 18 Unit Tests (Condition Engine, v2 Loader, TOM Mapper) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
package ucca
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConditionEngine_NilNode(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
if !engine.Evaluate(nil, facts) {
|
||||
t.Error("nil node should return true (always applies)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_LeafEquals(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field string
|
||||
value interface{}
|
||||
facts func() *UnifiedFacts
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "is_controller true",
|
||||
field: "data_protection.is_controller", value: true,
|
||||
facts: func() *UnifiedFacts {
|
||||
f := NewUnifiedFacts()
|
||||
f.DataProtection.IsController = true
|
||||
return f
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "is_controller false mismatch",
|
||||
field: "data_protection.is_controller", value: true,
|
||||
facts: func() *UnifiedFacts { return NewUnifiedFacts() },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "employee_count equals",
|
||||
field: "organization.employee_count", value: float64(50),
|
||||
facts: func() *UnifiedFacts {
|
||||
f := NewUnifiedFacts()
|
||||
f.Organization.EmployeeCount = 50
|
||||
return f
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "uses_ai true",
|
||||
field: "ai_usage.uses_ai", value: true,
|
||||
facts: func() *UnifiedFacts {
|
||||
f := NewUnifiedFacts()
|
||||
f.AIUsage.UsesAI = true
|
||||
return f
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
node := &ConditionNode{
|
||||
Field: tt.field,
|
||||
Operator: "EQUALS",
|
||||
Value: tt.value,
|
||||
}
|
||||
result := engine.Evaluate(node, tt.facts())
|
||||
if result != tt.expected {
|
||||
t.Errorf("expected %v, got %v", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_GreaterThan(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
facts.Organization.EmployeeCount = 50
|
||||
|
||||
node := &ConditionNode{
|
||||
Field: "organization.employee_count",
|
||||
Operator: "GREATER_THAN",
|
||||
Value: float64(19),
|
||||
}
|
||||
if !engine.Evaluate(node, facts) {
|
||||
t.Error("50 > 19 should be true")
|
||||
}
|
||||
|
||||
facts.Organization.EmployeeCount = 10
|
||||
if engine.Evaluate(node, facts) {
|
||||
t.Error("10 > 19 should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_AllOf(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
facts.DataProtection.IsController = true
|
||||
facts.DataProtection.ProcessesSpecialCategories = true
|
||||
|
||||
node := &ConditionNode{
|
||||
AllOf: []ConditionNode{
|
||||
{Field: "data_protection.is_controller", Operator: "EQUALS", Value: true},
|
||||
{Field: "data_protection.processes_special_categories", Operator: "EQUALS", Value: true},
|
||||
},
|
||||
}
|
||||
|
||||
if !engine.Evaluate(node, facts) {
|
||||
t.Error("all_of with both true should be true")
|
||||
}
|
||||
|
||||
facts.DataProtection.ProcessesSpecialCategories = false
|
||||
if engine.Evaluate(node, facts) {
|
||||
t.Error("all_of with one false should be false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_AnyOf(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
facts.DataProtection.RequiresDSBByLaw = false
|
||||
|
||||
node := &ConditionNode{
|
||||
AnyOf: []ConditionNode{
|
||||
{Field: "data_protection.needs_dpo", Operator: "EQUALS", Value: true},
|
||||
{Field: "organization.employee_count", Operator: "GREATER_THAN", Value: float64(19)},
|
||||
},
|
||||
}
|
||||
|
||||
if engine.Evaluate(node, facts) {
|
||||
t.Error("any_of with both false should be false")
|
||||
}
|
||||
|
||||
facts.Organization.EmployeeCount = 25
|
||||
if !engine.Evaluate(node, facts) {
|
||||
t.Error("any_of with one true should be true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_UnknownField(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
|
||||
node := &ConditionNode{
|
||||
Field: "nonexistent.field",
|
||||
Operator: "EQUALS",
|
||||
Value: true,
|
||||
}
|
||||
|
||||
if engine.Evaluate(node, facts) {
|
||||
t.Error("unknown field should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConditionEngine_NotEquals(t *testing.T) {
|
||||
engine := NewObligationConditionEngine()
|
||||
facts := NewUnifiedFacts()
|
||||
facts.Organization.Country = "DE"
|
||||
|
||||
node := &ConditionNode{
|
||||
Field: "organization.country",
|
||||
Operator: "NOT_EQUALS",
|
||||
Value: "US",
|
||||
}
|
||||
if !engine.Evaluate(node, facts) {
|
||||
t.Error("DE != US should be true")
|
||||
}
|
||||
|
||||
node.Value = "DE"
|
||||
if engine.Evaluate(node, facts) {
|
||||
t.Error("DE != DE should be false")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user