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>
177 lines
4.1 KiB
Go
177 lines
4.1 KiB
Go
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")
|
|
}
|
|
}
|