review(OpcUaServer): fix silent auto-unshelve failure (empty User -> 'system')
Cross-module fix from the review sweep. -007 (Medium): OnTimedUnshelve built its AlarmCommand with User=string.Empty, so Part9StateMachine.ApplyUnshelve rejected it (ArgumentException, swallowed) and a TimedShelve never auto-expired. Pass the canonical 'system' user; the AlarmAck-gate bypass is preserved. Repurposed the test that had encoded the bug.
This commit is contained in:
@@ -270,8 +270,15 @@ public sealed class AlarmCommandRouterTests : IDisposable
|
||||
|
||||
/// <summary>OnTimedUnshelve fires with the SDK's system context (no session, no user identity) —
|
||||
/// the real SDK path when a TimedShelve duration expires. The gate must NOT veto: the result must be
|
||||
/// Good, the router must be invoked exactly once with Operation == "Unshelve" and User == empty,
|
||||
/// and no UnshelveAtUtc is carried.</summary>
|
||||
/// Good, the router must be invoked exactly once with Operation == "Unshelve", and no UnshelveAtUtc is
|
||||
/// carried.
|
||||
/// <para>
|
||||
/// Regression (OpcUaServer-007): the routed command's <see cref="AlarmCommand.User"/> must be a
|
||||
/// NON-EMPTY system user (<c>"system"</c>). The engine's <c>Part9StateMachine.ApplyUnshelve</c> rejects
|
||||
/// a null/whitespace user with <c>ArgumentException("User required.")</c>; that exception is swallowed
|
||||
/// downstream, so an empty user made the timed auto-unshelve SILENTLY no-op — a TimedShelve would never
|
||||
/// auto-expire. Asserting a non-empty user here pins the value the state machine requires.
|
||||
/// </para></summary>
|
||||
[Fact]
|
||||
public async Task OnTimedUnshelve_with_system_context_returns_good_and_routes_unshelve()
|
||||
{
|
||||
@@ -296,7 +303,9 @@ public sealed class AlarmCommandRouterTests : IDisposable
|
||||
var cmd = captured[0];
|
||||
cmd.AlarmId.ShouldBe("alm-tu"); // == ScriptedAlarmId / condition NodeId identifier
|
||||
cmd.Operation.ShouldBe("Unshelve");
|
||||
cmd.User.ShouldBe(string.Empty); // no client principal — system-initiated
|
||||
// System-initiated (no client principal), but the user MUST be non-empty so ApplyUnshelve accepts it.
|
||||
cmd.User.ShouldNotBeNullOrWhiteSpace();
|
||||
cmd.User.ShouldBe("system");
|
||||
cmd.UnshelveAtUtc.ShouldBeNull();
|
||||
|
||||
await host.DisposeAsync();
|
||||
|
||||
Reference in New Issue
Block a user