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
@@ -11,7 +11,7 @@ using ZB.MOM.WW.ScadaBridge.TestSupport;
namespace ZB.MOM.WW.ScadaBridge.ExternalSystemGateway.Tests;
/// <summary>
/// WP-9: Tests for Database access — connection resolution, cached writes.
/// Tests for Database access — connection resolution, cached writes.
/// </summary>
public class DatabaseGatewayTests : IDisposable
{
@@ -55,7 +55,7 @@ public class DatabaseGatewayTests : IDisposable
/// <summary>
/// Configures the repository substitute for the name-keyed connection-resolution
/// path used by <c>DatabaseGateway</c> (ExternalSystemGateway-011). A <c>null</c>
/// path used by <c>DatabaseGateway</c>. A <c>null</c>
/// connection models a "not found" — the substitute returns <c>null</c> by default,
/// so no stub is needed for the absent entity.
/// </summary>
@@ -68,6 +68,8 @@ public class DatabaseGatewayTests : IDisposable
}
}
/// <summary>Verifies that GetConnection throws when the connection is not found.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GetConnection_NotFound_Throws()
{
@@ -81,6 +83,8 @@ public class DatabaseGatewayTests : IDisposable
() => gateway.GetConnectionAsync("nonexistent"));
}
/// <summary>Verifies that CachedWrite throws when no store-and-forward service is configured.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_NoStoreAndForward_Throws()
{
@@ -96,6 +100,8 @@ public class DatabaseGatewayTests : IDisposable
() => gateway.CachedWriteAsync("testDb", "INSERT INTO t VALUES (1)"));
}
/// <summary>Verifies that CachedWrite throws when the connection is not found.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_ConnectionNotFound_Throws()
{
@@ -109,8 +115,10 @@ public class DatabaseGatewayTests : IDisposable
() => gateway.CachedWriteAsync("nonexistent", "INSERT INTO t VALUES (1)"));
}
// ── ExternalSystemGateway-014: CachedWrite happy-path buffering ──
// ── CachedWrite happy-path buffering ──
/// <summary>Verifies that CachedWrite buffers the write using the connection's retry settings.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_BuffersTheWriteWithConnectionRetrySettings()
{
@@ -135,7 +143,7 @@ public class DatabaseGatewayTests : IDisposable
var sf = new ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService(
storage, sfOptions, NullLogger<ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService>.Instance);
// M2.3 (#7): CachedWriteAsync now attempts the write immediately and
// CachedWriteAsync now attempts the write immediately and
// only buffers on a TRANSIENT failure. The stub forces a transient
// outcome so this test exercises the buffering path deterministically
// without a real SQL Server.
@@ -144,7 +152,7 @@ public class DatabaseGatewayTests : IDisposable
sf,
onExecute: () => throw new TransientDatabaseException("deadlock", errorNumber: 1205));
// Audit Log #23 (ExecutionId Task 4): a known execution id / source
// ExecutionId provenance: a known execution id / source
// script so the gateway -> EnqueueAsync hop can be asserted below.
var executionId = Guid.NewGuid();
const string sourceScript = "ScriptActor:WriteAudit";
@@ -160,17 +168,19 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(5, buffered.MaxRetries);
Assert.Equal((long)TimeSpan.FromSeconds(12).TotalMilliseconds, buffered.RetryIntervalMs);
// ExecutionId Task 4: the gateway must forward executionId / sourceScript
// ExecutionId provenance: the gateway must forward executionId / sourceScript
// into EnqueueAsync, and the S&F layer must persist them on the
// sf_messages row so the retry loop can stamp the right provenance.
Assert.Equal(executionId, buffered.ExecutionId);
Assert.Equal(sourceScript, buffered.SourceScript);
}
/// <summary>Verifies that a zero MaxRetries is treated as unset rather than as retry-forever.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_ZeroMaxRetriesIsTreatedAsUnsetNotRetryForever()
{
// ExternalSystemGateway-015: a stored MaxRetries of 0 is interpreted by the
// A stored MaxRetries of 0 is interpreted by the
// Store-and-Forward retry sweep as "no limit" (retry forever). The entity's
// non-nullable int default is also 0, so the gateway must treat the
// connection's MaxRetries == 0 as "unset" and pass null — the bounded S&F
@@ -196,7 +206,7 @@ public class DatabaseGatewayTests : IDisposable
var sf = new ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService(
storage, sfOptions, NullLogger<ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService>.Instance);
// M2.3 (#7): force a transient outcome so the write reaches S&F.
// Force a transient outcome so the write reaches S&F.
var gateway = new ExecuteStubGateway(
_repository,
sf,
@@ -210,7 +220,7 @@ public class DatabaseGatewayTests : IDisposable
Assert.NotEqual(0, maxRetries);
}
// ── M2.3 (#7): transient-vs-permanent SQL classification on the immediate
// ── Transient-vs-permanent SQL classification on the immediate
// cached-write attempt + the buffered retry path ──
/// <summary>
@@ -235,6 +245,8 @@ public class DatabaseGatewayTests : IDisposable
return (sf, localDb.Db);
}
/// <summary>Verifies that a permanent SQL error returns a failure synchronously and is not buffered.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_PermanentSqlError_ReturnsFailedSynchronously_NotBuffered()
{
@@ -263,6 +275,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(0, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that a transient SQL error is buffered to store-and-forward.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_TransientSqlError_BuffersToStoreAndForward()
{
@@ -294,6 +308,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(1, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that an immediate success is not buffered and returns Delivered.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_ImmediateSuccess_NotBuffered_ReturnsDelivered()
{
@@ -316,6 +332,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(0, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that a transient SQL error during buffered delivery is rethrown so the engine retries.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DeliverBuffered_TransientSqlError_RethrowsSoEngineRetries()
{
@@ -343,6 +361,8 @@ public class DatabaseGatewayTests : IDisposable
() => gateway.DeliverBufferedAsync(message));
}
/// <summary>Verifies that a permanent SQL error during buffered delivery returns false so the message parks.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DeliverBuffered_PermanentSqlError_ReturnsFalseSoMessageParks()
{
@@ -372,7 +392,7 @@ public class DatabaseGatewayTests : IDisposable
Assert.False(delivered); // permanent — the S&F engine parks the message
}
// ── M2.3 (#7) code-review fix: ExecuteWriteAsync must classify NON-SqlException
// ── ExecuteWriteAsync must classify NON-SqlException
// DB outages as transient (buffer+retry) and propagate cancellation —
// mirroring the HTTP path's ordered catches in InvokeHttpAsync. The pre-fix
// code only caught SqlException, so a live outage surfacing as
@@ -381,6 +401,8 @@ public class DatabaseGatewayTests : IDisposable
// buffering. These tests drive the RAW execution seam (RunSqlAsync) so the
// PRODUCTION classification in ExecuteWriteAsync runs end-to-end. ──
/// <summary>Provides the non-SQL outage exceptions that must be classified as transient.</summary>
/// <returns>The outage exceptions, one per test case.</returns>
public static IEnumerable<object[]> TransientNonSqlOutages()
{
// A live DB outage that surfaces as a non-SqlException: connection-state,
@@ -391,6 +413,9 @@ public class DatabaseGatewayTests : IDisposable
yield return new object[] { new TimeoutException("The operation has timed out.") };
}
/// <summary>Verifies that a non-SQL outage is classified as transient and buffered rather than crashing.</summary>
/// <param name="outage">The outage exception raised by the write.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[MemberData(nameof(TransientNonSqlOutages))]
public async Task CachedWrite_NonSqlOutage_ClassifiedTransient_BuffersNotCrash(Exception outage)
@@ -422,6 +447,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(1, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that a requested cancellation propagates OperationCanceledException and is not reclassified.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_CancellationRequested_PropagatesOperationCanceled_NotReclassified()
{
@@ -447,6 +474,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(0, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that an unexpected exception propagates and is not classified as transient.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_UnexpectedException_Propagates_NotClassifiedTransient()
{
@@ -468,6 +497,8 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(0, ReadBufferDepth(sfDb));
}
/// <summary>Verifies that a non-SQL outage during buffered delivery is rethrown as transient so the engine retries.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DeliverBuffered_NonSqlOutage_RethrowsAsTransient_SoEngineRetries()
{
@@ -495,13 +526,15 @@ public class DatabaseGatewayTests : IDisposable
() => gateway.DeliverBufferedAsync(message));
}
// ── ExternalSystemGateway-025: a caller-token cancel that surfaces from the SQL
// ── A caller-token cancel that surfaces from the SQL
// driver as a SqlException (mid-flight cancel) must propagate as
// OperationCanceledException — never reclassified as a permanent DB error.
// The fix re-checks the caller's token at the TOP of `catch (SqlException)`
// via cancellationToken.ThrowIfCancellationRequested(), so the cancel wins
// regardless of the driver's exception shape (version-independent). ──
/// <summary>Verifies that a cancellation surfacing as a SqlException propagates as canceled and is not reclassified as permanent.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CachedWrite_CancellationSurfacingAsSqlException_PropagatesCanceled_NotReclassifiedPermanent()
{
@@ -584,6 +617,10 @@ public class DatabaseGatewayTests : IDisposable
{
private readonly Action _onExecute;
/// <summary>Initializes the stub gateway.</summary>
/// <param name="repository">The external system repository.</param>
/// <param name="storeAndForward">The store-and-forward service, or null.</param>
/// <param name="onExecute">The callback invoked when a write executes.</param>
public ExecuteStubGateway(
IExternalSystemRepository repository,
ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService? storeAndForward,
@@ -591,6 +628,7 @@ public class DatabaseGatewayTests : IDisposable
: base(repository, NullLogger<DatabaseGateway>.Instance, storeAndForward)
=> _onExecute = onExecute;
/// <inheritdoc />
internal override Task ExecuteWriteAsync(
string connectionName,
string connectionString,
@@ -617,6 +655,10 @@ public class DatabaseGatewayTests : IDisposable
{
private readonly Action _onRunSql;
/// <summary>Initializes the stub gateway.</summary>
/// <param name="repository">The external system repository.</param>
/// <param name="storeAndForward">The store-and-forward service, or null.</param>
/// <param name="onRunSql">The callback invoked when SQL is run.</param>
public RawExecuteStubGateway(
IExternalSystemRepository repository,
ZB.MOM.WW.ScadaBridge.StoreAndForward.StoreAndForwardService? storeAndForward,
@@ -624,6 +666,7 @@ public class DatabaseGatewayTests : IDisposable
: base(repository, NullLogger<DatabaseGateway>.Instance, storeAndForward)
=> _onRunSql = onRunSql;
/// <inheritdoc />
internal override Task RunSqlAsync(
string connectionString,
string sql,
@@ -654,8 +697,10 @@ public class DatabaseGatewayTests : IDisposable
return result;
}
// ── ExternalSystemGateway-001: buffered CachedDbWrite delivery handler ──
// ── Buffered CachedDbWrite delivery handler ──
/// <summary>Verifies that buffered delivery returns false so the message parks when the connection no longer exists.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DeliverBuffered_ConnectionNoLongerExists_ReturnsFalseSoMessageParks()
{
@@ -676,14 +721,16 @@ public class DatabaseGatewayTests : IDisposable
Assert.False(delivered); // permanent — the S&F engine parks the message
}
// ── ExternalSystemGateway-018: malformed JSON payload must park, not retry-forever ──
// ── Malformed JSON payload must park, not retry-forever ──
/// <summary>Verifies that a malformed JSON payload returns false so the message parks.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DeliverBuffered_MalformedJsonPayload_ReturnsFalseSoMessageParks()
{
// No connection stub needed — deserialization fails before any
// resolution or SQL execution. If the JsonException were to escape (the
// pre-018 behaviour) the S&F engine would treat it as transient and
// previous behaviour) the S&F engine would treat it as transient and
// retry the same poison row forever.
var gateway = new DatabaseGateway(_repository, NullLogger<DatabaseGateway>.Instance);
@@ -701,8 +748,9 @@ public class DatabaseGatewayTests : IDisposable
Assert.False(delivered); // permanent — the S&F engine parks the message
}
// ── ExternalSystemGateway-020: decimal SQL parameter precision survives JsonElement round-trip ──
// ── Decimal SQL parameter precision survives JsonElement round-trip ──
/// <summary>Verifies that a decimal-shaped JSON number preserves its precision by being converted to a decimal.</summary>
[Fact]
public void JsonElementToParameterValue_DecimalShapedNumber_PreservesPrecisionViaDecimal()
{
@@ -710,7 +758,7 @@ public class DatabaseGatewayTests : IDisposable
// (System.Text.Json has no decimal type tag), then on the cached-write
// retry path the buffered payload is re-deserialised into a
// JsonElement and the gateway must materialise a CLR value for the
// parameter. Pre-020 it called GetDouble() for any non-Int64 number,
// parameter. Previously it called GetDouble() for any non-Int64 number,
// which silently downcast every decimal to a binary float and lost
// precision (1234567890.1234567890 -> 1234567890.1234567 as double).
// The 020 fix prefers decimal — round-tripping must preserve every
@@ -724,13 +772,14 @@ public class DatabaseGatewayTests : IDisposable
var materialised = DatabaseGateway.JsonElementToParameterValue(element);
// The materialised value must be the original decimal, not a double.
// Asserting on the type alone is enough to fail pre-020 (which
// Asserting on the type alone is enough to fail the previous implementation (which
// produced a System.Double); the value assertion locks in the
// precision invariant.
var asDecimal = Assert.IsType<decimal>(materialised);
Assert.Equal(1234567890.1234567890m, asDecimal);
}
/// <summary>Verifies that a whole JSON number takes the fast path and returns a long.</summary>
[Fact]
public void JsonElementToParameterValue_WholeNumber_FastPathReturnsLong()
{
@@ -745,6 +794,7 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(42L, materialised);
}
/// <summary>Verifies that a number outside the decimal range falls through to a double.</summary>
[Fact]
public void JsonElementToParameterValue_OutOfDecimalRangeNumber_FallsThroughToDouble()
{
@@ -761,8 +811,10 @@ public class DatabaseGatewayTests : IDisposable
Assert.Equal(1e40, (double)materialised);
}
// ── ExternalSystemGateway-010: SqlConnection must not leak when OpenAsync fails ──
// ── SqlConnection must not leak when OpenAsync fails ──
/// <summary>Verifies that the connection is disposed before the exception is rethrown when opening fails.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GetConnection_OpenFails_DisposesConnectionBeforeRethrowing()
{
@@ -783,27 +835,36 @@ public class DatabaseGatewayTests : IDisposable
{
private readonly DbConnection _connection;
/// <summary>Initializes the stub gateway with the connection it will hand out.</summary>
/// <param name="repository">The external system repository.</param>
/// <param name="connection">The connection returned by the factory method.</param>
public ConnectionFactoryStubGateway(IExternalSystemRepository repository, DbConnection connection)
: base(repository, NullLogger<DatabaseGateway>.Instance) => _connection = connection;
/// <inheritdoc />
internal override DbConnection CreateConnection(string connectionString) => _connection;
}
/// <summary>A DbConnection whose OpenAsync always fails, tracking whether it was disposed.</summary>
private sealed class ThrowingDbConnection : DbConnection
{
/// <summary>Gets a value indicating whether the connection was disposed.</summary>
public bool WasDisposed { get; private set; }
/// <inheritdoc />
public override Task OpenAsync(CancellationToken cancellationToken) =>
throw new InvalidOperationException("simulated open failure");
/// <inheritdoc />
public override void Open() => throw new InvalidOperationException("simulated open failure");
/// <inheritdoc />
protected override void Dispose(bool disposing)
{
if (disposing) WasDisposed = true;
base.Dispose(disposing);
}
/// <inheritdoc />
public override ValueTask DisposeAsync()
{
WasDisposed = true;
@@ -811,15 +872,24 @@ public class DatabaseGatewayTests : IDisposable
}
// Unused abstract members.
/// <inheritdoc />
[System.Diagnostics.CodeAnalysis.AllowNull]
public override string ConnectionString { get; set; } = string.Empty;
/// <inheritdoc />
public override string Database => string.Empty;
/// <inheritdoc />
public override string DataSource => string.Empty;
/// <inheritdoc />
public override string ServerVersion => string.Empty;
/// <inheritdoc />
public override ConnectionState State => ConnectionState.Closed;
/// <inheritdoc />
public override void ChangeDatabase(string databaseName) => throw new NotSupportedException();
/// <inheritdoc />
public override void Close() { }
/// <inheritdoc />
protected override DbTransaction BeginDbTransaction(IsolationLevel il) => throw new NotSupportedException();
/// <inheritdoc />
protected override DbCommand CreateDbCommand() => throw new NotSupportedException();
}
}