package onvif import ( "context" "io" "net/http" "net/http/httptest" "strings" "testing" ) // Test device information from real camera: // Manufacturer: Bosch // Model: FLEXIDOME indoor 5100i IR // Firmware: 8.71.0066 // Serial Number: 404754734001050102 // Hardware ID: F000B543 // TestGetMediaServiceCapabilities_Bosch tests GetMediaServiceCapabilities with real camera response. func TestGetMediaServiceCapabilities_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) // Note: Adapted to match the expected nested structure in the code realResponse := ` ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Validate request body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) // Validate SOAP request contains GetServiceCapabilities if !strings.Contains(bodyStr, "GetServiceCapabilities") { t.Errorf("Request should contain GetServiceCapabilities, got: %s", bodyStr) } if !strings.Contains(bodyStr, "http://www.onvif.org/ver10/media/wsdl") { t.Errorf("Request should contain media namespace, got: %s", bodyStr) } // Return real camera response w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() capabilities, err := client.GetMediaServiceCapabilities(ctx) if err != nil { t.Fatalf("GetMediaServiceCapabilities() failed: %v", err) } // Validate response matches real camera if capabilities.MaximumNumberOfProfiles != 32 { t.Errorf("Expected MaximumNumberOfProfiles=32 (Bosch FLEXIDOME), got %d", capabilities.MaximumNumberOfProfiles) } if !capabilities.RTPMulticast { t.Error("Expected RTPMulticast=true (Bosch FLEXIDOME)") } if !capabilities.RTPRTSPTCP { t.Error("Expected RTPRTSPTCP=true (Bosch FLEXIDOME)") } if capabilities.SnapshotURI { t.Error("Expected SnapshotURI=false (Bosch FLEXIDOME)") } if !capabilities.Rotation { t.Error("Expected Rotation=true (Bosch FLEXIDOME)") } } // TestGetProfiles_Bosch tests GetProfiles with real camera response. func TestGetProfiles_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` Profile_L1S1 Camera_1 4 1 Balanced 2 MP 1 H264 1920 1080 0 30 1 5200 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Validate request body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) // Validate SOAP request if !strings.Contains(bodyStr, "GetProfiles") { t.Errorf("Request should contain GetProfiles, got: %s", bodyStr) } // Return real camera response w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() profiles, err := client.GetProfiles(ctx) if err != nil { t.Fatalf("GetProfiles() failed: %v", err) } // Validate response matches real camera if len(profiles) == 0 { t.Fatal("Expected at least one profile from Bosch FLEXIDOME") } if profiles[0].Token != "0" { t.Errorf("Expected profile token=0 (Bosch FLEXIDOME), got %s", profiles[0].Token) } if profiles[0].Name != "Profile_L1S1" { t.Errorf("Expected profile name=Profile_L1S1 (Bosch FLEXIDOME), got %s", profiles[0].Name) } if profiles[0].VideoEncoderConfiguration == nil { t.Fatal("Expected VideoEncoderConfiguration from Bosch FLEXIDOME") } if profiles[0].VideoEncoderConfiguration.Token != "EncCfg_L1S1" { t.Errorf("Expected encoder token=EncCfg_L1S1 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Token) } if profiles[0].VideoEncoderConfiguration.Encoding != "H264" { t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", profiles[0].VideoEncoderConfiguration.Encoding) } if profiles[0].VideoEncoderConfiguration.Resolution.Width != 1920 { t.Errorf("Expected width=1920 (Bosch FLEXIDOME), got %d", profiles[0].VideoEncoderConfiguration.Resolution.Width) } if profiles[0].VideoEncoderConfiguration.Resolution.Height != 1080 { t.Errorf("Expected height=1080 (Bosch FLEXIDOME), got %d", profiles[0].VideoEncoderConfiguration.Resolution.Height) } } // TestGetVideoSources_Bosch tests GetVideoSources with real camera response. func TestGetVideoSources_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` 30 1920 1080 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetVideoSources") { t.Errorf("Request should contain GetVideoSources, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() sources, err := client.GetVideoSources(ctx) if err != nil { t.Fatalf("GetVideoSources() failed: %v", err) } // Validate response matches real camera if len(sources) == 0 { t.Fatal("Expected at least one video source from Bosch FLEXIDOME") } if sources[0].Token != "1" { t.Errorf("Expected source token=1 (Bosch FLEXIDOME), got %s", sources[0].Token) } if sources[0].Framerate != 30 { t.Errorf("Expected framerate=30 (Bosch FLEXIDOME), got %f", sources[0].Framerate) } if sources[0].Resolution.Width != 1920 { t.Errorf("Expected width=1920 (Bosch FLEXIDOME), got %d", sources[0].Resolution.Width) } if sources[0].Resolution.Height != 1080 { t.Errorf("Expected height=1080 (Bosch FLEXIDOME), got %d", sources[0].Resolution.Height) } } // TestGetAudioSources_Bosch tests GetAudioSources with real camera response. func TestGetAudioSources_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` 2 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetAudioSources") { t.Errorf("Request should contain GetAudioSources, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() sources, err := client.GetAudioSources(ctx) if err != nil { t.Fatalf("GetAudioSources() failed: %v", err) } // Validate response matches real camera if len(sources) == 0 { t.Fatal("Expected at least one audio source from Bosch FLEXIDOME") } if sources[0].Token != "1" { t.Errorf("Expected source token=1 (Bosch FLEXIDOME), got %s", sources[0].Token) } if sources[0].Channels != 2 { t.Errorf("Expected channels=2 (Bosch FLEXIDOME), got %d", sources[0].Channels) } } // TestGetAudioOutputs_Bosch tests GetAudioOutputs with real camera response. func TestGetAudioOutputs_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetAudioOutputs") { t.Errorf("Request should contain GetAudioOutputs, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() outputs, err := client.GetAudioOutputs(ctx) if err != nil { t.Fatalf("GetAudioOutputs() failed: %v", err) } // Validate response matches real camera if len(outputs) == 0 { t.Fatal("Expected at least one audio output from Bosch FLEXIDOME") } if outputs[0].Token != "AudioOut 1" { t.Errorf("Expected output token=AudioOut 1 (Bosch FLEXIDOME), got %s", outputs[0].Token) } } // TestGetStreamURI_Bosch tests GetStreamURI with real camera response. func TestGetStreamURI_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` rtsp://192.168.1.201/rtsp_tunnel?p=0&line=1&inst=1&vcd=2 false true 0 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetStreamUri") { t.Errorf("Request should contain GetStreamUri, got: %s", bodyStr) } if !strings.Contains(bodyStr, "ProfileToken") { t.Errorf("Request should contain ProfileToken, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() uri, err := client.GetStreamURI(ctx, "0") if err != nil { t.Fatalf("GetStreamURI() failed: %v", err) } // Validate response matches real camera if !strings.Contains(uri.URI, "rtsp://") { t.Errorf("Expected RTSP URI from Bosch FLEXIDOME, got %s", uri.URI) } if !strings.Contains(uri.URI, "rtsp_tunnel") { t.Errorf("Expected rtsp_tunnel in URI from Bosch FLEXIDOME, got %s", uri.URI) } if uri.InvalidAfterReboot != true { t.Error("Expected InvalidAfterReboot=true from Bosch FLEXIDOME") } } // TestGetSnapshotURI_Bosch tests GetSnapshotURI with real camera response. func TestGetSnapshotURI_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` http://192.168.1.201/snap.jpg?JpegCam=1 false true 0 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetSnapshotUri") { t.Errorf("Request should contain GetSnapshotUri, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() uri, err := client.GetSnapshotURI(ctx, "0") if err != nil { t.Fatalf("GetSnapshotURI() failed: %v", err) } // Validate response matches real camera if !strings.Contains(uri.URI, "http://") { t.Errorf("Expected HTTP URI from Bosch FLEXIDOME, got %s", uri.URI) } if !strings.Contains(uri.URI, "snap.jpg") { t.Errorf("Expected snap.jpg in URI from Bosch FLEXIDOME, got %s", uri.URI) } if !strings.Contains(uri.URI, "JpegCam=1") { t.Errorf("Expected JpegCam=1 in URI from Bosch FLEXIDOME, got %s", uri.URI) } } // TestGetVideoEncoderConfiguration_Bosch tests GetVideoEncoderConfiguration with real camera response. func TestGetVideoEncoderConfiguration_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` Balanced 2 MP 1 H264 1920 1080 0 30 1 5200 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetVideoEncoderConfiguration") { t.Errorf("Request should contain GetVideoEncoderConfiguration, got: %s", bodyStr) } if !strings.Contains(bodyStr, "ConfigurationToken") { t.Errorf("Request should contain ConfigurationToken, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() config, err := client.GetVideoEncoderConfiguration(ctx, "EncCfg_L1S1") if err != nil { t.Fatalf("GetVideoEncoderConfiguration() failed: %v", err) } // Validate response matches real camera if config.Token != "EncCfg_L1S1" { t.Errorf("Expected token=EncCfg_L1S1 (Bosch FLEXIDOME), got %s", config.Token) } if config.Name != "Balanced 2 MP" { t.Errorf("Expected name=Balanced 2 MP (Bosch FLEXIDOME), got %s", config.Name) } if config.Encoding != "H264" { t.Errorf("Expected encoding=H264 (Bosch FLEXIDOME), got %s", config.Encoding) } if config.Resolution.Width != 1920 { t.Errorf("Expected width=1920 (Bosch FLEXIDOME), got %d", config.Resolution.Width) } if config.Resolution.Height != 1080 { t.Errorf("Expected height=1080 (Bosch FLEXIDOME), got %d", config.Resolution.Height) } if config.RateControl.FrameRateLimit != 30 { t.Errorf("Expected FrameRateLimit=30 (Bosch FLEXIDOME), got %d", config.RateControl.FrameRateLimit) } if config.RateControl.BitrateLimit != 5200 { t.Errorf("Expected BitrateLimit=5200 (Bosch FLEXIDOME), got %d", config.RateControl.BitrateLimit) } } // TestGetVideoEncoderConfigurationOptions_Bosch tests GetVideoEncoderConfigurationOptions with real camera response. func TestGetVideoEncoderConfigurationOptions_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` 0 100 1920 1080 1 255 1 30 1 1 Main ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetVideoEncoderConfigurationOptions") { t.Errorf("Request should contain GetVideoEncoderConfigurationOptions, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() options, err := client.GetVideoEncoderConfigurationOptions(ctx, "EncCfg_L1S1") if err != nil { t.Fatalf("GetVideoEncoderConfigurationOptions() failed: %v", err) } // Validate response matches real camera if options.QualityRange == nil { t.Fatal("Expected QualityRange from Bosch FLEXIDOME") } if options.QualityRange.Min != 0 || options.QualityRange.Max != 100 { t.Errorf("Expected QualityRange 0-100 (Bosch FLEXIDOME), got %f-%f", options.QualityRange.Min, options.QualityRange.Max) } if options.H264 == nil { t.Fatal("Expected H264 options from Bosch FLEXIDOME") } if len(options.H264.ResolutionsAvailable) == 0 { t.Fatal("Expected at least one resolution from Bosch FLEXIDOME") } if options.H264.ResolutionsAvailable[0].Width != 1920 { t.Errorf("Expected resolution width=1920 (Bosch FLEXIDOME), got %d", options.H264.ResolutionsAvailable[0].Width) } if options.H264.FrameRateRange.Min != 1 || options.H264.FrameRateRange.Max != 30 { t.Errorf("Expected FrameRateRange 1-30 (Bosch FLEXIDOME), got %f-%f", options.H264.FrameRateRange.Min, options.H264.FrameRateRange.Max) } if len(options.H264.H264ProfilesSupported) == 0 || options.H264.H264ProfilesSupported[0] != "Main" { t.Errorf("Expected H264 profile=Main (Bosch FLEXIDOME), got %v", options.H264.H264ProfilesSupported) } } // TestGetAudioEncoderConfigurationOptions_Bosch tests GetAudioEncoderConfigurationOptions with real camera response. func TestGetAudioEncoderConfigurationOptions_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetAudioEncoderConfigurationOptions") { t.Errorf("Request should contain GetAudioEncoderConfigurationOptions, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() options, err := client.GetAudioEncoderConfigurationOptions(ctx, "", "") if err != nil { t.Fatalf("GetAudioEncoderConfigurationOptions() failed: %v", err) } // Validate response - Bosch FLEXIDOME returns empty options if options == nil { t.Fatal("Expected options struct from Bosch FLEXIDOME") } } // TestGetAudioOutputConfigurationOptions_Bosch tests GetAudioOutputConfigurationOptions with real camera response. func TestGetAudioOutputConfigurationOptions_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` AudioOut 1 ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetAudioOutputConfigurationOptions") { t.Errorf("Request should contain GetAudioOutputConfigurationOptions, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() options, err := client.GetAudioOutputConfigurationOptions(ctx, "") if err != nil { t.Fatalf("GetAudioOutputConfigurationOptions() failed: %v", err) } // Validate response matches real camera if len(options.OutputTokensAvailable) == 0 { t.Fatal("Expected at least one output token from Bosch FLEXIDOME") } if options.OutputTokensAvailable[0] != "AudioOut 1" { t.Errorf("Expected AudioOut 1 (Bosch FLEXIDOME), got %s", options.OutputTokensAvailable[0]) } } // TestGetMetadataConfigurationOptions_Bosch tests GetMetadataConfigurationOptions with real camera response. func TestGetMetadataConfigurationOptions_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` false false ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetMetadataConfigurationOptions") { t.Errorf("Request should contain GetMetadataConfigurationOptions, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() options, err := client.GetMetadataConfigurationOptions(ctx, "", "") if err != nil { t.Fatalf("GetMetadataConfigurationOptions() failed: %v", err) } // Validate response matches real camera if options.PTZStatusFilterOptions == nil { t.Fatal("Expected PTZStatusFilterOptions from Bosch FLEXIDOME") } if options.PTZStatusFilterOptions.Status != false { t.Error("Expected Status=false from Bosch FLEXIDOME") } if options.PTZStatusFilterOptions.Position != false { t.Error("Expected Position=false from Bosch FLEXIDOME") } } // TestGetAudioDecoderConfigurationOptions_Bosch tests GetAudioDecoderConfigurationOptions with real camera response. func TestGetAudioDecoderConfigurationOptions_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "GetAudioDecoderConfigurationOptions") { t.Errorf("Request should contain GetAudioDecoderConfigurationOptions, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() options, err := client.GetAudioDecoderConfigurationOptions(ctx, "") if err != nil { t.Fatalf("GetAudioDecoderConfigurationOptions() failed: %v", err) } // Validate response matches real camera if options == nil { t.Fatal("Expected options from Bosch FLEXIDOME") } if options.G711DecOptions == nil { t.Error("Expected G711DecOptions from Bosch FLEXIDOME") } } // TestSetSynchronizationPoint_Bosch tests SetSynchronizationPoint with real camera response. func TestSetSynchronizationPoint_Bosch(t *testing.T) { // Real SOAP response from Bosch FLEXIDOME indoor 5100i IR (FW: 8.71.0066) realResponse := ` ` server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { t.Fatalf("Failed to read request body: %v", err) } bodyStr := string(body) if !strings.Contains(bodyStr, "SetSynchronizationPoint") { t.Errorf("Request should contain SetSynchronizationPoint, got: %s", bodyStr) } if !strings.Contains(bodyStr, "ProfileToken") { t.Errorf("Request should contain ProfileToken, got: %s", bodyStr) } w.Header().Set("Content-Type", "application/soap+xml") w.WriteHeader(http.StatusOK) w.Write([]byte(realResponse)) })) defer server.Close() client, err := NewClient(server.URL, WithCredentials("service", "Service.1234")) if err != nil { t.Fatalf("NewClient() failed: %v", err) } client.mediaEndpoint = server.URL ctx := context.Background() err = client.SetSynchronizationPoint(ctx, "0") if err != nil { t.Fatalf("SetSynchronizationPoint() failed: %v", err) } }