package area import ( "context" "errors" "testing" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func areaTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatal(err) } if err = db.AutoMigrate(&Definition{}, &Version{}); err != nil { t.Fatal(err) } statements := []string{ `CREATE TABLE sense_devices (id text primary key, name text, location text, status text)`, `CREATE TABLE sense_admission_profiles (device_id text, token text, name text, width integer, height integer, encoding text, verification_status text)`, `CREATE TABLE sense_media_routes (id text primary key, device_id text, profile_token text)`, `INSERT INTO sense_devices VALUES ('device-1','东门摄像机','教学楼东门','active')`, `INSERT INTO sense_admission_profiles VALUES ('device-1','main','主码流',1920,1080,'H264','ready')`, `INSERT INTO sense_media_routes VALUES ('device-1:main','device-1','main')`, } for _, statement := range statements { if err = db.Exec(statement).Error; err != nil { t.Fatal(err) } } return db } func triangleRequest() UpsertRequest { return UpsertRequest{Name: "操场危险区域", Kind: KindPolygon, RouteID: "device-1:main", Points: []Point{{0.1, 0.1}, {0.8, 0.1}, {0.5, 0.8}}, Enabled: true, UpdateBy: 7} } func TestVersionsAreAppendOnlyAndUseOptimisticConcurrency(t *testing.T) { service := NewService(areaTestDB(t)) created, err := service.Create(context.Background(), triangleRequest()) if err != nil || created.Version != 1 || created.ProfileWidth != 1920 { t.Fatalf("created=%+v err=%v", created, err) } request := triangleRequest() request.Name = "操场危险区域(校准)" request.ExpectedVersion = 1 updated, err := service.Update(context.Background(), created.ID, request) if err != nil || updated.Version != 2 { t.Fatalf("updated=%+v err=%v", updated, err) } if _, err = service.Update(context.Background(), created.ID, request); !errors.Is(err, ErrConflict) { t.Fatalf("stale update err=%v", err) } versions, err := service.Versions(context.Background(), created.ID) if err != nil || len(versions) != 2 || versions[0].Version != 2 || versions[1].Version != 1 || versions[0].SupersedesID != versions[1].ID { t.Fatalf("versions=%+v err=%v", versions, err) } } func TestProfileReplacementMarksOnlyChangedBindings(t *testing.T) { db := areaTestDB(t) service := NewService(db) created, err := service.Create(context.Background(), triangleRequest()) if err != nil { t.Fatal(err) } if err = MarkProfilesReplaced(db, "device-1", []ProfileSnapshot{{Token: "main", Width: 1920, Height: 1080, Encoding: "H264"}}); err != nil { t.Fatal(err) } item, _ := service.Get(context.Background(), created.ID) if item.NeedsRecalibration { t.Fatal("unchanged profile was marked for recalibration") } if err = MarkProfilesReplaced(db, "device-1", []ProfileSnapshot{{Token: "main", Width: 1280, Height: 720, Encoding: "H264"}}); err != nil { t.Fatal(err) } item, _ = service.Get(context.Background(), created.ID) if !item.NeedsRecalibration { t.Fatal("resolution change did not mark recalibration") } } func TestListIsPaginatedAndDoesNotExposeMediaSecrets(t *testing.T) { service := NewService(areaTestDB(t)) if _, err := service.Create(context.Background(), triangleRequest()); err != nil { t.Fatal(err) } items, total, err := service.List(context.Background(), PageRequest{Keyword: "操场", PageIndex: 1, PageSize: 10}) if err != nil || total != 1 || len(items) != 1 || items[0].RouteID != "device-1:main" { t.Fatalf("items=%+v total=%d err=%v", items, total, err) } }