Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.AuditLog.Tests/Integration/Infrastructure/DirectActorSiteStreamAuditClient.cs
T
Joseph Doherty 513fa1aa16 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.
2026-10-07 15:05:49 -04:00

149 lines
6.1 KiB
C#

using Akka.Actor;
using ZB.MOM.WW.ScadaBridge.AuditLog.Site.Telemetry;
using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Integration.Infrastructure;
/// <summary>
/// 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 sync-call and
/// cached-call end-to-end suites can reuse it.
/// </summary>
/// <remarks>
/// <para>
/// The class deliberately mirrors the production <c>SiteStreamGrpcServer</c>
/// flow: decode each DTO into the in-process entity, Ask the central ingest
/// actor with the matching Akka command, and convert the Akka reply's accepted
/// id list into the proto <see cref="IngestAck"/> the telemetry actor / forwarder
/// expects. The actor wiring (single-repository vs. <see cref="IServiceProvider"/>
/// ctor) lives in the central ingest actor itself — this stub just routes the
/// command.
/// </para>
/// <para>
/// <see cref="FailNextCallCount"/> arms a deterministic number of failures
/// 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.
/// </para>
/// </remarks>
public sealed class DirectActorSiteStreamAuditClient : ISiteStreamAuditClient
{
private readonly IActorRef _ingestActor;
private int _failsRemaining;
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));
}
/// <summary>
/// When &gt; 0, the next <c>FailNextCallCount</c> invocations of either
/// RPC throw to simulate a gRPC error; after that count is exhausted, calls
/// succeed normally.
/// </summary>
public int FailNextCallCount
{
get => _failsRemaining;
set => _failsRemaining = value;
}
/// <summary>
/// Total successful + failed invocations of <see cref="IngestAuditEventsAsync"/>.
/// </summary>
public int CallCount => Volatile.Read(ref _callCount);
/// <summary>
/// Total successful + failed invocations of <see cref="IngestCachedTelemetryAsync"/>.
/// Separate counter so cached-call tests can assert dispatch independently of
/// any sync-call traffic going through the same stub.
/// </summary>
public int CachedTelemetryCallCount => Volatile.Read(ref _cachedTelemetryCallCount);
/// <inheritdoc />
public async Task<IngestAck> IngestAuditEventsAsync(AuditEventBatch batch, CancellationToken ct)
{
Interlocked.Increment(ref _callCount);
// Atomically consume one of the queued failures, if any. This lets the
// test arm a deterministic number of failures before the stub recovers.
if (Interlocked.Decrement(ref _failsRemaining) >= 0)
{
throw new InvalidOperationException("simulated gRPC failure for test");
}
// Clamp at -1 to keep the field bounded under many calls.
Interlocked.Exchange(ref _failsRemaining, -1);
// Decode the proto batch back into AuditEvent records — mirrors what
// SiteStreamGrpcServer does before dispatching to the ingest actor.
var events = new List<AuditEvent>(batch.Events.Count);
foreach (var dto in batch.Events)
{
events.Add(AuditEventDtoMapper.FromDto(dto));
}
// Ask the central actor; the reply carries the accepted EventIds.
var reply = await _ingestActor
.Ask<IngestAuditEventsReply>(
new IngestAuditEventsCommand(events),
TimeSpan.FromSeconds(10))
.ConfigureAwait(false);
var ack = new IngestAck();
foreach (var id in reply.AcceptedEventIds)
{
ack.AcceptedEventIds.Add(id.ToString());
}
return ack;
}
// 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);
if (Interlocked.Decrement(ref _failsRemaining) >= 0)
{
throw new InvalidOperationException("simulated gRPC failure for test");
}
Interlocked.Exchange(ref _failsRemaining, -1);
var entries = new List<CachedTelemetryEntry>(batch.Packets.Count);
foreach (var packet in batch.Packets)
{
var audit = AuditEventDtoMapper.FromDto(packet.AuditEvent);
var siteCall = SiteCallDtoMapper.FromDto(packet.Operational);
entries.Add(new CachedTelemetryEntry(audit, siteCall));
}
var reply = await _ingestActor
.Ask<IngestCachedTelemetryReply>(
new IngestCachedTelemetryCommand(entries),
TimeSpan.FromSeconds(10))
.ConfigureAwait(false);
var ack = new IngestAck();
foreach (var id in reply.AcceptedEventIds)
{
ack.AcceptedEventIds.Add(id.ToString());
}
return ack;
}
}