271 lines
10 KiB
Go
271 lines
10 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"3588AdminBackend/internal/config"
|
|
"3588AdminBackend/internal/storage"
|
|
)
|
|
|
|
func TestConfigPreviewServiceListsSources(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustWrite(t, filepath.Join(root, "configs", "templates", "std_workshop_face_recognition_shoe_alarm.json"), `{}`)
|
|
mustWrite(t, filepath.Join(root, "configs", "profiles", "local_3588_test.json"), `{}`)
|
|
mustWrite(t, filepath.Join(root, "configs", "overlays", "face_debug.json"), `{}`)
|
|
|
|
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root})
|
|
sources, err := svc.ListSources()
|
|
if err != nil {
|
|
t.Fatalf("ListSources: %v", err)
|
|
}
|
|
|
|
if sources.Root != root {
|
|
t.Fatalf("expected root %q, got %q", root, sources.Root)
|
|
}
|
|
if got := sourceNames(sources.Templates); strings.Join(got, ",") != "std_workshop_face_recognition_shoe_alarm" {
|
|
t.Fatalf("unexpected templates: %v", got)
|
|
}
|
|
if got := sourceNames(sources.Profiles); strings.Join(got, ",") != "local_3588_test" {
|
|
t.Fatalf("unexpected profiles: %v", got)
|
|
}
|
|
if got := sourceNames(sources.Overlays); strings.Join(got, ",") != "face_debug" {
|
|
t.Fatalf("unexpected overlays: %v", got)
|
|
}
|
|
}
|
|
|
|
func TestConfigPreviewServiceRejectsUnsafeNames(t *testing.T) {
|
|
root := t.TempDir()
|
|
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root})
|
|
|
|
_, err := svc.Render(ConfigPreviewRequest{
|
|
Template: "../secret",
|
|
Profile: "local_3588_test",
|
|
ConfigID: "preview",
|
|
ConfigVersion: "v1",
|
|
})
|
|
if err == nil {
|
|
t.Fatal("expected unsafe template name to be rejected")
|
|
}
|
|
}
|
|
|
|
func TestConfigPreviewServiceImportsAssetsIntoSQLite(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustWrite(t, filepath.Join(root, "configs", "templates", "helmet.json"), `{"name":"helmet","description":"helmet template","template":{"nodes":[],"edges":[]}}`)
|
|
mustWrite(t, filepath.Join(root, "configs", "profiles", "gate_a.json"), `{"name":"gate_a","business_name":"Gate A","description":"gate profile","instances":[{"name":"cam1","template":"helmet","params":{"display_name":"Gate A","rtsp_url":"rtsp://10.0.0.1/live"}}]}`)
|
|
mustWrite(t, filepath.Join(root, "configs", "overlays", "night_relaxed.json"), `{"name":"night_relaxed","description":"overlay","instance_overrides":{"cam1":{"override":{}}}}`)
|
|
|
|
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{MediaRepoPath: root}, repo)
|
|
result, err := svc.ImportAssetsFromMediaRepo()
|
|
if err != nil {
|
|
t.Fatalf("ImportAssetsFromMediaRepo: %v", err)
|
|
}
|
|
if result.Templates != 0 || result.Profiles != 1 || result.Overlays != 1 {
|
|
t.Fatalf("unexpected import result: %#v", result)
|
|
}
|
|
|
|
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 after import: %#v", got)
|
|
}
|
|
if record, err := repo.GetTemplate("helmet"); err != nil {
|
|
t.Fatalf("GetTemplate: %v", err)
|
|
} else if record != nil {
|
|
t.Fatalf("expected builtin template to remain outside sqlite, got %#v", record)
|
|
}
|
|
}
|
|
|
|
func TestConfigPreviewServiceExportsAssetJSONFromSQLite(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())
|
|
const raw = "{\n \"name\": \"helmet\",\n \"template\": {\n \"nodes\": [],\n \"edges\": []\n }\n}\n"
|
|
if err := repo.SaveTemplate("helmet", "helmet template", raw); err != nil {
|
|
t.Fatalf("SaveTemplate: %v", err)
|
|
}
|
|
|
|
svc := NewConfigPreviewService(&config.Config{}, repo)
|
|
body, filename, err := svc.ExportAssetJSON("templates", "helmet")
|
|
if err != nil {
|
|
t.Fatalf("ExportAssetJSON: %v", err)
|
|
}
|
|
if filename != "helmet.json" {
|
|
t.Fatalf("unexpected export filename: %q", filename)
|
|
}
|
|
if string(body) != raw {
|
|
t.Fatalf("unexpected export body: %s", string(body))
|
|
}
|
|
}
|
|
|
|
func TestConfigPreviewServiceIntegrationParamsForProfile(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())
|
|
saveIntegrationServiceForPreviewTest(t, repo, "minio_main", "object_storage", `{"name":"minio_main","type":"object_storage","config":{"endpoint":"http://10.0.0.49:9000","bucket":"myminio","access_key":"admin","secret_key":"password"}}`)
|
|
saveIntegrationServiceForPreviewTest(t, repo, "token_main", "token_service", `{"name":"token_main","type":"token_service","config":{"get_token_url":"http://10.0.0.49:8080/api/getToken","tenant_code":"32"}}`)
|
|
saveIntegrationServiceForPreviewTest(t, repo, "alarm_main", "alarm_service", `{"name":"alarm_main","type":"alarm_service","config":{"put_message_url":"http://10.0.0.49:8080/api/putMessage","tenant_code":"32"}}`)
|
|
|
|
svc := NewConfigPreviewService(&config.Config{}, repo)
|
|
params, err := svc.integrationParamsForProfile(map[string]any{
|
|
"object_storage_ref": "minio_main",
|
|
"token_service_ref": "token_main",
|
|
"alarm_service_ref": "alarm_main",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("integrationParamsForProfile: %v", err)
|
|
}
|
|
for key, want := range map[string]string{
|
|
"minio_endpoint": "http://10.0.0.49:9000",
|
|
"minio_bucket": "myminio",
|
|
"minio_access_key": "admin",
|
|
"minio_secret_key": "password",
|
|
"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",
|
|
} {
|
|
if got := params[key]; got != want {
|
|
t.Fatalf("expected %s=%q, got %#v", key, want, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestConfigPreviewServiceRenderProfileEditorExpandsIntegrationRefs(t *testing.T) {
|
|
root := t.TempDir()
|
|
mustWrite(t, filepath.Join(root, "configs", "templates", "std_workshop_face_recognition_shoe_alarm.json"), `{
|
|
"name":"std_workshop_face_recognition_shoe_alarm",
|
|
"params":{"existing":"keep"},
|
|
"template":{"nodes":[],"edges":[]}
|
|
}`)
|
|
mustWrite(t, filepath.Join(root, "tools", "render_config.py"), `import argparse
|
|
import json
|
|
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--template", required=True)
|
|
parser.add_argument("--profile", required=True)
|
|
parser.add_argument("--out", required=True)
|
|
parser.add_argument("--config-id", required=True)
|
|
parser.add_argument("--config-version", required=True)
|
|
parser.add_argument("--rendered-at", required=True)
|
|
parser.add_argument("--overlay", action="append", default=[])
|
|
args = parser.parse_args()
|
|
|
|
with open(args.template, "r", encoding="utf-8") as fh:
|
|
template = json.load(fh)
|
|
with open(args.profile, "r", encoding="utf-8") as fh:
|
|
profile = json.load(fh)
|
|
|
|
doc = {
|
|
"metadata": {
|
|
"template": template.get("name"),
|
|
"profile": profile.get("name"),
|
|
},
|
|
"template_params": template.get("params", {}),
|
|
"profile_refs": {
|
|
"object_storage_ref": profile.get("object_storage_ref"),
|
|
"token_service_ref": profile.get("token_service_ref"),
|
|
"alarm_service_ref": profile.get("alarm_service_ref"),
|
|
}
|
|
}
|
|
with open(args.out, "w", encoding="utf-8") as fh:
|
|
json.dump(doc, fh, ensure_ascii=False, indent=2)
|
|
`)
|
|
|
|
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())
|
|
saveIntegrationServiceForPreviewTest(t, repo, "minio_main", "object_storage", `{"name":"minio_main","type":"object_storage","config":{"endpoint":"http://10.0.0.49:9000","bucket":"myminio","access_key":"admin","secret_key":"password"}}`)
|
|
saveIntegrationServiceForPreviewTest(t, repo, "token_main", "token_service", `{"name":"token_main","type":"token_service","config":{"get_token_url":"http://10.0.0.49:8080/api/getToken","tenant_code":"32"}}`)
|
|
saveIntegrationServiceForPreviewTest(t, repo, "alarm_main", "alarm_service", `{"name":"alarm_main","type":"alarm_service","config":{"put_message_url":"http://10.0.0.49:8080/api/putMessage","tenant_code":"32"}}`)
|
|
|
|
svc := NewConfigPreviewService(&config.Config{MediaRepoPath: root}, repo)
|
|
editor := ConfigProfileEditor{
|
|
Name: "line_a",
|
|
ObjectStorageRef: "minio_main",
|
|
TokenServiceRef: "token_main",
|
|
AlarmServiceRef: "alarm_main",
|
|
Instances: []ConfigProfileInstanceEditor{{
|
|
Name: "cam1",
|
|
Template: "std_workshop_face_recognition_shoe_alarm",
|
|
RTSPURL: "rtsp://10.0.0.1/live",
|
|
}},
|
|
}
|
|
|
|
result, err := svc.RenderProfileEditor(editor, ConfigPreviewRequest{
|
|
Template: "std_workshop_face_recognition_shoe_alarm",
|
|
ConfigID: "preview",
|
|
ConfigVersion: "v1",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("RenderProfileEditor: %v", err)
|
|
}
|
|
|
|
var doc map[string]any
|
|
if err := json.Unmarshal([]byte(result.JSON), &doc); err != nil {
|
|
t.Fatalf("unmarshal render result: %v", err)
|
|
}
|
|
templateParams, _ := doc["template_params"].(map[string]any)
|
|
for key, want := range map[string]string{
|
|
"existing": "keep",
|
|
"minio_endpoint": "http://10.0.0.49:9000",
|
|
"minio_bucket": "myminio",
|
|
"minio_access_key": "admin",
|
|
"minio_secret_key": "password",
|
|
"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",
|
|
} {
|
|
if got := stringValue(templateParams[key]); got != want {
|
|
t.Fatalf("expected template param %s=%q, got %#v", key, want, templateParams[key])
|
|
}
|
|
}
|
|
}
|
|
|
|
func saveIntegrationServiceForPreviewTest(t *testing.T, repo *storage.AssetsRepo, name string, serviceType string, body string) {
|
|
t.Helper()
|
|
if err := repo.SaveIntegrationService(name, serviceType, name, true, body); err != nil {
|
|
t.Fatalf("SaveIntegrationService(%s): %v", name, err)
|
|
}
|
|
}
|
|
|
|
func sourceNames(items []ConfigSource) []string {
|
|
out := make([]string, 0, len(items))
|
|
for _, item := range items {
|
|
out = append(out, item.Name)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func mustWrite(t *testing.T, path string, body string) {
|
|
t.Helper()
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
t.Fatalf("mkdir %s: %v", filepath.Dir(path), err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(body), 0644); err != nil {
|
|
t.Fatalf("write %s: %v", path, err)
|
|
}
|
|
}
|