package iace import ( "testing" "github.com/google/uuid" ) func TestGetBuiltinHazardLibrary_EntryCount(t *testing.T) { entries := GetBuiltinHazardLibrary() // Original 37 + 12 new categories (10+8+6+6+4+5+8+8+5+8+5+6 = 79) = 116 if len(entries) < 100 { t.Fatalf("GetBuiltinHazardLibrary returned %d entries, want at least 100", len(entries)) } } func TestGetBuiltinHazardLibrary_AllBuiltinAndNoTenant(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if !e.IsBuiltin { t.Errorf("entries[%d] (%s): IsBuiltin = false, want true", i, e.Name) } if e.TenantID != nil { t.Errorf("entries[%d] (%s): TenantID = %v, want nil", i, e.Name, e.TenantID) } } } func TestGetBuiltinHazardLibrary_UniqueNonZeroUUIDs(t *testing.T) { entries := GetBuiltinHazardLibrary() seen := make(map[uuid.UUID]string) for i, e := range entries { if e.ID == uuid.Nil { t.Errorf("entries[%d] (%s): ID is zero UUID", i, e.Name) } if prev, exists := seen[e.ID]; exists { t.Errorf("entries[%d] (%s): duplicate UUID %s, same as %q", i, e.Name, e.ID, prev) } seen[e.ID] = e.Name } } func TestGetBuiltinHazardLibrary_AllCategoriesPresent(t *testing.T) { entries := GetBuiltinHazardLibrary() // All 24 categories (12 original + 12 new) expectedCategories := map[string]bool{ "false_classification": false, "timing_error": false, "data_poisoning": false, "model_drift": false, "sensor_spoofing": false, "communication_failure": false, "unauthorized_access": false, "firmware_corruption": false, "safety_boundary_violation": false, "mode_confusion": false, "unintended_bias": false, "update_failure": false, // New categories "software_fault": false, "hmi_error": false, "mechanical_hazard": false, "electrical_hazard": false, "thermal_hazard": false, "emc_hazard": false, "configuration_error": false, "safety_function_failure": false, "logging_audit_failure": false, "integration_error": false, "environmental_hazard": false, "maintenance_hazard": false, } for _, e := range entries { if _, ok := expectedCategories[e.Category]; !ok { t.Errorf("unexpected category %q in entry %q", e.Category, e.Name) } expectedCategories[e.Category] = true } for cat, found := range expectedCategories { if !found { t.Errorf("expected category %q not found in any entry", cat) } } } func TestGetBuiltinHazardLibrary_CategoryCounts(t *testing.T) { entries := GetBuiltinHazardLibrary() // Original 12 categories: exact counts must remain unchanged originalCounts := map[string]int{ "false_classification": 4, "timing_error": 3, "data_poisoning": 2, "model_drift": 3, "sensor_spoofing": 3, "communication_failure": 3, "unauthorized_access": 4, "firmware_corruption": 3, "safety_boundary_violation": 4, "mode_confusion": 3, "unintended_bias": 2, "update_failure": 3, } // New 12 categories: must each have at least 1 entry newCategories := []string{ "software_fault", "hmi_error", "mechanical_hazard", "electrical_hazard", "thermal_hazard", "emc_hazard", "configuration_error", "safety_function_failure", "logging_audit_failure", "integration_error", "environmental_hazard", "maintenance_hazard", } actualCounts := make(map[string]int) for _, e := range entries { actualCounts[e.Category]++ } for cat, expected := range originalCounts { if actualCounts[cat] != expected { t.Errorf("original category %q: count = %d, want %d", cat, actualCounts[cat], expected) } } for _, cat := range newCategories { if actualCounts[cat] < 1 { t.Errorf("new category %q: count = %d, want >= 1", cat, actualCounts[cat]) } } } func TestGetBuiltinHazardLibrary_SeverityRange(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.DefaultSeverity < 1 || e.DefaultSeverity > 5 { t.Errorf("entries[%d] (%s): DefaultSeverity = %d, want 1-5", i, e.Name, e.DefaultSeverity) } } } func TestGetBuiltinHazardLibrary_ProbabilityRange(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.DefaultProbability < 1 || e.DefaultProbability > 5 { t.Errorf("entries[%d] (%s): DefaultProbability = %d, want 1-5", i, e.Name, e.DefaultProbability) } } } func TestGetBuiltinHazardLibrary_NonEmptyFields(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.Name == "" { t.Errorf("entries[%d]: Name is empty", i) } if e.Category == "" { t.Errorf("entries[%d] (%s): Category is empty", i, e.Name) } if len(e.ApplicableComponentTypes) == 0 { t.Errorf("entries[%d] (%s): ApplicableComponentTypes is empty", i, e.Name) } if len(e.RegulationReferences) == 0 { t.Errorf("entries[%d] (%s): RegulationReferences is empty", i, e.Name) } } } func TestHazardUUID_Deterministic(t *testing.T) { tests := []struct { category string index int }{ {"false_classification", 1}, {"timing_error", 2}, {"data_poisoning", 1}, {"update_failure", 3}, {"mode_confusion", 1}, } for _, tt := range tests { t.Run(tt.category, func(t *testing.T) { id1 := hazardUUID(tt.category, tt.index) id2 := hazardUUID(tt.category, tt.index) if id1 != id2 { t.Errorf("hazardUUID(%q, %d) not deterministic: %s != %s", tt.category, tt.index, id1, id2) } if id1 == uuid.Nil { t.Errorf("hazardUUID(%q, %d) returned zero UUID", tt.category, tt.index) } }) } } func TestHazardUUID_DifferentInputsProduceDifferentUUIDs(t *testing.T) { tests := []struct { cat1 string idx1 int cat2 string idx2 int }{ {"false_classification", 1, "false_classification", 2}, {"false_classification", 1, "timing_error", 1}, {"data_poisoning", 1, "data_poisoning", 2}, {"mode_confusion", 1, "mode_confusion", 3}, } for _, tt := range tests { name := tt.cat1 + "_" + tt.cat2 t.Run(name, func(t *testing.T) { id1 := hazardUUID(tt.cat1, tt.idx1) id2 := hazardUUID(tt.cat2, tt.idx2) if id1 == id2 { t.Errorf("hazardUUID(%q,%d) == hazardUUID(%q,%d): %s", tt.cat1, tt.idx1, tt.cat2, tt.idx2, id1) } }) } } func TestGetBuiltinHazardLibrary_CreatedAtSet(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.CreatedAt.IsZero() { t.Errorf("entries[%d] (%s): CreatedAt is zero", i, e.Name) } } } func TestGetBuiltinHazardLibrary_DescriptionPresent(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.Description == "" { t.Errorf("entries[%d] (%s): Description is empty", i, e.Name) } } } func TestGetBuiltinHazardLibrary_SuggestedMitigationsPresent(t *testing.T) { entries := GetBuiltinHazardLibrary() for i, e := range entries { if e.SuggestedMitigations == nil || len(e.SuggestedMitigations) == 0 { t.Errorf("entries[%d] (%s): SuggestedMitigations is nil/empty", i, e.Name) } } } func TestGetBuiltinHazardLibrary_ApplicableComponentTypesAreValid(t *testing.T) { entries := GetBuiltinHazardLibrary() validTypes := map[string]bool{ string(ComponentTypeSoftware): true, string(ComponentTypeFirmware): true, string(ComponentTypeAIModel): true, string(ComponentTypeHMI): true, string(ComponentTypeSensor): true, string(ComponentTypeActuator): true, string(ComponentTypeController): true, string(ComponentTypeNetwork): true, string(ComponentTypeOther): true, } for i, e := range entries { for _, ct := range e.ApplicableComponentTypes { if !validTypes[ct] { t.Errorf("entries[%d] (%s): invalid component type %q in ApplicableComponentTypes", i, e.Name, ct) } } } } func TestGetBuiltinHazardLibrary_UUIDsMatchExpected(t *testing.T) { // Verify the first entry of each category has the expected UUID // based on the deterministic hazardUUID function. entries := GetBuiltinHazardLibrary() categoryFirstSeen := make(map[string]uuid.UUID) for _, e := range entries { if _, exists := categoryFirstSeen[e.Category]; !exists { categoryFirstSeen[e.Category] = e.ID } } for cat, actualID := range categoryFirstSeen { expectedID := hazardUUID(cat, 1) if actualID != expectedID { t.Errorf("first entry of category %q: ID = %s, want %s", cat, actualID, expectedID) } } } func TestGetBuiltinHazardLibrary_ConsistentAcrossCalls(t *testing.T) { entries1 := GetBuiltinHazardLibrary() entries2 := GetBuiltinHazardLibrary() if len(entries1) != len(entries2) { t.Fatalf("inconsistent lengths: %d vs %d", len(entries1), len(entries2)) } for i := range entries1 { if entries1[i].ID != entries2[i].ID { t.Errorf("entries[%d]: ID mismatch across calls: %s vs %s", i, entries1[i].ID, entries2[i].ID) } if entries1[i].Name != entries2[i].Name { t.Errorf("entries[%d]: Name mismatch across calls: %q vs %q", i, entries1[i].Name, entries2[i].Name) } if entries1[i].Category != entries2[i].Category { t.Errorf("entries[%d]: Category mismatch across calls: %q vs %q", i, entries1[i].Category, entries2[i].Category) } } }