docs(src): add missing XML docs and strip tracking-ID comments

Sweep of 203 source files resolving CommentChecker findings: add
<summary>/<param>/<returns>/<inheritdoc> where missing, and remove
resolved task/issue tracking markers (Tests-NNN, Worker-NNN, Server-NNN,
Task N) from code comments. Comment/doc-only — no logic changes.
Server+Tests build clean under TreatWarningsAsErrors.
This commit is contained in:
Joseph Doherty
2026-07-07 14:09:49 -04:00
parent 8914472706
commit fca978de07
203 changed files with 1834 additions and 1383 deletions
@@ -17,6 +17,7 @@ public sealed class EventStreamServiceTests
private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(5);
/// <summary>Verifies that events from the worker stream maintain their original sequence order.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_YieldsEventsInWorkerOrder()
{
@@ -38,6 +39,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that a second event subscriber is rejected when one is already active.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenSecondSubscriberStarts_RejectsClearly()
{
@@ -67,6 +69,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that canceling an event stream detaches the subscriber cleanly.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenCanceled_DetachesSubscriber()
{
@@ -89,6 +92,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that disposing an event stream with buffered events resets the queue depth metric.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenDisposedWithBufferedEvents_ResetsStreamQueueDepth()
{
@@ -116,6 +120,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that queue depth metrics correctly track concurrent event streams across multiple sessions.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WithConcurrentStreams_TracksAggregateQueueDepth()
{
@@ -157,7 +162,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Re-targeted in Task 5: a per-subscriber channel overflow in the session's
/// A per-subscriber channel overflow in the session's
/// <see cref="SessionEventDistributor"/> faults the whole session under the legacy
/// single-subscriber FailFast policy (the default, single-subscriber mode) and records
/// the overflow + fault metrics. The distributor completes this subscriber's channel
@@ -165,6 +170,7 @@ public sealed class EventStreamServiceTests
/// <see cref="SessionManagerErrorCode.EventQueueOverflow"/> the pre-epic per-RPC
/// overflow produced.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenStreamQueueOverflows_FaultsSessionAndReportsOverflow()
{
@@ -189,7 +195,7 @@ public sealed class EventStreamServiceTests
.StreamEventsAsync(CreateRequest(session.SessionId), CancellationToken.None)
.GetAsyncEnumerator();
// The pump fans 50 events into a capacity-1 subscriber channel faster than this
// The pump fans 50 events into a subscriber channel with capacity 1 faster than this
// single reader drains, so one of the reads observes the terminal overflow fault.
SessionManagerException exception = await Assert.ThrowsAsync<SessionManagerException>(
async () =>
@@ -211,12 +217,13 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Re-targeted in Task 5: under the DisconnectSubscriber policy a per-subscriber
/// channel overflow disconnects only that subscriber's stream (terminal
/// Under the DisconnectSubscriber policy a per-subscriber channel overflow
/// disconnects only that subscriber's stream (terminal
/// <see cref="SessionManagerErrorCode.EventQueueOverflow"/>) and records the overflow
/// metric, but leaves the session <see cref="SessionState.Ready"/> and records no
/// fault. The session, pump, and any other subscribers are unaffected.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenStreamQueueOverflowsWithDisconnectPolicy_LeavesSessionReady()
{
@@ -259,6 +266,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that the event stream does not synthesize OperationComplete events from write completions.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_DoesNotSynthesizeOperationComplete()
{
@@ -276,6 +284,7 @@ public sealed class EventStreamServiceTests
}
/// <summary>Verifies that a terminal fault from the worker event stream propagates and faults the session.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_WhenWorkerEventStreamFaults_PropagatesTerminalFault()
{
@@ -301,9 +310,10 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Task 12: resuming with AfterWorkerSequence inside the retained window replays exactly
/// Resuming with AfterWorkerSequence inside the retained window replays exactly
/// the newer retained events (in order, no dup) then live, with NO ReplayGap sentinel.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_ResumeWithinRetainedWindow_ReplaysNewerThenLive_NoSentinel()
{
@@ -340,9 +350,10 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Task 12: resuming with AfterWorkerSequence older than the oldest retained yields the
/// Resuming with AfterWorkerSequence older than the oldest retained yields the
/// ReplayGap sentinel FIRST (correct requested/oldest), then the retained tail, then live.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_ResumeOlderThanOldestRetained_EmitsSentinelFirst_ThenTailThenLive()
{
@@ -385,9 +396,10 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Task 12: the replay→live boundary is contiguous — no duplicate and no skip — even
/// The replay→live boundary is contiguous — no duplicate and no skip — even
/// when events span the handoff.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_ResumeHandoff_IsContiguous_NoDuplicateNoSkip()
{
@@ -424,9 +436,10 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Task 12: the per-item filter applies to REPLAYED events identically to live — a
/// The per-item filter applies to REPLAYED events identically to live — a
/// replayed event at/below the requested watermark is never delivered.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_ResumeReplay_AppliesPerItemFilter_DropsAtOrBelowWatermark()
{
@@ -466,9 +479,10 @@ public sealed class EventStreamServiceTests
}
/// <summary>
/// Task 12: AfterWorkerSequence == 0 is a fresh stream (not a resume) — no replay, no
/// AfterWorkerSequence == 0 is a fresh stream (not a resume) — no replay, no
/// sentinel, just live events as before.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEventsAsync_FreshStreamAfterSequenceZero_NoReplayNoSentinel()
{
@@ -816,7 +830,8 @@ public sealed class EventStreamServiceTests
State = WorkerClientState.Faulted;
}
/// <inheritdoc />
/// <summary>No-op disposal; the fake holds no unmanaged resources.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;
@@ -29,6 +29,7 @@ public sealed class GalaxyRepositoryHostWiringTests
/// <see cref="GatewayBrowseScopeProvider"/> threads the constraint into the lib
/// service → <c>BrowseChildren</c> returns empty children.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task BrowseChildren_BrowseSubtreesConstraintThroughHostWiring_FiltersChildren()
{
@@ -130,13 +131,17 @@ public sealed class GalaxyRepositoryHostWiringTests
/// <summary>Immediately-ready stub: Current returns the seeded entry, loads are instant.</summary>
private sealed class StubGalaxyHierarchyCache(GalaxyHierarchyCacheEntry current) : IGalaxyHierarchyCache
{
/// <inheritdoc />
/// <summary>Gets the seeded cache entry passed to the stub's constructor.</summary>
public GalaxyHierarchyCacheEntry Current { get; } = current;
/// <inheritdoc />
/// <summary>No-op: the stub's entry is always considered already loaded.</summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task RefreshAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <inheritdoc />
/// <summary>No-op: the stub's entry is always considered already loaded.</summary>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A task that represents the asynchronous operation.</returns>
public Task WaitForFirstLoadAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
@@ -146,23 +151,33 @@ public sealed class GalaxyRepositoryHostWiringTests
/// </summary>
private sealed class StubGalaxyRepository : IGalaxyRepository
{
/// <inheritdoc />
/// <summary>Always throws: <c>BrowseChildren</c> never calls the repository directly.</summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Never returns; always throws.</returns>
public Task<bool> TestConnectionAsync(CancellationToken ct = default) =>
throw new NotSupportedException("Not called during BrowseChildren.");
/// <inheritdoc />
/// <summary>Always throws: <c>BrowseChildren</c> never calls the repository directly.</summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Never returns; always throws.</returns>
public Task<DateTime?> GetLastDeployTimeAsync(CancellationToken ct = default) =>
throw new NotSupportedException("Not called during BrowseChildren.");
/// <inheritdoc />
/// <summary>Always throws: <c>BrowseChildren</c> never calls the repository directly.</summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Never returns; always throws.</returns>
public Task<List<GalaxyHierarchyRow>> GetHierarchyAsync(CancellationToken ct = default) =>
throw new NotSupportedException("Not called during BrowseChildren.");
/// <inheritdoc />
/// <summary>Always throws: <c>BrowseChildren</c> never calls the repository directly.</summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Never returns; always throws.</returns>
public Task<List<GalaxyAttributeRow>> GetAttributesAsync(CancellationToken ct = default) =>
throw new NotSupportedException("Not called during BrowseChildren.");
/// <inheritdoc />
/// <summary>Always throws: <c>BrowseChildren</c> never calls the repository directly.</summary>
/// <param name="ct">Cancellation token.</param>
/// <returns>Never returns; always throws.</returns>
public Task<List<GalaxyAlarmAttributeRow>> GetAlarmAttributesAsync(CancellationToken ct = default) =>
throw new NotSupportedException("Not called during BrowseChildren.");
}
@@ -13,7 +13,7 @@ using ZB.MOM.WW.MxGateway.Tests.TestSupport;
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Grpc;
/// <summary>
/// Tests for Server-021. <c>MxAccessGatewayService.ApplyConstraintsAsync</c> and
/// <c>MxAccessGatewayService.ApplyConstraintsAsync</c> and
/// the <c>BulkConstraintPlan</c> / <c>ReadBulkConstraintPlan</c> /
/// <c>WriteBulkConstraintPlan</c> / <c>SubscribeBulkConstraintPlan</c> reply-merge
/// logic was previously exercised only with an allow-all enforcer, so denial
@@ -34,6 +34,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// worker once with only the allowed tags, then splice the denied entries
/// back into the reply at their original indices.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_AddItemBulk_WithMixedDenials_InterleavesDeniedAndAllowedInOriginalIndexOrder()
{
@@ -95,6 +96,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <see cref="BulkConstraintPlan.HasAllowedItems"/> false, return the
/// denied-only reply, and never call the session manager.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_SubscribeBulk_WhenAllTagsDenied_DoesNotCallWorkerAndReturnsDeniedReply()
{
@@ -118,6 +120,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <c>FilterHandleBulkAsync</c> against <c>CheckReadHandleAsync</c>. Partial
/// denial must still produce a merged-by-index <c>BulkSubscribeReply</c>.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_AdviseItemBulk_WithMixedHandleDenials_MergesDeniedIntoReply()
{
@@ -166,6 +169,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// unchanged — the constraint plan is null and no merge occurs. Regression
/// guard against accidentally engaging the merge path for the common case.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_SubscribeBulk_WithAllowAllEnforcer_PassesThroughUnchanged()
{
@@ -208,6 +212,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// the SubscribeBulk family because the reply slot is
/// <c>BulkReadReply</c>, not <c>BulkSubscribeReply</c>.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_ReadBulk_WithMixedDenials_MergesDeniedBulkReadResults()
{
@@ -255,6 +260,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <see cref="MxAccessGatewayService"/>'s <c>ReadBulkConstraintPlan</c>
/// <c>CreateDeniedReply</c> path.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_ReadBulk_WhenAllTagsDenied_ShortCircuitsWithDeniedOnlyReply()
{
@@ -279,6 +285,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// forwarded command and splice a denied <c>BulkWriteResult</c> back in at
/// the original index.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteBulk_WithDeniedHandle_DropsEntryFromWorkerCallAndMergesDenialIntoReply()
{
@@ -329,6 +336,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// switch arm than plain <c>WriteBulk</c>. The merge logic is shared, so a
/// full denial here is enough to prove the secured-bulk routing.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteSecuredBulk_WhenAllHandlesDenied_ShortCircuitsWithDeniedOnlyReply()
{
@@ -347,13 +355,14 @@ public sealed class MxAccessGatewayServiceConstraintTests
}
/// <summary>
/// Tests-020: <c>Write2Bulk</c> takes the third <c>GetPayload</c>/<c>SetPayload</c>
/// <c>Write2Bulk</c> takes the third <c>GetPayload</c>/<c>SetPayload</c>
/// switch arm in <c>WriteBulkConstraintPlan</c>. The merge logic is shared with
/// <c>WriteBulk</c>, but a full denial through the <c>CreateDeniedReply</c> path
/// proves the <c>Write2Bulk</c> arm of the per-kind <c>SetPayload</c> switch fires
/// (and not, say, <c>WriteBulk</c> by mistake) — guarding against a refactor that
/// drops or misroutes the <c>Write2Bulk</c> case.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_Write2Bulk_WhenAllHandlesDenied_ShortCircuitsWithDeniedOnlyReply()
{
@@ -377,11 +386,12 @@ public sealed class MxAccessGatewayServiceConstraintTests
}
/// <summary>
/// Tests-020: <c>WriteSecured2Bulk</c> takes the fourth <c>GetPayload</c>/<c>SetPayload</c>
/// <c>WriteSecured2Bulk</c> takes the fourth <c>GetPayload</c>/<c>SetPayload</c>
/// switch arm in <c>WriteBulkConstraintPlan</c>. Same reasoning as
/// <c>Write2Bulk</c> — assert the <c>WriteSecured2Bulk</c> reply slot is populated
/// to prove that arm of the switch fires.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteSecured2Bulk_WhenAllHandlesDenied_ShortCircuitsWithDeniedOnlyReply()
{
@@ -404,10 +414,10 @@ public sealed class MxAccessGatewayServiceConstraintTests
Assert.Empty(reply.WriteSecuredBulk?.Results ?? new Google.Protobuf.Collections.RepeatedField<BulkWriteResult>());
}
// === Worker reply-count divergence (Tests-024) ===
// === Worker reply-count divergence ===
/// <summary>
/// Tests-024: <c>WriteBulkConstraintPlan.MergeDeniedInto</c> dequeues from
/// <c>WriteBulkConstraintPlan.MergeDeniedInto</c> dequeues from
/// <c>allowedResults</c> per non-denied slot via <c>Queue.TryDequeue</c>,
/// which silently returns <c>false</c> when the queue is empty. Pin the
/// observable behaviour when the worker returns FEWER allowed results than
@@ -417,6 +427,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// This fixture makes that "silent truncate" behaviour explicit so a future
/// change either fills the gap with a synthetic failure or fails this test.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteBulk_WhenWorkerReturnsFewerResultsThanAllowed_MergedReplyIsTruncated()
{
@@ -464,12 +475,13 @@ public sealed class MxAccessGatewayServiceConstraintTests
}
/// <summary>
/// Tests-024: when the worker returns MORE allowed results than the
/// When the worker returns MORE allowed results than the
/// gateway forwarded, the extras must be silently ignored — the merged
/// reply length stays at <c>OriginalCount</c>. This pins the
/// <c>for index &lt; OriginalCount</c> loop bound so a regression that
/// accidentally surfaces extras as trailing results is caught.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteBulk_WhenWorkerReturnsExtraResults_IgnoresExtras()
{
@@ -527,6 +539,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <see cref="StatusCode.PermissionDenied"/> via <c>EnforceWriteHandleAsync</c>
/// and never reach the session manager.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_Write_WithDeniedHandle_ThrowsPermissionDeniedAndDoesNotCallWorker()
{
@@ -554,6 +567,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// enforce helper -> <c>RecordDenialAsync</c>, so the recorded denial carries the exact
/// id the client sent (including non-GUID trace ids used by Rust/Python/Java clients).
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_Write_WithDeniedHandle_ThreadsClientCorrelationIdIntoRecordedDenial()
{
@@ -581,6 +595,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <c>ApplyConstraintsAsync</c> (Write/Write2/WriteSecured/WriteSecured2) is
/// reachable for at least one of the secured kinds.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WriteSecured_WithDeniedHandle_ThrowsPermissionDenied()
{
@@ -604,6 +619,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <see cref="StatusCode.PermissionDenied"/> via <c>EnforceReadTagAsync</c>
/// and never reach the session manager.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_AddItem_WithDeniedTag_ThrowsPermissionDeniedAndDoesNotCallWorker()
{
@@ -881,11 +897,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
/// <param name="session">The session to seed.</param>
public void SeedSession(GatewaySession session) => seededSessions[session.SessionId] = session;
/// <summary>Opens a test session asynchronously.</summary>
/// <param name="request">The session open request.</param>
/// <param name="clientIdentity">The client identity, if any.</param>
/// <param name="ownerKeyId">The API key identifier of the caller, if any.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<GatewaySession> OpenSessionAsync(
SessionOpenRequest request,
string? clientIdentity,
@@ -893,9 +905,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
CancellationToken cancellationToken) =>
Task.FromResult(seededSessions.Values.First());
/// <summary>Tries to get a test session by identifier.</summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="session">The session, if found.</param>
/// <inheritdoc />
public bool TryGetSession(string sessionId, out GatewaySession session)
{
if (seededSessions.TryGetValue(sessionId, out GatewaySession? seeded))
@@ -914,10 +924,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
return true;
}
/// <summary>Invokes a worker command and returns the reply asynchronously.</summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="command">The worker command.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<WorkerCommandReply> InvokeAsync(
string sessionId,
WorkerCommand command,
@@ -928,9 +935,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
return Task.FromResult(InvokeReply);
}
/// <summary>Reads events from the session asynchronously.</summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public async IAsyncEnumerable<WorkerEvent> ReadEventsAsync(
string sessionId,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
@@ -943,33 +948,25 @@ public sealed class MxAccessGatewayServiceConstraintTests
}
}
/// <summary>Closes a test session asynchronously.</summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<SessionCloseResult> CloseSessionAsync(
string sessionId,
CancellationToken cancellationToken) =>
Task.FromResult(new SessionCloseResult(sessionId, SessionState.Closed, AlreadyClosed: false));
/// <summary>Kills a worker process asynchronously.</summary>
/// <param name="sessionId">The session identifier.</param>
/// <param name="reason">The reason for killing the worker.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<SessionCloseResult> KillWorkerAsync(
string sessionId,
string reason,
CancellationToken cancellationToken) =>
Task.FromResult(new SessionCloseResult(sessionId, SessionState.Closed, AlreadyClosed: false));
/// <summary>Closes expired session leases asynchronously.</summary>
/// <param name="now">The current time to check against.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<int> CloseExpiredLeasesAsync(
DateTimeOffset now,
CancellationToken cancellationToken) => Task.FromResult(0);
/// <summary>Shuts down the test session manager asynchronously.</summary>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task ShutdownAsync(CancellationToken cancellationToken) => Task.CompletedTask;
private static GatewaySession CreateFallbackSession(string sessionId)
@@ -994,9 +991,7 @@ public sealed class MxAccessGatewayServiceConstraintTests
private sealed class FakeEventStreamService(FakeSessionManager sessionManager) : IEventStreamService
{
/// <summary>Streams events for the test session asynchronously.</summary>
/// <param name="request">The stream events request.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public async IAsyncEnumerable<MxEvent> StreamEventsAsync(
StreamEventsRequest request,
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
@@ -1012,33 +1007,28 @@ public sealed class MxAccessGatewayServiceConstraintTests
private sealed class FakeWorkerClient : IWorkerClient
{
/// <summary>Gets the test session identifier.</summary>
/// <inheritdoc />
public string SessionId { get; } = MxAccessGatewayServiceConstraintTests.SessionId;
/// <summary>Gets the test worker process identifier.</summary>
/// <inheritdoc />
public int? ProcessId { get; } = 1234;
/// <summary>Gets the test worker client state.</summary>
/// <inheritdoc />
public WorkerClientState State { get; } = WorkerClientState.Ready;
/// <summary>Gets the last recorded heartbeat time.</summary>
/// <inheritdoc />
public DateTimeOffset LastHeartbeatAt { get; } = DateTimeOffset.UtcNow;
/// <summary>Starts the test worker client asynchronously.</summary>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>Invokes a command on the test worker asynchronously.</summary>
/// <param name="command">The worker command.</param>
/// <param name="timeout">Maximum time to wait for completion.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task<WorkerCommandReply> InvokeAsync(
WorkerCommand command,
TimeSpan timeout,
CancellationToken cancellationToken) => Task.FromResult(new WorkerCommandReply());
/// <summary>Reads events from the test worker asynchronously.</summary>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public async IAsyncEnumerable<WorkerEvent> ReadEventsAsync(
[System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken cancellationToken)
{
@@ -1046,18 +1036,16 @@ public sealed class MxAccessGatewayServiceConstraintTests
yield break;
}
/// <summary>Shuts down the test worker client asynchronously.</summary>
/// <param name="timeout">Maximum time to wait for completion.</param>
/// <param name="cancellationToken">Token to observe for cancellation.</param>
/// <inheritdoc />
public Task ShutdownAsync(TimeSpan timeout, CancellationToken cancellationToken) => Task.CompletedTask;
/// <summary>Kills the test worker process.</summary>
/// <param name="reason">The reason for killing the worker.</param>
/// <inheritdoc />
public void Kill(string reason)
{
}
/// <inheritdoc />
/// <summary>Disposes the test worker client. No-op — there is no unmanaged state to release.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}
}
@@ -18,6 +18,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Grpc;
public sealed class MxAccessGatewayServiceTests
{
/// <summary>Verifies that OpenSession returns correct session details for a valid request.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task OpenSession_WithValidRequest_ReturnsSessionDetails()
{
@@ -56,6 +57,7 @@ public sealed class MxAccessGatewayServiceTests
/// <c>TryGetSession</c> return false, so this test fails if the service drops
/// its missing-session check rather than passing for the wrong reason.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WhenSessionMissing_ThrowsNotFound()
{
@@ -78,6 +80,7 @@ public sealed class MxAccessGatewayServiceTests
/// manager when <see cref="FakeSessionManager.ResolveOnlySeededSessions"/> is on,
/// confirming the missing-session test above is gated on a real lookup.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WhenSessionSeeded_ResolvesAndInvokes()
{
@@ -94,6 +97,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies that Invoke throws InvalidArgument and does not invoke the session manager when payload is mismatched.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WithMismatchedPayload_ThrowsInvalidArgumentAndDoesNotCallSessionManager()
{
@@ -117,6 +121,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies that Invoke returns HResult status and method payload from worker reply.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Invoke_WithWorkerReply_ReturnsHresultStatusAndMethodPayload()
{
@@ -172,6 +177,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies that StreamEvents writes only events after the specified worker sequence.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEvents_WithAfterSequence_WritesOnlyLaterEvents()
{
@@ -197,15 +203,8 @@ public sealed class MxAccessGatewayServiceTests
/// <summary>
/// Verifies that <c>StreamEvents</c> records the send-duration histogram per event.
///
/// <para>Tests-027 (concurrency flake): the listener must filter by the specific
/// <see cref="System.Diagnostics.Metrics.Meter"/> instance owned by this test, not by the process-shared
/// <see cref="GatewayMetrics.MeterName"/>. Otherwise a parallel test that constructs its own
/// <see cref="GatewayMetrics"/> and records <c>mxgateway.events.stream_send.duration</c> would
/// cross-contaminate <c>families</c> and break the equality assertion below. See the companion
/// <see cref="StreamEvents_RecordSendDurationListener_IgnoresMeasurementsFromOtherMetersWithSameName"/>
/// regression for the cross-talk reproduction.</para>
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEvents_WhenEventIsWritten_RecordsSendDuration()
{
@@ -252,13 +251,14 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>
/// Tests-027 regression: a <see cref="MeterListener"/> that filters by the specific
/// A <see cref="MeterListener"/> that filters by the specific
/// <see cref="System.Diagnostics.Metrics.Meter"/> instance (via <see cref="object.ReferenceEquals"/>)
/// must NOT observe measurements recorded on a different <see cref="GatewayMetrics"/> that shares
/// the same <see cref="GatewayMetrics.MeterName"/>. This is the cross-talk vector that previously
/// caused <c>StreamEvents_WhenEventIsWritten_RecordsSendDuration</c> to fail intermittently when
/// run in parallel with another test recording the same histogram.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamEvents_RecordSendDurationListener_IgnoresMeasurementsFromOtherMetersWithSameName()
{
@@ -315,6 +315,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies that CloseSession throws InvalidArgument when session ID is blank.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task CloseSession_WithBlankSessionId_ThrowsInvalidArgument()
{
@@ -335,6 +336,7 @@ public sealed class MxAccessGatewayServiceTests
// alarm feed. CreateService injects FakeGatewayAlarmService.
/// <summary>Verifies AcknowledgeAlarm rejects an empty alarm_full_reference.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task AcknowledgeAlarm_WithMissingAlarmReference_ThrowsInvalidArgument()
{
@@ -349,6 +351,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies AcknowledgeAlarm delegates a valid request to the alarm service.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task AcknowledgeAlarm_WithValidRequest_DelegatesToAlarmService()
{
@@ -369,6 +372,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies StreamAlarms forwards the central alarm feed, ending with snapshot_complete.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task StreamAlarms_ForwardsTheCentralAlarmFeed()
{
@@ -386,6 +390,7 @@ public sealed class MxAccessGatewayServiceTests
}
/// <summary>Verifies OpenSession advertises the alarm RPC capability strings.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task OpenSession_AdvertisesAlarmRpcCapabilities()
{
@@ -715,7 +720,8 @@ public sealed class MxAccessGatewayServiceTests
{
}
/// <inheritdoc />
/// <summary>Completes immediately without disposing any resources.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
public ValueTask DisposeAsync()
{
return ValueTask.CompletedTask;