gateway: AcknowledgeAlarm + QueryActiveAlarms handler tests (PR A.4) #113

Merged
dohertj2 merged 1 commits from track-a4-conditionrefresh-coverage into main 2026-04-30 21:23:22 -04:00
@@ -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 = [];