test(gateway): cover failback reason, FromFeed/SinceUtc badge paths; style + bounded drain (Tests-032..035)
This commit is contained in:
@@ -218,6 +218,126 @@ public sealed class GatewayAlarmMonitorProviderModeTests
|
||||
await monitor.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests-032: pins the monitor's <c>toMode → AlarmProviderSwitchReason</c>
|
||||
/// derivation (<c>GatewayAlarmMonitor.ApplyProviderModeChangeAsync</c>): an
|
||||
/// alarmmgr→subtag change must emit <c>reason=failover</c> and a subtag→alarmmgr
|
||||
/// change must emit <c>reason=failback</c>. Captures the <c>reason</c> tag off the
|
||||
/// <c>mxgateway.alarms.provider_switches</c> counter — a regression that swapped
|
||||
/// the Failover/Failback arms or collapsed them to Unknown would be caught here,
|
||||
/// whereas the count-only tests above would still pass.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ProviderModeChange_FailoverThenFailback_RecordsCorrectReasonTags()
|
||||
{
|
||||
using GatewayMetrics metrics = new();
|
||||
List<string> capturedReasons = [];
|
||||
using MeterListener listener = new();
|
||||
listener.InstrumentPublished = (instrument, meterListener) =>
|
||||
{
|
||||
if (ReferenceEquals(instrument.Meter, metrics.Meter)
|
||||
&& instrument.Name == "mxgateway.alarms.provider_switches")
|
||||
{
|
||||
meterListener.EnableMeasurementEvents(instrument);
|
||||
}
|
||||
};
|
||||
listener.SetMeasurementEventCallback<long>(
|
||||
(instrument, _, tags, _) =>
|
||||
{
|
||||
if (!ReferenceEquals(instrument.Meter, metrics.Meter)
|
||||
|| instrument.Name != "mxgateway.alarms.provider_switches")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object?> tag in tags)
|
||||
{
|
||||
if (tag.Key == "reason" && tag.Value is string reasonTag)
|
||||
{
|
||||
lock (capturedReasons)
|
||||
{
|
||||
capturedReasons.Add(reasonTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
listener.Start();
|
||||
|
||||
FakeSessionManager sessions = new();
|
||||
using GatewayAlarmMonitor monitor = CreateMonitor(sessions, metrics);
|
||||
|
||||
using CancellationTokenSource cts = new();
|
||||
await monitor.StartAsync(cts.Token);
|
||||
await sessions.WaitForSubscribeAsync(WaitTimeout);
|
||||
|
||||
// Register a live subscriber and gate the mode-change events until the baseline
|
||||
// ProviderStatus message has been drained, so neither event is dropped.
|
||||
List<AlarmFeedMessage> received = [];
|
||||
TaskCompletionSource baselineReceived = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
using CancellationTokenSource streamCts = new();
|
||||
Task reader = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await foreach (AlarmFeedMessage message in monitor.StreamAsync(null, streamCts.Token))
|
||||
{
|
||||
lock (received)
|
||||
{
|
||||
received.Add(message);
|
||||
if (received.Count == 1)
|
||||
{
|
||||
baselineReceived.TrySetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Expected when the test cancels the stream.
|
||||
}
|
||||
});
|
||||
|
||||
await baselineReceived.Task.WaitAsync(WaitTimeout);
|
||||
|
||||
// alarmmgr (baseline) → subtag: must classify as a failover.
|
||||
sessions.EmitEvent(new MxEvent
|
||||
{
|
||||
OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent
|
||||
{
|
||||
Mode = AlarmProviderMode.Subtag,
|
||||
Reason = "alarmmgr failed",
|
||||
At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
||||
},
|
||||
});
|
||||
await WaitUntilAsync(
|
||||
() => { lock (capturedReasons) { return capturedReasons.Count >= 1; } },
|
||||
WaitTimeout);
|
||||
|
||||
// subtag → alarmmgr: must classify as a failback.
|
||||
sessions.EmitEvent(new MxEvent
|
||||
{
|
||||
OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent
|
||||
{
|
||||
Mode = AlarmProviderMode.Alarmmgr,
|
||||
Reason = "alarmmgr recovered",
|
||||
At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
||||
},
|
||||
});
|
||||
await WaitUntilAsync(
|
||||
() => { lock (capturedReasons) { return capturedReasons.Count >= 2; } },
|
||||
WaitTimeout);
|
||||
|
||||
lock (capturedReasons)
|
||||
{
|
||||
Assert.Equal(new[] { "failover", "failback" }, capturedReasons);
|
||||
}
|
||||
|
||||
await streamCts.CancelAsync();
|
||||
await reader;
|
||||
await cts.CancelAsync();
|
||||
await monitor.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NewSubscriber_ReceivesProviderStatusAsFirstMessage()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user