Files
onvif-go/event_test copy.go
T
2026-01-16 04:11:59 +00:00

739 lines
21 KiB
Go

package onvif
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
const testEventXMLHeader = `<?xml version="1.0" encoding="UTF-8"?>`
func newMockEventServer() *httptest.Server {
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/soap+xml")
body := make([]byte, r.ContentLength)
_, _ = r.Body.Read(body)
bodyStr := string(body)
var response string
switch {
case strings.Contains(bodyStr, "GetServiceCapabilities"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:GetServiceCapabilitiesResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl">
<tev:Capabilities
WSSubscriptionPolicySupport="true"
WSPausableSubscriptionManagerInterfaceSupport="true"
MaxNotificationProducers="10"
MaxPullPoints="5"
PersistentNotificationStorage="true"
EventBrokerProtocols="mqtt mqtts"
MaxEventBrokers="3"
MetadataOverMQTT="true"/>
</tev:GetServiceCapabilitiesResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "CreatePullPointSubscription"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:CreatePullPointSubscriptionResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl">
<tev:SubscriptionReference>
<wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing">http://192.168.1.100/onvif/subscription/1</wsa:Address>
</tev:SubscriptionReference>
<tev:CurrentTime>2025-01-15T10:30:00Z</tev:CurrentTime>
<tev:TerminationTime>2025-01-15T11:30:00Z</tev:TerminationTime>
</tev:CreatePullPointSubscriptionResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "PullMessages"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:PullMessagesResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl">
<tev:CurrentTime>2025-01-15T10:30:00Z</tev:CurrentTime>
<tev:TerminationTime>2025-01-15T11:30:00Z</tev:TerminationTime>
<wsnt:NotificationMessage xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2">
<wsnt:Topic>tns1:VideoSource/MotionAlarm</wsnt:Topic>
<wsnt:ProducerReference>
<wsa:Address xmlns:wsa="http://www.w3.org/2005/08/addressing">http://192.168.1.100</wsa:Address>
</wsnt:ProducerReference>
<wsnt:Message PropertyOperation="Changed" UtcTime="2025-01-15T10:29:55Z">
<tt:Source xmlns:tt="http://www.onvif.org/ver10/schema">
<tt:SimpleItem Name="VideoSourceToken" Value="video_src_001"/>
</tt:Source>
<tt:Key xmlns:tt="http://www.onvif.org/ver10/schema">
<tt:SimpleItem Name="RuleToken" Value="rule_001"/>
</tt:Key>
<tt:Data xmlns:tt="http://www.onvif.org/ver10/schema">
<tt:SimpleItem Name="State" Value="true"/>
</tt:Data>
</wsnt:Message>
</wsnt:NotificationMessage>
</tev:PullMessagesResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "Seek"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:SeekResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "SetSynchronizationPoint"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:SetSynchronizationPointResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "Unsubscribe"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<wsnt:UnsubscribeResponse xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "Renew"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<wsnt:RenewResponse xmlns:wsnt="http://docs.oasis-open.org/wsn/b-2">
<wsnt:CurrentTime>2025-01-15T10:30:00Z</wsnt:CurrentTime>
<wsnt:TerminationTime>2025-01-15T12:30:00Z</wsnt:TerminationTime>
</wsnt:RenewResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetEventProperties"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:GetEventPropertiesResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl">
<tev:TopicNamespaceLocation>http://www.onvif.org/onvif/ver10/topics/topicns.xml</tev:TopicNamespaceLocation>
<tev:FixedTopicSet>true</tev:FixedTopicSet>
<tev:TopicExpressionDialect>http://www.onvif.org/ver10/tev/topicExpression/ConcreteSet</tev:TopicExpressionDialect>
<tev:MessageContentFilterDialect>http://www.onvif.org/ver10/tev/messageContentFilter/ItemFilter</tev:MessageContentFilterDialect>
<tev:ProducerPropertiesFilterDialect>http://www.onvif.org/ver10/tev/producerPropertiesFilter</tev:ProducerPropertiesFilterDialect>
<tev:MessageContentSchemaLocation>http://www.onvif.org/onvif/ver10/schema/onvif.xsd</tev:MessageContentSchemaLocation>
</tev:GetEventPropertiesResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "AddEventBroker"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:AddEventBrokerResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "DeleteEventBroker"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:DeleteEventBrokerResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl"/>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
case strings.Contains(bodyStr, "GetEventBrokers"):
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<tev:GetEventBrokersResponse xmlns:tev="http://www.onvif.org/ver10/events/wsdl">
<tev:EventBroker>
<tev:Address>mqtt://broker.example.com:1883</tev:Address>
<tev:TopicPrefix>onvif/</tev:TopicPrefix>
<tev:UserName>mqtt_user</tev:UserName>
<tev:QoS>1</tev:QoS>
<tev:Status>Connected</tev:Status>
<tev:CertPathValidation>true</tev:CertPathValidation>
</tev:EventBroker>
</tev:GetEventBrokersResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
default:
response = testEventXMLHeader + `
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope">
<SOAP-ENV:Body>
<SOAP-ENV:Fault>
<SOAP-ENV:Code><SOAP-ENV:Value>SOAP-ENV:Receiver</SOAP-ENV:Value></SOAP-ENV:Code>
<SOAP-ENV:Reason><SOAP-ENV:Text>Unknown action</SOAP-ENV:Text></SOAP-ENV:Reason>
</SOAP-ENV:Fault>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>`
}
_, _ = w.Write([]byte(response))
}))
}
func TestGetEventServiceCapabilities(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
caps, err := client.GetEventServiceCapabilities(ctx)
if err != nil {
t.Fatalf("GetEventServiceCapabilities failed: %v", err)
}
if !caps.WSSubscriptionPolicySupport {
t.Error("Expected WSSubscriptionPolicySupport to be true")
}
if !caps.WSPausableSubscriptionManagerInterfaceSupport {
t.Error("Expected WSPausableSubscriptionManagerInterfaceSupport to be true")
}
if caps.MaxNotificationProducers != 10 {
t.Errorf("Expected MaxNotificationProducers to be 10, got %d", caps.MaxNotificationProducers)
}
if caps.MaxPullPoints != 5 {
t.Errorf("Expected MaxPullPoints to be 5, got %d", caps.MaxPullPoints)
}
if !caps.PersistentNotificationStorage {
t.Error("Expected PersistentNotificationStorage to be true")
}
if len(caps.EventBrokerProtocols) != 2 {
t.Errorf("Expected 2 EventBrokerProtocols, got %d", len(caps.EventBrokerProtocols))
}
if caps.MaxEventBrokers != 3 {
t.Errorf("Expected MaxEventBrokers to be 3, got %d", caps.MaxEventBrokers)
}
if !caps.MetadataOverMQTT {
t.Error("Expected MetadataOverMQTT to be true")
}
}
func TestCreatePullPointSubscription(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test with no filter and default termination time.
sub, err := client.CreatePullPointSubscription(ctx, "", nil, "")
if err != nil {
t.Fatalf("CreatePullPointSubscription failed: %v", err)
}
if sub.SubscriptionReference == "" {
t.Error("Expected SubscriptionReference to be set")
}
if sub.CurrentTime.IsZero() {
t.Error("Expected CurrentTime to be set")
}
if sub.TerminationTime.IsZero() {
t.Error("Expected TerminationTime to be set")
}
// Test with filter and termination time.
termTime := 1 * time.Hour
sub2, err := client.CreatePullPointSubscription(ctx, "tns1:VideoSource/MotionAlarm", &termTime, "policy1")
if err != nil {
t.Fatalf("CreatePullPointSubscription with filter failed: %v", err)
}
if sub2.SubscriptionReference == "" {
t.Error("Expected SubscriptionReference to be set")
}
}
func TestCreatePullPointSubscriptionInvalidTerminationTime(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test with invalid (negative) termination time.
invalidTime := -1 * time.Hour
_, err = client.CreatePullPointSubscription(ctx, "", &invalidTime, "")
if !errors.Is(err, ErrInvalidTerminationTime) {
t.Errorf("Expected ErrInvalidTerminationTime, got %v", err)
}
}
func TestPullMessages(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
messages, err := client.PullMessages(ctx, server.URL+"/subscription/1", 30*time.Second, 10)
if err != nil {
t.Fatalf("PullMessages failed: %v", err)
}
if len(messages) == 0 {
t.Error("Expected at least one notification message")
}
if len(messages) > 0 {
msg := messages[0]
if msg.Topic == "" {
t.Error("Expected Topic to be set")
}
if msg.Message.PropertyOperation == "" {
t.Error("Expected PropertyOperation to be set")
}
if len(msg.Message.Source) == 0 {
t.Error("Expected Source items to be present")
}
if len(msg.Message.Data) == 0 {
t.Error("Expected Data items to be present")
}
}
}
func TestPullMessagesValidation(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test empty subscription reference.
_, err = client.PullMessages(ctx, "", 30*time.Second, 10)
if !errors.Is(err, ErrInvalidSubscriptionReference) {
t.Errorf("Expected ErrInvalidSubscriptionReference, got %v", err)
}
// Test invalid timeout.
_, err = client.PullMessages(ctx, server.URL+"/subscription/1", 0, 10)
if !errors.Is(err, ErrInvalidTimeout) {
t.Errorf("Expected ErrInvalidTimeout, got %v", err)
}
// Test invalid message limit.
_, err = client.PullMessages(ctx, server.URL+"/subscription/1", 30*time.Second, 0)
if !errors.Is(err, ErrInvalidMessageLimit) {
t.Errorf("Expected ErrInvalidMessageLimit, got %v", err)
}
}
func TestSeek(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.Seek(ctx, server.URL+"/subscription/1", time.Now().Add(-1*time.Hour), false)
if err != nil {
t.Fatalf("Seek failed: %v", err)
}
// Test with reverse.
err = client.Seek(ctx, server.URL+"/subscription/1", time.Now().Add(-1*time.Hour), true)
if err != nil {
t.Fatalf("Seek with reverse failed: %v", err)
}
}
func TestSeekInvalidSubscriptionReference(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.Seek(ctx, "", time.Now(), false)
if !errors.Is(err, ErrInvalidSubscriptionReference) {
t.Errorf("Expected ErrInvalidSubscriptionReference, got %v", err)
}
}
func TestSetEventSynchronizationPoint(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.SetEventSynchronizationPoint(ctx, server.URL+"/subscription/1")
if err != nil {
t.Fatalf("SetEventSynchronizationPoint failed: %v", err)
}
}
func TestSetEventSynchronizationPointInvalidSubscriptionReference(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.SetEventSynchronizationPoint(ctx, "")
if !errors.Is(err, ErrInvalidSubscriptionReference) {
t.Errorf("Expected ErrInvalidSubscriptionReference, got %v", err)
}
}
func TestUnsubscribe(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.Unsubscribe(ctx, server.URL+"/subscription/1")
if err != nil {
t.Fatalf("Unsubscribe failed: %v", err)
}
}
func TestUnsubscribeInvalidSubscriptionReference(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.Unsubscribe(ctx, "")
if !errors.Is(err, ErrInvalidSubscriptionReference) {
t.Errorf("Expected ErrInvalidSubscriptionReference, got %v", err)
}
}
func TestRenewSubscription(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
currentTime, terminationTime, err := client.RenewSubscription(ctx, server.URL+"/subscription/1", 2*time.Hour)
if err != nil {
t.Fatalf("RenewSubscription failed: %v", err)
}
if currentTime.IsZero() {
t.Error("Expected CurrentTime to be set")
}
if terminationTime.IsZero() {
t.Error("Expected TerminationTime to be set")
}
}
func TestRenewSubscriptionValidation(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test empty subscription reference.
_, _, err = client.RenewSubscription(ctx, "", time.Hour)
if !errors.Is(err, ErrInvalidSubscriptionReference) {
t.Errorf("Expected ErrInvalidSubscriptionReference, got %v", err)
}
// Test invalid termination time.
_, _, err = client.RenewSubscription(ctx, server.URL+"/subscription/1", 0)
if !errors.Is(err, ErrInvalidTerminationTime) {
t.Errorf("Expected ErrInvalidTerminationTime, got %v", err)
}
}
func TestGetEventProperties(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
props, err := client.GetEventProperties(ctx)
if err != nil {
t.Fatalf("GetEventProperties failed: %v", err)
}
if len(props.TopicNamespaceLocation) == 0 {
t.Error("Expected TopicNamespaceLocation to be set")
}
if !props.FixedTopicSet {
t.Error("Expected FixedTopicSet to be true")
}
if len(props.TopicExpressionDialects) == 0 {
t.Error("Expected TopicExpressionDialects to be set")
}
if len(props.MessageContentFilterDialects) == 0 {
t.Error("Expected MessageContentFilterDialects to be set")
}
}
func TestAddEventBroker(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
config := &EventBrokerConfig{
Address: "mqtt://broker.example.com:1883",
TopicPrefix: "onvif/",
UserName: "mqtt_user",
Password: "mqtt_pass",
QoS: 1,
}
err = client.AddEventBroker(ctx, config)
if err != nil {
t.Fatalf("AddEventBroker failed: %v", err)
}
}
func TestAddEventBrokerValidation(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
// Test nil config.
err = client.AddEventBroker(ctx, nil)
if err == nil {
t.Error("Expected error for nil config")
}
// Test empty address.
config := &EventBrokerConfig{Address: ""}
err = client.AddEventBroker(ctx, config)
if !errors.Is(err, ErrInvalidEventBrokerAddress) {
t.Errorf("Expected ErrInvalidEventBrokerAddress, got %v", err)
}
}
func TestDeleteEventBroker(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.DeleteEventBroker(ctx, "mqtt://broker.example.com:1883")
if err != nil {
t.Fatalf("DeleteEventBroker failed: %v", err)
}
}
func TestDeleteEventBrokerInvalidAddress(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
err = client.DeleteEventBroker(ctx, "")
if !errors.Is(err, ErrInvalidEventBrokerAddress) {
t.Errorf("Expected ErrInvalidEventBrokerAddress, got %v", err)
}
}
func TestGetEventBrokers(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
ctx := context.Background()
brokers, err := client.GetEventBrokers(ctx)
if err != nil {
t.Fatalf("GetEventBrokers failed: %v", err)
}
if len(brokers) == 0 {
t.Error("Expected at least one event broker")
}
if len(brokers) > 0 {
broker := brokers[0]
if broker.Address == "" {
t.Error("Expected Address to be set")
}
if broker.TopicPrefix == "" {
t.Error("Expected TopicPrefix to be set")
}
if broker.Status == "" {
t.Error("Expected Status to be set")
}
}
}
func TestFormatDuration(t *testing.T) {
tests := []struct {
duration time.Duration
expected string
}{
{30 * time.Second, "PT30S"},
{60 * time.Second, "PT1M"},
{90 * time.Second, "PT1M30S"},
{5 * time.Minute, "PT5M"},
{65 * time.Second, "PT1M5S"},
}
for _, tt := range tests {
result := formatDuration(tt.duration)
if result != tt.expected {
t.Errorf("formatDuration(%v) = %s, expected %s", tt.duration, result, tt.expected)
}
}
}
func TestSplitSpaceSeparated(t *testing.T) {
tests := []struct {
input string
expected []string
}{
{"", nil},
{"mqtt", []string{"mqtt"}},
{"mqtt mqtts", []string{"mqtt", "mqtts"}},
{" mqtt mqtts ", []string{"mqtt", "mqtts"}},
{"a b c", []string{"a", "b", "c"}},
}
for _, tt := range tests {
result := splitSpaceSeparated(tt.input)
if len(result) != len(tt.expected) {
t.Errorf("splitSpaceSeparated(%q) returned %d items, expected %d", tt.input, len(result), len(tt.expected))
continue
}
for i, v := range result {
if v != tt.expected[i] {
t.Errorf("splitSpaceSeparated(%q)[%d] = %q, expected %q", tt.input, i, v, tt.expected[i])
}
}
}
}
func TestSetEventEndpoint(t *testing.T) {
server := newMockEventServer()
defer server.Close()
client, err := NewClient(server.URL, WithCredentials("admin", "password"))
if err != nil {
t.Fatalf("Failed to create client: %v", err)
}
newEndpoint := "http://192.168.1.100/onvif/events"
client.SetEventEndpoint(newEndpoint)
// Verify endpoint was set.
endpoint := client.getEventEndpoint()
if endpoint != newEndpoint {
t.Errorf("Expected event endpoint %s, got %s", newEndpoint, endpoint)
}
}