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:
+35
-9
@@ -36,17 +36,16 @@ using System.Text.Encodings.Web;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — M4 Bundle E (Task E4) cross-boundary safety suite verifying
|
||||
/// Cross-boundary safety suite verifying
|
||||
/// the alog.md §13 contract: an always-throwing audit writer NEVER aborts the
|
||||
/// user-facing action. Exercises every boundary that emits audit rows in M2,
|
||||
/// M3, and M4:
|
||||
/// user-facing action. Exercises every boundary that emits audit rows:
|
||||
///
|
||||
/// <list type="bullet">
|
||||
/// <item><description>External system sync call (M2 Bundle F).</description></item>
|
||||
/// <item><description>External system cached call (M3 Bundle E).</description></item>
|
||||
/// <item><description>Database sync write (M4 Bundle A).</description></item>
|
||||
/// <item><description>Inbound API request (M4 Bundle D).</description></item>
|
||||
/// <item><description>Notification dispatcher (M4 Bundle B).</description></item>
|
||||
/// <item><description>External system sync call (sync).</description></item>
|
||||
/// <item><description>External system cached call (cached).</description></item>
|
||||
/// <item><description>Database sync write (sync write).</description></item>
|
||||
/// <item><description>Inbound API request (request).</description></item>
|
||||
/// <item><description>Notification dispatcher (dispatch).</description></item>
|
||||
/// </list>
|
||||
///
|
||||
/// <para>
|
||||
@@ -64,6 +63,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the suite with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared migrated MS SQL database fixture.</param>
|
||||
public AuditWriteFailureSafetyTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -81,8 +82,10 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
private sealed class ThrowingAuditWriter : IAuditWriter
|
||||
{
|
||||
private int _attempts;
|
||||
/// <summary>Gets the number of times <c>WriteAsync</c> has been attempted.</summary>
|
||||
public int Attempts => Volatile.Read(ref _attempts);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref _attempts);
|
||||
@@ -100,8 +103,10 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
private sealed class ThrowingCentralAuditWriter : ICentralAuditWriter
|
||||
{
|
||||
private int _attempts;
|
||||
/// <summary>Gets the number of times <c>WriteAsync</c> has been attempted.</summary>
|
||||
public int Attempts => Volatile.Read(ref _attempts);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref _attempts);
|
||||
@@ -118,8 +123,10 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
private sealed class ThrowingCachedForwarder : ICachedCallTelemetryForwarder
|
||||
{
|
||||
private int _attempts;
|
||||
/// <summary>Gets the number of times <c>ForwardAsync</c> has been attempted.</summary>
|
||||
public int Attempts => Volatile.Read(ref _attempts);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ForwardAsync(CachedCallTelemetry telemetry, CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref _attempts);
|
||||
@@ -132,6 +139,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
// Test 1 — ESG sync call still returns the original ExternalCallResult.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that a synchronous external system call still returns its result when the audit writer throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EsgSyncCall_BrokenAuditWriter_StillReturnsResult()
|
||||
{
|
||||
@@ -171,6 +180,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
// Test 2 — ESG cached call still returns a TrackedOperationId.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that a cached external system call still returns a tracked operation id when the audit writer throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task EsgCachedCall_BrokenAuditWriter_StillReturnsTrackedOperationId()
|
||||
{
|
||||
@@ -214,6 +225,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
// Test 3 — DB sync write still returns the rows-affected count.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that a synchronous database write still returns the rows-affected count when the audit writer throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DbSyncWrite_BrokenAuditWriter_StillReturnsRowsAffected()
|
||||
{
|
||||
@@ -269,6 +282,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
// Test 4 — Inbound API request still returns HTTP 200.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that an inbound API request still returns HTTP 200 when the audit writer throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InboundApi_BrokenAuditWriter_StillReturns200()
|
||||
{
|
||||
@@ -290,6 +305,8 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
// Test 5 — Notification dispatcher still transitions to Delivered.
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that a notification dispatch still transitions to delivered when the audit writer throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task NotificationDispatch_BrokenAuditWriter_StillTransitionsToDelivered()
|
||||
{
|
||||
@@ -381,7 +398,7 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
private async Task SeedSmtpConfigAsync()
|
||||
{
|
||||
await using var ctx = CreateContext();
|
||||
// NO-002: dispatcher clamps non-positive RetryDelay to the 1-minute fallback;
|
||||
// The dispatcher clamps non-positive RetryDelay to the 1-minute fallback;
|
||||
// use 1 ms so a transient outcome's NextAttemptAt is still effectively due.
|
||||
ctx.SmtpConfigurations.Add(new SmtpConfiguration(
|
||||
"smtp.example.com", "Basic", "noreply@example.com")
|
||||
@@ -418,8 +435,12 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
private sealed class SingleOutcomeAdapter : INotificationDeliveryAdapter
|
||||
{
|
||||
private readonly DeliveryOutcome _outcome;
|
||||
/// <summary>Initializes the adapter with the outcome to return for every delivery.</summary>
|
||||
/// <param name="outcome">The delivery outcome returned by every call.</param>
|
||||
public SingleOutcomeAdapter(DeliveryOutcome outcome) { _outcome = outcome; }
|
||||
/// <inheritdoc />
|
||||
public NotificationType Type => NotificationType.Email;
|
||||
/// <inheritdoc />
|
||||
public Task<DeliveryOutcome> DeliverAsync(
|
||||
Notification notification, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(_outcome);
|
||||
@@ -472,12 +493,17 @@ public class AuditWriteFailureSafetyTests : TestKit, IClassFixture<MsSqlMigratio
|
||||
: Microsoft.AspNetCore.Authentication.AuthenticationHandler<
|
||||
Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions>
|
||||
{
|
||||
/// <summary>Initializes the handler with the standard authentication handler dependencies.</summary>
|
||||
/// <param name="options">The authentication scheme options monitor.</param>
|
||||
/// <param name="logger">The logger factory.</param>
|
||||
/// <param name="encoder">The URL encoder.</param>
|
||||
public AlwaysAuthenticatedHandler(
|
||||
IOptionsMonitor<Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions> options,
|
||||
Microsoft.Extensions.Logging.ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>
|
||||
HandleAuthenticateAsync()
|
||||
{
|
||||
|
||||
+10
-2
@@ -15,7 +15,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle G G2 end-to-end suite for cached <c>ExternalSystem.CachedCall</c>
|
||||
/// lifecycle telemetry (Audit Log #23 / M3). Wires the full M3 pipeline:
|
||||
/// lifecycle telemetry. Wires the full pipeline:
|
||||
/// site-local SQLite audit writer + operation tracking store + the production
|
||||
/// <see cref="CachedCallTelemetryForwarder"/> + the test-side
|
||||
/// <see cref="CombinedTelemetryDispatcher"/> that ALSO pushes each combined
|
||||
@@ -28,7 +28,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
/// <para>
|
||||
/// The bridge is driven directly via <see cref="CombinedTelemetryHarness.EmitAttemptAsync"/>
|
||||
/// — these tests do NOT spin up the actual S&F retry loop; that would
|
||||
/// require a full SiteRuntime host and is out of scope for M3 (the S&F
|
||||
/// require a full SiteRuntime host and is out of scope here (the S&F
|
||||
/// observer hooks are exercised in <c>ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests</c> at
|
||||
/// unit level). The submit row is emitted via
|
||||
/// <see cref="CombinedTelemetryHarness.EmitSubmitAsync"/> because the
|
||||
@@ -45,6 +45,8 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test with the shared SQL Server migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public CachedCallCombinedTelemetryTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -106,6 +108,8 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
DurationMs: 42,
|
||||
SourceInstanceId: "Plant.Pump42");
|
||||
|
||||
/// <summary>Verifies that two failed attempts followed by a success emit five audit rows and one delivered site call.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CachedCall_FailFailSuccess_Emits_5_AuditRows_AND_1_SiteCall_Delivered()
|
||||
{
|
||||
@@ -177,6 +181,8 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
Assert.NotNull(snapshot.TerminalAtUtc);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cached call whose attempts all fail and are parked emits a terminal Parked row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CachedCall_AllAttemptsFailedAndParked_Emits_Terminal_Parked()
|
||||
{
|
||||
@@ -227,6 +233,8 @@ public class CachedCallCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMigr
|
||||
Assert.NotNull(snapshot.TerminalAtUtc);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an immediate success without store-and-forward emits an attempted row and resolves as delivered.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CachedCall_ImmediateSuccess_NoSF_Emits_Attempted_And_Resolve_Delivered()
|
||||
{
|
||||
|
||||
+6
@@ -34,6 +34,8 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared SQL Server migration fixture.</summary>
|
||||
/// <param name="fixture">The shared SQL Server migration fixture.</param>
|
||||
public CachedWriteCombinedTelemetryTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -93,6 +95,8 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
DurationMs: 12,
|
||||
SourceInstanceId: "Plant.Pump42");
|
||||
|
||||
/// <summary>Verifies that a successful cached write emits the full lifecycle ending in a delivered row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CachedWrite_Success_Emits_Delivered_Lifecycle()
|
||||
{
|
||||
@@ -144,6 +148,8 @@ public class CachedWriteCombinedTelemetryTests : TestKit, IClassFixture<MsSqlMig
|
||||
Assert.Equal("Delivered", snapshot!.Status);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a parked cached write emits a terminal parked row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CachedWrite_Parked_Emits_Terminal_Parked()
|
||||
{
|
||||
|
||||
+6
@@ -34,6 +34,8 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the suite with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared migrated MS SQL database fixture.</param>
|
||||
public CombinedTelemetryIdempotencyTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -100,6 +102,8 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
return batch;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a duplicate telemetry packet leaves a single audit row and upserts the site call monotonically.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task DuplicatePacket_AuditLogStaysAtOneRow_SiteCallsUpserted_Monotonically()
|
||||
{
|
||||
@@ -144,6 +148,8 @@ public class CombinedTelemetryIdempotencyTests : TestKit, IClassFixture<MsSqlMig
|
||||
Assert.Equal(0, siteCalls[0].RetryCount);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an older status arriving after a newer one is a no-op.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task OutOfOrderPackets_OlderStatus_ArrivesAfterNewer_IsNoOp()
|
||||
{
|
||||
|
||||
+8
-2
@@ -23,7 +23,7 @@ using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — M4 Bundle E (Task E1) end-to-end suite verifying every
|
||||
/// End-to-end suite verifying every
|
||||
/// synchronous <c>Database.Connection(name).Execute*</c> /
|
||||
/// <c>ExecuteReader</c> call made via the Bundle A
|
||||
/// <see cref="ScriptRuntimeContext.DatabaseHelper"/> emits exactly one
|
||||
@@ -33,7 +33,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Composes the same pipeline as the M2 <see cref="SyncCallEmissionEndToEndTests"/>:
|
||||
/// Composes the same pipeline as the <see cref="SyncCallEmissionEndToEndTests"/>:
|
||||
/// in-memory <see cref="SqliteAuditWriter"/> + <see cref="RingBufferFallback"/> +
|
||||
/// <see cref="FallbackAuditWriter"/> on the site, drained by a real
|
||||
/// <see cref="SiteAuditTelemetryActor"/> through a
|
||||
@@ -57,6 +57,8 @@ public class DatabaseSyncEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the suite with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared migrated MS SQL database fixture.</param>
|
||||
public DatabaseSyncEmissionEndToEndTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -170,6 +172,8 @@ public class DatabaseSyncEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMig
|
||||
sourceScript: SourceScript,
|
||||
cachedForwarder: null);
|
||||
|
||||
/// <summary>Verifies that a database insert emits one central audit row carrying a write operation marker and the rows-affected count.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task DbWrite_Insert_Emits_OneCentralRow_WithExtraOpWrite_AndRowsAffected()
|
||||
{
|
||||
@@ -240,6 +244,8 @@ public class DatabaseSyncEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMig
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a database select emits one central audit row carrying a read operation marker and the rows-returned count.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task DbWrite_Select_Emits_OneCentralRow_WithExtraOpRead_AndRowsReturned()
|
||||
{
|
||||
|
||||
+7
-3
@@ -22,7 +22,7 @@ using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — ExecutionId end-to-end correlation suite verifying the
|
||||
/// ExecutionId end-to-end correlation suite verifying the
|
||||
/// universal per-run correlation promise: <b>every audit row produced by one
|
||||
/// script execution carries the same non-null <see cref="AuditEvent.ExecutionId"/></b>.
|
||||
/// </summary>
|
||||
@@ -39,8 +39,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
/// reads the rows back from the central store and asserts the shared id.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Composes the same pipeline as the M2 <see cref="SyncCallEmissionEndToEndTests"/>
|
||||
/// and the M4 <see cref="DatabaseSyncEmissionEndToEndTests"/>: an in-memory
|
||||
/// Composes the same pipeline as the sync-call <see cref="SyncCallEmissionEndToEndTests"/>
|
||||
/// and the database-write <see cref="DatabaseSyncEmissionEndToEndTests"/>: an in-memory
|
||||
/// <see cref="SqliteAuditWriter"/> + <see cref="RingBufferFallback"/> +
|
||||
/// <see cref="FallbackAuditWriter"/> on the site, drained by a real
|
||||
/// <see cref="SiteAuditTelemetryActor"/> through the shared
|
||||
@@ -57,6 +57,8 @@ public class ExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMigration
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the suite with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public ExecutionIdCorrelationTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -146,6 +148,8 @@ public class ExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMigration
|
||||
FastTelemetryOptions(),
|
||||
NullLogger<SiteAuditTelemetryActor>.Instance)));
|
||||
|
||||
/// <summary>Verifies that one execution performing an API call and a database write produces central rows that all share one non-null execution id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task OneExecution_ApiCallAndDbWrite_AllCentralRows_ShareOneNonNullExecutionId()
|
||||
{
|
||||
|
||||
@@ -24,7 +24,7 @@ using System.Text.Encodings.Web;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — M4 Bundle E (Task E3) end-to-end audit trail for the
|
||||
/// End-to-end audit trail for the
|
||||
/// inbound API surface. Wires the production
|
||||
/// <see cref="AuditWriteMiddleware"/> into a Microsoft.AspNetCore.TestHost
|
||||
/// pipeline that mirrors the production
|
||||
@@ -53,6 +53,8 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public InboundApiAuditTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -144,12 +146,17 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
/// </summary>
|
||||
private sealed class AlwaysAuthenticatedHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
/// <summary>Initializes the handler with the standard authentication-handler dependencies.</summary>
|
||||
/// <param name="options">The authentication scheme options monitor.</param>
|
||||
/// <param name="logger">The logger factory.</param>
|
||||
/// <param name="encoder">The URL encoder.</param>
|
||||
public AlwaysAuthenticatedHandler(
|
||||
Microsoft.Extensions.Options.IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
Microsoft.Extensions.Logging.ILoggerFactory logger,
|
||||
UrlEncoder encoder)
|
||||
: base(options, logger, encoder) { }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var identity = new ClaimsIdentity(
|
||||
@@ -200,6 +207,8 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
return Assert.Single(finalRows);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a post with a valid actor emits a delivered inbound-request audit row with HTTP status 200 and the actor populated.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task PostToApi_WithValidActor_Emits_InboundRequest_StatusDelivered_HttpStatus200_ActorPopulated()
|
||||
{
|
||||
@@ -232,6 +241,8 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.NotNull(evt.AsRow().IngestedAtUtc);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a post without authentication emits a failed inbound-auth-failure audit row with HTTP status 401 and no actor.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task PostToApi_Without_Auth_Emits_InboundAuthFailure_StatusFailed_HttpStatus401_ActorNull()
|
||||
{
|
||||
@@ -260,12 +271,14 @@ public class InboundApiAuditTests : IClassFixture<MsSqlMigrationFixture>
|
||||
Assert.Equal(AuditStatus.Failed, evt.AsRow().Status);
|
||||
Assert.Equal(401, evt.AsRow().HttpStatus);
|
||||
// Never echo back an unauthenticated principal — middleware suppresses
|
||||
// the framework user resolution on 401/403 paths. C3 (Task 2.5): the
|
||||
// the framework user resolution on 401/403 paths. The
|
||||
// canonical Actor is a non-null string (empty when absent); the row view
|
||||
// maps empty → null, preserving the "no principal" assertion.
|
||||
Assert.Null(evt.AsRow().Actor);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a post whose endpoint returns 500 emits a failed inbound-request audit row with HTTP status 500.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task PostToApi_Returning500_Emits_InboundRequest_StatusFailed_HttpStatus500()
|
||||
{
|
||||
|
||||
+10
-6
@@ -20,12 +20,12 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Production wiring keeps the wire push deferred to M6 — the site SQLite hot
|
||||
/// path is the source of truth and a future M6 drain will push the rows
|
||||
/// Production wiring keeps the wire push deferred — the site SQLite hot
|
||||
/// path is the source of truth and a future drain will push the rows
|
||||
/// through the gRPC client. For end-to-end testing today we need a way to
|
||||
/// exercise the central dual-write transaction immediately, so this
|
||||
/// dispatcher synthesises the wire packet inline and round-trips it through
|
||||
/// the stub client. The shape mirrors what the M6 drain will eventually emit.
|
||||
/// the stub client. The shape mirrors what the future drain will eventually emit.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <b>Best-effort:</b> both the inner forwarder call and the wire push are
|
||||
@@ -40,6 +40,9 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
private readonly ICachedCallTelemetryForwarder _inner;
|
||||
private readonly ISiteStreamAuditClient _wireClient;
|
||||
|
||||
/// <summary>Initializes the dispatcher with the inner forwarder and the wire client.</summary>
|
||||
/// <param name="inner">The forwarder that handles site-local telemetry.</param>
|
||||
/// <param name="wireClient">The client used to push audit rows over the wire.</param>
|
||||
public CombinedTelemetryDispatcher(
|
||||
ICachedCallTelemetryForwarder inner,
|
||||
ISiteStreamAuditClient wireClient)
|
||||
@@ -48,7 +51,8 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
_wireClient = wireClient ?? throw new ArgumentNullException(nameof(wireClient));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
// Forwards the telemetry through the inner forwarder and then pushes its audit rows to the wire client.
|
||||
/// <inheritdoc />
|
||||
public async Task ForwardAsync(CachedCallTelemetry telemetry, CancellationToken ct = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(telemetry);
|
||||
@@ -67,7 +71,7 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
}
|
||||
|
||||
// Project the same packet onto the wire and push it through the stub
|
||||
// client. This is the bit a future M6 drain will subsume — until
|
||||
// client. This is the bit a future drain will subsume — until
|
||||
// then the test wraps the two halves into one observer-driven step.
|
||||
try
|
||||
{
|
||||
@@ -79,7 +83,7 @@ public sealed class CombinedTelemetryDispatcher : ICachedCallTelemetryForwarder
|
||||
{
|
||||
// Swallow — the audit row is still in SQLite for a future drain;
|
||||
// the central row will materialise the next time the wire path
|
||||
// is exercised (or via the M6 reconciliation pull).
|
||||
// is exercised (or via the reconciliation pull).
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-1
@@ -21,7 +21,7 @@ using ZB.MOM.WW.ScadaBridge.SiteRuntime.Tracking;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
|
||||
/// <summary>
|
||||
/// Shared end-to-end harness for the M3 cached-call combined telemetry tests
|
||||
/// Shared end-to-end harness for the cached-call combined telemetry tests
|
||||
/// (G2/G3/G4). Composes the full pipeline:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Site-local SQLite <see cref="SqliteAuditWriter"/> (in-memory) +
|
||||
@@ -48,15 +48,25 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
/// </remarks>
|
||||
public sealed class CombinedTelemetryHarness : IAsyncDisposable
|
||||
{
|
||||
/// <summary>The site SQLite audit writer under test.</summary>
|
||||
public SqliteAuditWriter SqliteWriter { get; }
|
||||
/// <summary>The in-memory ring buffer fallback for audit events.</summary>
|
||||
public RingBufferFallback Ring { get; }
|
||||
/// <summary>The fallback audit writer that wraps the SQLite writer and the ring buffer.</summary>
|
||||
public FallbackAuditWriter FallbackWriter { get; }
|
||||
/// <summary>The site-local operation tracking store.</summary>
|
||||
public OperationTrackingStore TrackingStore { get; }
|
||||
/// <summary>The inner cached-call telemetry forwarder.</summary>
|
||||
public CachedCallTelemetryForwarder InnerForwarder { get; }
|
||||
/// <summary>The combined telemetry dispatcher that sends audit and operational state together.</summary>
|
||||
public CombinedTelemetryDispatcher Dispatcher { get; }
|
||||
/// <summary>The cached-call lifecycle bridge wired to the forwarder.</summary>
|
||||
public CachedCallLifecycleBridge Bridge { get; }
|
||||
/// <summary>The stub site-stream audit client that delivers packets directly to the ingest actor.</summary>
|
||||
public DirectActorSiteStreamAuditClient StubClient { get; }
|
||||
/// <summary>The central audit ingest actor.</summary>
|
||||
public IActorRef IngestActor { get; }
|
||||
/// <summary>The service provider backing the central repositories.</summary>
|
||||
public IServiceProvider ServiceProvider { get; }
|
||||
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
@@ -64,6 +74,10 @@ public sealed class CombinedTelemetryHarness : IAsyncDisposable
|
||||
private readonly ServiceProvider _trackingLocalDbProvider;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>Builds the harness, wiring the site writer chain, dispatcher and central ingest actor against the shared fixture.</summary>
|
||||
/// <param name="fixture">The MS SQL migration fixture providing the central database.</param>
|
||||
/// <param name="testKit">The Akka test kit that hosts the ingest actor.</param>
|
||||
/// <param name="siteCallRepoOverride">Optional factory that replaces the default site-call audit repository.</param>
|
||||
public CombinedTelemetryHarness(
|
||||
MsSqlMigrationFixture fixture,
|
||||
Akka.TestKit.Xunit2.TestKit testKit,
|
||||
@@ -133,15 +147,23 @@ public sealed class CombinedTelemetryHarness : IAsyncDisposable
|
||||
/// dispatcher (the bridge's hooks fire only for S&F retry-loop
|
||||
/// attempts; submit-row emission happens at the script call site).
|
||||
/// </summary>
|
||||
/// <param name="submit">The submit packet to forward.</param>
|
||||
/// <param name="ct">A token to cancel the forward.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task EmitSubmitAsync(CachedCallTelemetry submit, CancellationToken ct = default) =>
|
||||
Dispatcher.ForwardAsync(submit, ct);
|
||||
|
||||
/// <summary>
|
||||
/// Convenience: route a per-attempt or terminal outcome through the bridge.
|
||||
/// </summary>
|
||||
/// <param name="context">The attempt or terminal-outcome context to route.</param>
|
||||
/// <param name="ct">A token to cancel the emit.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task EmitAttemptAsync(CachedCallAttemptContext context, CancellationToken ct = default) =>
|
||||
Bridge.OnAttemptCompletedAsync(context, ct);
|
||||
|
||||
/// <summary>Creates a fresh central database context for reading back persisted rows.</summary>
|
||||
/// <returns>A new <see cref="ScadaBridgeDbContext"/>.</returns>
|
||||
public ScadaBridgeDbContext CreateReadContext()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<ScadaBridgeDbContext>()
|
||||
@@ -173,6 +195,8 @@ public sealed class CombinedTelemetryHarness : IAsyncDisposable
|
||||
return services.BuildServiceProvider();
|
||||
}
|
||||
|
||||
/// <summary>Releases the resources held by this test class asynchronously.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
+12
-16
@@ -12,8 +12,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
/// Shared component-level <see cref="ISiteStreamAuditClient"/> test double that
|
||||
/// short-circuits the gRPC wire and forwards each batch directly to a central
|
||||
/// <see cref="AuditLog.Central.AuditLogIngestActor"/> via Akka <see cref="Futures.Ask"/>.
|
||||
/// Lives under <c>Integration/Infrastructure/</c> so both the M2 sync-call and
|
||||
/// M3 cached-call end-to-end suites can reuse it.
|
||||
/// Lives under <c>Integration/Infrastructure/</c> so both the sync-call and
|
||||
/// cached-call end-to-end suites can reuse it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -27,8 +27,8 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="FailNextCallCount"/> arms a deterministic number of failures
|
||||
/// before the stub recovers; it applies to BOTH RPCs because the M2 sync-call
|
||||
/// retry behaviour and the M3 cached-telemetry retry behaviour share a single
|
||||
/// before the stub recovers; it applies to BOTH RPCs because the sync-call
|
||||
/// retry behaviour and the cached-telemetry retry behaviour share a single
|
||||
/// SiteAuditTelemetryActor drain. Tests that need to differentiate per-RPC
|
||||
/// failures should reach for a per-test wrapper rather than extending this
|
||||
/// shared infrastructure.
|
||||
@@ -41,6 +41,8 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
private int _callCount;
|
||||
private int _cachedTelemetryCallCount;
|
||||
|
||||
/// <summary>Initializes the client with the central ingest actor that receives every forwarded batch.</summary>
|
||||
/// <param name="ingestActor">The central <c>AuditLogIngestActor</c> reference batches are asked of.</param>
|
||||
public DirectActorSiteStreamAuditClient(IActorRef ingestActor)
|
||||
{
|
||||
_ingestActor = ingestActor ?? throw new ArgumentNullException(nameof(ingestActor));
|
||||
@@ -69,6 +71,7 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
/// </summary>
|
||||
public int CachedTelemetryCallCount => Volatile.Read(ref _cachedTelemetryCallCount);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IngestAck> IngestAuditEventsAsync(AuditEventBatch batch, CancellationToken ct)
|
||||
{
|
||||
Interlocked.Increment(ref _callCount);
|
||||
@@ -106,18 +109,11 @@ public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
|
||||
return ack;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// M3 dual-write path: decode each <see cref="CachedTelemetryPacket"/> into
|
||||
/// the paired (<see cref="AuditEvent"/>, <see cref="SiteCall"/>) entry and
|
||||
/// Ask the central ingest actor with an <see cref="IngestCachedTelemetryCommand"/>.
|
||||
/// The accepted EventIds returned by the actor's dual-write transaction map
|
||||
/// back into the proto ack.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Uses the shared <see cref="AuditEventDtoMapper.FromDto"/> for the audit half
|
||||
/// and <see cref="SiteCallDtoMapper.FromDto"/> for the SiteCall half — the same
|
||||
/// canonical mappers the production <c>SiteStreamGrpcServer</c> uses.
|
||||
/// </remarks>
|
||||
// Dual-write path: decodes each CachedTelemetryPacket into the paired (AuditEvent, SiteCall)
|
||||
// entry via the shared AuditEventDtoMapper.FromDto and SiteCallDtoMapper.FromDto (the same
|
||||
// canonical mappers the production SiteStreamGrpcServer uses), then Asks the central ingest
|
||||
// actor with an IngestCachedTelemetryCommand; the accepted EventIds map back into the proto ack.
|
||||
/// <inheritdoc />
|
||||
public async Task<IngestAck> IngestCachedTelemetryAsync(CachedTelemetryBatch batch, CancellationToken ct)
|
||||
{
|
||||
Interlocked.Increment(ref _cachedTelemetryCallCount);
|
||||
|
||||
+14
-2
@@ -20,7 +20,7 @@ using ZB.MOM.WW.ScadaBridge.NotificationOutbox.Messages;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — M4 Bundle E (Task E2): end-to-end audit trail produced by
|
||||
/// End-to-end audit trail produced by
|
||||
/// the central <see cref="NotificationOutboxActor"/> dispatcher loop. Wires
|
||||
/// the production <see cref="CentralAuditWriter"/> onto the real
|
||||
/// <see cref="AuditLogRepository"/> against the per-class
|
||||
@@ -51,6 +51,8 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared MSSQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MSSQL migration fixture.</param>
|
||||
public NotifyDispatcherAuditTrailTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -105,13 +107,17 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
private readonly Queue<DeliveryOutcome> _outcomes;
|
||||
public int CallCount;
|
||||
|
||||
/// <summary>Initializes the adapter with the delivery outcomes it returns in order.</summary>
|
||||
/// <param name="outcomes">The outcomes returned by successive delivery calls.</param>
|
||||
public QueuedOutcomeAdapter(params DeliveryOutcome[] outcomes)
|
||||
{
|
||||
_outcomes = new Queue<DeliveryOutcome>(outcomes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public NotificationType Type => NotificationType.Email;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DeliveryOutcome> DeliverAsync(
|
||||
Notification notification, CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -133,7 +139,7 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
/// pair rather than the conservative fallback. A tiny positive RetryDelay
|
||||
/// means a transient outcome's <c>NextAttemptAt</c> is immediately due —
|
||||
/// useful so the SECOND DispatchTick re-claims the row without waiting.
|
||||
/// NO-002: the dispatcher now clamps a non-positive RetryDelay to the
|
||||
/// The dispatcher now clamps a non-positive RetryDelay to the
|
||||
/// 1-minute fallback to avoid burn-looping on transient failures, so this
|
||||
/// must be a strictly positive value (1 ms is fine for tests).
|
||||
/// </summary>
|
||||
@@ -242,6 +248,8 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
PurgeInterval = TimeSpan.FromDays(1),
|
||||
};
|
||||
|
||||
/// <summary>Verifies that a transient failure followed by a success emits two attempt rows and a terminal delivered row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task NotifyDispatcher_FailThenSuccess_Emits_TwoAttempts_OneDelivered_Terminal()
|
||||
{
|
||||
@@ -319,6 +327,8 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
StopDispatcher(actor);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing audit writer does not prevent the notification delivery from succeeding.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task NotifyDispatcher_AuditWriter_Throws_DeliveryStillSucceeds()
|
||||
{
|
||||
@@ -394,8 +404,10 @@ public class NotifyDispatcherAuditTrailTests : TestKit, IClassFixture<MsSqlMigra
|
||||
private sealed class ThrowingCentralAuditWriter : ICentralAuditWriter
|
||||
{
|
||||
private int _attemptCount;
|
||||
/// <summary>Gets the number of write attempts made against this writer.</summary>
|
||||
public int AttemptCount => Volatile.Read(ref _attemptCount);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default)
|
||||
{
|
||||
Interlocked.Increment(ref _attemptCount);
|
||||
|
||||
+18
-5
@@ -21,7 +21,7 @@ using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle F (#23 M6-T10) end-to-end test for the central-outage + reconciliation
|
||||
/// End-to-end test for the central-outage + reconciliation
|
||||
/// recovery loop. Wires the real site SQLite hot-path
|
||||
/// (<see cref="SqliteAuditWriter"/>) and the central <see cref="SiteAuditReconciliationActor"/>
|
||||
/// with an <see cref="AuditLogIngestActor"/> backed by the real
|
||||
@@ -39,7 +39,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
/// the real repository.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The <see cref="CombinedTelemetryHarness"/> from M3 is push-only — it has no
|
||||
/// The <see cref="CombinedTelemetryHarness"/> is push-only — it has no
|
||||
/// reconciliation puller — so we build the smaller stub inline rather than
|
||||
/// retrofitting the shared harness with a code path it doesn't otherwise
|
||||
/// need.
|
||||
@@ -49,6 +49,8 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public OutageReconciliationTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -77,19 +79,23 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
private sealed class QueueBackedPullClient : IPullAuditEventsClient
|
||||
{
|
||||
private readonly ISiteAuditQueue _siteQueue;
|
||||
/// <summary>Gets the number of times <c>PullAsync</c> has been invoked.</summary>
|
||||
public int CallCount { get; private set; }
|
||||
|
||||
/// <summary>Initializes the client over the given site audit queue.</summary>
|
||||
/// <param name="siteQueue">The site audit queue to pull from.</param>
|
||||
public QueueBackedPullClient(ISiteAuditQueue siteQueue)
|
||||
{
|
||||
_siteQueue = siteQueue ?? throw new ArgumentNullException(nameof(siteQueue));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<PullAuditEventsResponse> PullAsync(
|
||||
string siteId, DateTime sinceUtc, string? afterId, int batchSize, CancellationToken ct)
|
||||
{
|
||||
CallCount++;
|
||||
|
||||
// Mirrors SiteStreamGrpcServer.PullAuditEvents exactly (WP2.3): the
|
||||
// Mirrors SiteStreamGrpcServer.PullAuditEvents exactly: the
|
||||
// INCOMING cursor is central's receipt, so everything at or before it
|
||||
// is retired FIRST; the rows this call serves are NOT retired, because
|
||||
// nothing yet proves central consumed them. A fault between here and
|
||||
@@ -119,7 +125,10 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
private sealed class StaticEnumerator : ISiteEnumerator
|
||||
{
|
||||
private readonly IReadOnlyList<SiteEntry> _sites;
|
||||
/// <summary>Initializes the enumerator with a fixed set of sites.</summary>
|
||||
/// <param name="sites">The fixed sites to enumerate.</param>
|
||||
public StaticEnumerator(params SiteEntry[] sites) => _sites = sites;
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SiteEntry>> EnumerateAsync(CancellationToken ct = default) =>
|
||||
Task.FromResult(_sites);
|
||||
}
|
||||
@@ -208,6 +217,8 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
// 1. CentralOutage_200Events_Buffer_Then_Reconciliation_Catches_Up_NoDuplicates
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that 200 events buffered during a central outage are caught up by reconciliation with no duplicates.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task CentralOutage_200Events_Buffer_Then_Reconciliation_Catches_Up_NoDuplicates()
|
||||
{
|
||||
@@ -297,8 +308,8 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
interval: TimeSpan.FromMilliseconds(100));
|
||||
|
||||
// Both actors have done everything the test needs; stop them HERE, before the
|
||||
// read-back, so nothing is still writing to the shared fixture DB while step 5
|
||||
// counts rows and so a step-5 failure cannot leak them (register row 95).
|
||||
// read-back, so nothing is still writing to the shared fixture DB while the read-back
|
||||
// counts rows and so a read-back failure cannot leak them (register row 95).
|
||||
StopActor(reconciliationActor);
|
||||
StopActor(ingest);
|
||||
|
||||
@@ -321,6 +332,8 @@ public class OutageReconciliationTests : TestKit, IClassFixture<MsSqlMigrationFi
|
||||
// 2. ReconciliationPull_Idempotent_Across_Two_Cycles
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that repeated reconciliation pull cycles are idempotent.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task ReconciliationPull_Idempotent_Across_Two_Cycles()
|
||||
{
|
||||
|
||||
+19
-2
@@ -40,7 +40,7 @@ using ZB.MOM.WW.ScadaBridge.TestSupport;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Audit Log #23 — <b>ParentExecutionId cross-execution correlation</b> headline
|
||||
/// <b>ParentExecutionId cross-execution correlation</b> headline
|
||||
/// end-to-end suite. Verifies the inbound-API → routed-site-script bridge: an
|
||||
/// inbound HTTP request runs an inbound method script that calls
|
||||
/// <c>Route.Call</c> into a site instance; the routed site script does a sync
|
||||
@@ -101,6 +101,8 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public ParentExecutionIdCorrelationTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -126,6 +128,8 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
return new ScadaBridgeDbContext(options);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that every audit row of a routed site run carries the inbound request's execution id as its parent execution id.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task InboundRoutedRun_AllRoutedRows_CarryInboundExecutionId_AsParentExecutionId()
|
||||
{
|
||||
@@ -507,7 +511,7 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
async () =>
|
||||
{
|
||||
var pending = await sqliteWriter.ReadPendingAsync(256);
|
||||
// AuditLog-001: ReadPendingAsync now excludes the cached-lifecycle
|
||||
// ReadPendingAsync now excludes the cached-lifecycle
|
||||
// kinds (they ride the combined-telemetry drain), so we union
|
||||
// them in via the dedicated read surface to keep the durability
|
||||
// assertion covering EVERY expected Kind.
|
||||
@@ -534,8 +538,10 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
/// </summary>
|
||||
private sealed class AlwaysDeliversAdapter : INotificationDeliveryAdapter
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public NotificationType Type => NotificationType.Email;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DeliveryOutcome> DeliverAsync(
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications.Notification notification,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -568,6 +574,12 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
/// </summary>
|
||||
public string? NotificationId { get; private set; }
|
||||
|
||||
/// <summary>Initializes the router with the collaborators the routed script run needs.</summary>
|
||||
/// <param name="siteId">The site identifier stamped on routed runs.</param>
|
||||
/// <param name="externalClient">The external system client used by the routed script.</param>
|
||||
/// <param name="auditWriter">The audit writer the routed script emits rows through.</param>
|
||||
/// <param name="cachedForwarder">The cached-call telemetry forwarder.</param>
|
||||
/// <param name="storeAndForward">The store-and-forward service that buffers the notification.</param>
|
||||
public BridgingInstanceRouter(
|
||||
string siteId,
|
||||
IExternalSystemClient externalClient,
|
||||
@@ -582,6 +594,8 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
_storeAndForward = storeAndForward;
|
||||
}
|
||||
|
||||
// Runs a routed site script body locally, with the inbound execution id as its parent execution id.
|
||||
/// <inheritdoc />
|
||||
public async Task<RouteToCallResponse> RouteToCallAsync(
|
||||
string siteId, RouteToCallRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -626,16 +640,19 @@ public class ParentExecutionIdCorrelationTests : TestKit, IClassFixture<MsSqlMig
|
||||
request.CorrelationId, true, "routed-ok", null, DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RouteToGetAttributesResponse> RouteToGetAttributesAsync(
|
||||
string siteId, RouteToGetAttributesRequest request, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<RouteToSetAttributesResponse> RouteToSetAttributesAsync(
|
||||
string siteId, RouteToSetAttributesRequest request, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
// WaitForAttribute is not part of this fixture's routed-Call audit scenario;
|
||||
// mirror the other non-Call methods (unexercised here).
|
||||
/// <inheritdoc />
|
||||
public Task<RouteToWaitForAttributeResponse> RouteToWaitForAttributeAsync(
|
||||
string siteId, RouteToWaitForAttributeRequest request, CancellationToken cancellationToken)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
@@ -11,7 +11,7 @@ using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle F (#23 M6-T12) end-to-end tests for the
|
||||
/// End-to-end tests for the
|
||||
/// <see cref="AuditLogPartitionMaintenanceService"/> hosted service running
|
||||
/// the real EF/MSSQL <see cref="AuditLogPartitionMaintenance"/> against the
|
||||
/// per-class <see cref="MsSqlMigrationFixture"/>. The migration seeds
|
||||
@@ -30,6 +30,8 @@ public class PartitionMaintenanceTests : IClassFixture<MsSqlMigrationFixture>
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test class with the shared MS SQL migration fixture.</summary>
|
||||
/// <param name="fixture">The shared MS SQL migration fixture.</param>
|
||||
public PartitionMaintenanceTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -116,6 +118,8 @@ public class PartitionMaintenanceTests : IClassFixture<MsSqlMigrationFixture>
|
||||
// 1. EndToEnd_DefaultLookahead_NoSplit_WhenFutureCovered
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that the default lookahead adds no new partition boundaries when future partitions are already covered.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_DefaultLookahead_NoSplit_WhenFutureCovered()
|
||||
{
|
||||
@@ -162,6 +166,8 @@ public class PartitionMaintenanceTests : IClassFixture<MsSqlMigrationFixture>
|
||||
// 2. EndToEnd_LookaheadLargerThanCovered_Splits_NewBoundaries
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that a lookahead larger than the covered range splits in new partition boundaries.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_LookaheadLargerThanCovered_Splits_NewBoundaries()
|
||||
{
|
||||
@@ -218,6 +224,8 @@ public class PartitionMaintenanceTests : IClassFixture<MsSqlMigrationFixture>
|
||||
// 3. EndToEnd_PartitionMaintenance_Idempotent_OverTwoRuns
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies that running partition maintenance twice adds boundaries only once.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_PartitionMaintenance_Idempotent_OverTwoRuns()
|
||||
{
|
||||
|
||||
@@ -19,7 +19,7 @@ using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle F (#23 M6-T11) end-to-end test for the daily partition-switch
|
||||
/// End-to-end test for the daily partition-switch
|
||||
/// purge: seeds three monthly partitions (Jan / Feb / Mar 2026) with direct
|
||||
/// INSERTs that bypass the standard repository ingest path (so the seed
|
||||
/// timestamps are explicit), drives <see cref="AuditLogPurgeActor"/> against
|
||||
@@ -46,6 +46,8 @@ public class PartitionPurgeTests : TestKit, IClassFixture<MsSqlMigrationFixture>
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="PartitionPurgeTests"/> class.</summary>
|
||||
/// <param name="fixture">The fixture used by the tests.</param>
|
||||
public PartitionPurgeTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -71,11 +73,11 @@ public class PartitionPurgeTests : TestKit, IClassFixture<MsSqlMigrationFixture>
|
||||
string kind = "ApiCall")
|
||||
{
|
||||
await using var cmd = conn.CreateCommand();
|
||||
// C5 (Task 2.5): dbo.AuditLog is now the 10 canonical columns + DetailsJson;
|
||||
// dbo.AuditLog is now the 10 canonical columns + DetailsJson;
|
||||
// the ScadaBridge domain fields (channel/kind/status/sourceSiteId) ride in
|
||||
// DetailsJson and the SourceSiteId/Kind/Status computed columns auto-derive.
|
||||
// Action = "{channel}.{kind}", Category = channel name, Outcome = Success.
|
||||
// The channel/kind are parameterized so the M5.5 per-channel purge test can
|
||||
// The channel/kind are parameterized so the per-channel purge test can
|
||||
// seed multiple channels into the same partition.
|
||||
cmd.CommandText = @"
|
||||
INSERT INTO dbo.AuditLog
|
||||
@@ -111,7 +113,7 @@ VALUES
|
||||
/// <summary>
|
||||
/// Asserts the post-purge index state: the partition-ALIGNED clustered
|
||||
/// <c>PK_AuditLog</c> is intact and the non-aligned <c>UX_AuditLog_EventId</c>
|
||||
/// is absent (WP2.2 — <c>AlignAuditLogEventIdUniqueness</c>).
|
||||
/// is absent (<c>AlignAuditLogEventIdUniqueness</c>).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The purge used to bracket its SWITCH with a DROP/CREATE of
|
||||
@@ -164,7 +166,7 @@ SELECT
|
||||
/// <para>
|
||||
/// Deferred-work register row 82. Every actor these tests create ticks every
|
||||
/// <c>100 ms</c> (<see cref="AuditLogPurgeOptions.IntervalOverride"/>) and switches
|
||||
/// out the Jan-2026 partition each time it finds one eligible. The tests relied on
|
||||
/// out the January 2026 partition each time it finds one eligible. The tests relied on
|
||||
/// <see cref="TestKit"/>'s <c>Dispose</c> → <c>Shutdown</c> to stop them between
|
||||
/// test methods, but that shutdown is bounded by a timeout and does NOT fail when
|
||||
/// it expires — so on a loaded machine a previous test's actor was still ticking
|
||||
@@ -172,9 +174,9 @@ SELECT
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// That is what made <c>PerChannelOverride_DeletesOnlyOverriddenChannelsOldRows</c>
|
||||
/// flake under a parallel full-solution run: it seeds a Jan-15-2026 row and expects
|
||||
/// flake under a parallel full-solution run: it seeds a 2026-01-15 row and expects
|
||||
/// its own per-channel DELETE to remove exactly one row, but a leaked actor could
|
||||
/// switch the whole Jan-2026 partition out first, leaving nothing to delete
|
||||
/// switch the whole January 2026 partition out first, leaving nothing to delete
|
||||
/// (observed: <c>Assert.Equal() Failure … Expected: 1, Actual: 0</c>). Stopping the
|
||||
/// actor inside the test that created it removes the dependency on shutdown timing
|
||||
/// altogether.
|
||||
@@ -228,9 +230,9 @@ SELECT
|
||||
var retentionDays = (int)(DateTime.UtcNow - thresholdAnchor).TotalDays + 1;
|
||||
|
||||
return (
|
||||
Old: new DateTime(2026, 1, 15, 0, 0, 0, DateTimeKind.Utc), // in Jan-2026 partition → PURGED
|
||||
Mid: new DateTime(2026, 4, 15, 0, 0, 0, DateTimeKind.Utc), // in Apr-2026 partition → KEPT
|
||||
Recent: new DateTime(2026, 6, 15, 0, 0, 0, DateTimeKind.Utc), // in Jun-2026 partition → KEPT
|
||||
Old: new DateTime(2026, 1, 15, 0, 0, 0, DateTimeKind.Utc), // in January 2026 partition → PURGED
|
||||
Mid: new DateTime(2026, 4, 15, 0, 0, 0, DateTimeKind.Utc), // in April 2026 partition → KEPT
|
||||
Recent: new DateTime(2026, 6, 15, 0, 0, 0, DateTimeKind.Utc), // in June 2026 partition → KEPT
|
||||
RetentionDays: retentionDays
|
||||
);
|
||||
}
|
||||
@@ -239,6 +241,8 @@ SELECT
|
||||
// 1. EndToEnd_OldestPartition_PurgedViaActor_NewerKept
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies the case: end To End, oldest Partition, purged Via Actor, newer Kept.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_OldestPartition_PurgedViaActor_NewerKept()
|
||||
{
|
||||
@@ -283,7 +287,7 @@ SELECT
|
||||
|
||||
var actor = CreateActor(sp, purgeOptions, auditOptions);
|
||||
|
||||
// The Jan-2026 partition boundary is the only eligible one in this fixture DB.
|
||||
// The January 2026 partition boundary is the only eligible one in this fixture DB.
|
||||
var janBoundary = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
var matched = probe.FishForMessage<AuditLogPurgedEvent>(
|
||||
isMessage: m => m.MonthBoundary == janBoundary,
|
||||
@@ -313,14 +317,16 @@ SELECT
|
||||
// 2. EndToEnd_AlignedUniqueness_AfterPurge
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies the case: end To End, aligned Uniqueness, after Purge.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_AlignedUniqueness_AfterPurge()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
// Same shape as test 1 — purge the Jan-2026 partition and then assert the
|
||||
// Same shape as test 1 — purge the January 2026 partition and then assert the
|
||||
// index state. RetentionDays is computed dynamically so the threshold always
|
||||
// lands near 2026-01-20 (see SeedOccurredAt()). Since WP2.2 the switch touches
|
||||
// lands near 2026-01-20 (see SeedOccurredAt()). The switch touches
|
||||
// no index at all: uniqueness rides the aligned clustered PK, so there is
|
||||
// nothing to drop before the SWITCH and nothing to rebuild after it.
|
||||
var siteId = "purge-uxidx-" + Guid.NewGuid().ToString("N").Substring(0, 8);
|
||||
@@ -370,14 +376,16 @@ SELECT
|
||||
// 3. EndToEnd_InsertIfNotExistsAsync_StillIdempotent_AfterPurge
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>Verifies the case: end To End, insert If Not Exists Async, still Idempotent, after Purge.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_InsertIfNotExistsAsync_StillIdempotent_AfterPurge()
|
||||
{
|
||||
Skip.IfNot(_fixture.Available, _fixture.SkipReason);
|
||||
|
||||
// Seed + purge the Jan-2026 row, THEN exercise InsertIfNotExistsAsync twice for
|
||||
// Seed + purge the January 2026 row, THEN exercise InsertIfNotExistsAsync twice for
|
||||
// a fresh recent EventId. The second call must be a no-op (duplicate-key collision
|
||||
// swallowed by the repository, per M2 Bundle A's race-fix) — which means the
|
||||
// swallowed by the repository, per the repository's race fix) — which means the
|
||||
// aligned clustered PK is still enforcing uniqueness as intended.
|
||||
// RetentionDays is computed dynamically so the threshold always lands near
|
||||
// 2026-01-20 (see SeedOccurredAt()).
|
||||
@@ -455,11 +463,11 @@ SELECT
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 4. PerChannelOverride_DeletesOnlyOverriddenChannelsOldRows (M5.5 T3)
|
||||
// 4. PerChannelOverride_DeletesOnlyOverriddenChannelsOldRows
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// M5.5 (T3): exercises <see cref="IAuditLogRepository.PurgeChannelOlderThanAsync"/>
|
||||
/// Exercises <see cref="IAuditLogRepository.PurgeChannelOlderThanAsync"/>
|
||||
/// directly against the real repository + fixture DB. Seeds, in the SAME partition,
|
||||
/// old + recent rows for an OVERRIDDEN channel (<c>ApiOutbound</c>) and old + recent
|
||||
/// rows for an UN-overridden channel (<c>DbOutbound</c>), then runs the per-channel
|
||||
@@ -474,6 +482,7 @@ SELECT
|
||||
/// the append-only writer-role DENYs do not bind (the role granularity is exercised
|
||||
/// in the repository/migration tests).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task PerChannelOverride_DeletesOnlyOverriddenChannelsOldRows()
|
||||
{
|
||||
@@ -538,17 +547,18 @@ SELECT
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// 5. SwitchOut_WithExplicitCommandTimeout_Succeeds (Task 4)
|
||||
// 5. SwitchOut_WithExplicitCommandTimeout_Succeeds
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Task 4 (arch-review 04, S2): proves the explicit-<see cref="TimeSpan"/> maintenance-timeout
|
||||
/// Proves the explicit-<see cref="TimeSpan"/> maintenance-timeout
|
||||
/// overload of <see cref="IAuditLogRepository.SwitchOutPartitionAsync"/> runs the real
|
||||
/// staging/switch batch to completion against SQL Server — the old row is purged, the kept row
|
||||
/// survives, and the returned sampled row-count reflects the switched partition. Exercising the
|
||||
/// timeout path end-to-end guards against a regression that only sets the timeout on the sample
|
||||
/// command and forgets the DDL batch (or vice versa).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task SwitchOut_WithExplicitCommandTimeout_Succeeds()
|
||||
{
|
||||
|
||||
+10
-2
@@ -20,7 +20,7 @@ using ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests.Migrations;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle H — end-to-end test wiring the full Audit Log #23 M2 sync-call pipeline:
|
||||
/// End-to-end test wiring the full sync-call audit pipeline:
|
||||
/// <see cref="FallbackAuditWriter"/> over a <see cref="SqliteAuditWriter"/> backed by
|
||||
/// an in-memory SQLite database; the <see cref="SiteAuditTelemetryActor"/> drains
|
||||
/// Pending rows and pushes them through a stub <see cref="ISiteStreamAuditClient"/>
|
||||
@@ -30,7 +30,7 @@ namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration;
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This is a <b>component-level</b> integration test, not a full Akka-cluster
|
||||
/// test (per the M2 brainstorm decision). The stub gRPC client short-circuits
|
||||
/// test. The stub gRPC client short-circuits
|
||||
/// the wire so we exercise the real telemetry actor, the real ingest actor, the
|
||||
/// real SQLite writer, and the real MSSQL repository — without standing up a
|
||||
/// Kestrel host or two-cluster topology.
|
||||
@@ -51,6 +51,8 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
{
|
||||
private readonly MsSqlMigrationFixture _fixture;
|
||||
|
||||
/// <summary>Initializes the test with the shared MSSQL migration fixture.</summary>
|
||||
/// <param name="fixture">The MSSQL fixture that supplies the central database.</param>
|
||||
public SyncCallEmissionEndToEndTests(MsSqlMigrationFixture fixture)
|
||||
{
|
||||
_fixture = fixture;
|
||||
@@ -129,6 +131,8 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
FastTelemetryOptions(),
|
||||
NullLogger<SiteAuditTelemetryActor>.Instance)));
|
||||
|
||||
/// <summary>Verifies that one written audit event reaches the central AuditLog within a reasonable time.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_OneWrittenEvent_Reaches_Central_AuditLog_Within_Reasonable_Time()
|
||||
{
|
||||
@@ -174,6 +178,8 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a gRPC stub error leaves the row pending and the next tick delivers it successfully.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_GrpcStubError_RowStays_Pending_NextTick_Succeeds()
|
||||
{
|
||||
@@ -246,6 +252,8 @@ public class SyncCallEmissionEndToEndTests : TestKit, IClassFixture<MsSqlMigrati
|
||||
}, TimeSpan.FromSeconds(15));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that submitting the same event twice results in only one central row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[SkippableFact]
|
||||
public async Task EndToEnd_DuplicateSubmit_OnlyOneCentralRow()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user