Add ONVIF types and structures for device capabilities and configurations
- Introduced comprehensive data structures for ONVIF device information, capabilities, media profiles, and configurations. - Added types for device services, network settings, imaging options, audio/video configurations, and user management. - Implemented structures for handling various ONVIF features including PTZ control, event subscriptions, and analytics configurations. - Enhanced support for network protocols, security settings, and system logging.
This commit is contained in:
@@ -0,0 +1,502 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config *Config
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "New with nil config uses default",
|
||||
config: nil,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "New with custom config",
|
||||
config: createTestConfig(),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
server, err := New(tt.config)
|
||||
if (err != nil) != tt.expectError {
|
||||
t.Errorf("New() error = %v, expectError %v", err, tt.expectError)
|
||||
|
||||
return
|
||||
}
|
||||
if server == nil && !tt.expectError {
|
||||
t.Error("New() returned nil server")
|
||||
|
||||
return
|
||||
}
|
||||
if server != nil && server.config == nil {
|
||||
t.Error("New() server.config is nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInitializesStreamsAndState(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, err := New(config)
|
||||
if err != nil {
|
||||
t.Fatalf("New() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify streams are initialized
|
||||
if len(server.streams) != len(config.Profiles) {
|
||||
t.Errorf("Expected %d streams, got %d", len(config.Profiles), len(server.streams))
|
||||
}
|
||||
|
||||
// Verify each stream has correct configuration
|
||||
for _, profile := range config.Profiles {
|
||||
stream, ok := server.streams[profile.Token]
|
||||
if !ok {
|
||||
t.Errorf("Stream not found for profile %s", profile.Token)
|
||||
|
||||
continue
|
||||
}
|
||||
if stream.ProfileToken != profile.Token {
|
||||
t.Errorf("Stream profile token mismatch: %s != %s", stream.ProfileToken, profile.Token)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify PTZ state is initialized for profiles with PTZ
|
||||
for _, profile := range config.Profiles {
|
||||
if profile.PTZ != nil {
|
||||
_, ok := server.ptzState[profile.Token]
|
||||
if !ok {
|
||||
t.Errorf("PTZ state not found for profile %s", profile.Token)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Verify imaging state is initialized
|
||||
if len(server.imagingState) != len(config.Profiles) {
|
||||
t.Errorf("Expected %d imaging states, got %d", len(config.Profiles), len(server.imagingState))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
got := server.GetConfig()
|
||||
if got != config {
|
||||
t.Error("GetConfig() returned different config")
|
||||
}
|
||||
if got.Profiles[0].Name != config.Profiles[0].Name {
|
||||
t.Errorf("GetConfig() profile name mismatch: %s != %s", got.Profiles[0].Name, config.Profiles[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetStreamConfig(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
profileToken := config.Profiles[0].Token
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expectOk bool
|
||||
checkFunc func(*StreamConfig) error
|
||||
}{
|
||||
{
|
||||
name: "Get existing stream",
|
||||
token: profileToken,
|
||||
expectOk: true,
|
||||
checkFunc: func(sc *StreamConfig) error {
|
||||
if sc.ProfileToken != profileToken {
|
||||
return errorf("profile token mismatch: %s != %s", sc.ProfileToken, profileToken)
|
||||
}
|
||||
if sc.StreamURI == "" {
|
||||
return errorf("StreamURI is empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get non-existent stream",
|
||||
token: "invalid-token",
|
||||
expectOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
stream, ok := server.GetStreamConfig(tt.token)
|
||||
if ok != tt.expectOk {
|
||||
t.Errorf("GetStreamConfig() ok = %v, expectOk %v", ok, tt.expectOk)
|
||||
|
||||
return
|
||||
}
|
||||
if ok && tt.checkFunc != nil {
|
||||
if err := tt.checkFunc(stream); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateStreamURI(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
profileToken := config.Profiles[0].Token
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
newURI string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "Update existing stream URI",
|
||||
token: profileToken,
|
||||
newURI: "rtsp://localhost:8554/newstream",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Update non-existent stream",
|
||||
token: "invalid-token",
|
||||
newURI: "rtsp://localhost:8554/stream",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := server.UpdateStreamURI(tt.token, tt.newURI)
|
||||
if (err != nil) != tt.expectError {
|
||||
t.Errorf("UpdateStreamURI() error = %v, expectError %v", err, tt.expectError)
|
||||
|
||||
return
|
||||
}
|
||||
if !tt.expectError {
|
||||
stream, _ := server.GetStreamConfig(tt.token)
|
||||
if stream.StreamURI != tt.newURI {
|
||||
t.Errorf("UpdateStreamURI() failed: %s != %s", stream.StreamURI, tt.newURI)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListProfiles(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
profiles := server.ListProfiles()
|
||||
|
||||
if len(profiles) != len(config.Profiles) {
|
||||
t.Errorf("ListProfiles() length = %d, want %d", len(profiles), len(config.Profiles))
|
||||
}
|
||||
|
||||
for i, profile := range profiles {
|
||||
if profile.Token != config.Profiles[i].Token {
|
||||
t.Errorf("ListProfiles()[%d] token mismatch: %s != %s", i, profile.Token, config.Profiles[i].Token)
|
||||
}
|
||||
if profile.Name != config.Profiles[i].Name {
|
||||
t.Errorf("ListProfiles()[%d] name mismatch: %s != %s", i, profile.Name, config.Profiles[i].Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPTZState(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
// Find a profile with PTZ
|
||||
var profileWithPTZ string
|
||||
for _, profile := range config.Profiles {
|
||||
if profile.PTZ != nil {
|
||||
profileWithPTZ = profile.Token
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if profileWithPTZ == "" {
|
||||
// Create config with PTZ
|
||||
config.Profiles[0].PTZ = &PTZConfig{
|
||||
NodeToken: "ptz_node",
|
||||
PanRange: Range{Min: -360, Max: 360},
|
||||
TiltRange: Range{Min: -90, Max: 90},
|
||||
ZoomRange: Range{Min: 0, Max: 10},
|
||||
}
|
||||
server, _ = New(config)
|
||||
profileWithPTZ = config.Profiles[0].Token
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expectOk bool
|
||||
}{
|
||||
{
|
||||
name: "Get PTZ state for profile with PTZ",
|
||||
token: profileWithPTZ,
|
||||
expectOk: true,
|
||||
},
|
||||
{
|
||||
name: "Get PTZ state for non-existent profile",
|
||||
token: "invalid-token",
|
||||
expectOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state, ok := server.GetPTZState(tt.token)
|
||||
if ok != tt.expectOk {
|
||||
t.Errorf("GetPTZState() ok = %v, expectOk %v", ok, tt.expectOk)
|
||||
|
||||
return
|
||||
}
|
||||
if ok && state == nil {
|
||||
t.Error("GetPTZState() returned nil state")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetImagingState(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
videoSourceToken := config.Profiles[0].VideoSource.Token
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
token string
|
||||
expectOk bool
|
||||
checkFunc func(*ImagingState) error
|
||||
}{
|
||||
{
|
||||
name: "Get imaging state for existing source",
|
||||
token: videoSourceToken,
|
||||
expectOk: true,
|
||||
checkFunc: func(state *ImagingState) error {
|
||||
if state.Brightness < 0 || state.Brightness > 100 {
|
||||
return errorf("brightness out of range: %f", state.Brightness)
|
||||
}
|
||||
if state.Contrast < 0 || state.Contrast > 100 {
|
||||
return errorf("contrast out of range: %f", state.Contrast)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Get imaging state for non-existent source",
|
||||
token: "invalid-token",
|
||||
expectOk: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state, ok := server.GetImagingState(tt.token)
|
||||
if ok != tt.expectOk {
|
||||
t.Errorf("GetImagingState() ok = %v, expectOk %v", ok, tt.expectOk)
|
||||
|
||||
return
|
||||
}
|
||||
if ok && tt.checkFunc != nil {
|
||||
if err := tt.checkFunc(state); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerInfo(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
info := server.ServerInfo()
|
||||
|
||||
if info == "" {
|
||||
t.Error("ServerInfo() returned empty string")
|
||||
}
|
||||
|
||||
// Check that key information is present
|
||||
if !contains(info, config.DeviceInfo.Manufacturer) {
|
||||
t.Errorf("ServerInfo() missing manufacturer: %s", config.DeviceInfo.Manufacturer)
|
||||
}
|
||||
if !contains(info, config.DeviceInfo.Model) {
|
||||
t.Errorf("ServerInfo() missing model: %s", config.DeviceInfo.Model)
|
||||
}
|
||||
if !contains(info, config.Profiles[0].Name) {
|
||||
t.Errorf("ServerInfo() missing profile name: %s", config.Profiles[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartContextTimeout(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
config.Port = 0 // Use random port
|
||||
server, _ := New(config)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
// Start should return due to context timeout
|
||||
err := server.Start(ctx)
|
||||
if err != nil {
|
||||
t.Logf("Start() error (expected): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func createTestConfig() *Config {
|
||||
return &Config{
|
||||
Host: "127.0.0.1",
|
||||
Port: 8080,
|
||||
BasePath: "/onvif",
|
||||
Timeout: 30 * time.Second,
|
||||
DeviceInfo: DeviceInfo{
|
||||
Manufacturer: "Test",
|
||||
Model: "TestCamera",
|
||||
FirmwareVersion: "1.0.0",
|
||||
SerialNumber: "12345",
|
||||
HardwareID: "HW001",
|
||||
},
|
||||
Username: "admin",
|
||||
Password: "password",
|
||||
Profiles: []ProfileConfig{
|
||||
{
|
||||
Token: "profile_token_1",
|
||||
Name: "Profile 1",
|
||||
VideoSource: VideoSourceConfig{
|
||||
Token: "video_source_1",
|
||||
Name: "Video Source 1",
|
||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
||||
Framerate: 30,
|
||||
Bounds: Bounds{
|
||||
X: 0,
|
||||
Y: 0,
|
||||
Width: 1920,
|
||||
Height: 1080,
|
||||
},
|
||||
},
|
||||
VideoEncoder: VideoEncoderConfig{
|
||||
Encoding: "H264",
|
||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 80,
|
||||
Framerate: 30,
|
||||
Bitrate: 2048,
|
||||
GovLength: 30,
|
||||
},
|
||||
PTZ: &PTZConfig{
|
||||
NodeToken: "ptz_node_1",
|
||||
PanRange: Range{Min: -360, Max: 360},
|
||||
TiltRange: Range{Min: -90, Max: 90},
|
||||
ZoomRange: Range{Min: 0, Max: 10},
|
||||
},
|
||||
Snapshot: SnapshotConfig{
|
||||
Enabled: true,
|
||||
Resolution: Resolution{Width: 1920, Height: 1080},
|
||||
Quality: 85.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
SupportPTZ: true,
|
||||
SupportImaging: true,
|
||||
SupportEvents: false,
|
||||
}
|
||||
}
|
||||
|
||||
func contains(s, substr string) bool {
|
||||
for i := 0; i < len(s)-len(substr)+1; i++ {
|
||||
if s[i:i+len(substr)] == substr {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type testError struct {
|
||||
msg string
|
||||
}
|
||||
|
||||
func (e *testError) Error() string {
|
||||
return e.msg
|
||||
}
|
||||
|
||||
func errorf(format string, args ...interface{}) error {
|
||||
return &testError{msg: fmt.Sprintf(format, args...)}
|
||||
}
|
||||
|
||||
func TestServerInfoMethod(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
info := server.ServerInfo()
|
||||
|
||||
if info == "" {
|
||||
t.Fatal("ServerInfo() returned empty string")
|
||||
}
|
||||
|
||||
// ServerInfo returns a formatted string with server information
|
||||
if !strings.Contains(info, "127.0.0.1") && !strings.Contains(info, "localhost") {
|
||||
t.Logf("ServerInfo may not contain host: %s", info)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGettersAndSetters(t *testing.T) {
|
||||
config := createTestConfig()
|
||||
server, _ := New(config)
|
||||
|
||||
// Test GetConfig
|
||||
cfg := server.GetConfig()
|
||||
if cfg == nil {
|
||||
t.Error("GetConfig returned nil")
|
||||
}
|
||||
|
||||
// Test GetStreamConfig
|
||||
streamCfg, _ := server.GetStreamConfig(config.Profiles[0].Token)
|
||||
if streamCfg == nil {
|
||||
t.Error("GetStreamConfig returned nil")
|
||||
}
|
||||
|
||||
// Test UpdateStreamURI
|
||||
newURI := "rtsp://example.com/stream"
|
||||
server.UpdateStreamURI(config.Profiles[0].Token, newURI)
|
||||
updated, _ := server.GetStreamConfig(config.Profiles[0].Token)
|
||||
if updated.StreamURI != newURI {
|
||||
t.Errorf("UpdateStreamURI failed: got %s, want %s", updated.StreamURI, newURI)
|
||||
}
|
||||
|
||||
// Test ListProfiles
|
||||
profiles := server.ListProfiles()
|
||||
if len(profiles) == 0 {
|
||||
t.Error("ListProfiles returned empty list")
|
||||
}
|
||||
|
||||
// Test GetPTZState
|
||||
ptzState, _ := server.GetPTZState(config.Profiles[0].Token)
|
||||
if ptzState == nil {
|
||||
t.Error("GetPTZState returned nil")
|
||||
}
|
||||
|
||||
// Test GetImagingState
|
||||
imgState, _ := server.GetImagingState(config.Profiles[0].VideoSource.Token)
|
||||
if imgState == nil {
|
||||
t.Error("GetImagingState returned nil")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user