Files
0x524a 9e3b5e0170 feat: add comprehensive ONVIF test reports and enhance documentation
- Introduced CAMERA_TEST_REPORT.md and COMPREHENSIVE_TEST_SUMMARY.md to document testing results for the Bosch FLEXIDOME indoor 5100i IR camera.
- Added detailed analysis of ONVIF Media Service operations and implementation status in MEDIA_OPERATIONS_ANALYSIS.md and MEDIA_WSDL_OPERATIONS_ANALYSIS.md.
- Updated implementation status documentation to reflect the completion of all 79 operations in the ONVIF Media Service.
- Enhanced existing comments and documentation across various files for better clarity and consistency.
2025-12-02 02:29:51 -05:00

680 lines
15 KiB
Go

package server
import (
"strings"
"testing"
"time"
)
func TestDefaultConfig(t *testing.T) {
config := DefaultConfig()
tests := []struct {
name string
checkFunc func(*Config) error
}{
{
name: "Host is set",
checkFunc: func(c *Config) error {
if c.Host == "" {
return errorf("Host is empty")
}
return nil
},
},
{
name: "Port is valid",
checkFunc: func(c *Config) error {
if c.Port <= 0 || c.Port > 65535 {
return errorf("Port is invalid: %d", c.Port)
}
return nil
},
},
{
name: "BasePath is set",
checkFunc: func(c *Config) error {
if c.BasePath == "" {
return errorf("BasePath is empty")
}
return nil
},
},
{
name: "Timeout is positive",
checkFunc: func(c *Config) error {
if c.Timeout <= 0 {
return errorf("Timeout is not positive: %v", c.Timeout)
}
return nil
},
},
{
name: "DeviceInfo is populated",
checkFunc: func(c *Config) error {
if c.DeviceInfo.Manufacturer == "" {
return errorf("Manufacturer is empty")
}
if c.DeviceInfo.Model == "" {
return errorf("Model is empty")
}
if c.DeviceInfo.FirmwareVersion == "" {
return errorf("FirmwareVersion is empty")
}
return nil
},
},
{
name: "Has at least one profile",
checkFunc: func(c *Config) error {
if len(c.Profiles) == 0 {
return errorf("No profiles configured")
}
return nil
},
},
{
name: "Profile has valid token",
checkFunc: func(c *Config) error {
if c.Profiles[0].Token == "" {
return errorf("Profile token is empty")
}
return nil
},
},
{
name: "Profile has valid name",
checkFunc: func(c *Config) error {
if c.Profiles[0].Name == "" {
return errorf("Profile name is empty")
}
return nil
},
},
{
name: "Profile has video source",
checkFunc: func(c *Config) error {
if c.Profiles[0].VideoSource.Token == "" {
return errorf("Video source token is empty")
}
if c.Profiles[0].VideoSource.Resolution.Width == 0 {
return errorf("Video resolution width is 0")
}
if c.Profiles[0].VideoSource.Resolution.Height == 0 {
return errorf("Video resolution height is 0")
}
return nil
},
},
{
name: "Profile has video encoder",
checkFunc: func(c *Config) error {
if c.Profiles[0].VideoEncoder.Encoding == "" {
return errorf("Video encoder encoding is empty")
}
if c.Profiles[0].VideoEncoder.Framerate == 0 {
return errorf("Video framerate is 0")
}
return nil
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.checkFunc(config); err != nil {
t.Error(err)
}
})
}
}
func TestResolution(t *testing.T) {
tests := []struct {
name string
resolution Resolution
expectValid bool
}{
{
name: "Valid resolution 1920x1080",
resolution: Resolution{Width: 1920, Height: 1080},
expectValid: true,
},
{
name: "Valid resolution 640x480",
resolution: Resolution{Width: 640, Height: 480},
expectValid: true,
},
{
name: "Zero width",
resolution: Resolution{Width: 0, Height: 1080},
expectValid: false,
},
{
name: "Zero height",
resolution: Resolution{Width: 1920, Height: 0},
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if (tt.resolution.Width > 0 && tt.resolution.Height > 0) != tt.expectValid {
t.Errorf("Resolution validation failed: Width=%d, Height=%d",
tt.resolution.Width, tt.resolution.Height)
}
})
}
}
func TestRange(t *testing.T) {
tests := []struct {
name string
rangeVal Range
testValue float64
expectIn bool
}{
{
name: "Value within range",
rangeVal: Range{Min: -360, Max: 360},
testValue: 0,
expectIn: true,
},
{
name: "Value at min boundary",
rangeVal: Range{Min: -90, Max: 90},
testValue: -90,
expectIn: true,
},
{
name: "Value at max boundary",
rangeVal: Range{Min: -90, Max: 90},
testValue: 90,
expectIn: true,
},
{
name: "Value below range",
rangeVal: Range{Min: 0, Max: 10},
testValue: -1,
expectIn: false,
},
{
name: "Value above range",
rangeVal: Range{Min: 0, Max: 10},
testValue: 11,
expectIn: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
inRange := tt.testValue >= tt.rangeVal.Min && tt.testValue <= tt.rangeVal.Max
if inRange != tt.expectIn {
t.Errorf("Range check failed: %f in [%f, %f] = %v, expect %v",
tt.testValue, tt.rangeVal.Min, tt.rangeVal.Max, inRange, tt.expectIn)
}
})
}
}
func TestBounds(t *testing.T) {
tests := []struct {
name string
bounds Bounds
expectValid bool
}{
{
name: "Valid bounds",
bounds: Bounds{X: 0, Y: 0, Width: 1920, Height: 1080},
expectValid: true,
},
{
name: "Zero width",
bounds: Bounds{X: 0, Y: 0, Width: 0, Height: 1080},
expectValid: false,
},
{
name: "Negative coordinates",
bounds: Bounds{X: -10, Y: -10, Width: 1920, Height: 1080},
expectValid: true, // Negative coordinates may be valid in some cases
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.bounds.Width > 0 && tt.bounds.Height > 0
if isValid != tt.expectValid {
t.Errorf("Bounds validation failed: %+v", tt.bounds)
}
})
}
}
func TestPreset(t *testing.T) {
tests := []struct {
name string
preset Preset
expectValid bool
}{
{
name: "Valid preset",
preset: Preset{
Token: "preset_1",
Name: "Home",
Position: PTZPosition{Pan: 0, Tilt: 0, Zoom: 0},
},
expectValid: true,
},
{
name: "Preset with empty token",
preset: Preset{
Token: "",
Name: "Home",
},
expectValid: false,
},
{
name: "Preset with empty name",
preset: Preset{
Token: "preset_1",
Name: "",
},
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.preset.Token != "" && tt.preset.Name != ""
if isValid != tt.expectValid {
t.Errorf("Preset validation failed: Token=%s, Name=%s",
tt.preset.Token, tt.preset.Name)
}
})
}
}
func TestPTZConfig(t *testing.T) {
tests := []struct {
name string
ptzConfig *PTZConfig
expectValid bool
}{
{
name: "Valid PTZ config",
ptzConfig: &PTZConfig{
NodeToken: "ptz_node",
PanRange: Range{Min: -360, Max: 360},
TiltRange: Range{Min: -90, Max: 90},
ZoomRange: Range{Min: 0, Max: 10},
},
expectValid: true,
},
{
name: "PTZ config with presets",
ptzConfig: &PTZConfig{
NodeToken: "ptz_node",
PanRange: Range{Min: -360, Max: 360},
TiltRange: Range{Min: -90, Max: 90},
ZoomRange: Range{Min: 0, Max: 10},
Presets: []Preset{
{Token: "preset_1", Name: "Home"},
{Token: "preset_2", Name: "Away"},
},
},
expectValid: true,
},
{
name: "PTZ config with empty node token",
ptzConfig: &PTZConfig{
NodeToken: "",
PanRange: Range{Min: -360, Max: 360},
},
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.ptzConfig.NodeToken != ""
if isValid != tt.expectValid {
t.Errorf("PTZ config validation failed: NodeToken=%s", tt.ptzConfig.NodeToken)
}
})
}
}
func TestVideoEncoderConfig(t *testing.T) {
tests := []struct {
name string
encoderConfig VideoEncoderConfig
expectValid bool
}{
{
name: "Valid H264 encoder",
encoderConfig: VideoEncoderConfig{
Encoding: "H264",
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 80,
Framerate: 30,
Bitrate: 2048,
},
expectValid: true,
},
{
name: "Valid H265 encoder",
encoderConfig: VideoEncoderConfig{
Encoding: "H265",
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 80,
Framerate: 30,
Bitrate: 1024,
},
expectValid: true,
},
{
name: "JPEG encoder",
encoderConfig: VideoEncoderConfig{
Encoding: "JPEG",
Resolution: Resolution{Width: 640, Height: 480},
Quality: 90,
Framerate: 15,
},
expectValid: true,
},
{
name: "Invalid quality (too high)",
encoderConfig: VideoEncoderConfig{
Encoding: "H264",
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 101,
Framerate: 30,
},
expectValid: false,
},
{
name: "Invalid quality (negative)",
encoderConfig: VideoEncoderConfig{
Encoding: "H264",
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: -1,
Framerate: 30,
},
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.encoderConfig.Encoding != "" &&
tt.encoderConfig.Quality >= 0 && tt.encoderConfig.Quality <= 100 &&
tt.encoderConfig.Resolution.Width > 0 && tt.encoderConfig.Resolution.Height > 0
if isValid != tt.expectValid {
t.Errorf("Encoder validation failed: Quality=%f", tt.encoderConfig.Quality)
}
})
}
}
func TestProfileConfig(t *testing.T) {
tests := []struct {
name string
profileConfig ProfileConfig
expectValid bool
}{
{
name: "Valid profile config",
profileConfig: ProfileConfig{
Token: "profile_1",
Name: "Profile 1",
VideoSource: VideoSourceConfig{
Token: "vs_1",
Name: "Video Source",
Resolution: Resolution{Width: 1920, Height: 1080},
Framerate: 30,
},
VideoEncoder: VideoEncoderConfig{
Encoding: "H264",
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 80,
Framerate: 30,
},
},
expectValid: true,
},
{
name: "Profile with empty token",
profileConfig: ProfileConfig{
Token: "",
Name: "Profile",
},
expectValid: false,
},
{
name: "Profile with empty name",
profileConfig: ProfileConfig{
Token: "profile_1",
Name: "",
},
expectValid: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
isValid := tt.profileConfig.Token != "" && tt.profileConfig.Name != ""
if isValid != tt.expectValid {
t.Errorf("Profile validation failed: Token=%s, Name=%s",
tt.profileConfig.Token, tt.profileConfig.Name)
}
})
}
}
func TestSnapshotConfig(t *testing.T) {
tests := []struct {
name string
snapshotConfig SnapshotConfig
expectValid bool
}{
{
name: "Valid snapshot config",
snapshotConfig: SnapshotConfig{
Enabled: true,
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 85.0,
},
expectValid: true,
},
{
name: "Disabled snapshot",
snapshotConfig: SnapshotConfig{
Enabled: false,
Resolution: Resolution{Width: 0, Height: 0},
Quality: 0,
},
expectValid: true,
},
{
name: "Enabled with resolution",
snapshotConfig: SnapshotConfig{
Enabled: true,
Resolution: Resolution{Width: 1280, Height: 720},
Quality: 75.0,
},
expectValid: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Snapshot config is valid if it has resolution and quality when enabled
isValid := !tt.snapshotConfig.Enabled ||
(tt.snapshotConfig.Resolution.Width > 0 && tt.snapshotConfig.Resolution.Height > 0)
if isValid != tt.expectValid {
t.Errorf("Snapshot validation failed: Enabled=%v, Resolution=%dx%d",
tt.snapshotConfig.Enabled, tt.snapshotConfig.Resolution.Width, tt.snapshotConfig.Resolution.Height)
}
})
}
}
func TestConfigTimeout(t *testing.T) {
config := DefaultConfig()
if config.Timeout == 0 {
t.Error("Timeout should not be 0")
}
if config.Timeout < 1*time.Second {
t.Errorf("Timeout too small: %v", config.Timeout)
}
if config.Timeout > 5*time.Minute {
t.Errorf("Timeout too large: %v", config.Timeout)
}
}
func TestServiceEndpoints(t *testing.T) {
tests := []struct {
name string
config *Config
host string
expectServices []string
}{
{
name: "Default endpoints",
config: &Config{
Host: "192.168.1.100",
Port: 8080,
BasePath: "/onvif",
SupportPTZ: true,
SupportEvents: true,
},
host: "",
expectServices: []string{"device", "media", "imaging", "ptz", "events"},
},
{
name: "Custom host",
config: &Config{
Host: "192.168.1.100",
Port: 8080,
BasePath: "/onvif",
SupportPTZ: false,
SupportEvents: false,
},
host: "custom.example.com",
expectServices: []string{"device", "media", "imaging"},
},
{
name: "Port 80",
config: &Config{
Host: "localhost",
Port: 80,
BasePath: "/onvif",
SupportPTZ: true,
},
host: "",
expectServices: []string{"device", "media", "imaging", "ptz"},
},
{
name: "Default host with 0.0.0.0",
config: &Config{
Host: "0.0.0.0",
Port: 8080,
BasePath: "/onvif",
},
host: "",
expectServices: []string{"device", "media", "imaging"},
},
{
name: "Empty host fallback",
config: &Config{
Host: "",
Port: 8080,
BasePath: "/onvif",
},
host: "",
expectServices: []string{"device", "media", "imaging"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
endpoints := tt.config.ServiceEndpoints(tt.host)
for _, svc := range tt.expectServices {
if _, ok := endpoints[svc]; !ok {
t.Errorf("Missing endpoint: %s", svc)
}
}
// Verify URL format
for name, url := range endpoints {
if !strings.HasPrefix(url, "http://") {
t.Errorf("Endpoint %s should start with http://: %s", name, url)
}
}
})
}
}
func TestServiceEndpointsURL(t *testing.T) {
config := &Config{
Host: "example.com",
Port: 9000,
BasePath: "/services",
SupportPTZ: true,
SupportEvents: true,
}
endpoints := config.ServiceEndpoints("example.com")
expectedDeviceURL := "http://example.com:9000/services/device_service"
if endpoints["device"] != expectedDeviceURL {
t.Errorf("Device endpoint mismatch: got %s, want %s", endpoints["device"], expectedDeviceURL)
}
}
func TestToONVIFProfile(t *testing.T) {
profile := &ProfileConfig{
Token: "profile_1",
Name: "HD Profile",
VideoSource: VideoSourceConfig{
Token: "source_1",
Framerate: 30,
Resolution: Resolution{Width: 1920, Height: 1080},
},
VideoEncoder: VideoEncoderConfig{
Encoding: "H264",
Bitrate: 4096,
Framerate: 30,
Resolution: Resolution{Width: 1920, Height: 1080},
},
Snapshot: SnapshotConfig{
Enabled: true,
Resolution: Resolution{Width: 1920, Height: 1080},
Quality: 85.0,
},
}
onvifProfile := profile.ToONVIFProfile()
if onvifProfile.Token != "profile_1" {
t.Errorf("Profile token mismatch: got %s", onvifProfile.Token)
}
if onvifProfile.Name != "HD Profile" {
t.Errorf("Profile name mismatch: got %s", onvifProfile.Name)
}
}