feat(server): route OPC UA Part 9 shelve methods to ScriptedAlarmEngine (#24)
OneShotShelve / TimedShelve / Unshelve now reach the ScriptedAlarmEngine. Scripted-alarm condition nodes get a ShelvedStateMachine subtree created before alarm.Create so the stack wires each shelve method's dispatch handler; AlarmConditionState.OnShelve / OnTimedUnshelve route to the engine and mirror the result onto the OPC UA node via SetShelvingState. The three per-instance shelve method NodeIds are indexed so the Call gate resolves them to OpcUaOperation.AlarmShelve instead of falling through to generic Call. Engine dispatch is split into the node-free InvokeEngineShelve so the routing decision is unit-testable. Adds 9 unit tests; updates phase-7-status.md Gap 1 (only AddComment remains unwired) and the #24 entry in looseends.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -410,6 +410,76 @@ public sealed class ScriptedAlarmMethodRoutingTests
|
||||
errors[0].StatusCode.ShouldBe((StatusCode)StatusCodes.BadInvalidArgument);
|
||||
}
|
||||
|
||||
// ---- Shelve routing (Task #24 follow-up) -------------------------------
|
||||
|
||||
[Fact]
|
||||
public void InvokeEngineShelve_oneshot_shelves_engine_state()
|
||||
{
|
||||
using var engine = BuildEngine("al-1");
|
||||
|
||||
var result = DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "al-1", "ops-user", shelving: true, oneShot: true, shelvingTime: 0, logger: null);
|
||||
|
||||
ServiceResult.IsBad(result).ShouldBeFalse("OneShotShelve succeeds");
|
||||
engine.GetState("al-1")!.Shelving.Kind.ShouldBe(ShelvingKind.OneShot);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokeEngineShelve_timed_shelves_engine_state()
|
||||
{
|
||||
using var engine = BuildEngine("al-1");
|
||||
|
||||
// shelvingTime is a Duration in ms — InvokeEngineShelve adds it to UtcNow.
|
||||
var result = DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "al-1", "ops-user", shelving: true, oneShot: false, shelvingTime: 60_000, logger: null);
|
||||
|
||||
ServiceResult.IsBad(result).ShouldBeFalse("TimedShelve succeeds");
|
||||
var state = engine.GetState("al-1")!;
|
||||
state.Shelving.Kind.ShouldBe(ShelvingKind.Timed);
|
||||
state.Shelving.UnshelveAtUtc.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokeEngineShelve_unshelve_clears_engine_state()
|
||||
{
|
||||
using var engine = BuildEngine("al-1");
|
||||
DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "al-1", "ops-user", shelving: true, oneShot: true, shelvingTime: 0, logger: null);
|
||||
engine.GetState("al-1")!.Shelving.Kind.ShouldBe(ShelvingKind.OneShot);
|
||||
|
||||
var result = DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "al-1", "ops-user", shelving: false, oneShot: false, shelvingTime: 0, logger: null);
|
||||
|
||||
ServiceResult.IsBad(result).ShouldBeFalse("Unshelve succeeds");
|
||||
engine.GetState("al-1")!.Shelving.Kind.ShouldBe(ShelvingKind.Unshelved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokeEngineShelve_timed_with_non_positive_duration_returns_BadInvalidArgument()
|
||||
{
|
||||
using var engine = BuildEngine("al-1");
|
||||
|
||||
// A TimedShelve resolving to an unshelve time at-or-before now is rejected by the
|
||||
// engine's Part 9 state machine (ArgumentOutOfRangeException → BadInvalidArgument).
|
||||
var result = DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "al-1", "ops-user", shelving: true, oneShot: false, shelvingTime: 0, logger: null);
|
||||
|
||||
ServiceResult.IsBad(result).ShouldBeTrue();
|
||||
result.StatusCode.ShouldBe((StatusCode)StatusCodes.BadInvalidArgument);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvokeEngineShelve_unknown_alarm_returns_BadInvalidArgument()
|
||||
{
|
||||
using var engine = BuildEngine("al-1");
|
||||
|
||||
var result = DriverNodeManager.InvokeEngineShelve(
|
||||
engine, "not-an-alarm", "ops-user", shelving: true, oneShot: true, shelvingTime: 0, logger: null);
|
||||
|
||||
ServiceResult.IsBad(result).ShouldBeTrue("unknown alarm id → error result");
|
||||
result.StatusCode.ShouldBe((StatusCode)StatusCodes.BadInvalidArgument);
|
||||
}
|
||||
|
||||
// ---- Phase7ComposedSources helpers -------------------------------------
|
||||
|
||||
private static Script ScriptRow(string id, string source) => new()
|
||||
|
||||
Reference in New Issue
Block a user