docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)
Comment/XML-doc changes only: - add summaries to ~10k test methods, fixtures, fakes and helpers (+ <param> on theories, <returns> on async Task methods, <inheritdoc/> on interface implementations the checker can resolve) - strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N, #NN, Component-0NN) from comments, keeping the explanatory text; reword false positives in comments only - real docs on fakes implementing BCL/library interfaces (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime) - reword the last 5 src/ false positives (CWE-1236, failures-1, year-0001, M365) Only non-comment edits: four one-line nested classes split across lines so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
+65
-13
@@ -9,7 +9,7 @@ using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for <see cref="SiteAlarmAggregatorActor"/> (plan #10, Task 4) — the transient,
|
||||
/// Tests for <see cref="SiteAlarmAggregatorActor"/> — the transient,
|
||||
/// in-memory, per-site live alarm aggregator. Covers the tricky bits: seed-then-stream
|
||||
/// ordering (a delta arriving before the seed completes is neither lost nor double-applied),
|
||||
/// dedup by AlarmKey, placeholder seeded-but-not-on-stream coherence, NodeA↔NodeB reconnect
|
||||
@@ -31,6 +31,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
private const string GrpcNodeB = "http://localhost:5200";
|
||||
private const string Instance = "Site1.Pump01";
|
||||
|
||||
/// <summary>Initializes the test class with a quiet Akka log level.</summary>
|
||||
public SiteAlarmAggregatorActorTests() : base(@"akka.loglevel = WARNING")
|
||||
{
|
||||
}
|
||||
@@ -50,8 +51,12 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
private readonly ConcurrentQueue<TaskCompletionSource<IReadOnlyList<AlarmStateChanged>>> _pending = new();
|
||||
private int _callCount;
|
||||
|
||||
/// <summary>Gets the number of seed calls made so far.</summary>
|
||||
public int CallCount => Volatile.Read(ref _callCount);
|
||||
|
||||
/// <summary>Starts a seed call that stays pending until the test completes it.</summary>
|
||||
/// <param name="ct">A token that cancels the pending seed call.</param>
|
||||
/// <returns>A task that completes with the seeded alarm rows once the test completes it.</returns>
|
||||
public Task<IReadOnlyList<AlarmStateChanged>> Seed(CancellationToken ct)
|
||||
{
|
||||
Interlocked.Increment(ref _callCount);
|
||||
@@ -63,6 +68,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
}
|
||||
|
||||
/// <summary>Completes the oldest not-yet-completed seed call with the given rows.</summary>
|
||||
/// <param name="rows">The alarm rows the pending seed call completes with.</param>
|
||||
public void CompleteNext(params AlarmStateChanged[] rows)
|
||||
{
|
||||
SpinWait.SpinUntil(() => _pending.TryPeek(out _), TimeSpan.FromSeconds(3));
|
||||
@@ -86,8 +92,12 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
private sealed class PublishSink
|
||||
{
|
||||
private readonly object _lock = new();
|
||||
/// <summary>Gets every snapshot published so far, in publish order.</summary>
|
||||
public List<IReadOnlyList<AlarmStateChanged>> Snapshots { get; } = new();
|
||||
|
||||
/// <summary>Records a published snapshot and its stream liveness.</summary>
|
||||
/// <param name="snapshot">The published alarm snapshot.</param>
|
||||
/// <param name="streamLive">Whether the stream was live at publish time.</param>
|
||||
public void Publish(IReadOnlyList<AlarmStateChanged> snapshot, bool streamLive)
|
||||
{
|
||||
lock (_lock) { Snapshots.Add(snapshot); StreamLive = streamLive; }
|
||||
@@ -96,11 +106,13 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
/// <summary>Liveness reported with the most recent publish.</summary>
|
||||
public bool StreamLive { get; private set; }
|
||||
|
||||
/// <summary>Gets the most recently published snapshot, or <c>null</c> if nothing has been published.</summary>
|
||||
public IReadOnlyList<AlarmStateChanged>? Latest
|
||||
{
|
||||
get { lock (_lock) { return Snapshots.Count == 0 ? null : Snapshots[^1]; } }
|
||||
}
|
||||
|
||||
/// <summary>Gets the number of snapshots published so far.</summary>
|
||||
public int Count { get { lock (_lock) { return Snapshots.Count; } } }
|
||||
}
|
||||
|
||||
@@ -114,15 +126,19 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
private readonly List<SiteSub> _subs = new();
|
||||
private readonly List<string> _unsubscribed = new();
|
||||
|
||||
/// <summary>Gets a copy of the subscriptions the aggregator opened.</summary>
|
||||
public List<SiteSub> Subs { get { lock (_lock) { return _subs.ToList(); } } }
|
||||
/// <summary>Gets a copy of the correlation ids that were unsubscribed.</summary>
|
||||
public List<string> Unsubscribed { get { lock (_lock) { return _unsubscribed.ToList(); } } }
|
||||
|
||||
/// <summary>Initializes a mock stream client with no real channel.</summary>
|
||||
public MockSiteAlarmStreamClient() : base() { }
|
||||
|
||||
/// <summary>When false the stream never reports connected — the site accepted the TCP
|
||||
/// call but never answered, so the aggregator's connect-driven re-seed must not run.</summary>
|
||||
public bool AutoConnect { get; set; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task SubscribeSiteAsync(
|
||||
string correlationId, Action<AlarmStateChanged> onAlarmEvent, Action<Exception> onError,
|
||||
Action onCompleted, CancellationToken ct, Action? onConnected = null)
|
||||
@@ -139,6 +155,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
return tcs.Task; // never completes until cancelled (simulates a live stream)
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Unsubscribe(string correlationId)
|
||||
{
|
||||
lock (_lock) { _unsubscribed.Add(correlationId); }
|
||||
@@ -149,18 +166,24 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, MockSiteAlarmStreamClient> _byEndpoint = new();
|
||||
|
||||
/// <summary>Initializes the factory with a null logger factory.</summary>
|
||||
public MockSiteAlarmStreamClientFactory()
|
||||
: base(Microsoft.Extensions.Logging.Abstractions.NullLoggerFactory.Instance) { }
|
||||
|
||||
/// <summary>Applied to every client this factory hands out (set before the actor starts).</summary>
|
||||
public bool AutoConnect { get; set; } = true;
|
||||
|
||||
/// <summary>Gets the mock client for an endpoint, creating it on first use.</summary>
|
||||
/// <param name="endpoint">The gRPC endpoint the client is keyed by.</param>
|
||||
/// <returns>The mock client for that endpoint.</returns>
|
||||
public MockSiteAlarmStreamClient ClientFor(string endpoint) =>
|
||||
_byEndpoint.GetOrAdd(endpoint, _ => new MockSiteAlarmStreamClient { AutoConnect = AutoConnect });
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SiteStreamGrpcClient GetOrCreate(string siteIdentifier, string grpcEndpoint)
|
||||
=> ClientFor(grpcEndpoint);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override SiteStreamGrpcClient? TryGet(string siteIdentifier, string grpcEndpoint)
|
||||
=> _byEndpoint.TryGetValue(grpcEndpoint, out var c) ? c : null;
|
||||
}
|
||||
@@ -203,7 +226,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
rows?.FirstOrDefault(r => r.AlarmName == alarmName && r.SourceReference == sourceRef);
|
||||
|
||||
/// <summary>
|
||||
/// Row-40 house pattern for an ABSENCE assertion: proves <paramref name="checks"/> keep
|
||||
/// House pattern for an ABSENCE assertion: proves <paramref name="checks"/> keep
|
||||
/// holding throughout <paramref name="window"/> by polling, rather than a single blind
|
||||
/// Thread.Sleep(window) followed by one end-of-window sample. A bare sleep-then-assert
|
||||
/// cannot distinguish "the mechanism correctly stayed quiet" from "the mechanism just
|
||||
@@ -226,6 +249,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
|
||||
// ── Tests ───────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>Verifies that a delta arriving before the seed completes is buffered and applied exactly once.</summary>
|
||||
[Fact]
|
||||
public void Delta_Arriving_Before_Seed_Completes_Is_Buffered_And_Applied_Exactly_Once()
|
||||
{
|
||||
@@ -250,6 +274,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Single(latest, r => r.AlarmName == "Overheat"); // exactly once
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a buffered delta older than the seeded entry is dropped.</summary>
|
||||
[Fact]
|
||||
public void Buffered_Delta_Older_Than_Seed_Entry_Is_Dropped()
|
||||
{
|
||||
@@ -273,6 +298,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Equal(500, pumpFault!.Priority); // seed value kept; older buffered delta dropped
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a buffered delta newer than the seeded entry is applied.</summary>
|
||||
[Fact]
|
||||
public void Buffered_Delta_Newer_Than_Seed_Entry_Is_Applied()
|
||||
{
|
||||
@@ -287,6 +313,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that deduplication by alarm key distinguishes alarms by their source reference.</summary>
|
||||
[Fact]
|
||||
public void Dedup_By_AlarmKey_Distinguishes_SourceReference()
|
||||
{
|
||||
@@ -306,6 +333,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.NotNull(Find(latest, "LevelAlarm", "Tank01.Level.LoLo"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a live update with the same key replaces the existing row in place.</summary>
|
||||
[Fact]
|
||||
public void Same_Key_Live_Update_Replaces_In_Place()
|
||||
{
|
||||
@@ -324,6 +352,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Single(sink.Latest!); // still exactly one row for the key
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a seeded native alarm and its live delta collapse onto one key.</summary>
|
||||
[Fact]
|
||||
public void Seeded_Native_Alarm_And_Its_Live_Delta_Collapse_Onto_One_Key()
|
||||
{
|
||||
@@ -348,6 +377,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Single(sink.Latest!); // exactly one row — no seed/live ghost duplicate
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a seeded placeholder coexists with a live real alarm and is not wiped.</summary>
|
||||
[Fact]
|
||||
public void Placeholder_Seeded_Coexists_With_Live_Real_Alarm_And_Is_Not_Wiped()
|
||||
{
|
||||
@@ -372,6 +402,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.NotNull(Find(latest, "Motor1.MotorAlarms", "Motor1.MotorAlarms.HiHi"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a gRPC error flips to the other node, reconnects and re-seeds.</summary>
|
||||
[Fact]
|
||||
public void GrpcError_Flips_Node_Reconnects_And_ReSeeds()
|
||||
{
|
||||
@@ -393,6 +424,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Contains("corr-1", factory.ClientFor(GrpcNodeA).Unsubscribed);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a periodic reconcile authoritatively replaces stale rows.</summary>
|
||||
[Fact]
|
||||
public void Periodic_Reconcile_Authoritatively_Replaces_Stale_Rows()
|
||||
{
|
||||
@@ -403,7 +435,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
seed.CompleteNext(Alarm("PumpFault", "", 500, t)); // seed has a row
|
||||
AwaitCondition(() => Find(sink.Latest, "PumpFault", "") is not null, TimeSpan.FromSeconds(3));
|
||||
|
||||
// Reconcile tick fires → seed #2 (e.g. instance was disabled) returns EMPTY.
|
||||
// Reconcile tick fires → the second seed (e.g. instance was disabled) returns EMPTY.
|
||||
AwaitCondition(() => seed.CallCount == 2, TimeSpan.FromSeconds(3));
|
||||
seed.CompleteNext(); // empty
|
||||
|
||||
@@ -411,6 +443,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => sink.Latest is { } l && l.Count == 0, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed initial seed publishes nothing but a later reconcile recovers.</summary>
|
||||
[Fact]
|
||||
public void Failed_Initial_Seed_Does_Not_Publish_But_A_Later_Reconcile_Recovers()
|
||||
{
|
||||
@@ -431,6 +464,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => Find(sink.Latest, "PumpFault", "") is not null, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a stream given up after the maximum retries is reopened on the next reconcile tick.</summary>
|
||||
[Fact]
|
||||
public void Stream_GivenUp_After_MaxRetries_Reopens_On_Reconcile_Tick()
|
||||
{
|
||||
@@ -461,6 +495,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
|
||||
// ── R2 T11: queued re-seed after reconnect + stream-generation stamp (N7.1/N7.2) ──
|
||||
|
||||
/// <summary>Verifies that a re-seed requested during an in-flight fan-out is queued rather than dropped.</summary>
|
||||
[Fact]
|
||||
public void ReseedRequestedDuringInFlightFanout_IsQueued_NotDropped()
|
||||
{
|
||||
@@ -470,7 +505,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitAssert(() => Assert.Equal(1, sink.Count));
|
||||
AwaitCondition(() => factory.ClientFor(GrpcNodeA).Subs.Count == 1, TimeSpan.FromSeconds(3));
|
||||
|
||||
// The first tick after a fan-out is deliberately skipped (WP2.3: the initial seed
|
||||
// The first tick after a fan-out is deliberately skipped (the initial seed
|
||||
// already covered this window); the second one actually fans out.
|
||||
actor.Tell(new RunReconcile()); // skipped — consumes the "already fanned out" flag
|
||||
actor.Tell(new RunReconcile()); // reconcile fan-out now in flight (CallCount 2)
|
||||
@@ -486,6 +521,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitAssert(() => Assert.Equal(3, seed.CallCount)); // queued re-seed ran immediately after
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a late error from a previous stream generation is ignored.</summary>
|
||||
[Fact]
|
||||
public void LateErrorFromAPreviousStreamGeneration_IsIgnored()
|
||||
{
|
||||
@@ -501,14 +537,15 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => factory.ClientFor(GrpcNodeB).Subs.Count == 1, TimeSpan.FromSeconds(5)); // gen 2 stream open
|
||||
AwaitAssert(() => Assert.Equal(2, TotalSubs()));
|
||||
|
||||
firstSub.OnError(new Exception("late zombie fault")); // stale gen-1 error races in
|
||||
firstSub.OnError(new Exception("late zombie fault")); // stale first-generation error races in
|
||||
// Pre-fix this burns retry budget and opens a THIRD stream; post-fix it is ignored.
|
||||
AssertHoldsFor(TimeSpan.FromMilliseconds(400),
|
||||
(() => TotalSubs() == 2, "a stale gen-1 error must not open a third stream"));
|
||||
}
|
||||
|
||||
// ── WP1.1: graceful (status OK) stream completion is a reconnect trigger ──
|
||||
// ── Graceful (status OK) stream completion is a reconnect trigger ──
|
||||
|
||||
/// <summary>Verifies that a graceful stream completion reopens on the next reconcile tick on the same node.</summary>
|
||||
[Fact]
|
||||
public void GracefulStreamCompletion_ReopensOnReconcileTick_OnTheSameNode()
|
||||
{
|
||||
@@ -530,6 +567,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Contains("corr-1", factory.ClientFor(GrpcNodeA).Unsubscribed);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a graceful stream completion neither spends nor refunds the error retry budget.</summary>
|
||||
[Fact]
|
||||
public void GracefulStreamCompletion_NeitherSpendsNorRefunds_TheErrorRetryBudget()
|
||||
{
|
||||
@@ -563,6 +601,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
(() => TotalSubs() == before, "budget exhausted — the 4th error must not reconnect"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a late completion from a previous stream generation is ignored.</summary>
|
||||
[Fact]
|
||||
public void LateCompletionFromAPreviousStreamGeneration_IsIgnored()
|
||||
{
|
||||
@@ -575,8 +614,8 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
firstSub.OnError(new Exception("real fault")); // gen 1 dies → gen 2 opens on NodeB
|
||||
AwaitCondition(() => factory.ClientFor(GrpcNodeB).Subs.Count == 1, TimeSpan.FromSeconds(5));
|
||||
|
||||
// A completion racing out of the dead gen-1 stream must not tear down the live
|
||||
// gen-2 stream (which would go deltaless until the next reconcile tick).
|
||||
// A completion racing out of the dead first-generation stream must not tear down the live
|
||||
// second-generation stream (which would go deltaless until the next reconcile tick).
|
||||
firstSub.OnCompleted();
|
||||
AssertHoldsFor(TimeSpan.FromMilliseconds(300),
|
||||
(() => !factory.ClientFor(GrpcNodeB).Unsubscribed.Contains("corr-1"),
|
||||
@@ -587,6 +626,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
|
||||
// ── R2 T10: live-delta publish coalescing (N6) ──
|
||||
|
||||
/// <summary>Verifies that a burst of deltas is coalesced into a few publishes that together contain every row.</summary>
|
||||
[Fact]
|
||||
public void DeltaBurst_IsCoalesced_IntoFewPublishes_ContainingEveryRow()
|
||||
{
|
||||
@@ -608,6 +648,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
}, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a zero coalesce window preserves the legacy publish-per-delta behavior.</summary>
|
||||
[Fact]
|
||||
public void ZeroCoalesce_PreservesLegacyPerDeltaPublish()
|
||||
{
|
||||
@@ -624,6 +665,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitAssert(() => Assert.Equal(3, sink.Count)); // 1 seed + 1 per delta
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the stop message tears down the gRPC stream and stops the actor.</summary>
|
||||
[Fact]
|
||||
public void Stop_Message_TearsDown_Grpc_And_Stops_Actor()
|
||||
{
|
||||
@@ -640,12 +682,13 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
// -- WP2.3: seed once per SUCCESSFUL (re)connect, and stream-truthful liveness --
|
||||
// -- Seed once per SUCCESSFUL (re)connect, and stream-truthful liveness --
|
||||
|
||||
/// <summary>Verifies that reconnect attempts do not each re-seed; only the successful connect does.</summary>
|
||||
[Fact]
|
||||
public void ReconnectAttempts_DoNotEachReSeed_OnlyTheSuccessfulConnectDoes()
|
||||
{
|
||||
// Finding #10's per-attempt re-fan-out: every reconnect ATTEMPT used to kick a
|
||||
// The per-attempt re-fan-out: every reconnect ATTEMPT used to kick a
|
||||
// whole-site snapshot fan-out - against a site that is, by definition of the
|
||||
// reconnect, unreachable. Now the re-seed is owed to the connect that succeeds.
|
||||
var (_, seed, _, factory) = CreateActor(
|
||||
@@ -673,6 +716,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
(() => seed.CallCount == 2, "the successful connect must re-seed exactly once"));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a reconcile tick is skipped when a fan-out already ran within the window.</summary>
|
||||
[Fact]
|
||||
public void ReconcileTick_IsSkipped_WhenAFanoutAlreadyRanInTheWindow()
|
||||
{
|
||||
@@ -692,6 +736,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => seed.CallCount == 2, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an unchanged reconcile snapshot is not republished.</summary>
|
||||
[Fact]
|
||||
public void UnchangedReconcileSnapshot_DoesNotRepublish()
|
||||
{
|
||||
@@ -720,10 +765,11 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitAssert(() => Assert.Equal(2, sink.Count));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a stream completion drops liveness and the reopen restores it.</summary>
|
||||
[Fact]
|
||||
public void StreamCompletion_DropsLiveness_AndTheReopenRestoresIt()
|
||||
{
|
||||
// WP2.3 carried residual: a completed (or given-up) stream kept reporting live
|
||||
// Carried residual: a completed (or given-up) stream kept reporting live
|
||||
// until the next reconcile publish, so the page grafted a freezing snapshot over
|
||||
// fresh poll data.
|
||||
// Reconcile is held far away so the drop can be observed before the reopen
|
||||
@@ -746,6 +792,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => sink.StreamLive, TimeSpan.FromSeconds(5));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a stream fault drops liveness immediately.</summary>
|
||||
[Fact]
|
||||
public void StreamFault_DropsLiveness_Immediately()
|
||||
{
|
||||
@@ -761,6 +808,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => !sink.StreamLive, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that consecutive reconcile ticks with no reconnect each run a fan-out.</summary>
|
||||
[Fact]
|
||||
public void ConsecutiveReconcileTicks_WithNoReconnect_EachRunAFanout()
|
||||
{
|
||||
@@ -789,6 +837,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Equal(5, seed.CallCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a connect-driven re-seed still stands the next reconcile tick down.</summary>
|
||||
[Fact]
|
||||
public void ConnectDrivenReseed_StillStandsTheNextTickDown()
|
||||
{
|
||||
@@ -827,6 +876,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => seed.CallCount == 4, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cancellation error not caused by the actor drops liveness and reopens on the other node.</summary>
|
||||
[Fact]
|
||||
public void ForeignCancelledStreamError_DropsLiveness_AndReopensOnTheOtherNode()
|
||||
{
|
||||
@@ -854,10 +904,10 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => seed.CallCount == 2, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
// ── P3-4: the stream open attempt is deadline-bounded ───────────────────────
|
||||
// ── The stream open attempt is deadline-bounded ───────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Proof of the P3-4 starvation and of its fix.
|
||||
/// Proof of the starvation and of its fix.
|
||||
/// <para>
|
||||
/// An open attempt is cleared by exactly three peer-produced outcomes: connected, error,
|
||||
/// or graceful completion. A stream that produces NONE of them pins <c>_openInFlight</c>.
|
||||
@@ -914,6 +964,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
AwaitCondition(() => sink.StreamLive, TimeSpan.FromSeconds(3));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the open deadline does not fire on a stream that connects, flip nodes or burn the retry budget.</summary>
|
||||
[Fact]
|
||||
public void OpenDeadline_DoesNotFire_OnAStreamThatConnects_NorFlipNodeOrBurnBudget()
|
||||
{
|
||||
@@ -946,6 +997,7 @@ public class SiteAlarmAggregatorActorTests : TestKit
|
||||
Assert.Single(factory.ClientFor(GrpcNodeA).Subs);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a failed seed is retried with backoff before the next reconcile.</summary>
|
||||
[Fact]
|
||||
public void FailedSeed_IsRetried_WithBackoff_BeforeTheNextReconcile()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user