Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/delta_analysis_test.go
T
Benjamin Admin 9a9a11b248 feat(iace): Sprint 4C — Delta Impact Analysis
Neuer Endpoint POST /projects/:id/delta-analysis:
- Input: aktuelle + vorgeschlagene Aenderung (Components, Energy, States, Roles)
- Output: Diff der Pattern-Matches (added/removed Patterns, Hazards, Measures)
- DeltaMatch() auf PatternEngine: Match(current) vs Match(proposed)
- DeltaResult mit AddedPatterns, RemovedPatterns, Counts, SummaryDE

Beispiel-Output: SPS hinzufuegen → +55 Patterns, +5 Hazard-Kategorien, +17 Massnahmen
Maintenance-State hinzufuegen → +10 Patterns, +2 Hazards, +2 Massnahmen

7 Tests: NoChange, AddComponent, RemoveComponent, AddState, AddRole, Summary, Symmetric

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 21:23:46 +02:00

139 lines
4.8 KiB
Go

package iace
import "testing"
func TestDeltaMatch_NoChange(t *testing.T) {
engine := NewPatternEngine()
input := MatchInput{
ComponentLibraryIDs: []string{"C001"},
EnergySourceIDs: []string{"EN01"},
}
result := engine.DeltaMatch(input, input)
if len(result.AddedPatterns) != 0 {
t.Errorf("expected 0 added patterns for no change, got %d", len(result.AddedPatterns))
}
if len(result.RemovedPatterns) != 0 {
t.Errorf("expected 0 removed patterns for no change, got %d", len(result.RemovedPatterns))
}
if result.SummaryDE != "Keine Auswirkung — die vorgeschlagene Aenderung veraendert die Risikobeurteilung nicht." {
t.Errorf("unexpected summary: %s", result.SummaryDE)
}
}
func TestDeltaMatch_AddComponent(t *testing.T) {
engine := NewPatternEngine()
current := MatchInput{
ComponentLibraryIDs: []string{"C001"},
EnergySourceIDs: []string{"EN01"},
}
proposed := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"}, // Add SPS (programmable)
EnergySourceIDs: []string{"EN01"},
}
result := engine.DeltaMatch(current, proposed)
if len(result.AddedPatterns) == 0 {
t.Error("expected added patterns when adding SPS component")
}
t.Logf("Adding C071 (SPS): +%d patterns, +%d hazard cats, +%d measures",
len(result.AddedPatterns), result.AddedHazardCount, result.AddedMeasureCount)
}
func TestDeltaMatch_RemoveComponent(t *testing.T) {
engine := NewPatternEngine()
current := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"},
EnergySourceIDs: []string{"EN01"},
}
proposed := MatchInput{
ComponentLibraryIDs: []string{"C001"}, // Remove SPS
EnergySourceIDs: []string{"EN01"},
}
result := engine.DeltaMatch(current, proposed)
if len(result.RemovedPatterns) == 0 {
t.Error("expected removed patterns when removing SPS component")
}
t.Logf("Removing C071: -%d patterns, -%d hazard cats",
len(result.RemovedPatterns), result.RemovedHazardCount)
}
func TestDeltaMatch_AddState(t *testing.T) {
engine := NewPatternEngine()
current := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"},
EnergySourceIDs: []string{"EN01"},
LifecyclePhases: []string{"normal_operation"},
OperationalStates: []string{"automatic_operation"},
HumanRoles: []string{"operator"},
}
proposed := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"},
EnergySourceIDs: []string{"EN01"},
LifecyclePhases: []string{"normal_operation", "maintenance"},
OperationalStates: []string{"automatic_operation", "maintenance"},
HumanRoles: []string{"operator", "maintenance_tech"},
}
result := engine.DeltaMatch(current, proposed)
t.Logf("Adding maintenance state+role: +%d patterns, +%d hazards, +%d measures",
len(result.AddedPatterns), result.AddedHazardCount, result.AddedMeasureCount)
// Adding maintenance should bring maintenance-specific patterns
if len(result.AddedPatterns) == 0 {
t.Error("expected added patterns when adding maintenance state")
}
}
func TestDeltaMatch_AddRole(t *testing.T) {
engine := NewPatternEngine()
current := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"},
EnergySourceIDs: []string{"EN01"},
LifecyclePhases: []string{"maintenance"},
OperationalStates: []string{"maintenance"},
HumanRoles: []string{"operator"}, // Only operator
}
proposed := MatchInput{
ComponentLibraryIDs: []string{"C001", "C071"},
EnergySourceIDs: []string{"EN01"},
LifecyclePhases: []string{"maintenance"},
OperationalStates: []string{"maintenance"},
HumanRoles: []string{"operator", "maintenance_tech"}, // Add maintenance_tech
}
result := engine.DeltaMatch(current, proposed)
t.Logf("Adding maintenance_tech role: +%d patterns", len(result.AddedPatterns))
// maintenance_tech role should unlock maintenance-specific patterns
if len(result.AddedPatterns) == 0 {
t.Error("expected added patterns when adding maintenance_tech role")
}
}
func TestDeltaMatch_SummaryNotEmpty(t *testing.T) {
engine := NewPatternEngine()
current := MatchInput{ComponentLibraryIDs: []string{"C001"}, EnergySourceIDs: []string{"EN01"}}
proposed := MatchInput{ComponentLibraryIDs: []string{"C001", "C071"}, EnergySourceIDs: []string{"EN01"}}
result := engine.DeltaMatch(current, proposed)
if result.SummaryDE == "" {
t.Error("expected non-empty summary")
}
t.Logf("Summary: %s", result.SummaryDE)
}
func TestDeltaMatch_Symmetric(t *testing.T) {
engine := NewPatternEngine()
a := MatchInput{ComponentLibraryIDs: []string{"C001"}, EnergySourceIDs: []string{"EN01"}}
b := MatchInput{ComponentLibraryIDs: []string{"C001", "C071"}, EnergySourceIDs: []string{"EN01"}}
forward := engine.DeltaMatch(a, b)
backward := engine.DeltaMatch(b, a)
if len(forward.AddedPatterns) != len(backward.RemovedPatterns) {
t.Errorf("forward added (%d) should equal backward removed (%d)",
len(forward.AddedPatterns), len(backward.RemovedPatterns))
}
}