gateway: AcknowledgeAlarm + QueryActiveAlarms handler tests (PR A.4) #113
@@ -229,6 +229,124 @@ public sealed class MxAccessGatewayServiceTests
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
}
|
||||
|
||||
// ===== PR A.4 — AcknowledgeAlarm + QueryActiveAlarms handler contract =====
|
||||
//
|
||||
// Worker-side dispatch (translating AcknowledgeAlarm to MxAccess Acknowledge,
|
||||
// walking the active-alarm collection for QueryActiveAlarms) is gated on PR
|
||||
// A.2's dev-rig validation. These tests pin the public surface so the worker
|
||||
// wiring lands without changing observable behaviour for clients.
|
||||
|
||||
/// <summary>Verifies AcknowledgeAlarm rejects empty session_id.</summary>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAlarm_WithMissingSessionId_ThrowsInvalidArgument()
|
||||
{
|
||||
MxAccessGatewayService service = CreateService(new FakeSessionManager());
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.AcknowledgeAlarm(
|
||||
new AcknowledgeAlarmRequest
|
||||
{
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
OperatorUser = "alice",
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies AcknowledgeAlarm rejects empty alarm_full_reference.</summary>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAlarm_WithMissingAlarmReference_ThrowsInvalidArgument()
|
||||
{
|
||||
MxAccessGatewayService service = CreateService(new FakeSessionManager());
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.AcknowledgeAlarm(
|
||||
new AcknowledgeAlarmRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
OperatorUser = "alice",
|
||||
},
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies AcknowledgeAlarm returns OK with a worker-pending diagnostic for valid input.</summary>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAlarm_WithValidRequest_ReturnsOkWithWorkerPendingDiagnostic()
|
||||
{
|
||||
MxAccessGatewayService service = CreateService(new FakeSessionManager());
|
||||
|
||||
AcknowledgeAlarmReply reply = await service.AcknowledgeAlarm(
|
||||
new AcknowledgeAlarmRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
ClientCorrelationId = "corr-1",
|
||||
AlarmFullReference = "Tank01.Level.HiHi",
|
||||
Comment = "investigating",
|
||||
OperatorUser = "alice",
|
||||
},
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Equal(ProtocolStatusCode.Ok, reply.ProtocolStatus.Code);
|
||||
Assert.Equal("session-1", reply.SessionId);
|
||||
Assert.Equal("corr-1", reply.CorrelationId);
|
||||
Assert.Contains("worker", reply.DiagnosticMessage, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>Verifies QueryActiveAlarms rejects empty session_id.</summary>
|
||||
[Fact]
|
||||
public async Task QueryActiveAlarms_WithMissingSessionId_ThrowsInvalidArgument()
|
||||
{
|
||||
MxAccessGatewayService service = CreateService(new FakeSessionManager());
|
||||
|
||||
RpcException exception = await Assert.ThrowsAsync<RpcException>(
|
||||
async () => await service.QueryActiveAlarms(
|
||||
new QueryActiveAlarmsRequest(),
|
||||
new RecordingStreamWriter<ActiveAlarmSnapshot>(),
|
||||
new TestServerCallContext()));
|
||||
|
||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies QueryActiveAlarms streams zero snapshots until PR A.2 wires the worker walk.</summary>
|
||||
[Fact]
|
||||
public async Task QueryActiveAlarms_WithValidRequest_StreamsZeroSnapshots()
|
||||
{
|
||||
MxAccessGatewayService service = CreateService(new FakeSessionManager());
|
||||
RecordingStreamWriter<ActiveAlarmSnapshot> sink = new();
|
||||
|
||||
await service.QueryActiveAlarms(
|
||||
new QueryActiveAlarmsRequest
|
||||
{
|
||||
SessionId = "session-1",
|
||||
AlarmFilterPrefix = "Tank01.",
|
||||
},
|
||||
sink,
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Empty(sink.Items);
|
||||
}
|
||||
|
||||
/// <summary>Verifies OpenSession advertises the alarm RPC capability strings.</summary>
|
||||
[Fact]
|
||||
public async Task OpenSession_AdvertisesAlarmRpcCapabilities()
|
||||
{
|
||||
FakeSessionManager sessionManager = new();
|
||||
GatewayRequestIdentityAccessor identityAccessor = new();
|
||||
MxAccessGatewayService service = CreateService(sessionManager, identityAccessor);
|
||||
|
||||
using IDisposable identityScope = identityAccessor.Push(CreateIdentity());
|
||||
|
||||
OpenSessionReply reply = await service.OpenSession(
|
||||
new OpenSessionRequest(),
|
||||
new TestServerCallContext());
|
||||
|
||||
Assert.Contains("unary-acknowledge-alarm", reply.Capabilities);
|
||||
Assert.Contains("server-stream-active-alarms", reply.Capabilities);
|
||||
}
|
||||
|
||||
private static MxAccessGatewayService CreateService(
|
||||
FakeSessionManager sessionManager,
|
||||
IGatewayRequestIdentityAccessor? identityAccessor = null,
|
||||
@@ -549,6 +667,18 @@ public sealed class MxAccessGatewayServiceTests
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingStreamWriter<T> : IServerStreamWriter<T>
|
||||
{
|
||||
public List<T> Items { get; } = new();
|
||||
public WriteOptions? WriteOptions { get; set; }
|
||||
|
||||
public Task WriteAsync(T message)
|
||||
{
|
||||
Items.Add(message);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class TestServerCallContext(CancellationToken cancellationToken = default) : ServerCallContext
|
||||
{
|
||||
private readonly Metadata requestHeaders = [];
|
||||
|
||||
Reference in New Issue
Block a user