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:
@@ -13,7 +13,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
private readonly SiteEventLogOptions _options;
|
||||
|
||||
/// <summary>
|
||||
/// SiteEventLogging-023: stop flag for the concurrent stress test. Declared as
|
||||
/// Stop flag for the concurrent stress test. Declared as
|
||||
/// a <c>volatile</c> field so every writer thread observes the main thread's
|
||||
/// `_stop = true` write without depending on JIT/runtime quirks. A plain
|
||||
/// <c>bool</c> local would be legal-cached in a register inside the tight
|
||||
@@ -21,6 +21,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
/// </summary>
|
||||
private volatile bool _stop;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="EventLogPurgeServiceTests"/> class with a temporary LocalDb file.</summary>
|
||||
public EventLogPurgeServiceTests()
|
||||
{
|
||||
_dbPath = Path.Combine(Path.GetTempPath(), $"test_purge_{Guid.NewGuid()}.db");
|
||||
@@ -37,6 +38,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
_localDb.Db);
|
||||
}
|
||||
|
||||
/// <summary>Releases the resources held by this test class.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_eventLogger.Dispose();
|
||||
@@ -85,6 +87,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge deletes events older than the retention window.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_DeletesOldEvents()
|
||||
{
|
||||
@@ -98,6 +101,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge keeps recent events.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_KeepsRecentEvents()
|
||||
{
|
||||
@@ -111,10 +115,11 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(3, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge deletes every expired row across multiple batch slices when the backlog exceeds one batch.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_OverBatchSize_DeletesAllExpiredRowsAcrossSlices()
|
||||
{
|
||||
// WP1.7: the retention purge must slice a large expired backlog into
|
||||
// The retention purge must slice a large expired backlog into
|
||||
// bounded DELETE batches (1000 rows/iteration, mirroring the storage-cap
|
||||
// purge a few lines below in the same file) rather than one unbounded
|
||||
// DELETE. Seed more than one slice's worth of expired rows plus a
|
||||
@@ -145,6 +150,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(2, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge deletes the oldest events when the database is over the cap.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_DeletesOldestWhenOverCap()
|
||||
{
|
||||
@@ -169,6 +175,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(0, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the database size query returns a positive value.</summary>
|
||||
[Fact]
|
||||
public void GetDatabaseSizeBytes_ReturnsPositiveValue()
|
||||
{
|
||||
@@ -241,11 +248,11 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge stops once under the cap and does not empty the table.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_StopsWhenUnderCap_DoesNotEmptyTable()
|
||||
{
|
||||
// Regression test for SiteEventLogging-001 / -002:
|
||||
// a realistic non-zero cap must trim the oldest events to the budget,
|
||||
// Regression test: a realistic non-zero cap must trim the oldest events to the budget,
|
||||
// not delete the entire table.
|
||||
InsertBulkEvents(3000);
|
||||
|
||||
@@ -275,10 +282,11 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
$"Database size {finalSize} must be at or below cap {finalCapBytes} after purge.");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge removes the oldest events first.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_RemovesOldestEventsFirst()
|
||||
{
|
||||
// Regression test for SiteEventLogging-002: only the oldest events should be
|
||||
// Regression test: only the oldest events should be
|
||||
// removed when trimming to the cap.
|
||||
//
|
||||
// "Oldest" used to mean "lowest autoincrement id". Since LocalDb Phase 1 the id
|
||||
@@ -320,10 +328,12 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1L, newestPresent);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that starting the service does not block on the initial purge.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StartAsync_DoesNotBlock_OnTheInitialPurge()
|
||||
{
|
||||
// SiteEventLogging-014 (re-triaged): on .NET 8+ BackgroundService runs
|
||||
// On .NET 8+ BackgroundService runs
|
||||
// ExecuteAsync on a thread-pool thread — the synchronous prelude (the
|
||||
// initial RunPurge()) does NOT execute on the host startup thread, so
|
||||
// StartAsync returns promptly and host startup / the /health/ready gate is
|
||||
@@ -356,10 +366,12 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
await purge.StopAsync(CancellationToken.None);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ExecuteAsync yields before running the initial purge synchronously.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ExecuteAsync_YieldsBeforeRunningTheInitialPurgeSynchronously()
|
||||
{
|
||||
// arch-review 2026-08-19 P4-5 — timing-independent companion to
|
||||
// Timing-independent companion to
|
||||
// StartAsync_DoesNotBlock_OnTheInitialPurge above.
|
||||
//
|
||||
// That test times BackgroundService.StartAsync as a whole and passes even on the
|
||||
@@ -368,7 +380,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
// never blocks regardless of whether ExecuteAsync yields first. Verified empirically
|
||||
// against this repo's actual net10.0 target (a BackgroundService whose ExecuteAsync
|
||||
// does Thread.Sleep(1500) before its first await still returns from StartAsync in
|
||||
// ~2 ms). So P4-5's literal "StartAsync blocks" symptom does not reproduce here, and
|
||||
// ~2 ms). So the literal "StartAsync blocks" symptom does not reproduce here, and
|
||||
// a wall-clock test cannot prove the fix.
|
||||
//
|
||||
// What CAN be proven without any timing is the ExecuteAsync-level contract itself:
|
||||
@@ -432,12 +444,13 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing active-node check is logged as inactive rather than active.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_ActiveNodeCheckThrows_TickLogsAsInactive_NotActive()
|
||||
{
|
||||
// arch-review 2026-08-19 P4-5: a throwing isActive predicate must fail CLOSED
|
||||
// A throwing isActive predicate must fail CLOSED
|
||||
// (logged as inactive) rather than silently claiming "active". The predicate no
|
||||
// longer gates any purge work (P4-4 — every node purges its own file every tick
|
||||
// longer gates any purge work (every node purges its own file every tick
|
||||
// regardless), so a wrong value here cannot skip work; but an operator correlating
|
||||
// this line against the real active-node state must not be misled into thinking the
|
||||
// check actually succeeded and reported active.
|
||||
@@ -459,10 +472,12 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Contains("active=False", tickLine.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that concurrent writes during a storage-cap purge do not corrupt the connection.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PurgeByStorageCap_ConcurrentWritesDoNotCorruptConnection()
|
||||
{
|
||||
// Regression test for SiteEventLogging-003: purge running on a background
|
||||
// Regression test: purge running on a background
|
||||
// thread while events are recorded on other threads must not throw
|
||||
// "DataReader already open" / "connection busy" from a shared connection.
|
||||
InsertBulkEvents(2000);
|
||||
@@ -477,7 +492,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
};
|
||||
|
||||
var exceptions = new System.Collections.Concurrent.ConcurrentBag<Exception>();
|
||||
// SiteEventLogging-023: must be volatile so the writer threads observe the
|
||||
// Must be volatile so the writer threads observe the
|
||||
// main thread's `stop = true` flip after the purge task completes. Without
|
||||
// it, a release-mode JIT is permitted to cache the `stop = false` read in
|
||||
// a register inside the tight `while (!stop)` loop and never see the flip,
|
||||
@@ -514,15 +529,16 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Empty(exceptions);
|
||||
}
|
||||
|
||||
// ── arch-review 2026-08-19 P4-4: purge runs on every node, not just the active one ──
|
||||
// (supersedes the old SiteEventLogging-019 "active-node-only gate" — DataConnectionActor
|
||||
// ── Purge runs on every node, not just the active one ──
|
||||
// (supersedes the old "active-node-only gate" — DataConnectionActor
|
||||
// writes site_events on both site nodes, so a standby that skipped its own purge would
|
||||
// grow its file unbounded on an unreplicated site.)
|
||||
|
||||
/// <summary>Verifies that a standby node still runs the purge.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_OnStandbyNode_StillRunsThePurge()
|
||||
{
|
||||
// P4-4 regression: before the fix, a standby (isActiveNode returning false) early-exited
|
||||
// Regression: before the fix, a standby (isActiveNode returning false) early-exited
|
||||
// the purge tick entirely, leaving this well-past-retention row in place forever on an
|
||||
// unreplicated standby. The gate is gone — the standby purges its own file exactly like
|
||||
// the active node.
|
||||
@@ -535,6 +551,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(0, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an active node runs the retention purge.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_OnActiveNode_RunsTheRetentionPurge()
|
||||
{
|
||||
@@ -549,11 +566,12 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the purge still runs when no active-node check is supplied.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_WithNullCheck_StillRuns()
|
||||
{
|
||||
// When no active-node check is supplied (the default for non-clustered hosts and
|
||||
// pre-existing tests), the service runs the purge — unaffected by P4-4 since the
|
||||
// pre-existing tests), the service runs the purge — unaffected by the every-node purge since the
|
||||
// check was never gating anything for this caller either.
|
||||
InsertEventWithTimestamp(DateTimeOffset.UtcNow.AddDays(-31));
|
||||
|
||||
@@ -573,6 +591,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
/// </summary>
|
||||
private const string BacklogNoteFragment = "transient LocalDb oplog backlog";
|
||||
|
||||
/// <summary>Verifies that the retention purge on a replicated node logs the oplog backlog note.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_OnReplicatedNode_LogsTheOplogBacklogNote()
|
||||
{
|
||||
@@ -592,6 +611,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(LogLevel.Information, note.Level);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge on an unreplicated node does not log the backlog note.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_OnUnreplicatedNode_DoesNotLogTheNote()
|
||||
{
|
||||
@@ -608,6 +628,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.DoesNotContain(logger.Entries, e => e.Message.Contains(BacklogNoteFragment, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge does not log the backlog note when no rows were deleted.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_WithNoRowsDeleted_DoesNotLogTheNote()
|
||||
{
|
||||
@@ -624,6 +645,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.DoesNotContain(logger.Entries, e => e.Message.Contains(BacklogNoteFragment, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge on a replicated node logs the oplog backlog note.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_OnReplicatedNode_LogsTheOplogBacklogNote()
|
||||
{
|
||||
@@ -650,6 +672,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(LogLevel.Information, note.Level);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the backlog note is logged once per tick when both purge phases delete rows.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_WhenBothPhasesDeleteRows_LogsTheBacklogNoteOnceForTheWholeTick()
|
||||
{
|
||||
@@ -691,6 +714,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Contains($"Purged {expiredRows + recentRows} site_events rows", note.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the purge still runs and swallows the error when the replication check throws.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_WhenReplicationCheckThrows_StillPurgesAndSwallows()
|
||||
{
|
||||
@@ -757,6 +781,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
PurgeCatchUpInterval = TimeSpan.FromSeconds(1),
|
||||
};
|
||||
|
||||
/// <summary>Verifies that the retention purge stops at the per-tick row ceiling.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_StopsAtThePerTickCeiling()
|
||||
{
|
||||
@@ -773,6 +798,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1500, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the retention purge with a ceiling converges across successive ticks.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_WithCeiling_ConvergesAcrossSuccessiveTicks()
|
||||
{
|
||||
@@ -792,6 +818,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an expired set that lands exactly on the ceiling is not counted as a ceiling hit.</summary>
|
||||
[Fact]
|
||||
public void PurgeByRetention_ExpiredSetLandingExactlyOnTheCeiling_IsNotACeilingHit()
|
||||
{
|
||||
@@ -806,10 +833,11 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(0, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a zero MaxPurgedRowsPerTick restores unbounded purges.</summary>
|
||||
[Fact]
|
||||
public void MaxPurgedRowsPerTick_Zero_RestoresUnboundedPurges()
|
||||
{
|
||||
// The documented escape hatch: 0 means unlimited, i.e. byte-for-byte the pre-row-34
|
||||
// The documented escape hatch: 0 means unlimited, i.e. byte-for-byte the pre-ceiling
|
||||
// behaviour, for an operator who would rather take one long burst than a paced one.
|
||||
InsertExpiredEvents(2500);
|
||||
|
||||
@@ -819,6 +847,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(0, GetEventCount());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that hitting the ceiling logs the ceiling line and increments the counter.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_HittingTheCeiling_LogsTheCeilingLineAndCountsIt()
|
||||
{
|
||||
@@ -838,6 +867,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(1, probe.Total);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that repeated ceiling hits rate-limit the log line but not the counter.</summary>
|
||||
[Fact]
|
||||
public void RunPurge_RepeatedCeilingHits_RateLimitTheLogLineButNotTheCounter()
|
||||
{
|
||||
@@ -855,6 +885,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.Equal(2, probe.Total);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge shares the per-tick budget with the retention purge.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_SharesTheTickBudgetWithRetention()
|
||||
{
|
||||
@@ -875,6 +906,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
Assert.DoesNotContain(logger.Entries, e => e.Message.Contains("exceeds cap", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the storage-cap purge with a ceiling stops at the budget and asks for a catch-up tick.</summary>
|
||||
[Fact]
|
||||
public void PurgeByStorageCap_WithCeiling_StopsAtTheBudgetAndAsksForCatchUp()
|
||||
{
|
||||
@@ -904,6 +936,8 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
private readonly System.Diagnostics.Metrics.MeterListener _listener = new();
|
||||
private long _total;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="CounterProbe"/> class listening to the named instrument.</summary>
|
||||
/// <param name="instrumentName">The name of the counter instrument to listen to.</param>
|
||||
public CounterProbe(string instrumentName)
|
||||
{
|
||||
_listener.InstrumentPublished = (instrument, listener) =>
|
||||
@@ -919,6 +953,7 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
/// <summary>Measurements recorded since construction.</summary>
|
||||
public long Total => Interlocked.Read(ref _total);
|
||||
|
||||
/// <summary>Releases the resources held by this test class.</summary>
|
||||
public void Dispose() => _listener.Dispose();
|
||||
}
|
||||
|
||||
@@ -929,12 +964,27 @@ public class EventLogPurgeServiceTests : IDisposable
|
||||
/// </summary>
|
||||
private sealed class CapturingLogger : ILogger<EventLogPurgeService>
|
||||
{
|
||||
/// <summary>Gets the captured log entries in the order they were written.</summary>
|
||||
public List<(LogLevel Level, string Message)> Entries { get; } = [];
|
||||
|
||||
/// <summary>Starts no scope; the fake does not track scopes.</summary>
|
||||
/// <typeparam name="TState">The type of the scope state.</typeparam>
|
||||
/// <param name="state">The scope state, ignored.</param>
|
||||
/// <returns>No scope token; the fake does not track scopes.</returns>
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
/// <summary>Reports every log level as enabled.</summary>
|
||||
/// <param name="logLevel">The log level being queried.</param>
|
||||
/// <returns>Always <see langword="true"/>.</returns>
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
/// <summary>Records the log entry for later assertions.</summary>
|
||||
/// <typeparam name="TState">The type of the log state.</typeparam>
|
||||
/// <param name="logLevel">The severity of the entry.</param>
|
||||
/// <param name="eventId">The event id of the entry.</param>
|
||||
/// <param name="state">The state object being logged.</param>
|
||||
/// <param name="exception">The exception associated with the entry, if any.</param>
|
||||
/// <param name="formatter">Formats the state and exception into the message text.</param>
|
||||
public void Log<TState>(
|
||||
LogLevel logLevel,
|
||||
EventId eventId,
|
||||
|
||||
Reference in New Issue
Block a user