Files
onvif/event/stream/soap_test.go
Sebastian Norling a1fc7832ef refactor(event/stream): align source and test files 1:1 by concern
Previously stream.go was a 621-line monolith holding the Stream type,
SOAP plumbing, pull loop, renew loop, recreate logic and jitter. The
test side had grown five orphan files (renew_test.go, reconnect_test.go,
soap_test.go, jitter_test.go, coverage_test.go) with no matching source
files. The mismatch made it harder than necessary to find the code
that backed a given test.

This commit splits stream.go by concern so each source file has its
own test file alongside it. Files <100 LOC (errors, jitter) were folded
into their conceptual parents rather than left as fragments.

New layout — 8 source + 8 test + helpers (test utility) + doc:

  stream.go     <-> stream_test.go      Stream type, Options, lifecycle
  soap.go       <-> soap_test.go        SOAP plumbing + fault detection
  renew.go      <-> renew_test.go       Renew loop and absolute time
  reconnect.go  <-> reconnect_test.go   Pull loop, recreate, jitter
  decode.go     <-> decode_test.go      NotificationMessage -> Event
  types.go      <-> types_test.go       Event types + typed errors
  topics.go     <-> topics_test.go      Classifier table
  doc.go                                Package godoc landing page
  helpers_test.go                       waitFor (test-only utility)

Mergers
-------
* errors.go (typed error wrappers, 42 LOC) -> types.go. ErrPullFailed /
  ErrRenewFailed / ErrRecreateFailed are part of the type system, not a
  separate concern.
* jitter.go (40 LOC) -> reconnect.go. jitter is an implementation detail
  of attemptRecreate, used nowhere else.

Test distribution
-----------------
* coverage_test.go was a catch-all; tests moved to the file matching
  the function under test:
    - Close*, NewStream_*, FakeCaller_* -> stream_test.go
    - DisableReconnect_*, RecreateResets_*, PullPointMutation_* ->
      reconnect_test.go
    - Decode_*, ExtractState_* -> decode_test.go
* soap_test.go shed the two orphans that did not belong there:
    - TestRenew_SendsAbsoluteDateTimeNotDuration -> renew_test.go
    - TestClose_BoundedByTimeoutOnHungUnsubscribe -> stream_test.go
* errors_test.go's pure type tests -> types_test.go
* errors_test.go's Stream-integration tests -> reconnect_test.go
* jitter_test.go -> reconnect_test.go

No behaviour change. Test suite passes -race clean.
2026-05-21 15:14:40 +02:00

87 lines
2.7 KiB
Go

package stream
import (
"context"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// --- SOAP fault detection ---------------------------------------------
func TestExtractSOAPFault_SOAP11(t *testing.T) {
body := `<?xml version="1.0"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body>
<env:Fault>
<faultcode>env:Client</faultcode>
<faultstring>The action requested requires authorization and the sender is not authorized</faultstring>
</env:Fault>
</env:Body>
</env:Envelope>`
got := extractSOAPFault(body)
assert.Contains(t, got, "not authorized")
}
func TestExtractSOAPFault_SOAP12(t *testing.T) {
body := `<?xml version="1.0"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope">
<env:Body>
<env:Fault>
<env:Code><env:Value>env:Sender</env:Value></env:Code>
<env:Reason><env:Text xml:lang="en">Subscription has expired</env:Text></env:Reason>
</env:Fault>
</env:Body>
</env:Envelope>`
got := extractSOAPFault(body)
assert.Contains(t, got, "Subscription has expired")
}
func TestExtractSOAPFault_NotAFault(t *testing.T) {
assert.Empty(t, extractSOAPFault(createPullPointResp))
}
func TestExtractSOAPFault_EmptyBody(t *testing.T) {
assert.Empty(t, extractSOAPFault(""))
}
func TestUnmarshalNode_ReturnsFaultReasonInsteadOfMissingElement(t *testing.T) {
body := `<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Body><env:Fault><faultstring>not authorized</faultstring></env:Fault></env:Body>
</env:Envelope>`
var out struct{}
err := unmarshalNode(body, "PullMessagesResponse", &out)
require.Error(t, err)
assert.Contains(t, err.Error(), "not authorized")
assert.NotContains(t, err.Error(), "missing PullMessagesResponse")
}
// --- Bounded body read -----------------------------------------------
func TestReadClose_LimitsBodySize(t *testing.T) {
if maxResponseBytes < 1024 {
t.Skip("limit too small for this test")
}
big := strings.Repeat("A", maxResponseBytes+1024)
body := "<env:Envelope><env:Body>" + big + "</env:Body></env:Envelope>"
fc := newFakeCaller()
fc.queueCallMethod(body, nil)
// Construction will fail because the truncated body has no
// CreatePullPointSubscriptionResponse — that's fine; what matters is
// the read completes without OOM.
_, err := newStream(testContext(t), fc, Options{})
assert.Error(t, err)
}
// testContext returns a Background context already wired to cancel via
// t.Cleanup so the test does not need to manage the cancellation
// goroutine inline.
func testContext(t *testing.T) context.Context {
t.Helper()
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)
return ctx
}