diff --git a/docs/plans/2026-10-13-restart-loss-reason-results.md b/docs/plans/2026-10-13-restart-loss-reason-results.md new file mode 100644 index 00000000..745c431d --- /dev/null +++ b/docs/plans/2026-10-13-restart-loss-reason-results.md @@ -0,0 +1,9 @@ +# Restart loss reason test correction — #702 + +The full integration reproduction failed at the restart wire test reason assertion: it required BadServerHalted but correctly received BadSessionIdInvalid. Production already supports that definitive loss reason. + +The test now accepts an explicit semantic set: four direct definitive session statuses and two sustained closed-channel statuses. All BadNoCommunication, retained-value, unhealthy-state, frozen-read-stamp, one-notification and explicit reinitialization/recovery assertions remain unchanged. Only test expectation/comments changed; production unchanged. Existing deterministic driver tests independently enforce direct-status ordering/count and the sustained-channel threshold. + +Verification: isolated restart1/1, deterministic status routes30/30, full OPC UA integration122/122. Full offline solution: 14,613 passed / 611 skipped / 0 failed, exit0, 61 reported suite summaries. Host integration266 passed /14 skipped; unavailable environment/fixture tests remain skips. Independent review found no important defects; git diff --check passed. Actual prior red run121pass/1fail, detailed console/TRX confirms the exact reason mismatch. + +Logs/TRX retained in the Codex thread workspace under work/issue702-*. No Gateway or deployment behavior changed by this test correction. Separate live gates remain open. diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverReconnectWireTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverReconnectWireTests.cs index d1c645c9..059126b6 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverReconnectWireTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverReconnectWireTests.cs @@ -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 DriverInstanceActor /// 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 (BadServerHalted), so the driver also raises -/// ConnectionLost and the host runs that sequence at once instead of after the watchdog's silence window. +/// Since #676 definitive session loss also raises ConnectionLost, so the host runs that sequence at once +/// instead of after the watchdog's silence window. Shutdown timing determines the SDK's loss status. /// [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 …