test: accept definitive session loss reasons during restart
v2-ci / build (pull_request) Successful in 12m16s
v2-ci / unit-tests (pull_request) Failing after 3h13m34s

This commit is contained in:
Joseph Doherty
2026-10-13 06:30:08 -04:00
parent a935e21bf8
commit dccab57d39
2 changed files with 25 additions and 6 deletions
@@ -13,8 +13,8 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests;
/// comms-loss watchdog trips on). Recovery is the watchdog's sequence, replayed here as <c>DriverInstanceActor</c>
/// runs it: ShutdownAsync, InitializeAsync, re-subscribe — after which the value comes back Good. There is no
/// in-driver reconnect any more (#670: the SDK reconnect handler kept everything Good and Healthy forever).
/// Since #676 a gracefully stopped server is a DEFINITIVE loss (<c>BadServerHalted</c>), so the driver also raises
/// <c>ConnectionLost</c> and the host runs that sequence at once instead of after the watchdog's silence window.
/// Since #676 definitive session loss also raises <c>ConnectionLost</c>, so the host runs that sequence at once
/// instead of after the watchdog's silence window. Shutdown timing determines the SDK's loss status.
/// </summary>
[Trait("Category", "Integration")]
public sealed class OpcUaClientDriverReconnectWireTests
@@ -72,12 +72,22 @@ public sealed class OpcUaClientDriverReconnectWireTests
await Task.Delay(TimeSpan.FromSeconds(3), ct); // three keep-alive intervals
drv.GetHealth().LastSuccessfulRead.ShouldBe(frozen);
// #676: a server that HALTED (graceful stop) is a definitive loss — measured here, the SDK reports
// BadServerHalted on every keep-alive and never re-opens the channel, so the session can never recover in
// place. The driver says so at once, so the host re-initialises it instead of waiting out the ~90 s watchdog.
// #676/#702: shutdown can report a halted server, an invalid/closed session, or a sustained closed
// channel. Each proves definitive loss; #702 reproduced BadSessionIdInvalid during this graceful stop.
// Deterministic ConnectionLossSignal tests cover the individual statuses and the sustained-channel gate.
await WaitAsync(() => { lock (lost) return lost.Count > 0; },
"a halted upstream must raise ConnectionLost", TimeSpan.FromSeconds(10));
lock (lost) lost.Single().ShouldContain("BadServerHalted");
lock (lost)
{
var reason = lost.Single();
string[] definitiveStatuses =
[
"BadSessionIdInvalid", "BadSessionClosed", "BadSessionNotActivated", "BadServerHalted",
"BadSecureChannelClosed", "BadConnectionClosed",
];
definitiveStatuses.ShouldContain(status => reason.EndsWith(status, StringComparison.Ordinal),
$"the restart must report definitive loss, but reported: {reason}");
}
});
// The restarted server knows nothing of the old session, so the driver stays down on its own …