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:
Joseph Doherty
2026-10-07 15:05:49 -04:00
parent 0ffd4bdbea
commit 513fa1aa16
998 changed files with 17896 additions and 2395 deletions
@@ -12,7 +12,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
namespace ZB.MOM.WW.ScadaBridge.SiteCallAudit.Tests;
/// <summary>
/// Reconciliation-tick tests for <see cref="SiteCallAuditActor"/> (#22, Piece A).
/// Reconciliation-tick tests for <see cref="SiteCallAuditActor"/> (Piece A).
/// These exercise the periodic per-site self-heal pull entirely in-memory —
/// fake <see cref="IPullSiteCallsClient"/> + <see cref="ISiteEnumerator"/> + a
/// recording <see cref="ISiteCallAuditRepository"/> — so they run in
@@ -63,7 +63,10 @@ public class SiteCallAuditReconciliationTests : TestKit
private sealed class StaticEnumerator : ISiteEnumerator
{
private readonly IReadOnlyList<SiteEntry> _sites;
/// <summary>Creates an enumerator that returns the given sites.</summary>
/// <param name="sites">The sites to return.</param>
public StaticEnumerator(params SiteEntry[] sites) => _sites = sites;
/// <inheritdoc />
public Task<IReadOnlyList<SiteEntry>> EnumerateAsync(CancellationToken ct = default) =>
Task.FromResult(_sites);
}
@@ -75,22 +78,32 @@ public class SiteCallAuditReconciliationTests : TestKit
/// </summary>
private sealed class ScriptedPullClient : IPullSiteCallsClient
{
/// <summary>Gets every pull invocation recorded so far.</summary>
public List<(string SiteId, DateTime SinceUtc, string? AfterId, int BatchSize)> Calls { get; } = new();
private readonly Dictionary<string, Queue<PullSiteCallsResponse>> _scripted = new();
private readonly Dictionary<string, Exception> _throwOnSite = new();
/// <summary>Queues the responses to return for the given site.</summary>
/// <param name="siteId">The site the responses are for.</param>
/// <param name="responses">The responses to return, in order.</param>
/// <returns>This client, for chaining.</returns>
public ScriptedPullClient Script(string siteId, params PullSiteCallsResponse[] responses)
{
_scripted[siteId] = new Queue<PullSiteCallsResponse>(responses);
return this;
}
/// <summary>Makes every pull for the given site throw.</summary>
/// <param name="siteId">The site whose pulls should fail.</param>
/// <param name="ex">The exception to throw.</param>
/// <returns>This client, for chaining.</returns>
public ScriptedPullClient ThrowFor(string siteId, Exception ex)
{
_throwOnSite[siteId] = ex;
return this;
}
/// <inheritdoc />
public Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -109,7 +122,7 @@ public class SiteCallAuditReconciliationTests : TestKit
}
/// <summary>
/// Keyset-honouring in-memory pull client (Task 16). Holds one static set of
/// Keyset-honouring in-memory pull client. Holds one static set of
/// rows and answers each pull as a real site would: returns the next
/// <c>batchSize</c> rows strictly greater than the composite
/// <c>(sinceUtc, afterId)</c> cursor under the deterministic
@@ -122,14 +135,18 @@ public class SiteCallAuditReconciliationTests : TestKit
private sealed class KeysetHonoringPullClient : IPullSiteCallsClient
{
private readonly IReadOnlyList<SiteCall> _rows;
/// <summary>Gets the number of pulls made so far.</summary>
public int CallCount { get; private set; }
/// <summary>Creates a client holding the given rows in keyset order.</summary>
/// <param name="rows">The rows the simulated site holds.</param>
public KeysetHonoringPullClient(IEnumerable<SiteCall> rows) =>
_rows = rows
.OrderBy(r => r.UpdatedAtUtc)
.ThenBy(r => r.TrackedOperationId.ToString(), StringComparer.Ordinal)
.ToList();
/// <inheritdoc />
public Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -148,7 +165,7 @@ public class SiteCallAuditReconciliationTests : TestKit
}
/// <summary>
/// Legacy pull client (Task 16) that IGNORES <c>afterId</c> and always
/// Legacy pull client that IGNORES <c>afterId</c> and always
/// returns the same saturated page (<c>MoreAvailable=true</c>) — models a
/// pre-keyset site the central actor cannot drain past. The composite cursor
/// cannot advance, so the actor must latch and publish
@@ -157,10 +174,14 @@ public class SiteCallAuditReconciliationTests : TestKit
private sealed class LegacyIgnoresAfterIdPullClient : IPullSiteCallsClient
{
private readonly IReadOnlyList<SiteCall> _rows;
/// <summary>Gets the number of pulls made so far.</summary>
public int CallCount { get; private set; }
/// <summary>Creates a client that always returns the given rows.</summary>
/// <param name="rows">The rows returned on every pull.</param>
public LegacyIgnoresAfterIdPullClient(IReadOnlyList<SiteCall> rows) => _rows = rows;
/// <inheritdoc />
public Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -172,7 +193,7 @@ public class SiteCallAuditReconciliationTests : TestKit
/// <summary>
/// Pull client that ALWAYS returns the same saturated response
/// (<c>MoreAvailable=true</c>) regardless of the <c>since</c> cursor —
/// simulates the SiteCallAudit-009 single-timestamp no-progress pin: a backlog
/// simulates the single-timestamp no-progress pin: a backlog
/// larger than the batch size all sharing one exact <c>UpdatedAtUtc</c>, so
/// the inclusive max-timestamp cursor never advances. Records every call so
/// the test can assert the within-tick drain is BOUNDED (the actor must not
@@ -181,10 +202,14 @@ public class SiteCallAuditReconciliationTests : TestKit
private sealed class SaturatedPinPullClient : IPullSiteCallsClient
{
private readonly IReadOnlyList<SiteCall> _rows;
/// <summary>Gets the number of pulls made so far.</summary>
public int CallCount { get; private set; }
/// <summary>Creates a client that always returns the given rows.</summary>
/// <param name="rows">The rows returned on every pull.</param>
public SaturatedPinPullClient(IReadOnlyList<SiteCall> rows) => _rows = rows;
/// <inheritdoc />
public Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -201,9 +226,12 @@ public class SiteCallAuditReconciliationTests : TestKit
/// </summary>
private sealed class RecordingRepo : ISiteCallAuditRepository
{
/// <summary>Gets the upserted rows keyed by tracked operation id.</summary>
public Dictionary<TrackedOperationId, SiteCall> Upserted { get; } = new();
/// <summary>Gets the number of upsert calls received.</summary>
public int UpsertCallCount { get; private set; }
/// <inheritdoc />
public Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default)
{
UpsertCallCount++;
@@ -211,24 +239,30 @@ public class SiteCallAuditReconciliationTests : TestKit
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<SiteCall?> GetAsync(TrackedOperationId id, CancellationToken ct = default) =>
Task.FromResult(Upserted.TryGetValue(id, out var row) ? row : null);
/// <inheritdoc />
public Task<IReadOnlyList<SiteCall>> QueryAsync(
SiteCallQueryFilter filter, SiteCallPaging paging, CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<SiteCall>>(Array.Empty<SiteCall>());
/// <inheritdoc />
public Task<int> PurgeTerminalAsync(DateTime olderThanUtc, CancellationToken ct = default) =>
Task.FromResult(0);
/// <inheritdoc />
public Task<SiteCallKpiSnapshot> ComputeKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default) =>
Task.FromResult(new SiteCallKpiSnapshot(0, 0, 0, 0, null, 0));
/// <inheritdoc />
public Task<IReadOnlyList<SiteCallSiteKpiSnapshot>> ComputePerSiteKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<SiteCallSiteKpiSnapshot>>(Array.Empty<SiteCallSiteKpiSnapshot>());
/// <inheritdoc />
public Task<IReadOnlyList<SiteCallNodeKpiSnapshot>> ComputePerNodeKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default) =>
Task.FromResult<IReadOnlyList<SiteCallNodeKpiSnapshot>>(Array.Empty<SiteCallNodeKpiSnapshot>());
@@ -252,7 +286,7 @@ public class SiteCallAuditReconciliationTests : TestKit
}
/// <summary>
/// Row-82/row-95 house pattern: every actor built above arms a fast-repeating
/// House pattern: every actor built above arms a fast-repeating
/// reconciliation (and, via <see cref="SiteCallAuditActor"/>, purge) ticker — as fast
/// as 100ms in most of the tests below. Left running past the owning test it keeps
/// firing against test doubles the test has stopped controlling, stranding later
@@ -269,6 +303,7 @@ public class SiteCallAuditReconciliationTests : TestKit
// 1. AbsentRow_PulledFromSite_IsUpserted
// ---------------------------------------------------------------------
/// <summary>Verifies that a row absent centrally is upserted from the site pull.</summary>
[Fact]
public void ReconciliationTick_AbsentRow_IsUpsertedFromSitePull()
{
@@ -305,6 +340,7 @@ public class SiteCallAuditReconciliationTests : TestKit
// 2. Cursor_Advances_ToMaxUpdatedAtUtc_NoRePullOfOldRows
// ---------------------------------------------------------------------
/// <summary>Verifies that a second tick advances the cursor past the rows already pulled.</summary>
[Fact]
public void ReconciliationTick_SecondTick_AdvancesCursorPastAlreadyPulledRows()
{
@@ -348,6 +384,7 @@ public class SiteCallAuditReconciliationTests : TestKit
// 3. OneSiteThrows_OtherSitesStillProcessed (failure isolation)
// ---------------------------------------------------------------------
/// <summary>Verifies that other sites are still reconciled when the pull for one site throws.</summary>
[Fact]
public void ReconciliationTick_OneSiteThrows_OtherSitesStillReconciled()
{
@@ -389,6 +426,7 @@ public class SiteCallAuditReconciliationTests : TestKit
// start the reconciliation tick
// ---------------------------------------------------------------------
/// <summary>Verifies that no reconciliation tick starts when the collaborators are not registered.</summary>
[Fact]
public void NoReconciliationCollaboratorsRegistered_DoesNotStartReconciliationTick()
{
@@ -436,11 +474,12 @@ public class SiteCallAuditReconciliationTests : TestKit
}
// ---------------------------------------------------------------------
// 5. SiteCallAudit-009: MoreAvailable drives a within-tick continuation
// 5. MoreAvailable drives a within-tick continuation
// drain — a multi-page backlog whose timestamps advance is fully drained
// in ONE tick rather than one page per tick.
// ---------------------------------------------------------------------
/// <summary>Verifies that a multi-page backlog is drained within a single tick when more rows are available.</summary>
[Fact]
public void ReconciliationTick_MoreAvailable_DrainsMultiplePagesWithinOneTick()
{
@@ -455,7 +494,7 @@ public class SiteCallAuditReconciliationTests : TestKit
var sites = new StaticEnumerator(new SiteEntry(siteId, "http://siteA:8083"));
// Page 1 saturates (MoreAvailable: true) → the actor continues pulling
// within the SAME tick; page 2 is the final page (MoreAvailable: false).
// The continuation pull's `since` must be t2 (page-1 max), proving the
// The continuation pull's `since` must be t2 (page 1 max), proving the
// cursor advanced page-to-page inside one tick rather than across ticks.
var client = new ScriptedPullClient().Script(siteId,
new PullSiteCallsResponse(new[] { p1a, p1b }, MoreAvailable: true),
@@ -477,7 +516,7 @@ public class SiteCallAuditReconciliationTests : TestKit
AwaitAssert(
() =>
{
// All three rows reconciled — including the page-2 row that only a
// All three rows reconciled — including the page 2 row that only a
// within-tick continuation pull could have fetched.
Assert.True(repo.Upserted.ContainsKey(p1a.TrackedOperationId));
Assert.True(repo.Upserted.ContainsKey(p1b.TrackedOperationId));
@@ -488,7 +527,7 @@ public class SiteCallAuditReconciliationTests : TestKit
interval: TimeSpan.FromMilliseconds(50));
// Exactly two pulls happened (page 1 + the continuation page 2) and the
// second pull's `since` cursor advanced to the page-1 max (t2).
// second pull's `since` cursor advanced to the page 1 max (t2).
Assert.True(client.Calls.Count >= 2, $"expected >= 2 pulls within the tick, got {client.Calls.Count}");
Assert.Equal(DateTime.MinValue, client.Calls[0].SinceUtc);
Assert.Equal(t2, client.Calls[1].SinceUtc);
@@ -500,12 +539,13 @@ public class SiteCallAuditReconciliationTests : TestKit
}
// ---------------------------------------------------------------------
// 6. SiteCallAudit-009: single-timestamp saturation pin does NOT spin —
// 6. Single-timestamp saturation pin does NOT spin —
// a saturated batch whose max UpdatedAtUtc never advances past `since`
// breaks the within-tick drain after one page (no unbounded re-pull),
// and still upserts the rows it saw.
// ---------------------------------------------------------------------
/// <summary>Verifies that a single-timestamp saturation does not spin the drain and makes no progress gracefully.</summary>
[Fact]
public void ReconciliationTick_SingleTimestampSaturation_DoesNotSpin_MakesNoProgressGracefully()
{
@@ -551,7 +591,7 @@ public class SiteCallAuditReconciliationTests : TestKit
duration: TimeSpan.FromSeconds(3),
interval: TimeSpan.FromMilliseconds(50));
// Critical SiteCallAudit-009 invariant: the within-tick drain BROKE on the
// Critical invariant: the within-tick drain BROKE on the
// no-progress pin rather than looping to the 50-page ceiling. With a 2s
// tick interval, only the first tick has fired in the window, so the pull
// count reflects ONE tick's within-loop behaviour. A correct break yields
@@ -568,12 +608,13 @@ public class SiteCallAuditReconciliationTests : TestKit
}
// ---------------------------------------------------------------------
// 7. Task 16: a keyset-honouring site drains a single-timestamp burst
// 7. A keyset-honouring site drains a single-timestamp burst
// LARGER than the batch size in ONE tick — the composite (UpdatedAtUtc,
// TrackedOperationId) cursor advances by id even when every row shares
// one exact timestamp, so the tail no longer pins forever.
// ---------------------------------------------------------------------
/// <summary>Verifies that the keyset cursor drains a single-timestamp burst larger than the batch size.</summary>
[Fact]
public void ReconciliationTick_KeysetCursor_DrainsSingleTimestampBurstBeyondBatchSize()
{
@@ -625,11 +666,12 @@ public class SiteCallAuditReconciliationTests : TestKit
}
// ---------------------------------------------------------------------
// 8. Task 16: a LEGACY site that ignores after_id can never be drained, so
// 8. A LEGACY site that ignores after_id can never be drained, so
// the actor latches and publishes SiteCallReconciliationPinnedChanged
// instead of silently logging (the dead-end is now health-observable).
// ---------------------------------------------------------------------
/// <summary>Verifies that a legacy site ignoring the after-id cursor causes a pinned-changed event to be published.</summary>
[Fact]
public void ReconciliationTick_LegacySiteIgnoresAfterId_PublishesPinnedChanged()
{
@@ -665,7 +707,7 @@ public class SiteCallAuditReconciliationTests : TestKit
}
// ---------------------------------------------------------------------
// 9. WP2.2: the reconciliation drain runs OFF the mailbox, so ingest,
// 9. The reconciliation drain runs OFF the mailbox, so ingest,
// query and KPI messages are answered while a long post-outage
// catch-up is still pulling.
// ---------------------------------------------------------------------
@@ -684,8 +726,10 @@ public class SiteCallAuditReconciliationTests : TestKit
/// <summary>Completes once the drain has actually started pulling.</summary>
public Task Entered => _entered.Task;
/// <summary>Releases the blocked pull.</summary>
public void Release() => _release.TrySetResult();
/// <inheritdoc />
public async Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -695,6 +739,8 @@ public class SiteCallAuditReconciliationTests : TestKit
}
}
/// <summary>Verifies that an in-flight reconciliation drain does not block an ingest upsert.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ReconciliationDrain_InFlight_DoesNotBlockIngestUpsert()
{
@@ -732,12 +778,14 @@ public class SiteCallAuditReconciliationTests : TestKit
{
// Unblock the drain FIRST so the actor is free to process the Stop rather
// than parked inside the blocked pull, then stop it deterministically
// rather than leaving its 100ms ticker running (row-95).
// rather than leaving its 100ms ticker running.
client.Release();
StopTicker(actor);
}
}
/// <summary>Verifies that reconciliation ticks do not overlap while a drain is in flight.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ReconciliationTicks_DoNotOverlap_WhileADrainIsInFlight()
{
@@ -764,8 +812,7 @@ public class SiteCallAuditReconciliationTests : TestKit
finally
{
// Unblock the drain FIRST so the actor is free to process the Stop, then
// stop it deterministically rather than leaving its 100ms ticker running
// (row-95).
// stop it deterministically rather than leaving its 100ms ticker running.
client.Release();
StopTicker(actor);
}
@@ -780,6 +827,7 @@ public class SiteCallAuditReconciliationTests : TestKit
/// upsert arriving on the mailbox. (Production is unaffected: each message and
/// each pass resolves its own scope, hence its own context.)
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task InjectedRepository_IsNeverCalledConcurrently_ByDrainAndMailbox()
{
@@ -835,10 +883,14 @@ public class SiteCallAuditReconciliationTests : TestKit
new(TaskCreationOptions.RunContinuationsAsynchronously);
private int _callCount;
/// <summary>Creates a client that returns the given rows on its first pull only.</summary>
/// <param name="rows">The rows returned by the first pull.</param>
public OneBatchThenEmptyPullClient(SiteCall[] rows) => _rows = rows;
/// <summary>Gets a task that completes once the first pull has started.</summary>
public Task Entered => _entered.Task;
/// <inheritdoc />
public Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{
@@ -861,24 +913,31 @@ public class SiteCallAuditReconciliationTests : TestKit
private int _maxObserved;
private int _upsertCount;
/// <summary>Creates a repository that holds each call open for the given time.</summary>
/// <param name="hold">How long each repository call stays in flight.</param>
public ConcurrencyDetectingRepo(TimeSpan hold) => _hold = hold;
/// <summary>Gets the peak number of overlapping repository calls.</summary>
public int MaxObservedConcurrency => Volatile.Read(ref _maxObserved);
/// <summary>Gets the number of upserts received.</summary>
public int UpsertCount => Volatile.Read(ref _upsertCount);
/// <inheritdoc />
public async Task UpsertAsync(SiteCall siteCall, CancellationToken ct = default)
{
Interlocked.Increment(ref _upsertCount);
await TrackAsync().ConfigureAwait(false);
}
/// <inheritdoc />
public async Task<SiteCall?> GetAsync(TrackedOperationId id, CancellationToken ct = default)
{
await TrackAsync().ConfigureAwait(false);
return null;
}
/// <inheritdoc />
public async Task<IReadOnlyList<SiteCall>> QueryAsync(
SiteCallQueryFilter filter, SiteCallPaging paging, CancellationToken ct = default)
{
@@ -886,12 +945,14 @@ public class SiteCallAuditReconciliationTests : TestKit
return Array.Empty<SiteCall>();
}
/// <inheritdoc />
public async Task<int> PurgeTerminalAsync(DateTime olderThanUtc, CancellationToken ct = default)
{
await TrackAsync().ConfigureAwait(false);
return 0;
}
/// <inheritdoc />
public async Task<SiteCallKpiSnapshot> ComputeKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default)
{
@@ -899,6 +960,7 @@ public class SiteCallAuditReconciliationTests : TestKit
return new SiteCallKpiSnapshot(0, 0, 0, 0, null, 0);
}
/// <inheritdoc />
public async Task<IReadOnlyList<SiteCallSiteKpiSnapshot>> ComputePerSiteKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default)
{
@@ -906,6 +968,7 @@ public class SiteCallAuditReconciliationTests : TestKit
return Array.Empty<SiteCallSiteKpiSnapshot>();
}
/// <inheritdoc />
public async Task<IReadOnlyList<SiteCallNodeKpiSnapshot>> ComputePerNodeKpisAsync(
DateTime stuckCutoff, DateTime intervalSince, CancellationToken ct = default)
{
@@ -948,12 +1011,16 @@ public class SiteCallAuditReconciliationTests : TestKit
new(TaskCreationOptions.RunContinuationsAsynchronously);
private int _callCount;
/// <summary>Gets a task that completes once the first pull has started.</summary>
public Task Entered => _entered.Task;
/// <summary>Gets the number of pulls started so far.</summary>
public int CallCount => Volatile.Read(ref _callCount);
/// <summary>Releases the blocked pull.</summary>
public void Release() => _release.TrySetResult();
/// <inheritdoc />
public async Task<PullSiteCallsResponse> PullAsync(
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
{