package service import ( "strings" "testing" ) func TestFeatureRegistry_ResolveTemplate_SingleFeature(t *testing.T) { reg := NewFeatureRegistry() result, err := reg.ResolveTemplate([]string{"face"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if result.Composed { t.Errorf("expected single template, got composed=true") } // Most specific template for face-only: std_face_recognition_stream (covers 1 feature) // std_workshop_face_recognition_shoe_alarm covers 2 features, less specific. if result.TemplateName != "std_face_recognition_stream" { t.Errorf("expected std_face_recognition_stream (most specific), got %s", result.TemplateName) } } func TestFeatureRegistry_ResolveTemplate_TwoFeaturesWithCommonTemplate(t *testing.T) { reg := NewFeatureRegistry() // face + shoe both have std_workshop_face_recognition_shoe_alarm in common result, err := reg.ResolveTemplate([]string{"face", "shoe"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if result.Composed { t.Errorf("expected single template, got composed=true") } if result.TemplateName != "std_workshop_face_recognition_shoe_alarm" { t.Errorf("expected std_workshop_face_recognition_shoe_alarm, got %s", result.TemplateName) } } func TestFeatureRegistry_ResolveTemplate_ComposedNeeded(t *testing.T) { reg := NewFeatureRegistry() // Add a feature with a separate template so intersection is empty. // We temporarily add intrusion with its own template for testing. reg.features["intrusion"] = &FeatureDef{ Key: "intrusion", Label: "区域入侵", Templates: []string{"std_intrusion_detection"}, } // face → [std_workshop..., std_face_recog] // intrusion → [std_intrusion_detection] // Intersection = empty → Composed = true result, err := reg.ResolveTemplate([]string{"face", "intrusion"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !result.Composed { t.Errorf("expected composed=true, got false") } if !strings.HasPrefix(result.TemplateName, "auto_") { t.Errorf("composed template should start with auto_, got %s", result.TemplateName) } // composePrimary is the first template from the first feature (face) if result.composePrimary != "std_workshop_face_recognition_shoe_alarm" { t.Errorf("expected compose primary std_workshop_face_recognition_shoe_alarm, got %s", result.composePrimary) } } func TestFeatureRegistry_ResolveTemplate_UnavailableFeature(t *testing.T) { reg := NewFeatureRegistry() // helmet has no templates yet _, err := reg.ResolveTemplate([]string{"helmet"}) if err == nil { t.Fatal("expected error for unavailable feature, got nil") } if !strings.Contains(err.Error(), "尚未就绪") { t.Errorf("error should mention 尚未就绪, got: %v", err) } } func TestFeatureRegistry_ResolveTemplate_UnknownFeature(t *testing.T) { reg := NewFeatureRegistry() _, err := reg.ResolveTemplate([]string{"face", "nonexistent"}) if err == nil { t.Fatal("expected error for unknown feature, got nil") } if !strings.Contains(err.Error(), "未知") { t.Errorf("error should mention 未知, got: %v", err) } } func TestFeatureRegistry_ResolveTemplate_Empty(t *testing.T) { reg := NewFeatureRegistry() _, err := reg.ResolveTemplate(nil) if err == nil { t.Fatal("expected error for nil, got nil") } } func TestFeatureRegistry_ResolveTemplate_Dedup(t *testing.T) { reg := NewFeatureRegistry() // Duplicate keys should be deduplicated result, err := reg.ResolveTemplate([]string{"face", "face", "shoe"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if result.Composed { t.Errorf("expected single template, got composed=true") } } func TestIntersectTemplateLists(t *testing.T) { tests := []struct { name string lists [][]string expect []string }{ { name: "empty", lists: nil, expect: nil, }, { name: "single list returns same list", lists: [][]string{{"a", "b", "c"}}, expect: []string{"a", "b", "c"}, }, { name: "partial intersection", lists: [][]string{{"a", "b", "c"}, {"b", "c", "d"}}, expect: []string{"b", "c"}, }, { name: "no intersection", lists: [][]string{{"a", "b"}, {"c", "d"}}, expect: nil, }, { name: "three lists", lists: [][]string{{"a", "b", "c"}, {"b", "c"}, {"c", "b", "a"}}, expect: []string{"b", "c"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := intersectTemplateLists(tt.lists) if len(got) != len(tt.expect) { t.Errorf("len = %d, want %d (got %v)", len(got), len(tt.expect), got) return } for i := range got { if got[i] != tt.expect[i] { t.Errorf("at %d: got %q, want %q", i, got[i], tt.expect[i]) } } }) } } func TestFeatureRegistry_AllFeatures(t *testing.T) { reg := NewFeatureRegistry() all := reg.AllFeatures() if len(all) < 3 { t.Errorf("expected at least 3 features, got %d", len(all)) } // Verify sorted by key for i := 1; i < len(all); i++ { if all[i].Key < all[i-1].Key { t.Errorf("features not sorted: %q before %q", all[i-1].Key, all[i].Key) } } }