383 lines
12 KiB
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)
|
|
}
|
|
}
|