feat(opcuaclient): subscription tuning, OperationLimits chunking, diagnostics counters, revocation options, DiscoveryUrl (#273 #275 #276 #277 #278)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests;
|
||||
|
||||
/// <summary>#276 — counters are thread-safe and surface through <c>DriverHealth.Diagnostics</c>.</summary>
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class OpcUaClientDiagnosticsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Fresh_snapshot_is_all_zero_with_the_full_key_set()
|
||||
{
|
||||
var snap = new OpcUaClientDiagnostics().Snapshot();
|
||||
|
||||
snap.Keys.ShouldBe(
|
||||
[
|
||||
"PublishResponseCount", "NotificationCount", "DataChangeCount", "BadStatusValueCount",
|
||||
"PublishErrorCount", "KeepAliveMissCount", "ReconnectAttemptCount", "ReconnectSuccessCount",
|
||||
"ReconnectExhaustedCount", "LastReconnectUtcTicks", "ReadCallCount", "ReadFailureCount",
|
||||
"WriteCallCount", "WriteFailureCount", "ChunkedRequestCount",
|
||||
], ignoreOrder: true);
|
||||
snap.Values.ShouldAllBe(v => v == 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Each_recorder_moves_exactly_its_own_counter()
|
||||
{
|
||||
var d = new OpcUaClientDiagnostics();
|
||||
d.RecordPublishResponse();
|
||||
d.RecordNotifications(3);
|
||||
d.RecordDataChange(bad: false);
|
||||
d.RecordDataChange(bad: true);
|
||||
d.RecordPublishError();
|
||||
d.RecordKeepAliveMiss();
|
||||
d.RecordReconnectAttempt();
|
||||
d.RecordReconnectSucceeded();
|
||||
d.RecordReconnectExhausted();
|
||||
d.RecordRead(failed: true);
|
||||
d.RecordWrite(failed: false);
|
||||
d.RecordChunkedRequest();
|
||||
|
||||
var s = d.Snapshot();
|
||||
s["PublishResponseCount"].ShouldBe(1);
|
||||
s["NotificationCount"].ShouldBe(3);
|
||||
s["DataChangeCount"].ShouldBe(2);
|
||||
s["BadStatusValueCount"].ShouldBe(1);
|
||||
s["PublishErrorCount"].ShouldBe(1);
|
||||
s["KeepAliveMissCount"].ShouldBe(1);
|
||||
s["ReconnectAttemptCount"].ShouldBe(1);
|
||||
s["ReconnectSuccessCount"].ShouldBe(1);
|
||||
s["ReconnectExhaustedCount"].ShouldBe(1);
|
||||
s["LastReconnectUtcTicks"].ShouldBeGreaterThan(0);
|
||||
s["ReadCallCount"].ShouldBe(1);
|
||||
s["ReadFailureCount"].ShouldBe(1);
|
||||
s["WriteCallCount"].ShouldBe(1);
|
||||
s["WriteFailureCount"].ShouldBe(0);
|
||||
s["ChunkedRequestCount"].ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Concurrent_increments_do_not_lose_updates()
|
||||
{
|
||||
var d = new OpcUaClientDiagnostics();
|
||||
const int threads = 8, each = 5_000;
|
||||
|
||||
Parallel.For(0, threads, _ =>
|
||||
{
|
||||
for (var i = 0; i < each; i++) { d.RecordDataChange(bad: i % 2 == 0); d.RecordNotifications(2); }
|
||||
});
|
||||
|
||||
var s = d.Snapshot();
|
||||
s["DataChangeCount"].ShouldBe(threads * each);
|
||||
s["BadStatusValueCount"].ShouldBe(threads * each / 2);
|
||||
s["NotificationCount"].ShouldBe(threads * each * 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Snapshot_is_a_point_in_time_copy()
|
||||
{
|
||||
var d = new OpcUaClientDiagnostics();
|
||||
var before = d.Snapshot();
|
||||
d.RecordKeepAliveMiss();
|
||||
|
||||
before["KeepAliveMissCount"].ShouldBe(0);
|
||||
d.Snapshot()["KeepAliveMissCount"].ShouldBe(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetHealth_carries_the_diagnostics_and_keeps_state_fields()
|
||||
{
|
||||
using var drv = new OpcUaClientDriver(new OpcUaClientDriverOptions(), "opcua-diag");
|
||||
drv.DiagnosticsForTest.RecordKeepAliveMiss();
|
||||
drv.DiagnosticsForTest.RecordKeepAliveMiss();
|
||||
|
||||
var h = drv.GetHealth();
|
||||
|
||||
h.State.ShouldBe(DriverState.Unknown);
|
||||
h.Diagnostics.ShouldNotBeNull();
|
||||
h.Diagnostics!["KeepAliveMissCount"].ShouldBe(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DriverHealth_without_diagnostics_stays_back_compatible()
|
||||
{
|
||||
var h = new DriverHealth(DriverState.Healthy, null, null);
|
||||
|
||||
h.Diagnostics.ShouldBeNull();
|
||||
h.ShouldBe(new DriverHealth(DriverState.Healthy, null, null));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user