Resolve IntegrationTests-007..010 code-review findings

IntegrationTests-007: the three live test classes contend for shared
singletons (one MXAccess COM, one ZB SQL DB, one GLAuth). Added
LiveResourcesCollection with DisableParallelization and applied it to all
three so they no longer run concurrently.

IntegrationTests-008: the three live fact attributes each re-implemented the
env-var check. Added IntegrationTestEnvironment.IsEnabled and all three now
delegate to it.

IntegrationTests-009: reworded the misleading "Mock server call context" XML
doc — it is a hand-written stub with no verification behavior.

IntegrationTests-010: WaitForMessageAsync ignored cancellation. It now takes
an optional CancellationToken linked with the timeout; the smoke test shares
one cancellation source with the StreamEvents call context.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-18 22:59:18 -04:00
parent 371bcb3f91
commit b4f5e8eb48
8 changed files with 61 additions and 27 deletions
@@ -17,6 +17,7 @@ using Xunit.Abstractions;
namespace MxGateway.IntegrationTests;
[Collection(LiveResourcesCollection.Name)]
public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
{
private static readonly TimeSpan CommandTimeout = TimeSpan.FromSeconds(15);
@@ -40,6 +41,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
string? sessionId = null;
RecordingServerStreamWriter<MxEvent>? eventWriter = null;
Task? streamTask = null;
using CancellationTokenSource streamCancellation = new();
try
{
@@ -61,7 +63,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
streamTask = fixture.Service.StreamEvents(
new StreamEventsRequest { SessionId = sessionId },
eventWriter,
new TestServerCallContext());
new TestServerCallContext(streamCancellation.Token));
MxCommandReply registerReply = await fixture.Service.Invoke(
CreateRegisterRequest(sessionId),
@@ -94,7 +96,8 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
MxEvent dataChange = await eventWriter
.WaitForMessageAsync(
candidate => candidate.Family == MxEventFamily.OnDataChange,
IntegrationTestEnvironment.LiveMxAccessEventTimeout)
IntegrationTestEnvironment.LiveMxAccessEventTimeout,
streamCancellation.Token)
.ConfigureAwait(false);
LogEvent(dataChange);
@@ -560,12 +563,20 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
/// </summary>
/// <param name="predicate">Filter the awaited message must satisfy.</param>
/// <param name="timeout">The maximum total time to wait.</param>
/// <param name="cancellationToken">
/// Token observed alongside the timeout so a per-test cancellation (for example the
/// gRPC call context's token) aborts the wait promptly instead of hanging until the
/// timeout elapses.
/// </param>
/// <returns>The first message that satisfies the predicate.</returns>
public async Task<T> WaitForMessageAsync(
Func<T, bool> predicate,
TimeSpan timeout)
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
using CancellationTokenSource timeoutCancellation = new(timeout);
using CancellationTokenSource linkedCancellation =
CancellationTokenSource.CreateLinkedTokenSource(timeoutCancellation.Token, cancellationToken);
int scanned = 0;
while (true)
@@ -586,7 +597,7 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
try
{
await messageArrived.WaitAsync(timeoutCancellation.Token).ConfigureAwait(false);
await messageArrived.WaitAsync(linkedCancellation.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (timeoutCancellation.IsCancellationRequested)
{
@@ -598,7 +609,9 @@ public sealed class WorkerLiveMxAccessSmokeTests(ITestOutputHelper output)
}
/// <summary>
/// Mock server call context for testing gRPC calls.
/// Minimal <see cref="ServerCallContext"/> stub for invoking the gRPC service
/// in-process. It is a hand-written fake with no verification behavior — it
/// only supplies the context values the service reads during a call.
/// </summary>
private sealed class TestServerCallContext(CancellationToken cancellationToken = default) : ServerCallContext
{