gateway: AcknowledgeAlarm + QueryActiveAlarms handler tests (PR A.4) #113
@@ -229,6 +229,124 @@ public sealed class MxAccessGatewayServiceTests
|
|||||||
Assert.Equal(StatusCode.InvalidArgument, exception.StatusCode);
|
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(
|
private static MxAccessGatewayService CreateService(
|
||||||
FakeSessionManager sessionManager,
|
FakeSessionManager sessionManager,
|
||||||
IGatewayRequestIdentityAccessor? identityAccessor = null,
|
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 sealed class TestServerCallContext(CancellationToken cancellationToken = default) : ServerCallContext
|
||||||
{
|
{
|
||||||
private readonly Metadata requestHeaders = [];
|
private readonly Metadata requestHeaders = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user