fix(ui): drop stale-site poll results on Alarm Summary site switch (plan R2-07 T7)
This commit is contained in:
@@ -291,6 +291,13 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await AlarmSummaryService.GetSiteAlarmsAsync(siteId);
|
var result = await AlarmSummaryService.GetSiteAlarmsAsync(siteId);
|
||||||
|
// Stale-site guard (arch-review R2 N4): the operator may have switched sites
|
||||||
|
// while this fan-out was in flight — drop the result rather than labeling
|
||||||
|
// site A's alarms under site B's picker. Mirrors OnLiveAlarmsChanged (:374).
|
||||||
|
if (_selectedSiteId != siteId)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
_rows = result.Alarms;
|
_rows = result.Alarms;
|
||||||
_notReporting = result.NotReportingInstances;
|
_notReporting = result.NotReportingInstances;
|
||||||
_rollup = AlarmSummaryService.ComputeRollup(_rows);
|
_rollup = AlarmSummaryService.ComputeRollup(_rows);
|
||||||
|
|||||||
@@ -208,6 +208,42 @@ public class AlarmSummaryRenderTests : BunitContext
|
|||||||
Assert.True(_liveCache.DisposeCount >= 1);
|
Assert.True(_liveCache.DisposeCount >= 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── arch-review R2 N4: stale-site poll guard ───────────────────────────────
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InFlightPollForPreviousSite_DoesNotOverwriteNewSitesRows()
|
||||||
|
{
|
||||||
|
// Site 1's fan-out hangs on a TCS; site 2 answers immediately (arch-review R2 N4).
|
||||||
|
var site1Tcs = new TaskCompletionSource<AlarmSummaryResult>();
|
||||||
|
_summary.GetSiteAlarmsAsync(1, Arg.Any<CancellationToken>()).Returns(site1Tcs.Task);
|
||||||
|
_summary.GetSiteAlarmsAsync(2, Arg.Any<CancellationToken>()).Returns(Task.FromResult(
|
||||||
|
new AlarmSummaryResult(
|
||||||
|
new List<AlarmSummaryRow>
|
||||||
|
{
|
||||||
|
new("Site2Instance", new AlarmStateChanged("Site2Instance", "S2-alarm", AlarmState.Active, 100, DateTimeOffset.UtcNow)),
|
||||||
|
},
|
||||||
|
Array.Empty<string>())));
|
||||||
|
|
||||||
|
var cut = Render<AlarmSummaryPage>();
|
||||||
|
cut.Find("[data-test='alarm-summary-site']").Change("1"); // poll in flight, hung
|
||||||
|
cut.Find("[data-test='alarm-summary-site']").Change("2"); // site 2 renders
|
||||||
|
cut.WaitForAssertion(() => Assert.Equal("Site2Instance", FirstRowInstance(cut)));
|
||||||
|
|
||||||
|
// The stale site-1 fan-out now completes — it must be dropped, not rendered.
|
||||||
|
site1Tcs.SetResult(new AlarmSummaryResult(
|
||||||
|
new List<AlarmSummaryRow>
|
||||||
|
{
|
||||||
|
new("StaleSite1", new AlarmStateChanged("StaleSite1", "S1-alarm", AlarmState.Active, 999, DateTimeOffset.UtcNow)),
|
||||||
|
},
|
||||||
|
new[] { "StaleNotReporting" }));
|
||||||
|
|
||||||
|
cut.WaitForAssertion(() =>
|
||||||
|
{
|
||||||
|
Assert.Equal("Site2Instance", FirstRowInstance(cut));
|
||||||
|
Assert.DoesNotContain("StaleNotReporting", cut.Markup);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Controllable in-memory fake of <see cref="ISiteAlarmLiveCache"/>: records
|
/// Controllable in-memory fake of <see cref="ISiteAlarmLiveCache"/>: records
|
||||||
/// subscribe/dispose counts and lets a test push a live snapshot (which flips
|
/// subscribe/dispose counts and lets a test push a live snapshot (which flips
|
||||||
|
|||||||
Reference in New Issue
Block a user