package iace import ( "testing" ) // TestIntegration_FullMatchFlow tests the complete pattern matching flow: // components → tags → patterns → hazards/measures/evidence func TestIntegration_FullMatchFlow(t *testing.T) { engine := NewPatternEngine() // Simulate a robot arm with electrical components and kinetic energy input := MatchInput{ ComponentLibraryIDs: []string{"C001", "C061", "C071"}, // Roboterarm, Schaltschrank, SPS EnergySourceIDs: []string{"EN01", "EN04"}, // Kinetic, Electrical LifecyclePhases: []string{}, CustomTags: []string{}, } output := engine.Match(input) // Should have matched patterns if len(output.MatchedPatterns) == 0 { t.Fatal("expected matched patterns for robot arm + electrical + SPS setup, got none") } // Should have suggested hazards if len(output.SuggestedHazards) == 0 { t.Fatal("expected suggested hazards, got none") } // Should have suggested measures if len(output.SuggestedMeasures) == 0 { t.Fatal("expected suggested measures, got none") } // Should have suggested evidence if len(output.SuggestedEvidence) == 0 { t.Fatal("expected suggested evidence, got none") } // Should have resolved tags if len(output.ResolvedTags) == 0 { t.Fatal("expected resolved tags, got none") } // Verify mechanical hazards are present (robot arm has moving_part, rotating_part) hasMechanical := false for _, h := range output.SuggestedHazards { if h.Category == "mechanical" || h.Category == "mechanical_hazard" { hasMechanical = true break } } if !hasMechanical { cats := make(map[string]bool) for _, h := range output.SuggestedHazards { cats[h.Category] = true } t.Errorf("expected mechanical hazards for robot arm, got categories: %v", cats) } // Verify electrical hazards are present (Schaltschrank has high_voltage) hasElectrical := false for _, h := range output.SuggestedHazards { if h.Category == "electrical" || h.Category == "electrical_hazard" { hasElectrical = true break } } if !hasElectrical { cats := make(map[string]bool) for _, h := range output.SuggestedHazards { cats[h.Category] = true } t.Errorf("expected electrical hazards for Schaltschrank, got categories: %v", cats) } } // TestIntegration_TagResolverToPatternEngine verifies the tag resolver output // feeds correctly into the pattern engine. func TestIntegration_TagResolverToPatternEngine(t *testing.T) { resolver := NewTagResolver() engine := NewPatternEngine() // Resolve tags for a hydraulic setup componentTags := resolver.ResolveComponentTags([]string{"C041"}) // Hydraulikpumpe energyTags := resolver.ResolveEnergyTags([]string{"EN05"}) // Hydraulische Energie allTags := resolver.ResolveTags([]string{"C041"}, []string{"EN05"}, nil) // All tags should be non-empty if len(componentTags) == 0 { t.Error("expected component tags for C041") } if len(energyTags) == 0 { t.Error("expected energy tags for EN05") } // Merged tags should include both tagSet := toSet(allTags) if !tagSet["hydraulic_part"] { t.Error("expected 'hydraulic_part' in merged tags") } if !tagSet["hydraulic_pressure"] { t.Error("expected 'hydraulic_pressure' in merged tags") } // Feed into pattern engine output := engine.Match(MatchInput{ ComponentLibraryIDs: []string{"C041"}, EnergySourceIDs: []string{"EN05"}, }) if len(output.MatchedPatterns) == 0 { t.Error("expected patterns to match for hydraulic setup") } } // TestIntegration_AllComponentCategoriesProduceMatches verifies that every // component category, when paired with its typical energy source, produces // at least one pattern match. func TestIntegration_AllComponentCategoriesProduceMatches(t *testing.T) { engine := NewPatternEngine() tests := []struct { name string componentIDs []string energyIDs []string }{ {"mechanical", []string{"C001"}, []string{"EN01"}}, // Roboterarm + Kinetic {"drive", []string{"C031"}, []string{"EN02"}}, // Elektromotor + Rotational {"hydraulic", []string{"C041"}, []string{"EN05"}}, // Hydraulikpumpe + Hydraulic {"pneumatic", []string{"C051"}, []string{"EN06"}}, // Pneumatikzylinder + Pneumatic {"electrical", []string{"C061"}, []string{"EN04"}}, // Schaltschrank + Electrical {"control", []string{"C071"}, []string{"EN04"}}, // SPS + Electrical {"safety", []string{"C101"}, []string{"EN04"}}, // Not-Halt + Electrical {"it_network", []string{"C111"}, []string{"EN04", "EN19"}}, // Switch + Electrical + Data } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { output := engine.Match(MatchInput{ ComponentLibraryIDs: tt.componentIDs, EnergySourceIDs: tt.energyIDs, }) if len(output.MatchedPatterns) == 0 { t.Errorf("category %s: expected at least one pattern match, got none (resolved tags: %v)", tt.name, output.ResolvedTags) } }) } } // TestIntegration_PatternsSuggestHazardCategories verifies that pattern-suggested // hazard categories cover the main safety domains. func TestIntegration_PatternsSuggestHazardCategories(t *testing.T) { engine := NewPatternEngine() // Full industrial setup: robot arm + electrical panel + PLC + network output := engine.Match(MatchInput{ ComponentLibraryIDs: []string{"C001", "C061", "C071", "C111"}, EnergySourceIDs: []string{"EN01", "EN04"}, }) categories := make(map[string]bool) for _, h := range output.SuggestedHazards { categories[h.Category] = true } // Should cover mechanical and electrical hazards (naming may use _hazard suffix) hasMech := categories["mechanical"] || categories["mechanical_hazard"] hasElec := categories["electrical"] || categories["electrical_hazard"] if !hasMech { t.Errorf("expected mechanical hazard category in suggestions, got: %v", categories) } if !hasElec { t.Errorf("expected electrical hazard category in suggestions, got: %v", categories) } } // TestIntegration_EvidenceSuggestionsPerReductionType tests that evidence // can be found for each reduction type. func TestIntegration_EvidenceSuggestionsPerReductionType(t *testing.T) { resolver := NewTagResolver() tests := []struct { reductionType string evidenceTags []string }{ {"design", []string{"design_evidence", "analysis_evidence"}}, {"protective", []string{"test_evidence", "inspection_evidence"}}, {"information", []string{"training_evidence", "operational_evidence"}}, } for _, tt := range tests { t.Run(tt.reductionType, func(t *testing.T) { evidence := resolver.FindEvidenceByTags(tt.evidenceTags) if len(evidence) == 0 { t.Errorf("no evidence found for %s reduction type (tags: %v)", tt.reductionType, tt.evidenceTags) } }) } } // TestIntegration_LibraryConsistency verifies components and energy sources have tags. func TestIntegration_LibraryConsistency(t *testing.T) { components := GetComponentLibrary() energySources := GetEnergySources() taxonomy := GetTagTaxonomy() // Taxonomy should be populated if len(taxonomy) == 0 { t.Fatal("tag taxonomy is empty") } // All components should have at least one tag for _, comp := range components { if len(comp.Tags) == 0 { t.Errorf("component %s has no tags", comp.ID) } } // All energy sources should have at least one tag for _, es := range energySources { if len(es.Tags) == 0 { t.Errorf("energy source %s has no tags", es.ID) } } // Component tags should mostly exist in taxonomy (allow some flexibility) taxonomyIDs := toSet(func() []string { ids := make([]string, len(taxonomy)) for i, tag := range taxonomy { ids[i] = tag.ID } return ids }()) missingCount := 0 totalTags := 0 for _, comp := range components { for _, tag := range comp.Tags { totalTags++ if !taxonomyIDs[tag] { missingCount++ } } } // At least 90% of component tags should be in taxonomy if totalTags > 0 { coverage := float64(totalTags-missingCount) / float64(totalTags) * 100 if coverage < 90 { t.Errorf("only %.0f%% of component tags exist in taxonomy (%d/%d)", coverage, totalTags-missingCount, totalTags) } } }