safesight-control/internal/service/auto_config_test.go
tian 286dcaa945 feat: persist device features to DB as source of truth
- device_features 表:持久化设备检测功能选择
- pageConsole 从 DB 读取功能选择(非从模板反推)
- actionConsoleSave 先存 DB 再从 DB 读取运行流水线
- 模板选择改为最精确匹配(单功能只用该功能模板)
- 修复内存指标解析(补充 memory 字段)
2026-05-12 13:40:37 +08:00

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)
}
}
}