139 lines
5.5 KiB
Go
139 lines
5.5 KiB
Go
package admission
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"testing"
|
|
|
|
coreService "github.com/go-admin-team/go-admin-core/sdk/service"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
|
|
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/area"
|
|
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/credential"
|
|
deviceModels "git.ilapage.cn/ila/yovision/Sense/server/app/sense/device/models"
|
|
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/onvif"
|
|
"git.ilapage.cn/ila/yovision/Sense/server/app/sense/rtsp"
|
|
)
|
|
|
|
type fakeONVIF struct {
|
|
err error
|
|
profiles []onvif.Profile
|
|
}
|
|
|
|
func (f fakeONVIF) Profiles(context.Context, string, onvif.Credential) ([]onvif.Profile, error) {
|
|
if f.err != nil {
|
|
return nil, f.err
|
|
}
|
|
if f.profiles != nil {
|
|
return f.profiles, nil
|
|
}
|
|
return []onvif.Profile{{Token: "main", Name: "主码流", Width: 1920, Height: 1080, Encoding: "H264", StreamURI: "rtsp://192.0.2.10/main"}, {Token: "sub", Name: "子码流", Width: 640, Height: 360, Encoding: "H264", StreamURI: "rtsp://192.0.2.10/sub"}}, nil
|
|
}
|
|
|
|
type fakeRTSP struct{}
|
|
|
|
func (fakeRTSP) Verify(context.Context, string, rtsp.Credential) (rtsp.Result, error) {
|
|
return rtsp.Result{Status: "ready", Detail: "码流可访问"}, nil
|
|
}
|
|
func admissionService(t *testing.T) *Service {
|
|
t.Helper()
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = db.AutoMigrate(&deviceModels.Device{}, &credential.DeviceCredential{}, &Result{}, &Profile{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
key := []byte("0123456789abcdef0123456789abcdef")
|
|
t.Setenv(credential.EnvironmentKey, base64.StdEncoding.EncodeToString(key))
|
|
vault, _ := credential.NewVault(key)
|
|
device := deviceModels.Device{ID: "device-1", Name: "东门摄像机", Modality: deviceModels.ModalityVideo, Version: 1, Status: "pending", AdapterStatus: "ready"}
|
|
if err = db.Create(&device).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, purpose := range []string{credential.PurposeONVIF, credential.PurposeRTSP} {
|
|
cipher, _ := vault.Encrypt(device.ID, purpose, "synthetic-user", "synthetic-password")
|
|
if err = db.Create(&credential.DeviceCredential{DeviceID: device.ID, Purpose: purpose, Ciphertext: cipher, KeyVersion: credential.Version()}).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return &Service{Service: coreService.Service{Orm: db}, ONVIF: fakeONVIF{}, RTSP: fakeRTSP{}}
|
|
}
|
|
func TestProbePersistsProfilesWithoutReturningCredentials(t *testing.T) {
|
|
service := admissionService(t)
|
|
result, err := service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 1})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != "ready" || len(result.Profiles) != 2 || result.Profiles[0].Kind != "main" || result.Profiles[1].Kind != "sub" {
|
|
t.Fatalf("result=%#v", result)
|
|
}
|
|
if result.Profiles[0].StreamURI == "" {
|
|
t.Fatal("stream URI missing")
|
|
}
|
|
saved, err := service.Get("device-1")
|
|
if err != nil || len(saved.Profiles) != 2 {
|
|
t.Fatalf("saved=%#v err=%v", saved, err)
|
|
}
|
|
}
|
|
func TestFailedReprobePreservesLastVerifiedProfiles(t *testing.T) {
|
|
service := admissionService(t)
|
|
if _, err := service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service.ONVIF = fakeONVIF{err: onvif.ErrAuthentication}
|
|
result, err := service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 2})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if result.Status != "authentication_failed" || len(result.Profiles) != 2 {
|
|
t.Fatalf("last verified profiles lost: %#v", result)
|
|
}
|
|
if _, err = service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 2}); !errors.Is(err, ErrConflict) {
|
|
t.Fatalf("stale error=%v", err)
|
|
}
|
|
}
|
|
|
|
func TestProbeErrorsHaveActionableStates(t *testing.T) {
|
|
for _, test := range []struct {
|
|
err error
|
|
status string
|
|
}{
|
|
{onvif.ErrAuthentication, "authentication_failed"},
|
|
{onvif.ErrTargetNotAllowed, "target_not_allowed"},
|
|
{errors.New("device clock time fault"), "clock_skew"},
|
|
{context.DeadlineExceeded, "timeout"},
|
|
} {
|
|
status, detail := classify(test.err)
|
|
if status != test.status || detail == "" {
|
|
t.Fatalf("error=%v status=%s detail=%s", test.err, status, detail)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProfileResolutionChangeMarksAreaForRecalibration(t *testing.T) {
|
|
service := admissionService(t)
|
|
if _, err := service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.Orm.AutoMigrate(&area.Definition{}, &area.Version{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
definition := area.Definition{ID: "area-1", Name: "东门警戒线", Kind: area.KindDirectionLine, DeviceID: "device-1", ProfileToken: "main", ProfileWidth: 1920, ProfileHeight: 1080, ProfileEncoding: "H264", CurrentVersion: 1, CurrentVersionID: "version-1", Enabled: true, CreatedBy: 7, UpdatedBy: 7}
|
|
if err := service.Orm.Create(&definition).Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
service.ONVIF = fakeONVIF{profiles: []onvif.Profile{{Token: "main", Name: "主码流", Width: 1280, Height: 720, Encoding: "H264", StreamURI: "rtsp://192.0.2.10/main"}}}
|
|
if _, err := service.Probe(context.Background(), ProbeRequest{DeviceID: "device-1", Address: "http://192.0.2.10/onvif", Version: 2}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := service.Orm.First(&definition, "id = ?", "area-1").Error; err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !definition.NeedsRecalibration {
|
|
t.Fatal("profile resolution change did not mark the bound area")
|
|
}
|
|
}
|