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:
+39
-7
@@ -53,6 +53,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
SourceScript: sourceScript,
|
||||
ParentExecutionId: parentExecutionId);
|
||||
|
||||
/// <summary>Verifies that a transient failure emits one Attempted row and no resolve row.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TransientFailure_EmitsOneAttemptedRow_NoResolve()
|
||||
{
|
||||
@@ -78,6 +80,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Null(packet.Operational.TerminalAtUtc);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a delivered attempt emits an Attempted row and a CachedResolve row with Delivered status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Delivered_EmitsAttemptedRow_AndCachedResolveDelivered()
|
||||
{
|
||||
@@ -104,6 +108,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(_id.Value, resolve.Audit.AsRow().CorrelationId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a permanent failure emits an Attempted row and a CachedResolve row with Parked status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task PermanentFailure_EmitsAttempted_AndCachedResolveParked()
|
||||
{
|
||||
@@ -123,6 +129,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal("Parked", captured[1].Operational.Status);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that parking after the maximum retries emits an Attempted row and a CachedResolve row with Parked status.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ParkedMaxRetries_EmitsAttempted_AndCachedResolveParked()
|
||||
{
|
||||
@@ -138,6 +146,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(AuditStatus.Parked, captured[1].Audit.AsRow().Status);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the database channel maps to the DbWriteCached kind and the DbOutbound channel.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DbChannel_MapsToDbWriteCachedKind_AndDbOutboundChannel()
|
||||
{
|
||||
@@ -157,6 +167,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(AuditChannel.DbOutbound, captured[1].Audit.AsRow().Channel);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the bridge does not throw when the telemetry forwarder throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task BridgeDoesNotThrow_WhenForwarderThrows()
|
||||
{
|
||||
@@ -170,6 +182,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
await sut.OnAttemptCompletedAsync(Ctx(CachedCallAttemptOutcome.Delivered));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the bridge populates provenance fields from the attempt context.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task BridgePopulatesProvenance_FromAttemptContext()
|
||||
{
|
||||
@@ -192,12 +206,14 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(_id.Value, captured.Audit.AsRow().CorrelationId);
|
||||
}
|
||||
|
||||
// ── Audit Log #23 (ExecutionId Task 4): ExecutionId / SourceScript ──
|
||||
// ── ExecutionId / SourceScript ──
|
||||
|
||||
/// <summary>Verifies that a retry-loop Attempted row carries the ExecutionId and SourceScript from the attempt context.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopAttemptedRow_CarriesExecutionIdAndSourceScript_FromContext()
|
||||
{
|
||||
// Task 4: the ExecutionId + SourceScript threaded through the S&F
|
||||
// The ExecutionId + SourceScript threaded through the S&F
|
||||
// buffer arrive on the CachedCallAttemptContext; the bridge must stamp
|
||||
// both onto the per-attempt ApiCallCached row (previously SourceScript
|
||||
// was hard-coded null with a "not threaded through S&F" comment).
|
||||
@@ -218,6 +234,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal("Plant.Pump42/OnTick", packet.Audit.AsRow().SourceScript);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop CachedResolve row carries the ExecutionId and SourceScript from the attempt context.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopCachedResolveRow_CarriesExecutionIdAndSourceScript_FromContext()
|
||||
{
|
||||
@@ -245,10 +263,12 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal("Plant.Tank/OnAlarm", attempted.Audit.AsRow().SourceScript);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop row leaves ExecutionId and SourceScript null when the context has none.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopRow_NullExecutionIdAndSourceScript_RemainNull()
|
||||
{
|
||||
// Back-compat: a pre-Task-4 buffered row has no ExecutionId /
|
||||
// Back-compat: a buffered row written before ExecutionId was threaded through has no ExecutionId /
|
||||
// SourceScript; the bridge must leave the audit row's fields null
|
||||
// rather than throwing.
|
||||
CachedCallTelemetry? captured = null;
|
||||
@@ -263,12 +283,14 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Null(captured.Audit.AsRow().SourceScript);
|
||||
}
|
||||
|
||||
// ── Audit Log #23 (ParentExecutionId Task 6): ParentExecutionId ──
|
||||
// ── ParentExecutionId ──
|
||||
|
||||
/// <summary>Verifies that a retry-loop Attempted row carries the ParentExecutionId from the attempt context.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopAttemptedRow_CarriesParentExecutionId_FromContext()
|
||||
{
|
||||
// Task 6: the ParentExecutionId threaded through the S&F buffer (the
|
||||
// The ParentExecutionId threaded through the S&F buffer (the
|
||||
// inbound-API run that spawned the originating script) arrives on the
|
||||
// CachedCallAttemptContext; the bridge must stamp it onto the
|
||||
// per-attempt ApiCallCached row beside ExecutionId.
|
||||
@@ -287,6 +309,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(parentExecutionId, packet.Audit.AsRow().ParentExecutionId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop CachedResolve row carries the ParentExecutionId from the attempt context.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopCachedResolveRow_CarriesParentExecutionId_FromContext()
|
||||
{
|
||||
@@ -312,6 +336,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Equal(parentExecutionId, attempted.Audit.AsRow().ParentExecutionId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop row leaves ParentExecutionId null when the context has none.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopRow_NullParentExecutionId_RemainsNull()
|
||||
{
|
||||
@@ -329,12 +355,14 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Null(captured!.Audit.AsRow().ParentExecutionId);
|
||||
}
|
||||
|
||||
// ── SourceNode-stamping (Task 14) ──
|
||||
// ── SourceNode stamping ──
|
||||
|
||||
/// <summary>Verifies that a retry-loop row stamps the source node name from the node identity provider.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopRow_StampsSourceNode_FromNodeIdentityProvider()
|
||||
{
|
||||
// SourceNode-stamping (Task 14): when an INodeIdentityProvider is
|
||||
// SourceNode stamping: when an INodeIdentityProvider is
|
||||
// wired the bridge stamps the local node name (node-a/node-b) onto
|
||||
// the SiteCallOperational.SourceNode column of every emitted packet.
|
||||
var nodeIdentity = Substitute.For<INodeIdentityProvider>();
|
||||
@@ -353,6 +381,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.All(captured, p => Assert.Equal("node-a", p.Operational.SourceNode));
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop row leaves the source node null when no node identity provider is configured.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopRow_NoNodeIdentityProvider_LeavesSourceNodeNull()
|
||||
{
|
||||
@@ -371,6 +401,8 @@ public class CachedCallLifecycleBridgeTests
|
||||
Assert.Null(packet.Operational.SourceNode);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a retry-loop row leaves the source node null when the node identity has a null node name.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RetryLoopRow_NodeIdentityWithNullNodeName_LeavesSourceNodeNull()
|
||||
{
|
||||
|
||||
+23
-3
@@ -111,6 +111,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
UpdatedAtUtc: _now,
|
||||
TerminalAtUtc: _now));
|
||||
|
||||
/// <summary>Verifies that forwarding a submit packet writes an audit event and records the enqueue.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_Submit_WritesAuditEvent_AndRecordsEnqueue()
|
||||
{
|
||||
@@ -130,7 +132,7 @@ public class CachedCallTelemetryForwarderTests
|
||||
// Tracking row: insert-if-not-exists with kind discriminator.
|
||||
// Default CreateSut() does NOT supply an INodeIdentityProvider, so the
|
||||
// forwarder passes null sourceNode to RecordEnqueueAsync (legacy / test
|
||||
// host behaviour). The Task 14 stamping path is covered by the
|
||||
// host behaviour). The stamping path is covered by the
|
||||
// ForwardAsync_Submit_StampsSourceNode_FromNodeIdentityProvider test
|
||||
// below.
|
||||
await _tracking.Received(1).RecordEnqueueAsync(
|
||||
@@ -147,6 +149,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
default, default!, default, default, default);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that forwarding an attempted packet writes an audit event and records the attempt.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_Attempted_WritesAuditEvent_AndRecordsAttempt()
|
||||
{
|
||||
@@ -170,6 +174,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
default, default!, default, default, default);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that forwarding a resolve packet writes an audit event and records the terminal state.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_Resolve_WritesAuditEvent_AndRecordsTerminal()
|
||||
{
|
||||
@@ -196,6 +202,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
// ── 2026-09-06: the audit row is enqueued, never awaited to commit ─────
|
||||
// Design: docs/plans/2026-09-06-tracking-store-async-writes-design.md
|
||||
|
||||
/// <summary>Verifies that forwarding returns while the audit commit is still pending.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_ReturnsWhileTheAuditCommitIsStillPending()
|
||||
{
|
||||
@@ -216,6 +224,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
_id, "Delivered", Arg.Any<string?>(), Arg.Any<int?>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a later audit commit fault is swallowed rather than surfaced to the caller.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_AuditCommitFaultsLater_IsSwallowed()
|
||||
{
|
||||
@@ -234,6 +244,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
Arg.Any<string?>(), Arg.Any<string?>(), Arg.Any<string?>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing audit writer is logged and the exception does not propagate.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_WriterThrows_Logs_DoesNotPropagate()
|
||||
{
|
||||
@@ -257,6 +269,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a throwing tracking store is logged and the exception does not propagate.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_TrackingStoreThrows_Logs_DoesNotPropagate()
|
||||
{
|
||||
@@ -279,6 +293,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
Arg.Any<AuditEvent>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that forwarding a null packet throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_NullPacket_Throws()
|
||||
{
|
||||
@@ -288,12 +304,14 @@ public class CachedCallTelemetryForwarderTests
|
||||
() => sut.ForwardAsync(null!, CancellationToken.None));
|
||||
}
|
||||
|
||||
// ── SourceNode-stamping (Task 14) ──
|
||||
// ── SourceNode stamping ──
|
||||
|
||||
/// <summary>Verifies that a submit packet is stamped with the source node from the node identity provider.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_Submit_StampsSourceNode_FromNodeIdentityProvider()
|
||||
{
|
||||
// SourceNode-stamping (Task 14): when an INodeIdentityProvider is
|
||||
// SourceNode stamping: when an INodeIdentityProvider is
|
||||
// wired the forwarder must stamp its NodeName onto the
|
||||
// RecordEnqueueAsync sourceNode parameter so the tracking row
|
||||
// captures the originating node (node-a/node-b).
|
||||
@@ -315,6 +333,8 @@ public class CachedCallTelemetryForwarderTests
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a node identity with a null node name results in a null source node.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ForwardAsync_Submit_NodeIdentityNullNodeName_PassesNullSourceNode()
|
||||
{
|
||||
|
||||
+10
-4
@@ -4,15 +4,17 @@ using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site.Telemetry;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle E E1 tests for <see cref="NoOpSiteStreamAuditClient"/>. The NoOp
|
||||
/// client is the default <see cref="ISiteStreamAuditClient"/> binding until M6
|
||||
/// delivers the gRPC-backed implementation; both <c>IngestAuditEventsAsync</c>
|
||||
/// (M2) and <c>IngestCachedTelemetryAsync</c> (M3) must return an empty ack
|
||||
/// Tests for <see cref="NoOpSiteStreamAuditClient"/>. The NoOp
|
||||
/// client is the default <see cref="ISiteStreamAuditClient"/> binding until the
|
||||
/// gRPC-backed implementation is delivered; both <c>IngestAuditEventsAsync</c>
|
||||
/// and <c>IngestCachedTelemetryAsync</c> must return an empty ack
|
||||
/// (no rows flipped to Forwarded) without throwing or partially handling the
|
||||
/// batch.
|
||||
/// </summary>
|
||||
public class NoOpSiteStreamAuditClientTests
|
||||
{
|
||||
/// <summary>Verifies that ingesting an empty cached-telemetry batch returns an empty acknowledgement.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetryAsync_EmptyBatch_ReturnsEmptyAck()
|
||||
{
|
||||
@@ -25,6 +27,8 @@ public class NoOpSiteStreamAuditClientTests
|
||||
Assert.Empty(ack.AcceptedEventIds);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ingesting a populated batch still returns an empty acknowledgement, because the no-op client forwards nothing.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetryAsync_PopulatedBatch_ReturnsEmptyAck()
|
||||
{
|
||||
@@ -47,6 +51,8 @@ public class NoOpSiteStreamAuditClientTests
|
||||
Assert.Empty(ack.AcceptedEventIds);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ingesting a null batch throws an <see cref="ArgumentNullException"/>.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetryAsync_NullBatch_Throws()
|
||||
{
|
||||
|
||||
+31
-5
@@ -55,7 +55,7 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
(IOperationTrackingStore?)null)));
|
||||
|
||||
/// <summary>
|
||||
/// AuditLog-001: builds an actor with the optional
|
||||
/// Builds an actor with the optional
|
||||
/// <see cref="IOperationTrackingStore"/> wired in so the cached-drain
|
||||
/// scheduler is armed alongside the audit-only drain. Used by the new
|
||||
/// cached-drain regression tests below.
|
||||
@@ -86,6 +86,9 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
return ack;
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a drain of large rows stays within the wire budget and does not acknowledge rows that were not sent.</summary>
|
||||
/// <param name="cached"><c>true</c> to drain cached-call rows, <c>false</c> to drain audit-only rows.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
@@ -126,6 +129,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Assert.Equal(256, sent.Count);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a drain of 50 pending rows sends one batch of 50 and then marks the rows as forwarded.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drain_With_50PendingRows_Sends_OneBatch_Of_50_Then_FlipsToForwarded()
|
||||
{
|
||||
@@ -166,6 +171,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that rows stay pending when the gRPC call throws and the next drain retries them.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drain_GrpcThrows_RowsStayPending_NextDrainRetries()
|
||||
{
|
||||
@@ -206,6 +213,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Assert.True(calls >= 2, $"Expected at least 2 client calls (1 failure + 1 retry); saw {calls}");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a drain with no pending rows schedules the next drain at the idle interval without calling the client.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drain_ZeroPending_SchedulesAtIdleInterval_NoClientCall()
|
||||
{
|
||||
@@ -230,6 +239,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Assert.InRange(readCalls, 1, 2);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a drain that finds pending rows schedules the next drain at the busy interval.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drain_NonZeroPending_SchedulesAtBusyInterval()
|
||||
{
|
||||
@@ -254,6 +265,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
$"Expected ≥2 pushes within 3s when busy=1s; saw {pushCalls}");
|
||||
}
|
||||
|
||||
/// <summary>Verifies that only the event ids the central ack accepted are marked as forwarded.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drain_AcceptedEventIdsSubset_OnlyMarksAccepted()
|
||||
{
|
||||
@@ -292,7 +305,7 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// AuditLog-001: combined-telemetry cached-drain regression tests. Verify
|
||||
// Combined-telemetry cached-drain regression tests. Verify
|
||||
// that the production wiring of the previously-unreachable cached transport
|
||||
// routes cached rows through ReadPendingCachedTelemetryAsync +
|
||||
// IngestCachedTelemetryAsync (and NOT IngestAuditEventsAsync), and that
|
||||
@@ -305,7 +318,7 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Guid? eventId = null,
|
||||
Guid? correlationId = null,
|
||||
string sourceSiteId = "site-1") =>
|
||||
// C3 (Task 2.5): canonical record via the shared factory; ForwardState is
|
||||
// Canonical record via the shared factory; ForwardState is
|
||||
// no longer a record field (the SQLite shim defaults it on INSERT).
|
||||
ScadaBridgeAuditEventFactory.Create(
|
||||
channel: AuditChannel.ApiOutbound,
|
||||
@@ -335,12 +348,14 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
SourceScript: "script-1",
|
||||
SourceNode: "node-a");
|
||||
|
||||
/// <summary>Verifies that cached rows are sent through the cached-telemetry RPC rather than the audit-events RPC.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CachedDrain_CachedRows_RouteThrough_IngestCachedTelemetry_NotIngestAuditEvents()
|
||||
{
|
||||
// Arrange — three cached audit rows on the cached queue, each with a
|
||||
// matching tracking snapshot. The audit-only queue is empty (those
|
||||
// rows are excluded by ReadPendingAsync after AuditLog-001).
|
||||
// rows are excluded by ReadPendingAsync after the combined-telemetry change).
|
||||
var cachedRows = new[]
|
||||
{
|
||||
NewCachedEvent(AuditKind.CachedSubmit),
|
||||
@@ -401,6 +416,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Assert.Equal(expectedIds, emittedEventIds);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cached row with no tracking snapshot past the grace period is abandoned while a valid row still flows.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CachedDrain_OrphanRow_PastGrace_IsAbandoned_AndTheValidRowStillFlows()
|
||||
{
|
||||
@@ -494,6 +511,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Assert.Equal(valid.EventId.ToString(), capturedBatch.Packets[0].AuditEvent.EventId);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a cached row with no tracking snapshot inside the grace period is retried rather than abandoned.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CachedDrain_OrphanRow_InsideGrace_IsRetried_NotAbandoned()
|
||||
{
|
||||
@@ -536,6 +555,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Arg.Any<IReadOnlyList<Guid>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a full batch of unresolvable cached rows does not starve the rest of the queue.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CachedDrain_FullBatchOfUnresolvableRows_DoesNotStarveTheQueue()
|
||||
{
|
||||
@@ -575,6 +596,8 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Arg.Any<CachedTelemetryBatch>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a tracking-store failure does not cause the cached row to be abandoned.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CachedDrain_TrackingStoreThrow_DoesNotAbandonTheRow()
|
||||
{
|
||||
@@ -608,13 +631,15 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
Arg.Any<IReadOnlyList<Guid>>(), Arg.Any<CancellationToken>());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that audit-only rows still drain when the cached drain is disabled.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task AuditOnlyDrain_StillFlows_When_CachedDrain_IsDisabled()
|
||||
{
|
||||
// Arrange — ordinary (non-cached) audit rows on the audit-only queue;
|
||||
// the actor is constructed WITHOUT a tracking store so the cached
|
||||
// scheduler is never armed. Regression guard against the audit-only
|
||||
// drain regressing during the AuditLog-001 refactor.
|
||||
// drain regressing during the combined-telemetry refactor.
|
||||
var rows = Enumerable.Range(0, 3).Select(_ => NewEvent()).ToList();
|
||||
_queue.ReadPendingAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
|
||||
.Returns(
|
||||
@@ -655,6 +680,7 @@ public class SiteAuditTelemetryActorTests : TestKit
|
||||
/// test in this class masks the bug by returning already-completed tasks
|
||||
/// from the mocks, which keeps the continuations on the actor thread.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drains_Whose_Awaits_Complete_Off_The_Actor_Thread_Keep_Draining_Without_Crashing()
|
||||
{
|
||||
|
||||
+13
-1
@@ -61,6 +61,8 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
UpdatedAtUtc = Timestamp.FromDateTime(new DateTime(2026, 5, 20, 10, 0, 0, DateTimeKind.Utc)),
|
||||
};
|
||||
|
||||
/// <summary>Verifies that a full acknowledgement maps all accepted event ids onto the returned ack.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestAuditEventsAsync_FullAck_MapsAllAcceptedIdsOntoAck()
|
||||
{
|
||||
@@ -88,6 +90,8 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
ack.AcceptedEventIds.ToHashSet());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a partial acknowledgement lists only the accepted event ids on the returned ack.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestAuditEventsAsync_PartialAck_OnlyAcceptedIdsAppearOnAck()
|
||||
{
|
||||
@@ -110,6 +114,8 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
ack.AcceptedEventIds.ToHashSet());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an Ask timeout throws so the drain loop keeps its rows pending.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestAuditEventsAsync_AskTimeout_Throws_SoDrainLoopKeepsRowsPending()
|
||||
{
|
||||
@@ -127,6 +133,8 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => task);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a faulted reply causes the ingest call to throw.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestAuditEventsAsync_FaultedReply_Throws()
|
||||
{
|
||||
@@ -136,13 +144,15 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
var task = sut.IngestAuditEventsAsync(BatchOf(new[] { NewEvent() }), CancellationToken.None);
|
||||
probe.ExpectMsg<IngestAuditEventsCommand>(TimeSpan.FromSeconds(3));
|
||||
|
||||
// A Status.Failure from central (Task 1: central does not swallow an
|
||||
// A Status.Failure from central (central does not swallow an
|
||||
// ingest fault into an empty ack) must propagate as a thrown exception.
|
||||
probe.Reply(new Status.Failure(new InvalidOperationException("central ingest faulted")));
|
||||
|
||||
await Assert.ThrowsAnyAsync<Exception>(() => task);
|
||||
}
|
||||
|
||||
/// <summary>Verifies that cached telemetry is routed as a command and the reply is mapped back.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetryAsync_RoutesCommand_AndMapsReply()
|
||||
{
|
||||
@@ -178,6 +188,8 @@ public class SiteCommunicationAuditClientTests : TestKit
|
||||
ack.AcceptedEventIds.ToHashSet());
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an Ask timeout during cached telemetry ingestion throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetryAsync_AskTimeout_Throws()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user