Subscription and method tests
Subscription, monitored-item, and method-call test recipes. Including the event + alarm flows that exercise the publish loop.
The recipes for the publish/subscribe surface and the method-call service.
Subscription tests
Basic 1-Hz subscription
create_subscription(publishingInterval=1000)
monitor(Dynamic/Counter, samplingInterval=1000)
→ one DataChange notification per second
→ values strictly increasing by 1
Run for ~5 seconds, verify 4-5 notifications with sequential values.
Fast subscription
create_subscription(publishingInterval=200)
monitor(Dynamic/FastCounter, samplingInterval=100)
→ ~5 notifications per second
The publishing interval is the upper bound on notification rate
— a 200 ms publishingInterval means at most 5/sec.
Multiple monitored items
create_subscription(publishingInterval=1000)
monitor(Dynamic/Counter)
monitor(Dynamic/SineWave)
monitor(Dynamic/StatusVariable)
→ notifications for all three, interleaved in each publish
Each publish response carries the latest value for every monitored item that changed.
Deadband filtering
monitor(Dynamic/SineWave, filter=DataChangeFilter(
trigger = StatusValue,
deadband: { type: Absolute, value: 0.5 },
))
→ notifications only when the value changes by > 0.5
→ over one sine period (~10s), expect ~4 notifications
Status-change trigger
monitor(Dynamic/StatusVariable, filter=DataChangeFilter(
trigger = Status, # not StatusValue, not Value
))
→ notification only when status code changes
→ at 3-second cadence (the cycle interval)
Subscription with publishing disabled
create_subscription(publishingInterval=1000, publishingEnabled=false)
monitor(Dynamic/Counter)
→ no notifications
publish_enabled(subscriptionId, true)
→ notifications resume
Useful to test publishing-state machinery in your client.
Subscription teardown
delete_subscription(subscriptionId)
→ DeleteSubscriptionsResponse status: Good
→ further publish responses for this id should not arrive
Method-call tests
Simple call
call(
objectId = ns=1;s=TestServer/Methods,
methodId = ns=1;s=TestServer/Methods/Add,
inputs = [2.5, 3.5],
)
→ status = Good
→ outputs = [6.0]
Method with no inputs
call(TestServer/Methods, TestServer/Methods/GetServerTime, inputs=[])
→ outputs = [DateTime close to now]
Failing method
call(TestServer/Methods, TestServer/Methods/Failing, inputs=[])
→ status = Bad_InternalError
The OPC UA call wire-completes successfully; the result status is Bad. Your client should distinguish wire failure (transport / encoding error) from result-status failure.
Method timeout
client.timeout = 5000 # 5 seconds
call(TestServer/Methods/LongRunning, inputs=[10000]) # 10s
→ client raises timeout
Useful to test cancellation / abort flows.
Array argument
call(TestServer/Methods/ArraySum, inputs=[[1.0, 2.0, 3.0, 4.0]])
→ outputs = [10.0]
Echo round-trip
For every OPC UA type:
sample = make_test_value(T)
call(TestServer/Methods/Echo, inputs=[sample])
→ outputs = [sample]
Tests that Variant encoding round-trips correctly for every type.
Invalid argument
call(TestServer/Methods/MatrixTranspose, inputs=[[1,2,3], 2, 2])
# 3 elements but 2*2 = 4 expected
→ status = Bad_InternalError
The handler does not validate matrix.Length == rows * cols,
so the mismatched length raises IndexOutOfRangeException
inside the method body. The generic wrapper in
TestNodeManager.CreateMethod catches every exception and
returns Bad_InternalError — not Bad_InvalidArgument.
Event subscription tests
Subscribe to periodic events
create_subscription(publishingInterval=1000)
monitor(Server, EventFilter(
selectClause = [EventId, EventType, Message, Severity, Time],
))
→ wait 3s → at least one event from the 2 s timer arrives
(Message = "Simple event #N", EventType = BaseEventType)
Filter by event type
The suite does not register any custom event types — all three
periodic timers emit BaseEventState instances stamped with
either ObjectTypeIds.BaseEventType or
ObjectTypeIds.SystemEventType. Use those standard NodeIds:
filter = EventFilter(
selectClause = [...],
whereClause = OfType(BaseEventType),
)
→ events from the 2 s and 5 s timers (both stamped BaseEventType)
filter = EventFilter(
selectClause = [...],
whereClause = OfType(SystemEventType),
)
→ events from the 10 s "System status" timer
Severity filter
filter = EventFilter(
selectClause = [...],
whereClause = Severity >= 300,
)
→ only the 5 s "Complex event" timer (Severity = 500); the
2 s timer (Severity = 200) and 10 s timer (Severity = 100)
are filtered out
On-demand event via method (no-op stub)
# Setup subscription
subscribe(Server, EventFilter(...))
# Call the stub
call(TestServer/Methods/GenerateEvent, inputs=["test event", 750])
→ method returns Good
→ NO event arrives. The handler only writes a console line on
the server (`Console.WriteLine`) — it does not call
ReportEvent. There is no on-demand event-emission path in
the current build.
Custom property in selectClause
There are no custom event-type properties in this server. A
selectClause like
[Message, Severity, EventId, SourceNode, ReceiveTime, …]
returns only the standard BaseEventType fields. Trying to
select a non-existent path such as EventPayload yields a null
value for that field.
Alarm subscription tests
Watch transitions
subscribe(Server, EventFilter(
selectClause = [
EventId, SourceName, Severity, Time,
ActiveState/Id, AckedState/Id,
ConditionType
],
))
# AlarmSourceValue oscillates ~50 + 60·sin(t)
# Over a 10s window, alarms transition naturally
→ multiple ConditionType events with transitioning states
Force a specific state
write(Alarms/AlarmSourceValue, 100.0)
→ within publishingInterval, alarm event arrives with
ActiveState/Id=true, severity reflecting HighHigh
Acknowledge round-trip
# 1. Force alarm
write(Alarms/AlarmSourceValue, 100.0)
# 2. Wait for event, capture EventId
event_id = ...
# 3. Acknowledge
call(<alarm>, Acknowledge, inputs=[event_id, "investigating"])
→ status = Good
# 4. Refresh — AckedState now true
OffNormal toggle
write(Alarms/OffNormalSource, true)
→ event: OffNormalAlarm ActiveState=true
write(Alarms/OffNormalSource, false)
→ event: OffNormalAlarm ActiveState=false
Historical data tests
Raw read
# Wait at least 10s after server start
read_history_raw(
Historical/HistoricalTemperature,
startTime = now - 10s,
endTime = now,
maxValues = 100,
)
→ ~10 samples, ~1s apart, values around 22 ± 8
Continuation
read_history_raw(
Historical/HistoricalCounter,
startTime = now - 30s,
endTime = now,
maxValues = 5,
)
→ 5 values + ContinuationPoint
read_history_raw(..., continuation=<from above>)
→ next 5 values
Aggregate
read_history_processed(
Historical/HistoricalTemperature,
startTime = now - 5min,
endTime = now,
interval = 60_000, # 1 minute
aggregateId = AggregateFunction.Average,
)
→ 5 buckets, each ≈ 22 (with small variance)
Where to read next
- Security tests — cert and auth recipes.
- The per-feature pages under Runtime features for deeper reference.