Files
onvif/Device_test.go
Sebastian Norling de3f049a63 feat(event/stream): echo WS-Addressing ReferenceParameters for AXIS
AXIS encodes pull-point subscription identity in
<wsa:ReferenceParameters> inside the CreatePullPointSubscription
response — a generic /onvif/services endpoint plus a
<dom0:SubscriptionId> child — rather than a per-subscription URL.
We were discarding the ReferenceParameters and POSTing to the
generic endpoint, which AXIS rejected with ter:InvalidArgs on every
PullMessages/Renew/Unsubscribe.

Per WS-Addressing 1.0 §3.1 each reference parameter MUST be echoed
as a SOAP Header block carrying wsa:IsReferenceParameter="true".

  - subscriptionRef now carries Address + the verbatim
    ReferenceParameters inner XML extracted from the create response.
  - buildRefParamsHeader walks the children, adds the attribute, and
    produces the SOAP Header content.
  - pullMessages, renewPullPoint, unsubscribePullPoint switch from
    SendSoap to a new SendSoapWithHeader path on the caller interface.
  - onvif.Device gains SendSoapWithHeader as a thin variant of
    SendSoap (existing SendSoap is now a one-liner delegating to it
    with empty header content, so all external callers are unaffected).

Verified end-to-end against an AXIS camera at 192.168.1.10: pulls
now stream the full topic tree (VMD, Object Analytics, IO, storage,
hardware-failure topics) instead of looping on ter:InvalidArgs.
2026-05-27 18:07:56 +02:00

74 lines
2.4 KiB
Go

package onvif
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDevice_SetDeviceInfoFromScopes(t *testing.T) {
const (
name = "DeviceName"
hardware = "M9000"
)
scopes := []string{
"onvif://www.onvif.org/Profile/Streaming",
"onvif://www.onvif.org/SomethingElse/value",
"onvif://www.onvif.org/name/" + name,
"onvif://www.onvif.org/hardware/" + hardware,
}
device := Device{}
device.SetDeviceInfoFromScopes(scopes)
assert.Equal(t, device.info.Name, name)
assert.Equal(t, device.info.Model, hardware)
}
// TestDevice_SendSoapWithHeader_InjectsHeaderXML verifies that the
// supplied header XML lands inside the SOAP <Header> element of the
// outgoing request. AXIS-style WS-Addressing reference parameter
// echoing depends on this — without it the camera returns
// ter:InvalidArgs on every PullMessages.
func TestDevice_SendSoapWithHeader_InjectsHeaderXML(t *testing.T) {
const headerXML = `<dom0:SubscriptionId xmlns:dom0="http://www.axis.com/2009/event" wsa:IsReferenceParameter="true">297</dom0:SubscriptionId>`
const bodyXML = `<tev:PullMessages xmlns:tev="http://www.onvif.org/ver10/events/wsdl"><tev:Timeout>PT5S</tev:Timeout><tev:MessageLimit>32</tev:MessageLimit></tev:PullMessages>`
var captured string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, _ := io.ReadAll(r.Body)
captured = string(b)
w.WriteHeader(http.StatusOK)
}))
t.Cleanup(srv.Close)
dev := Device{
params: DeviceParams{
Xaddr: strings.TrimPrefix(srv.URL, "http://"),
HttpClient: srv.Client(),
},
}
resp, err := dev.SendSoapWithHeader(srv.URL, bodyXML, headerXML)
require.NoError(t, err)
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
headerStart := strings.Index(captured, "Header>")
headerEnd := strings.Index(captured, "</")
require.NotEqual(t, -1, headerStart, "envelope must contain <Header>; got: %s", captured)
assert.Greater(t, headerEnd, headerStart, "envelope must close the header")
headerSlice := captured[headerStart:strings.Index(captured, "Body>")]
assert.Contains(t, headerSlice, "SubscriptionId",
"injected header element must land inside SOAP <Header>")
assert.Contains(t, headerSlice, "297")
bodySlice := captured[strings.Index(captured, "Body>"):]
assert.Contains(t, bodySlice, "PullMessages",
"body content must land inside SOAP <Body>")
}