513fa1aa16
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.
143 lines
5.7 KiB
C#
143 lines
5.7 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using ZB.MOM.WW.ScadaBridge.AuditLog.Site;
|
|
using ZB.MOM.WW.Audit;
|
|
using IAuditWriter = ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services.IAuditWriter;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
|
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
|
|
|
|
namespace ZB.MOM.WW.ScadaBridge.AuditLog.Tests.Site;
|
|
|
|
/// <summary>
|
|
/// Tests for <see cref="FallbackAuditWriter"/> — composes the
|
|
/// primary <see cref="SqliteAuditWriter"/>, the drop-oldest
|
|
/// <see cref="RingBufferFallback"/>, and an
|
|
/// <see cref="IAuditWriteFailureCounter"/> health counter.
|
|
/// </summary>
|
|
public class FallbackAuditWriterTests
|
|
{
|
|
private static AuditEvent NewEvent(string? target = null) => ScadaBridgeAuditEventFactory.Create(
|
|
eventId: Guid.NewGuid(),
|
|
occurredAtUtc: DateTime.UtcNow,
|
|
channel: AuditChannel.ApiOutbound,
|
|
kind: AuditKind.ApiCall,
|
|
status: AuditStatus.Delivered,
|
|
target: target);
|
|
|
|
/// <summary>Flip-switch primary writer mock.</summary>
|
|
private sealed class FlipSwitchPrimary : IAuditWriter
|
|
{
|
|
/// <summary>Gets or sets a value indicating whether the next write fails.</summary>
|
|
public bool FailNext { get; set; }
|
|
/// <summary>Gets the events the primary has accepted.</summary>
|
|
public List<AuditEvent> Written { get; } = new();
|
|
|
|
/// <inheritdoc />
|
|
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default)
|
|
{
|
|
if (FailNext)
|
|
{
|
|
return Task.FromException(new InvalidOperationException("primary down"));
|
|
}
|
|
Written.Add(evt);
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|
|
/// <summary>Verifies that when the primary writer throws, the event lands in the ring buffer and the call still returns successfully.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task WriteAsync_PrimaryThrows_EventLandsInRing_CallReturnsSuccess()
|
|
{
|
|
var primary = new FlipSwitchPrimary { FailNext = true };
|
|
var ring = new RingBufferFallback(capacity: 16);
|
|
var counter = Substitute.For<IAuditWriteFailureCounter>();
|
|
|
|
var fallback = new FallbackAuditWriter(primary, ring, counter, NullLogger<FallbackAuditWriter>.Instance);
|
|
|
|
var evt = NewEvent("doomed");
|
|
// Must NOT throw — audit failures are always swallowed at this layer.
|
|
await fallback.WriteAsync(evt);
|
|
|
|
Assert.Equal(1, ring.Count);
|
|
counter.Received(1).Increment();
|
|
}
|
|
|
|
/// <summary>Verifies that after the primary recovers, the ring buffer drains in FIFO order on the next write.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task WriteAsync_PrimaryRecovers_RingDrains_InFIFOOrder_OnNextWrite()
|
|
{
|
|
var primary = new FlipSwitchPrimary { FailNext = true };
|
|
var ring = new RingBufferFallback(capacity: 16);
|
|
var counter = Substitute.For<IAuditWriteFailureCounter>();
|
|
|
|
var fallback = new FallbackAuditWriter(primary, ring, counter, NullLogger<FallbackAuditWriter>.Instance);
|
|
|
|
var failed = new[] { NewEvent("a"), NewEvent("b"), NewEvent("c") };
|
|
foreach (var e in failed)
|
|
{
|
|
await fallback.WriteAsync(e);
|
|
}
|
|
|
|
Assert.Equal(3, ring.Count);
|
|
|
|
// Primary recovers; the very next successful write should drain the
|
|
// ring in FIFO order through the primary.
|
|
primary.FailNext = false;
|
|
var trigger = NewEvent("trigger");
|
|
await fallback.WriteAsync(trigger);
|
|
|
|
Assert.Equal(0, ring.Count);
|
|
// Order: the triggering event reaches the primary first (that's the
|
|
// signal the primary has recovered), then the backlog drains in FIFO
|
|
// submission order behind it.
|
|
Assert.Equal(4, primary.Written.Count);
|
|
Assert.Equal("trigger", primary.Written[0].Target);
|
|
Assert.Equal("a", primary.Written[1].Target);
|
|
Assert.Equal("b", primary.Written[2].Target);
|
|
Assert.Equal("c", primary.Written[3].Target);
|
|
}
|
|
|
|
/// <summary>Verifies that the ring buffer stays empty when the primary writer always succeeds.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task WriteAsync_PrimaryAlwaysSucceeds_Ring_StaysEmpty()
|
|
{
|
|
var primary = new FlipSwitchPrimary();
|
|
var ring = new RingBufferFallback(capacity: 16);
|
|
var counter = Substitute.For<IAuditWriteFailureCounter>();
|
|
|
|
var fallback = new FallbackAuditWriter(primary, ring, counter, NullLogger<FallbackAuditWriter>.Instance);
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
await fallback.WriteAsync(NewEvent());
|
|
}
|
|
|
|
Assert.Equal(0, ring.Count);
|
|
Assert.Equal(10, primary.Written.Count);
|
|
counter.DidNotReceive().Increment();
|
|
}
|
|
|
|
/// <summary>Verifies that the failure counter is incremented once per primary failure.</summary>
|
|
/// <returns>A task that represents the asynchronous operation.</returns>
|
|
[Fact]
|
|
public async Task WriteAsync_FailureCounter_Incremented_Per_PrimaryFailure()
|
|
{
|
|
var primary = new FlipSwitchPrimary { FailNext = true };
|
|
var ring = new RingBufferFallback(capacity: 16);
|
|
var counter = Substitute.For<IAuditWriteFailureCounter>();
|
|
|
|
var fallback = new FallbackAuditWriter(primary, ring, counter, NullLogger<FallbackAuditWriter>.Instance);
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
await fallback.WriteAsync(NewEvent());
|
|
}
|
|
|
|
counter.Received(5).Increment();
|
|
}
|
|
}
|