fix(dashboard): alarms feed doc row + monitor-contract comment + mid-truncation attach test
The In-process page feeds table still described the alarms page's subscription as
provider status only, contradicting the three passages updated in 7b6dfba.
The loop's catch-all comment (and the cadence bullet that repeated it) claimed the
monitor completes a subscriber's stream on restart. It does not: ClearCache pushes
snapshot_status(false) through the still-open channel, and a subscriber is only
completed-with-error on a failed TryWrite, or removed by its own disposal.
The SnapshotStatus arm's priming claim had no test behind it — every push test
attached to an untruncated feed and pushed the edge itself. ScriptedAlarmFeed now
replays an optional open sequence, and a new test attaches to a feed already
primed provider_status then snapshot_status(truncated) and asserts the banner
comes up with no edge pushed after render.
This commit is contained in:
@@ -258,10 +258,13 @@
|
||||
}
|
||||
catch
|
||||
{
|
||||
// The monitor completes a subscriber's stream when it falls behind, and
|
||||
// again when the monitor restarts. Both are recoverable by resubscribing;
|
||||
// the badge and banner hold their last values in the meantime, and the
|
||||
// resubscribe is primed with the current ones.
|
||||
// The monitor drops a subscriber whose queue it cannot write to, completing
|
||||
// that stream with an error; short of cancellation or disposal that is the
|
||||
// only way this enumeration ends. A monitor restart is NOT one of them — it
|
||||
// keeps the channel and pushes the cleared status frames through it — so this
|
||||
// catch is the fell-behind case, recoverable by resubscribing. The badge and
|
||||
// banner hold their last values meanwhile, and the resubscribe is primed with
|
||||
// the current ones.
|
||||
}
|
||||
|
||||
try
|
||||
|
||||
@@ -134,6 +134,44 @@ public sealed class AlarmsPageTruncationBannerTests
|
||||
"banner to clear after a complete snapshot_status frame");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attach while the verdict is already truncated. The page carries no
|
||||
/// priming logic of its own — it relies on <c>StreamAsync</c> opening every
|
||||
/// subscription with a <c>snapshot_status</c> baseline — so the caveat has
|
||||
/// to come up off the open sequence alone, with no edge pushed afterwards.
|
||||
/// A page that only handled the edge would show an un-caveated alarm list
|
||||
/// to every operator who opened it after the truncation began.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AlarmsPage_AttachingToAnAlreadyTruncatedFeed_RaisesTheBannerFromThePriming()
|
||||
{
|
||||
// The monitor's open sequence: provider status, then the unconditional
|
||||
// completeness baseline. Nothing is pushed after this.
|
||||
ScriptedAlarmFeed feed = new()
|
||||
{
|
||||
Priming =
|
||||
[
|
||||
new AlarmFeedMessage { ProviderStatus = new AlarmProviderStatus() },
|
||||
SnapshotStatusFrame(truncated: true),
|
||||
],
|
||||
};
|
||||
|
||||
await using ServiceProvider provider = BuildPushServices(feed);
|
||||
await using HtmlRenderer renderer = new(
|
||||
provider,
|
||||
provider.GetRequiredService<ILoggerFactory>());
|
||||
|
||||
HtmlRootComponent page = await renderer.Dispatcher.InvokeAsync(
|
||||
() => renderer.RenderComponentAsync<AlarmsPage>());
|
||||
|
||||
await WaitForHtmlAsync(
|
||||
renderer,
|
||||
page,
|
||||
html => html.Contains(BannerMarker, StringComparison.Ordinal),
|
||||
"banner to appear from the feed's open-time snapshot_status baseline");
|
||||
}
|
||||
|
||||
private static AlarmFeedMessage SnapshotStatusFrame(bool truncated)
|
||||
{
|
||||
return new AlarmFeedMessage
|
||||
@@ -280,6 +318,14 @@ public sealed class AlarmsPageTruncationBannerTests
|
||||
/// <inheritdoc />
|
||||
public bool SnapshotTruncated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Frames replayed at the head of every subscription, standing in for the
|
||||
/// monitor's open sequence (provider status, then the unconditional
|
||||
/// completeness baseline). Empty means the subscriber sees only what the
|
||||
/// test pushes.
|
||||
/// </summary>
|
||||
public IReadOnlyList<AlarmFeedMessage> Priming { get; init; } = [];
|
||||
|
||||
/// <summary>Pushes one frame onto the feed the page is subscribed to.</summary>
|
||||
/// <param name="message">The feed frame to deliver.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
@@ -290,6 +336,12 @@ public sealed class AlarmsPageTruncationBannerTests
|
||||
string? alarmFilterPrefix,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
foreach (AlarmFeedMessage primed in Priming)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
yield return primed;
|
||||
}
|
||||
|
||||
await foreach (AlarmFeedMessage message in _frames.Reader
|
||||
.ReadAllAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
|
||||
Reference in New Issue
Block a user