3588AdminBackend/internal/service/config_assets_test.go

383 lines
12 KiB
Go

package service
import (
"encoding/json"
"path/filepath"
"testing"
"3588AdminBackend/internal/config"
"3588AdminBackend/internal/storage"
)
func TestConfigPreviewServiceGetsProfileAssetSummary(t *testing.T) {
root := t.TempDir()
mustWrite(t, filepath.Join(root, "configs", "templates", "workshop_face_shoe_alarm.json"), `{
"name": "workshop_face_shoe_alarm",
"params": {
"minio_endpoint": "http://10.0.0.49:9000",
"minio_bucket": "myminio",
"external_get_token_url": "http://10.0.0.49:8080/api/getToken",
"external_put_message_url": "http://10.0.0.49:8080/api/putMessage",
"tenant_code": "32",
"snapshot_region": "us-east-1"
},
"template": {"nodes": [], "edges": []}
}`)
mustWrite(t, filepath.Join(root, "configs", "profiles", "local_3588_test.json"), `{
"name": "local_3588_test",
"business_name": "A厂区视觉识别",
"description": "test profile",
"queue": {"size": 8, "strategy": "drop_oldest"},
"instances": [{
"name": "cam1",
"template": "workshop_face_shoe_alarm",
"params": {
"display_name": "东门入口",
"device_code": "rk3588-a-001",
"site_name": "A厂区",
"rtsp_url": "rtsp://10.0.0.1/live",
"publish_hls_path": "./web/hls/cam1/index.m3u8",
"publish_rtsp_port": 8555,
"publish_rtsp_path": "/live/cam1",
"channel_no": "cam1",
"queue_debug": true
}
}]
}`)
mustWrite(t, filepath.Join(root, "configs", "overlays", "face_debug.json"), `{}`)
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root})
item, err := svc.GetProfileAsset("local_3588_test")
if err != nil {
t.Fatalf("GetProfileAsset: %v", err)
}
if item.Name != "local_3588_test" || len(item.Instances) != 1 {
t.Fatalf("unexpected profile summary: %#v", item)
}
inst := item.Instances[0]
if inst.DisplayName != "东门入口" {
t.Fatalf("expected display name to be parsed, got %#v", inst)
}
if inst.PublishRTSPPort != "8555" {
t.Fatalf("expected rtsp port to be stringified, got %#v", inst.PublishRTSPPort)
}
if _, ok := inst.AdvancedParams["queue_debug"]; !ok {
t.Fatalf("expected advanced params to preserve extra keys, got %#v", inst.AdvancedParams)
}
if item, err := svc.GetTemplateAsset("workshop_face_shoe_alarm"); err != nil {
t.Fatalf("GetTemplateAsset: %v", err)
} else {
if item.MinIOEndpoint != "http://10.0.0.49:9000" || item.TenantCode != "32" {
t.Fatalf("expected shared service params on template asset, got %#v", item)
}
if _, ok := item.AdvancedParams["snapshot_region"]; !ok {
t.Fatalf("expected template advanced params to preserve extra keys, got %#v", item.AdvancedParams)
}
}
}
func TestConfigPreviewServiceGetsOverlayAssetTargets(t *testing.T) {
root := t.TempDir()
mustWrite(t, filepath.Join(root, "configs", "templates", "workshop_face_shoe_alarm.json"), `{}`)
mustWrite(t, filepath.Join(root, "configs", "profiles", "local_3588_test.json"), `{}`)
mustWrite(t, filepath.Join(root, "configs", "overlays", "face_debug.json"), `{
"description": "debug overlay",
"instance_overrides": {
"*": {"override": {}},
"cam1": {"override": {}}
}
}`)
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root})
item, err := svc.GetOverlayAsset("face_debug")
if err != nil {
t.Fatalf("GetOverlayAsset: %v", err)
}
if item.OverrideTargetNum != 2 {
t.Fatalf("expected 2 override targets, got %#v", item)
}
if item.OverrideTargets[0] != "*" || item.OverrideTargets[1] != "cam1" {
t.Fatalf("unexpected targets: %#v", item.OverrideTargets)
}
}
func TestConfigPreviewServiceBuildsProfileEditor(t *testing.T) {
root := t.TempDir()
mustWrite(t, filepath.Join(root, "configs", "profiles", "local_3588_test.json"), `{
"name": "local_3588_test",
"business_name": "A厂区视觉识别",
"description": "test profile",
"queue": {"size": 8, "strategy": "drop_oldest"},
"instances": [
{
"name": "cam1",
"template": "workshop_face_shoe_alarm",
"params": {
"display_name": "东门入口",
"device_code": "rk3588-a-001",
"site_name": "A厂区",
"rtsp_url": "rtsp://10.0.0.1/live",
"publish_hls_path": "./web/hls/cam1/index.m3u8",
"publish_rtsp_port": 8555,
"publish_rtsp_path": "/live/cam1",
"channel_no": "cam1",
"queue_debug": true
}
},
{
"name": "cam2",
"template": "workshop_face_shoe_alarm",
"params": {
"display_name": "西门入口",
"rtsp_url": "rtsp://10.0.0.2/live",
"publish_hls_path": "./web/hls/cam2/index.m3u8",
"publish_rtsp_port": 8555,
"publish_rtsp_path": "/live/cam2",
"channel_no": "cam2"
}
}
]
}`)
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root})
editor, err := svc.GetProfileEditor("local_3588_test")
if err != nil {
t.Fatalf("GetProfileEditor: %v", err)
}
if editor.Name != "local_3588_test" {
t.Fatalf("unexpected profile name: %#v", editor)
}
if editor.BusinessName != "A厂区视觉识别" {
t.Fatalf("expected business name to be parsed, got %#v", editor.BusinessName)
}
if editor.SiteName != "A厂区" {
t.Fatalf("expected profile site name to be parsed, got %#v", editor.SiteName)
}
if editor.DeviceCode != "rk3588-a-001" {
t.Fatalf("expected legacy device code to be preserved internally, got %#v", editor.DeviceCode)
}
if len(editor.Instances) != 2 {
t.Fatalf("expected two instances, got %#v", editor.Instances)
}
if editor.Instances[0].Name != "cam1" || editor.Instances[0].DisplayName != "东门入口" {
t.Fatalf("unexpected first instance summary: %#v", editor.Instances[0])
}
if editor.Instances[1].Name != "cam2" || editor.Instances[1].RTSPURL != "rtsp://10.0.0.2/live" {
t.Fatalf("unexpected second instance summary: %#v", editor.Instances[1])
}
if editor.Instances[0].PublishRTSPPort != "8555" {
t.Fatalf("expected rtsp port to be stringified, got %#v", editor.Instances[0].PublishRTSPPort)
}
if editor.Queue.Size != "8" || editor.Queue.Strategy != "drop_oldest" {
t.Fatalf("unexpected queue model: %#v", editor.Queue)
}
if _, ok := editor.Instances[0].AdvancedParams["queue_debug"]; !ok {
t.Fatalf("expected advanced param to remain in editor, got %#v", editor.Instances[0].AdvancedParams)
}
}
func TestConfigPreviewServiceBuildsProfileDocumentFromEditor(t *testing.T) {
svc := NewConfigPreviewService(&config.Config{})
editor := ConfigProfileEditor{
Name: "local_3588_test",
BusinessName: "A厂区视觉识别",
Description: "test profile",
DeviceCode: "rk3588-a-001",
SiteName: "A厂区",
Queue: ConfigProfileQueueEditor{
Size: "8",
Strategy: "drop_oldest",
},
Instances: []ConfigProfileInstanceEditor{
{
Name: "cam1",
Template: "workshop_face_shoe_alarm",
DisplayName: "东门入口",
RTSPURL: "rtsp://10.0.0.1/live",
PublishHLSPath: "./web/hls/cam1/index.m3u8",
PublishRTSPPort: "8555",
PublishRTSPPath: "/live/cam1",
ChannelNo: "cam1",
AdvancedParams: map[string]any{
"queue_debug": true,
},
},
{
Name: "cam2",
Template: "workshop_face_shoe_alarm",
DisplayName: "视觉识别终端-B厂区",
RTSPURL: "rtsp://10.0.0.2/live",
PublishHLSPath: "./web/hls/cam2/index.m3u8",
PublishRTSPPort: "8556",
PublishRTSPPath: "/live/cam2",
ChannelNo: "cam2",
},
},
}
doc, err := svc.BuildProfileDocument(editor)
if err != nil {
t.Fatalf("BuildProfileDocument: %v", err)
}
if doc["name"] != "local_3588_test" {
t.Fatalf("unexpected doc name: %#v", doc)
}
if doc["business_name"] != "A厂区视觉识别" {
t.Fatalf("unexpected business name: %#v", doc)
}
queue, _ := doc["queue"].(map[string]any)
if queue["size"] != 8 || queue["strategy"] != "drop_oldest" {
t.Fatalf("unexpected queue doc: %#v", queue)
}
instances, _ := doc["instances"].([]map[string]any)
if len(instances) != 2 {
t.Fatalf("expected two instances, got %#v", doc["instances"])
}
params, _ := instances[0]["params"].(map[string]any)
if params["publish_rtsp_port"] != 8555 {
t.Fatalf("expected numeric rtsp port, got %#v", params["publish_rtsp_port"])
}
if params["queue_debug"] != true {
t.Fatalf("expected advanced param to survive rebuild, got %#v", params)
}
if params["device_code"] != "rk3588-a-001" {
t.Fatalf("expected legacy device code to be preserved in params, got %#v", params)
}
if params["site_name"] != "A厂区" {
t.Fatalf("expected profile site name to be written to instance params, got %#v", params)
}
params2, _ := instances[1]["params"].(map[string]any)
if params2["publish_rtsp_path"] != "/live/cam2" {
t.Fatalf("expected second instance to survive rebuild, got %#v", params2)
}
if params2["site_name"] != "A厂区" {
t.Fatalf("expected profile site name on second instance, got %#v", params2)
}
}
func TestConfigPreviewServiceBuildProfileDocumentRejectsBadPort(t *testing.T) {
svc := NewConfigPreviewService(&config.Config{})
_, err := svc.BuildProfileDocument(ConfigProfileEditor{
Name: "local_3588_test",
Instances: []ConfigProfileInstanceEditor{
{
Name: "cam1",
DisplayName: "视觉识别终端-A厂区",
RTSPURL: "rtsp://10.0.0.1/live",
PublishRTSPPort: "bad-port",
},
},
})
if err == nil {
t.Fatal("expected invalid port error")
}
}
func TestConfigPreviewServiceBuildProfileDocumentJSONShape(t *testing.T) {
svc := NewConfigPreviewService(&config.Config{})
doc, err := svc.BuildProfileDocument(ConfigProfileEditor{
Name: "local_3588_test",
Instances: []ConfigProfileInstanceEditor{
{
Name: "cam1",
DisplayName: "视觉识别终端-A厂区",
RTSPURL: "rtsp://10.0.0.1/live",
},
},
})
if err != nil {
t.Fatalf("BuildProfileDocument: %v", err)
}
body, err := json.Marshal(doc)
if err != nil {
t.Fatalf("Marshal: %v", err)
}
if len(body) == 0 {
t.Fatal("expected json body")
}
}
func TestConfigPreviewServiceListsSourcesFromAssetsRepo(t *testing.T) {
store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "app.db"))
if err != nil {
t.Fatalf("OpenSQLite: %v", err)
}
defer store.Close()
repo := storage.NewAssetsRepo(store.DB())
if err := repo.SaveTemplate("helmet", "helmet template", `{"name":"helmet","template":{"nodes":[],"edges":[]}}`); err != nil {
t.Fatalf("SaveTemplate: %v", err)
}
if err := repo.SaveProfile("gate_a", "helmet", "gate", "gate profile", `{"name":"gate_a","business_name":"gate","instances":[{"name":"cam1","template":"helmet","params":{"display_name":"Gate A","rtsp_url":"rtsp://10.0.0.1/live"}}]}`); err != nil {
t.Fatalf("SaveProfile: %v", err)
}
if err := repo.SaveOverlay("night_relaxed", "night overlay", `{"name":"night_relaxed","instance_overrides":{"cam1":{"override":{}}}}`); err != nil {
t.Fatalf("SaveOverlay: %v", err)
}
svc := NewConfigPreviewService(&config.Config{}, repo)
sources, err := svc.ListSources()
if err != nil {
t.Fatalf("ListSources: %v", err)
}
if got := sourceNames(sources.Templates); len(got) != 1 || got[0] != "helmet" {
t.Fatalf("unexpected templates: %#v", got)
}
if got := sourceNames(sources.Profiles); len(got) != 1 || got[0] != "gate_a" {
t.Fatalf("unexpected profiles: %#v", got)
}
if got := sourceNames(sources.Overlays); len(got) != 1 || got[0] != "night_relaxed" {
t.Fatalf("unexpected overlays: %#v", got)
}
}
func TestConfigPreviewServiceSavesProfileEditorToAssetsRepo(t *testing.T) {
store, err := storage.OpenSQLite(filepath.Join(t.TempDir(), "app.db"))
if err != nil {
t.Fatalf("OpenSQLite: %v", err)
}
defer store.Close()
repo := storage.NewAssetsRepo(store.DB())
svc := NewConfigPreviewService(&config.Config{}, repo)
editor := ConfigProfileEditor{
Name: "gate_a",
BusinessName: "厂区入口",
Description: "白班识别",
SiteName: "A厂区",
Instances: []ConfigProfileInstanceEditor{
{
Name: "cam1",
Template: "helmet",
DisplayName: "东门入口",
RTSPURL: "rtsp://10.0.0.1/live",
},
},
}
if err := svc.SaveProfileEditor(editor); err != nil {
t.Fatalf("SaveProfileEditor: %v", err)
}
saved, err := repo.GetProfile("gate_a")
if err != nil {
t.Fatalf("GetProfile: %v", err)
}
if saved == nil {
t.Fatal("expected saved profile")
}
if saved.BusinessName != "厂区入口" {
t.Fatalf("expected business name, got %#v", saved)
}
if saved.TemplateName != "helmet" {
t.Fatalf("expected template name to be inferred, got %#v", saved)
}
if saved.Description != "白班识别" {
t.Fatalf("expected description, got %#v", saved)
}
}