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:
@@ -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.");
|
||||
}
|
||||
|
||||
+42
-54
@@ -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 < 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;
|
||||
|
||||
Reference in New Issue
Block a user