docs(tests): fix CommentChecker findings across tests/ — 13,392 → 0 (repo-wide scan clean)

Comment/XML-doc changes only:
- add summaries to ~10k test methods, fixtures, fakes and helpers
  (+ <param> on theories, <returns> on async Task methods,
  <inheritdoc/> on interface implementations the checker can resolve)
- strip ~2.6k project-tracking references (WP*, P*-*, row-NN, Task N,
  #NN, Component-0NN) from comments, keeping the explanatory text;
  reword false positives in comments only
- real docs on fakes implementing BCL/library interfaces
  (IDisposable, ILogger, IOptionsMonitor, IAsyncLifetime)
- reword the last 5 src/ false positives (CWE-1236, failures-1,
  year-0001, M365)

Only non-comment edits: four one-line nested classes split across lines
so members can carry docs. Build 0 errors / 0 warnings.
This commit is contained in:
Joseph Doherty
2026-10-07 15:05:49 -04:00
parent 0ffd4bdbea
commit 513fa1aa16
998 changed files with 17896 additions and 2395 deletions
@@ -8,7 +8,7 @@ using ZB.MOM.WW.ScadaBridge.TestSupport;
namespace ZB.MOM.WW.ScadaBridge.StoreAndForward.Tests;
/// <summary>
/// WP-10/12/13/14: Tests for the StoreAndForwardService retry engine and management.
/// Tests for the StoreAndForwardService retry engine and management.
///
/// Register row 32: several facts here call <c>StartAsync</c>, which writes the
/// process-global queue-depth provider slot, so this class joins
@@ -24,6 +24,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
private readonly StoreAndForwardOptions _options;
private readonly List<TestLocalDb> _extraLocalDbs = new();
/// <summary>Creates a temporary local database, storage and service with a zero retry interval for the tests.</summary>
public StoreAndForwardServiceTests()
{
_localDb = TestLocalDb.CreateTemp("SvcTests");
@@ -41,10 +42,15 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
_storage, _options, NullLogger<StoreAndForwardService>.Instance);
}
/// <summary>Prepares the fixture before the tests run.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public async Task InitializeAsync() => await _storage.InitializeAsync();
/// <summary>Releases the resources held by this fixture asynchronously.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task DisposeAsync() => Task.CompletedTask;
/// <summary>Releases the resources held by this test class.</summary>
public void Dispose()
{
DisposeLocalDb(_localDb);
@@ -81,8 +87,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
return new StoreAndForwardService(storage, options, NullLogger<StoreAndForwardService>.Instance);
}
// ── WP-10: Immediate delivery ──
// ── Immediate delivery ──
/// <summary>Verifies that an immediately delivered message is accepted and not buffered.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_ImmediateDeliverySuccess_ReturnsAcceptedNotBuffered()
{
@@ -97,6 +105,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.False(result.WasBuffered);
}
/// <summary>Verifies that a permanent failure on immediate delivery returns a result that is not accepted.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_PermanentFailure_ReturnsNotAccepted()
{
@@ -111,6 +121,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.False(result.WasBuffered);
}
/// <summary>Verifies that a transient failure on immediate delivery buffers the message for retry.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_TransientFailure_BuffersForRetry()
{
@@ -127,11 +139,13 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
var msg = await _storage.GetMessageByIdAsync(result.MessageId);
Assert.NotNull(msg);
Assert.Equal(StoreAndForwardMessageStatus.Pending, msg!.Status);
// StoreAndForward-003: RetryCount counts sweep retries only; the immediate
// RetryCount counts sweep retries only; the immediate
// attempt is attempt 0, so a freshly buffered message has RetryCount 0.
Assert.Equal(0, msg.RetryCount);
}
/// <summary>Verifies that a message enqueued with no registered handler is buffered for later.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_NoHandler_BuffersForLater()
{
@@ -143,8 +157,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.True(result.WasBuffered);
}
// ── WP-10: Retry engine ──
// ── Retry engine ──
/// <summary>Verifies that a successful retry removes the message from the buffer.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryPendingMessagesAsync_SuccessfulRetry_RemovesMessage()
{
@@ -167,6 +183,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Null(msg);
}
/// <summary>Verifies that a message that reaches its maximum retries is parked.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryPendingMessagesAsync_MaxRetriesReached_ParksMessage()
{
@@ -177,7 +195,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
StoreAndForwardCategory.ExternalSystem, "api", """{}""",
maxRetries: 2);
// StoreAndForward-003: MaxRetries bounds sweep retries (not the immediate
// MaxRetries bounds sweep retries (not the immediate
// attempt), so a message with MaxRetries=2 needs two retry sweeps to park.
await _service.RetryPendingMessagesAsync();
var afterFirst = await _storage.GetMessageByIdAsync(result.MessageId);
@@ -190,8 +208,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(StoreAndForwardMessageStatus.Parked, msg!.Status);
}
// ── StoreAndForward-003: retry-count accounting ──
// ── Retry-count accounting ──
/// <summary>Verifies that with a maximum of one retry exactly one retry is performed before the message is parked.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryPendingMessagesAsync_MaxRetriesOne_PerformsExactlyOneRetryBeforeParking()
{
@@ -218,12 +238,14 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(1, msg.RetryCount); // one sweep retry recorded
}
// ── StoreAndForward-005: sweep-vs-management race hardening ──
// ── Sweep-vs-management race hardening ──
/// <summary>Verifies that a sweep park write is skipped when the message status changed during delivery.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryMessageAsync_StatusChangedDuringDelivery_SweepParkWriteIsSkipped()
{
// StoreAndForward-005: the retry sweep's state-changing writes must be
// The retry sweep's state-changing writes must be
// conditional on the status it observed, so a concurrent operator action that
// moved the row out of Pending (e.g. between the sweep's snapshot load and its
// park write) is not silently overwritten by the sweep's stale view.
@@ -261,6 +283,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal("operator/other writer", msg.LastError);
}
/// <summary>Verifies that a permanent failure during a retry parks the message.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryPendingMessagesAsync_PermanentFailureOnRetry_ParksMessage()
{
@@ -283,8 +307,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(StoreAndForwardMessageStatus.Parked, msg!.Status);
}
// ── WP-12: Parked message management ──
// ── Parked message management ──
/// <summary>Verifies that retrying a parked message moves it back to the queue.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryParkedMessageAsync_MovesBackToQueue()
{
@@ -309,9 +335,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
}
/// <summary>
/// StoreAndForward-017: the Retry activity-log entry must carry the parked
/// The Retry activity-log entry must carry the parked
/// message's true category, not a hard-coded ExternalSystem.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryParkedMessageAsync_ActivityUsesMessageRealCategory()
{
@@ -336,9 +363,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
}
/// <summary>
/// StoreAndForward-017: the Discard activity-log entry must carry the parked
/// The Discard activity-log entry must carry the parked
/// message's true category, not a hard-coded ExternalSystem.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DiscardParkedMessageAsync_ActivityUsesMessageRealCategory()
{
@@ -362,6 +390,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(new[] { StoreAndForwardCategory.CachedDbWrite }, categories);
}
/// <summary>Verifies that discarding a parked message removes it permanently.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task DiscardParkedMessageAsync_PermanentlyRemoves()
{
@@ -381,6 +411,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Null(msg);
}
/// <summary>Verifies that fetching parked messages returns paginated results.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GetParkedMessagesAsync_ReturnsPaginatedResults()
{
@@ -403,8 +435,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.True(total >= 3);
}
// ── WP-13: Messages survive instance deletion ──
// ── Messages survive instance deletion ──
/// <summary>Verifies that buffered messages survive the deletion of their instance.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task MessagesForInstance_SurviveAfterDeletion()
{
@@ -420,8 +454,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(2, count);
}
// ── WP-14: Health metrics ──
// ── Health metrics ──
/// <summary>Verifies that the buffer depth is reported correctly.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task GetBufferDepthAsync_ReturnsCorrectDepth()
{
@@ -439,6 +475,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.True(depth.GetValueOrDefault(StoreAndForwardCategory.Notification) >= 1);
}
/// <summary>Verifies that the activity event is raised when a message is enqueued.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task OnActivity_RaisedOnEnqueue()
{
@@ -453,6 +491,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Contains("Delivered", activities);
}
/// <summary>Verifies that the activity event is raised when a message is buffered.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task OnActivity_RaisedOnBuffer()
{
@@ -467,12 +507,14 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Contains("Queued", activities);
}
// ── StoreAndForward-009: faulting activity subscriber must not corrupt delivery ──
// ── Faulting activity subscriber must not corrupt delivery ──
/// <summary>Verifies that a faulting activity subscriber does not stop an immediately delivered message from being reported as delivered.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_ImmediateDeliverySuccess_FaultingActivitySubscriber_StillReportsDelivered()
{
// StoreAndForward-009: a throwing OnActivity subscriber (e.g. the site event
// A throwing OnActivity subscriber (e.g. the site event
// log) must not be misclassified as a transient delivery failure. Pre-fix the
// subscriber's exception escaped RaiseActivity, was caught by EnqueueAsync's
// transient-failure handler, and a successfully delivered message was buffered.
@@ -490,10 +532,12 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Null(msg); // nothing left in the buffer
}
/// <summary>Verifies that a faulting activity subscriber does not increment the retry count of a successful retry.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetryMessageAsync_FaultingActivitySubscriber_DoesNotIncrementRetryCount()
{
// StoreAndForward-009: a throwing subscriber raised after a successful retry
// A throwing subscriber raised after a successful retry
// delivery must not be caught by the retry-failure handler and counted as a
// transient failure.
var result = await _service.EnqueueAsync(
@@ -511,8 +555,10 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Null(msg);
}
// ── WP-10: Per-source-entity retry settings ──
// ── Per-source-entity retry settings ──
/// <summary>Verifies that custom retry settings supplied at enqueue time are respected.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_CustomRetrySettings_Respected()
{
@@ -531,6 +577,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
// ── attemptImmediateDelivery: false — caller already attempted delivery ──
/// <summary>Verifies that enqueuing with immediate delivery disabled buffers the message without invoking the handler.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task EnqueueAsync_AttemptImmediateDeliveryFalse_BuffersWithoutInvokingHandler()
{
@@ -550,19 +598,20 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
var msg = await _storage.GetMessageByIdAsync(result.MessageId);
Assert.NotNull(msg);
Assert.Equal(StoreAndForwardMessageStatus.Pending, msg!.Status);
// StoreAndForward-003: the caller's own attempt is attempt 0; RetryCount
// The caller's own attempt is attempt 0; RetryCount
// counts only sweep retries, so a freshly buffered message has RetryCount 0.
Assert.Equal(0, msg.RetryCount);
}
// ─── StoreAndForward-024: StopAsync waits for the in-flight sweep ───
// ─── StopAsync waits for the in-flight sweep ───
/// <summary>
/// StoreAndForward-024: <see cref="StoreAndForwardService.StopAsync"/> must
/// <see cref="StoreAndForwardService.StopAsync"/> must
/// not return until any in-flight retry sweep has completed (or the bounded
/// shutdown timeout fires). Pre-fix it disposed the timer and returned
/// immediately, leaving a mid-flight sweep touching disposed dependencies.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StopAsync_AwaitsInFlightRetrySweep_BeforeReturning()
{
@@ -622,10 +671,12 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
"Sweep handler must have finished before StopAsync returned.");
}
// ── Task 3 (arch review 02, Stability #2): active-node delivery gate ──
// ── Active-node delivery gate ──
// The standby site node applies replicated buffer operations but must never
// deliver; the sweep runs only when the gate reports the node is active.
/// <summary>Verifies that the retry sweep skips delivery while the delivery gate reports standby.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_SkipsDelivery_WhenDeliveryGateReportsStandby()
{
@@ -644,6 +695,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(StoreAndForwardMessageStatus.Pending, row!.Status); // row untouched
}
/// <summary>Verifies that the retry sweep resumes delivery when the gate flips to active.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_ResumesDelivery_WhenGateFlipsActive()
{
@@ -664,6 +717,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(1, delivered);
}
/// <summary>Verifies that the retry sweep treats a throwing gate as standby.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_TreatsThrowingGateAsStandby()
{
@@ -678,11 +733,13 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(0, delivered);
}
// ── Task 8 (arch review 02, Performance): per-target short-circuit ──
// ── Per-target short-circuit ──
// After the first transient failure to a (category, target) the remaining
// messages for that pair are skipped this sweep — N dead-target messages
// cost one timeout, not N. Skipped rows keep their RetryCount.
/// <summary>Verifies that after the first transient failure for a target the sweep short-circuits that target's remaining messages.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_ShortCircuitsTarget_AfterFirstTransientFailure()
{
@@ -706,6 +763,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(1, attemptsByTarget["healthy-target"]); // healthy lane unaffected
}
/// <summary>Verifies that messages skipped by the sweep do not accrue a retry count.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_SkippedMessages_DoNotAccrueRetryCount()
{
@@ -726,10 +785,12 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
Assert.Equal(new[] { 0, 1 }, new[] { r1, r2 }.OrderBy(x => x).ToArray());
}
// ── Task 9 (arch review 02, Performance): parallel per-target lanes ──
// ── Parallel per-target lanes ──
// A slow/dead target no longer serializes the whole sweep; delivery within a
// single (category, target) lane stays sequential (per-target FIFO).
/// <summary>Verifies that a slow target does not block delivery to other targets.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_SlowTarget_DoesNotBlockOtherTargets()
{
@@ -754,6 +815,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
await sweep;
}
/// <summary>Verifies that messages within one target lane are delivered sequentially.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RetrySweep_WithinTargetLane_StaysSequential()
{
@@ -787,7 +850,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
}
/// <summary>
/// Register row-40 house pattern for an ABSENCE assertion (mirrors the Medium-wave
/// House pattern for an ABSENCE assertion (mirrors the Medium-wave
/// <c>SiteAlarmAggregatorActorTests.AssertHoldsFor</c> treatment, ported here async since
/// this class's facts run on the async test thread): proves <paramref name="holds"/> keeps
/// being true for the whole <paramref name="window"/> by polling, rather than a single
@@ -809,7 +872,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
}
}
// ── Task 12: deferToSweep enqueue mode ──
// ── deferToSweep enqueue mode ──
/// <summary>
/// <c>deferToSweep: true</c> must NOT invoke the delivery handler inline (the
@@ -817,6 +880,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
/// the very next sweep — <c>LastAttemptAt</c> stays null so it is not held back by
/// RetryInterval.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Enqueue_DeferToSweep_DoesNotInvokeHandlerInline_AndRowIsDueImmediately()
{
@@ -839,6 +903,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
/// healthy-path latency is milliseconds, not one timer interval — proven with a
/// timer set to an hour so only the kicked sweep can deliver.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Enqueue_DeferToSweep_AfterStart_KicksAnImmediateSweep()
{
@@ -857,6 +922,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
// ── R2 T8: _sweepTask clobber (N3) ──
/// <summary>Verifies that triggering a sweep while one is in flight does not clobber the drain handle.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task TriggerSweep_WhileSweepInFlight_DoesNotClobberTheDrainHandle()
{
@@ -890,6 +957,8 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
}
}
/// <summary>Verifies that stopping waits for the real in-flight sweep even after a redundant trigger.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StopAsync_WaitsForTheRealInFlightSweep_EvenAfterARedundantTrigger()
{
@@ -923,22 +992,26 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
await stop.WaitAsync(TimeSpan.FromSeconds(5)); // drains the real sweep promptly once released
}
// ── WP2.6c: bounded, DropOldest observer queue + drop counter ──
// ── Bounded, DropOldest observer queue + drop counter ──
private sealed class BlockingObserver : ICachedCallLifecycleObserver
{
private readonly TaskCompletionSource _gate;
/// <summary>Creates an observer that blocks every attempt notification until the gate completes.</summary>
/// <param name="gate">The completion source that releases the blocked notifications.</param>
public BlockingObserver(TaskCompletionSource gate) => _gate = gate;
/// <inheritdoc />
public async Task OnAttemptCompletedAsync(CachedCallAttemptContext context, CancellationToken ct = default)
=> await _gate.Task;
}
/// <summary>
/// WP2.6c: the cached-call audit-observer queue is bounded — once the single-reader
/// The cached-call audit-observer queue is bounded — once the single-reader
/// pump is stuck awaiting a slow/stuck observer, further posted notifications must
/// evict the oldest queued one (DropOldest) instead of growing without bound, and
/// every eviction must increment <see cref="StoreAndForwardService.ObserverQueueDroppedCount"/>.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ObserverQueue_BoundedCapacity_DropsOldestAndCountsDrops()
{
@@ -1010,15 +1083,30 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
private readonly List<(LogLevel Level, string Message)> _entries = new();
private readonly object _gate = new();
/// <summary>Gets a snapshot of the log entries captured so far.</summary>
public IReadOnlyList<(LogLevel Level, string Message)> Entries
{
get { lock (_gate) return _entries.ToList(); }
}
/// <summary>Returns no scope; the fake logger does not track scopes.</summary>
/// <typeparam name="TState">The type of the scope state.</typeparam>
/// <param name="state">The scope state, ignored.</param>
/// <returns>Always null or a no-op scope.</returns>
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
/// <summary>Reports every log level as enabled.</summary>
/// <param name="logLevel">The log level being queried.</param>
/// <returns>Always true.</returns>
public bool IsEnabled(LogLevel logLevel) => true;
/// <summary>Records the log entry for later assertions.</summary>
/// <typeparam name="TState">The type of the log state.</typeparam>
/// <param name="logLevel">The severity of the entry.</param>
/// <param name="eventId">The event identifier.</param>
/// <param name="state">The log state.</param>
/// <param name="exception">The associated exception, if any.</param>
/// <param name="formatter">Formats the state and exception into the message.</param>
public void Log<TState>(
LogLevel logLevel, EventId eventId, TState state, Exception? exception,
Func<TState, Exception?, string> formatter)
@@ -1038,6 +1126,7 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
/// produce exactly ONE observer-queue-drop Warning log — the first-drop Warning — because
/// the episode never runs long enough to cross the periodic rollup interval.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task ObserverQueue_ManyDropsInOneEpisode_LogsExactlyOneWarning()
{
@@ -1105,6 +1194,9 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
TestLocalDb.DeleteFiles(path);
}
}
/// <summary>Verifies that a delivery gate flipping to standby during delivery does not start another attempt, for the same or a different target lane.</summary>
/// <param name="differentLane">True when the second message targets a different lane than the one in flight.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
[Theory]
[InlineData(false)]
[InlineData(true)]