- device_features 表:持久化设备检测功能选择 - pageConsole 从 DB 读取功能选择(非从模板反推) - actionConsoleSave 先存 DB 再从 DB 读取运行流水线 - 模板选择改为最精确匹配(单功能只用该功能模板) - 修复内存指标解析(补充 memory 字段)
179 lines
5.7 KiB
Go
179 lines
5.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|