fix(client): TimedShelve milliseconds + shelve node guard + service-layer tests
Important 1: ShelveAlarmAsync Timed branch now multiplies shelvingTimeSeconds × 1000.0
before passing to CallMethodAsync — OPC UA Part 9 TimedShelve ShelvingTime is a Duration
in milliseconds, not seconds. IOpcUaClientService XML doc and ShelveCommand --duration
description updated to document the seconds-in / ms-out contract.
Important 2: ShelveAlarmAsync builds shelvingStateNodeId with the same
EndsWith(".ShelvingState") guard already used by the .Condition suffix in
AcknowledgeAlarmAsync / ConfirmAlarmAsync, preventing double-append.
Important 3: Add 6 service-layer tests to OpcUaClientServiceTests —
ConfirmAlarmAsync_OnSuccess_ReturnsGood
ConfirmAlarmAsync_OnServiceResultException_ReturnsBadStatusCode
ShelveAlarmAsync_OneShot_CallsMethodWithNoArgs
ShelveAlarmAsync_Timed_PassesDurationInMilliseconds (regression guard for Important 1)
ShelveAlarmAsync_Unshelve_CallsMethodWithNoArgs
ShelveAlarmAsync_OnServiceResultException_ReturnsBadStatusCode
FakeSessionAdapter extended with CallMethodInputArgs list to record per-call input
arguments so the Timed test can assert the ms value.
Minor 4: ShelveCommand output changed from "Shelve (OneShot) successful" to
"{shelveKind} successful/failed" so Unshelve reads "Unshelve successful: …".
Minor 6: ShelveCommand --duration description updated to "(must be > 0; in seconds,
converted to milliseconds for the OPC UA call; required for --kind Timed)".
This commit is contained in:
@@ -1057,6 +1057,123 @@ public class OpcUaClientServiceTests : IDisposable
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
// --- ConfirmAlarm tests ---
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a successful confirm call returns <see cref="StatusCodes.Good"/>
|
||||
/// and reaches the session adapter's CallMethodAsync exactly once.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ConfirmAlarmAsync_OnSuccess_ReturnsGood()
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a <see cref="ServiceResultException"/> from the session is captured and returned
|
||||
/// as a bad <see cref="StatusCode"/> rather than propagating to the caller.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ConfirmAlarmAsync_OnServiceResultException_ReturnsBadStatusCode()
|
||||
{
|
||||
var session = new FakeSessionAdapter
|
||||
{
|
||||
CallMethodException = new ServiceResultException(
|
||||
StatusCodes.BadConditionAlreadyEnabled, "already confirmed")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
var result = await _service.ConfirmAlarmAsync("ns=2;s=Cond", new byte[] { 1, 2 }, "confirmed");
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyEnabled);
|
||||
}
|
||||
|
||||
// --- ShelveAlarm tests ---
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that OneShot shelving calls the session adapter once with no input arguments.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ShelveAlarmAsync_OneShot_CallsMethodWithNoArgs()
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
session.CallMethodInputArgs[0].ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Timed shelving passes the duration converted to milliseconds (seconds × 1000)
|
||||
/// as the first input argument — regression guard for the Important 1 units bug.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ShelveAlarmAsync_Timed_PassesDurationInMilliseconds()
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
const double durationSeconds = 30.0;
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Timed, durationSeconds);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
session.CallMethodInputArgs[0].Length.ShouldBe(1);
|
||||
session.CallMethodInputArgs[0][0].ShouldBe(durationSeconds * 1000.0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that Unshelve calls the session adapter once with no input arguments.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ShelveAlarmAsync_Unshelve_CallsMethodWithNoArgs()
|
||||
{
|
||||
var session = new FakeSessionAdapter();
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.Unshelve);
|
||||
|
||||
result.ShouldBe(StatusCodes.Good);
|
||||
session.CallMethodCount.ShouldBe(1);
|
||||
session.CallMethodInputArgs[0].ShouldBeEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that a <see cref="ServiceResultException"/> from the session is captured and returned
|
||||
/// as a bad <see cref="StatusCode"/> rather than propagating to the caller.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ShelveAlarmAsync_OnServiceResultException_ReturnsBadStatusCode()
|
||||
{
|
||||
var session = new FakeSessionAdapter
|
||||
{
|
||||
CallMethodException = new ServiceResultException(
|
||||
StatusCodes.BadConditionAlreadyShelved, "already shelved")
|
||||
};
|
||||
_sessionFactory.EnqueueSession(session);
|
||||
await _service.ConnectAsync(ValidSettings());
|
||||
|
||||
var result = await _service.ShelveAlarmAsync("ns=2;s=Cond", ShelveKind.OneShot);
|
||||
|
||||
StatusCode.IsBad(result).ShouldBeTrue();
|
||||
result.Code.ShouldBe(StatusCodes.BadConditionAlreadyShelved);
|
||||
}
|
||||
|
||||
// --- Alarm fallback path (Client.Shared-011) ---
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user