Files
mxaccessgw/src/ZB.MOM.WW.MxGateway.Tests/TestSupport/FakeGatewayAlarmService.cs
T
Joseph Doherty 0b4a1682c8 fix(grpc): typed alarm-feed overflow, memoized alarm snapshot, cancellable stream writes, uniform reply correlation id
- StreamAlarms: slow subscribers are completed with
  AlarmFeedSubscriberOverflowException and surface as ResourceExhausted
  (like a StreamEvents overflow) instead of warning-with-stack + Unavailable.
- GatewayAlarmMonitor.StreamAsync reuses the memoized alarm-set projection
  instead of cloning the whole set under the broadcast lock per attach; the
  prefix filter runs outside the lock.
- StreamEvents/StreamAlarms pass the call token to response writes; a write
  failure after client cancel maps to Cancelled without a warning.
- Every MxCommandReply.correlation_id now echoes the request's
  client_correlation_id (previously only constraint-denied replies did;
  normal replies leaked the internal envelope id).
2026-10-22 06:27:36 -04:00

68 lines
2.3 KiB
C#

using System.Runtime.CompilerServices;
using ZB.MOM.WW.MxGateway.Contracts.Proto;
using ZB.MOM.WW.MxGateway.Server.Alarms;
namespace ZB.MOM.WW.MxGateway.Tests.TestSupport;
/// <summary>
/// <see cref="IGatewayAlarmService"/> test double — serves a scripted
/// active-alarm set and acknowledges every request with an OK status,
/// so gRPC service tests can exercise the alarm handlers without the
/// real gateway alarm monitor or a worker.
/// </summary>
public sealed class FakeGatewayAlarmService : IGatewayAlarmService
{
/// <inheritdoc />
public GatewayAlarmMonitorState State { get; set; } = GatewayAlarmMonitorState.Monitoring;
/// <inheritdoc />
public string? LastError { get; set; }
/// <inheritdoc />
public int? WorkerProcessId { get; set; }
/// <inheritdoc />
public IReadOnlyList<ActiveAlarmSnapshot> CurrentAlarms { get; set; } = [];
/// <inheritdoc />
public bool SnapshotTruncated { get; set; }
/// <inheritdoc />
public async IAsyncEnumerable<AlarmFeedMessage> StreamAsync(
string? alarmFilterPrefix,
[EnumeratorCancellation] CancellationToken cancellationToken)
{
foreach (ActiveAlarmSnapshot alarm in CurrentAlarms)
{
cancellationToken.ThrowIfCancellationRequested();
yield return new AlarmFeedMessage { ActiveAlarm = alarm };
}
yield return new AlarmFeedMessage { SnapshotComplete = true };
await Task.CompletedTask.ConfigureAwait(false);
if (StreamFault is not null)
{
throw StreamFault;
}
}
/// <summary>
/// Gets or sets an exception the feed throws after its snapshot, standing in for the monitor
/// completing a subscriber with a fault (for example a slow-subscriber overflow).
/// </summary>
public Exception? StreamFault { get; set; }
/// <inheritdoc />
public Task<AcknowledgeAlarmReply> AcknowledgeAsync(
AcknowledgeAlarmRequest request,
CancellationToken cancellationToken)
{
return Task.FromResult(new AcknowledgeAlarmReply
{
CorrelationId = request.ClientCorrelationId,
ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.Ok },
DiagnosticMessage = string.Empty,
});
}
}