fix(client): alarm filter selected ActiveState/Id as ONE QualifiedName — every event read inactive
EventFilter.AddSelectClause(type, "ActiveState/Id") does not split the string; it selects a single QualifiedName named "ActiveState/Id", which no event carries. AckedState/Id, ActiveState/Id and EnabledState/Id therefore always arrived null and Client.CLI 'alarms' (and Client.UI) reported every alarm inactive and unacknowledged while a direct Read said Active. Pre-existing since a2883b82; server-side event delivery was correct (proven with two-element select clauses, incl. after #629). Select them with explicit two-element browse paths. Over-the-wire test drives OpcUaClientService.SubscribeAlarmsAsync against the in-process server (native + scripted); ServerEventRig now also selects ActiveState/Id, Severity and Retain. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
using Opc.Ua;
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Client.Shared.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the browse paths of the alarm subscription's select clauses. <c>EventFilter.AddSelectClause(type,
|
||||
/// "ActiveState/Id")</c> does NOT split the string — it yields ONE QualifiedName named <c>ActiveState/Id</c>,
|
||||
/// which matches nothing — so the client reported every alarm inactive and unacknowledged. The over-the-wire
|
||||
/// proof is <c>ClientAlarmMethodTests.The_client_alarm_subscription_reports_active_acked_and_exact_severity</c>.
|
||||
/// </summary>
|
||||
public sealed class AlarmEventFilterTests
|
||||
{
|
||||
/// <summary>No select clause carries a QualifiedName with a path separator in it.</summary>
|
||||
[Fact]
|
||||
public void No_browse_path_element_contains_a_separator()
|
||||
{
|
||||
var filter = OpcUaClientService.CreateAlarmEventFilter();
|
||||
|
||||
filter.SelectClauses.SelectMany(c => c.BrowsePath).ShouldAllBe(q => !q.Name.Contains('/'));
|
||||
}
|
||||
|
||||
/// <summary>The two-state ids are selected by their two-element browse paths, at the indices the parser reads.</summary>
|
||||
[Fact]
|
||||
public void Two_state_ids_are_two_element_browse_paths()
|
||||
{
|
||||
var clauses = OpcUaClientService.CreateAlarmEventFilter().SelectClauses;
|
||||
|
||||
string Path(int i) => string.Join("/", clauses[i].BrowsePath.Select(q => q.Name));
|
||||
Path(8).ShouldBe("AckedState/Id");
|
||||
clauses[8].BrowsePath.Count.ShouldBe(2);
|
||||
Path(9).ShouldBe("ActiveState/Id");
|
||||
clauses[9].BrowsePath.Count.ShouldBe(2);
|
||||
Path(10).ShouldBe("EnabledState/Id");
|
||||
clauses[10].BrowsePath.Count.ShouldBe(2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user