docs: strip task-tracking IDs from code comments and fill missing XML docs
Tracking IDs are review bookkeeping that rots in shipped source; the rationale they tagged is kept. Comment-only change (CommentChecker 458 -> 16 false positives).
This commit is contained in:
@@ -16,7 +16,7 @@ using AuthenticationOptions = ZB.MOM.WW.MxGateway.Server.Configuration.Authentic
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Covers SEC-40: the alarm feed requires a real authenticated dashboard role, so the read-only
|
||||
/// Covers the rule that the alarm feed requires a real authenticated dashboard role, so the read-only
|
||||
/// environment bypasses (<c>Authentication:Mode=Disabled</c>, anonymous localhost) no longer
|
||||
/// receive galaxy-wide alarm metadata.
|
||||
/// </summary>
|
||||
@@ -65,7 +65,7 @@ public sealed class AlarmFeedAuthorizationTests
|
||||
|
||||
/// <summary>
|
||||
/// An authenticated Viewer — the ordinary LDAP-logged-in dashboard user — does satisfy the
|
||||
/// alarm requirement: SEC-40 excludes the environment bypasses, not Viewers. Alarm metadata
|
||||
/// alarm requirement: the rule excludes the environment bypasses, not Viewers. Alarm metadata
|
||||
/// stays galaxy-wide for them because the monitor is gateway-central and carries no
|
||||
/// per-session provenance to tag-scope by.
|
||||
/// </summary>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that <see cref="AlarmsHubPublisher"/> honours
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c> (TST-16): the alarm value fields of
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c>: the alarm value fields of
|
||||
/// both value-bearing payload arms are stripped from the copy broadcast to
|
||||
/// browser clients when the flag is off, present when it is on, and the source
|
||||
/// <see cref="AlarmFeedMessage"/> — shared with the gRPC <c>StreamAlarms</c>
|
||||
@@ -286,22 +286,48 @@ public sealed class AlarmsHubPublisherTests
|
||||
/// <summary>Gets the capturing client proxy shared by this fake.</summary>
|
||||
public CapturingClientProxy GroupProxy { get; } = new();
|
||||
|
||||
/// <summary>Gets the shared capturing proxy for all clients.</summary>
|
||||
public IClientProxy All => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy, ignoring the exclusions.</summary>
|
||||
/// <param name="excludedConnectionIds">Connection ids to exclude.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy AllExcept(IReadOnlyList<string> excludedConnectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for one connection.</summary>
|
||||
/// <param name="connectionId">The target connection id.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Client(string connectionId) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for several connections.</summary>
|
||||
/// <param name="connectionIds">The target connection ids.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Clients(IReadOnlyList<string> connectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for one group.</summary>
|
||||
/// <param name="groupName">The target group name.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Group(string groupName) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for a group, ignoring the exclusions.</summary>
|
||||
/// <param name="groupName">The target group name.</param>
|
||||
/// <param name="excludedConnectionIds">Connection ids to exclude.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for several groups.</summary>
|
||||
/// <param name="groupNames">The target group names.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Groups(IReadOnlyList<string> groupNames) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for one user.</summary>
|
||||
/// <param name="userId">The target user id.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy User(string userId) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for several users.</summary>
|
||||
/// <param name="userIds">The target user ids.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Users(IReadOnlyList<string> userIds) => GroupProxy;
|
||||
}
|
||||
|
||||
@@ -342,9 +368,19 @@ public sealed class AlarmsHubPublisherTests
|
||||
|
||||
private sealed class NoopGroupManager : IGroupManager
|
||||
{
|
||||
/// <summary>Ignores the membership change and completes synchronously.</summary>
|
||||
/// <param name="connectionId">The connection being added.</param>
|
||||
/// <param name="groupName">The group the connection joins.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Ignores the removal and completes synchronously.</summary>
|
||||
/// <param name="connectionId">The connection being removed.</param>
|
||||
/// <param name="groupName">The group the connection leaves.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@ public sealed class DashboardBrowseAndAlarmModelTests
|
||||
/// <summary>
|
||||
/// An explicitly-degraded status whose mode is still Alarmmgr (the
|
||||
/// <c>Degraded || Mode==Subtag</c> guard's second, independent branch) must map to the
|
||||
/// amber badge, but not to the failover label: GWC-37 gives that combination a meaning —
|
||||
/// amber badge, but not to the failover label: that combination has its own meaning —
|
||||
/// the alarm manager is serving and the subtag standby behind it covers nothing — so
|
||||
/// reporting "Subtag monitoring (degraded)" would name the wrong live provider.
|
||||
/// </summary>
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that <see cref="DashboardEventBroadcaster"/> honours
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c> (SEC-25): tag values are stripped
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c>: tag values are stripped
|
||||
/// from the mirrored copy when the flag is off, present when it is on, and the
|
||||
/// shared source event is never mutated. Also verifies the viewer gate — the
|
||||
/// mirror does no work at all for a session nobody is watching.
|
||||
@@ -325,22 +325,48 @@ public sealed class DashboardEventBroadcasterTests
|
||||
/// <summary>Gets the capturing client proxy shared by this fake.</summary>
|
||||
public CapturingClientProxy GroupProxy { get; } = new();
|
||||
|
||||
/// <summary>Gets the shared capturing proxy for all connections.</summary>
|
||||
public IClientProxy All => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy, ignoring exclusions.</summary>
|
||||
/// <param name="excludedConnectionIds">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy AllExcept(IReadOnlyList<string> excludedConnectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any connection.</summary>
|
||||
/// <param name="connectionId">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Client(string connectionId) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any connection set.</summary>
|
||||
/// <param name="connectionIds">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Clients(IReadOnlyList<string> connectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any group.</summary>
|
||||
/// <param name="groupName">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Group(string groupName) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any group, ignoring exclusions.</summary>
|
||||
/// <param name="groupName">Ignored.</param>
|
||||
/// <param name="excludedConnectionIds">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy GroupExcept(string groupName, IReadOnlyList<string> excludedConnectionIds) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any group set.</summary>
|
||||
/// <param name="groupNames">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Groups(IReadOnlyList<string> groupNames) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any user.</summary>
|
||||
/// <param name="userId">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy User(string userId) => GroupProxy;
|
||||
|
||||
/// <summary>Returns the shared capturing proxy for any user set.</summary>
|
||||
/// <param name="userIds">Ignored.</param>
|
||||
/// <returns>The shared capturing proxy.</returns>
|
||||
public IClientProxy Users(IReadOnlyList<string> userIds) => GroupProxy;
|
||||
}
|
||||
|
||||
@@ -367,9 +393,19 @@ public sealed class DashboardEventBroadcasterTests
|
||||
|
||||
private sealed class NoopGroupManager : IGroupManager
|
||||
{
|
||||
/// <summary>Ignores the group membership change.</summary>
|
||||
/// <param name="connectionId">Ignored.</param>
|
||||
/// <param name="groupName">Ignored.</param>
|
||||
/// <param name="cancellationToken">Ignored.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Ignores the group membership change.</summary>
|
||||
/// <param name="connectionId">Ignored.</param>
|
||||
/// <param name="groupName">Ignored.</param>
|
||||
/// <param name="cancellationToken">Ignored.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task RemoveFromGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// <summary>
|
||||
/// Verifies a tag already in the advise set is not subscribed again on a later read.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenTagAlreadySubscribed_DoesNotResubscribe()
|
||||
{
|
||||
@@ -41,6 +42,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// Verifies the advise set is capped: subscribing past the cap unadvises the
|
||||
/// least-recently-read tag on the worker and leaves the re-read tag advised.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_PastCap_EvictsLeastRecentlyReadTag()
|
||||
{
|
||||
@@ -78,6 +80,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// than the cap keeps them all (a read never evicts a tag it is about to return),
|
||||
/// and the next read that subscribes anything squeezes the overshoot back out.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WithMoreDistinctTagsThanCap_KeepsThemAllThenSelfCorrects()
|
||||
{
|
||||
@@ -108,6 +111,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// touches nearly the whole advise set evicts only the untouched remainder, ends over
|
||||
/// the cap, and the following read trims it back.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenTouchedTagsFillTheCap_EvictsOnlyUntouchedTags()
|
||||
{
|
||||
@@ -135,6 +139,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// Verifies a tag the worker failed to advise still occupies a slot but is evicted
|
||||
/// without any unsubscribe command — there is no item handle to unadvise.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenAdviseFailed_EvictsTagWithoutUnsubscribing()
|
||||
{
|
||||
@@ -165,6 +170,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// Verifies a failed unadvise of an evicted tag does not fail the read, and the
|
||||
/// evicted tag is dropped from tracking anyway.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenEvictionUnsubscribeFails_StillCompletesRead()
|
||||
{
|
||||
@@ -190,10 +196,11 @@ public sealed class DashboardLiveDataServiceTests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the <c>/browse</c> live-value seam honours
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c> (TST-16): with the flag off — the
|
||||
/// <c>MxGateway:Dashboard:ShowTagValues</c>: with the flag off — the
|
||||
/// default — the value text the page renders is the redaction placeholder,
|
||||
/// never the formatted tag value.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenShowTagValuesFalse_RedactsValueTextButKeepsMetadata()
|
||||
{
|
||||
@@ -220,6 +227,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies the formatted value is served when the flag is on.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenShowTagValuesTrue_ServesFormattedValue()
|
||||
{
|
||||
@@ -243,6 +251,7 @@ public sealed class DashboardLiveDataServiceTests
|
||||
/// withheld, and its diagnostic still reaches the panel. Redacting it would
|
||||
/// contradict the error the same row displays.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_WhenReadFailedAndShowTagValuesFalse_LeavesPlaceholderUnredacted()
|
||||
{
|
||||
@@ -489,7 +498,8 @@ public sealed class DashboardLiveDataServiceTests
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Completes immediately because the fake holds no resources.</summary>
|
||||
/// <returns>A completed value task.</returns>
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
|
||||
private BulkSubscribeReply Subscribe(IEnumerable<string> tagAddresses)
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Covers <see cref="DashboardSessionAcl"/>, the single decision both dashboard subscribe seams
|
||||
/// consult (SEC-25 / TST-15).
|
||||
/// consult.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Every branch is asserted in its denying direction as well as its allowing one, because the
|
||||
@@ -77,6 +77,7 @@ public sealed class DashboardSessionAclTests
|
||||
}
|
||||
|
||||
/// <summary>A blank session id is denied without consulting anything.</summary>
|
||||
/// <param name="sessionId">A blank or whitespace-only session id.</param>
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
@@ -146,8 +147,8 @@ public sealed class DashboardSessionAclTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A principal carrying no tag claims — the anonymous-localhost / empty-grant Viewer of
|
||||
/// SEC-02 — sees a tagged session never, and an untagged one only when the operator opted
|
||||
/// A principal carrying no tag claims — the anonymous-localhost / empty-grant Viewer —
|
||||
/// sees a tagged session never, and an untagged one only when the operator opted
|
||||
/// into <see cref="UntaggedSessionVisibility.AllViewers"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
@@ -179,7 +180,7 @@ public sealed class DashboardSessionAclTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SEC-41, the load-bearing regression: a cookie-style principal whose tag claim was stamped
|
||||
/// The load-bearing regression: a cookie-style principal whose tag claim was stamped
|
||||
/// at login under an older <c>GroupToTag</c> map is DENIED once that map no longer grants the
|
||||
/// tag to any group the principal belongs to. The auth cookie survives the restart that
|
||||
/// rebinds options, so trusting the stamped claim let a config-revoked grant live on for the
|
||||
@@ -224,7 +225,7 @@ public sealed class DashboardSessionAclTests
|
||||
/// <summary>
|
||||
/// Re-resolution is ordinal-ignore-case on both sides — the group key against the map (which
|
||||
/// <see cref="DashboardGroupTagMapping"/> owns) and the resolved tag against the session's tag
|
||||
/// set. Pinned because the pre-SEC-41 claim scan got the second comparison from
|
||||
/// set. Pinned because the earlier claim scan got the second comparison from
|
||||
/// <c>Session.Tags</c>'s own comparer, and the re-resolution arm probes the grant set in the
|
||||
/// opposite direction.
|
||||
/// </summary>
|
||||
@@ -294,7 +295,7 @@ public sealed class DashboardSessionAclTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds a dashboard principal. <paramref name="groups"/> is the SEC-41 discriminator: a
|
||||
/// Builds a dashboard principal. <paramref name="groups"/> is the discriminator: a
|
||||
/// principal WITH group claims is re-resolved against the current map (the cookie seam), one
|
||||
/// WITHOUT falls back to its stamped tag claims (the hub-token seam). Existing cases pass
|
||||
/// tags only, so they continue to exercise the fallback arm exactly as before.
|
||||
|
||||
@@ -7,7 +7,7 @@ using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Covers the ACL gate on <see cref="EventsHub.SubscribeSession"/> (SEC-25 / TST-15).
|
||||
/// Covers the ACL gate on <see cref="EventsHub.SubscribeSession"/> .
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The denial assertions are the load-bearing ones — before the gate existed every caller was
|
||||
@@ -131,7 +131,11 @@ public sealed class EventsHubTests
|
||||
/// <summary>Gets the (connection id, group name) pairs added, in order.</summary>
|
||||
public List<(string ConnectionId, string GroupName)> Added { get; } = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Records the group membership and completes synchronously.</summary>
|
||||
/// <param name="connectionId">The connection being added.</param>
|
||||
/// <param name="groupName">The group the connection joins.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task AddToGroupAsync(string connectionId, string groupName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Added.Add((connectionId, groupName));
|
||||
@@ -139,7 +143,11 @@ public sealed class EventsHubTests
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Ignores the removal and completes synchronously.</summary>
|
||||
/// <param name="connectionId">The connection being removed.</param>
|
||||
/// <param name="groupName">The group the connection leaves.</param>
|
||||
/// <param name="cancellationToken">Token to observe for cancellation.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
public Task RemoveFromGroupAsync(
|
||||
string connectionId,
|
||||
string groupName,
|
||||
|
||||
@@ -129,6 +129,8 @@ public sealed class EventsHubViewerRegistryTests
|
||||
}
|
||||
|
||||
/// <summary>Blank connection or session ids are ignored rather than tracked.</summary>
|
||||
/// <param name="connectionId">The hub connection id, possibly blank.</param>
|
||||
/// <param name="sessionId">The session id, possibly blank.</param>
|
||||
[Theory]
|
||||
[InlineData("", "session-1")]
|
||||
[InlineData(" ", "session-1")]
|
||||
|
||||
@@ -178,7 +178,7 @@ public sealed class HubTokenServiceTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The dashboard visibility grant (SEC-25) survives the mint/validate round-trip: tags are
|
||||
/// The dashboard visibility grant survives the mint/validate round-trip: tags are
|
||||
/// resolved from the caller's LDAP-group claims through <c>Dashboard:GroupToTag</c> at
|
||||
/// <see cref="HubTokenService.Issue(ClaimsPrincipal)"/> and rehydrated as
|
||||
/// <see cref="DashboardAuthenticationDefaults.DashboardTagClaimType"/> claims on the principal
|
||||
|
||||
@@ -16,7 +16,7 @@ using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
|
||||
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
|
||||
|
||||
/// <summary>
|
||||
/// Covers the ACL gate on the session-details page's in-process subscribe seam (SEC-25 / TST-15).
|
||||
/// Covers the ACL gate on the session-details page's in-process subscribe seam.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
@@ -214,7 +214,7 @@ public sealed class SessionDetailsPageEventAclTests
|
||||
/// <inheritdoc />
|
||||
public ChannelReader<MxEvent> Reader => _channel.Reader;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Marks the subscription as released and completes its channel.</summary>
|
||||
public void Dispose()
|
||||
{
|
||||
IsDisposed = true;
|
||||
|
||||
@@ -81,7 +81,7 @@ public sealed class GatewayApplicationTests
|
||||
{
|
||||
// Bind an ephemeral port (:0) — xUnit runs test collections in parallel, so any
|
||||
// started-host test must avoid a fixed port to prevent a bind collision. The auth SQLite
|
||||
// store path is isolated to a per-process temp file by TestHostEnvironmentInitializer (SEC-33).
|
||||
// store path is isolated to a per-process temp file by TestHostEnvironmentInitializer.
|
||||
await using WebApplication app = GatewayApplication.Build(["--urls=http://127.0.0.1:0"]);
|
||||
await app.StartAsync();
|
||||
try
|
||||
@@ -266,7 +266,7 @@ public sealed class GatewayApplicationTests
|
||||
{
|
||||
// Bind an ephemeral port (:0) — xUnit runs test collections in parallel, so any
|
||||
// WebApplication-building test must avoid a fixed port to prevent a bind collision. The auth
|
||||
// store path is isolated by TestHostEnvironmentInitializer (SEC-33), so startup opens a
|
||||
// store path is isolated by TestHostEnvironmentInitializer, so startup opens a
|
||||
// writable store and the injected misconfiguration is what fails validation.
|
||||
await using WebApplication app = GatewayApplication.Build(
|
||||
[$"--{key}={value}", "--urls=http://127.0.0.1:0"]);
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace ZB.MOM.WW.MxGateway.Tests.Gateway;
|
||||
|
||||
/// <summary>
|
||||
/// End-to-end reconnect/replay tests through the real gRPC <c>StreamEvents</c> path via the
|
||||
/// fake worker harness (TST-01, server half). A single subscriber detaches (cancel + await
|
||||
/// fake worker harness (server half). A single subscriber detaches (cancel + await
|
||||
/// the stream task so its lease is fully disposed), the session is retained by detach-grace
|
||||
/// while the distributor pump keeps appending worker events to the replay ring, and a second
|
||||
/// stream reconnects with <see cref="StreamEventsRequest.AfterWorkerSequence"/>. Covers both
|
||||
@@ -233,7 +233,7 @@ public sealed class GatewayEndToEndReconnectReplayTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-25 regression, end to end: when age eviction has emptied the replay ring, the
|
||||
/// Regression, end to end: when age eviction has emptied the replay ring, the
|
||||
/// <c>ReplayGap</c> sentinel carries the next deliverable sequence (highest observed + 1),
|
||||
/// so a client applying the documented
|
||||
/// <c>after_worker_sequence = oldest_available_sequence - 1</c> formula on its follow-up
|
||||
@@ -305,7 +305,7 @@ public sealed class GatewayEndToEndReconnectReplayTests
|
||||
Assert.NotNull(sentinel.ReplayGap);
|
||||
Assert.Equal(staleCursor, sentinel.ReplayGap.RequestedAfterSequence);
|
||||
|
||||
// The whole point of GWC-25: the sentinel anchors the resume at the next sequence that
|
||||
// The whole point of the fix: the sentinel anchors the resume at the next sequence that
|
||||
// can still be delivered, not at 0.
|
||||
Assert.Equal(highestSeen + 1, sentinel.ReplayGap.OldestAvailableSequence);
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class GalaxyRepositoryHostWiringTests
|
||||
/// Verifies the full production authz-filtering chain end-to-end.
|
||||
/// Phase 1: an identity carrying no <c>BrowseSubtrees</c> pushed → <c>BrowseChildren</c>
|
||||
/// returns both children (unconstrained). That, not an absent identity, is production's
|
||||
/// unscoped case since GWC-38 — with <c>Authentication:Mode=ApiKey</c> an absent identity
|
||||
/// unscoped case — with <c>Authentication:Mode=ApiKey</c> an absent identity
|
||||
/// now denies, which the isolated provider test pins.
|
||||
/// Phase 2: identity with <c>BrowseSubtrees = ["NonExistent"]</c>
|
||||
/// pushed via <see cref="GatewayRequestIdentityAccessor"/> → the real
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class MxAccessGatewayServiceTests
|
||||
|
||||
/// <summary>
|
||||
/// Verifies OpenSession forwards the calling key's dashboard-visibility tags, so the
|
||||
/// session's tags are derived from the owning API key rather than the wire request (SEC-25).
|
||||
/// session's tags are derived from the owning API key rather than the wire request.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class MxAccessGrpcMapperTests
|
||||
|
||||
/// <summary>
|
||||
/// The command-only overload exists so <c>Invoke</c> does not deep-clone the whole request just to
|
||||
/// overwrite and discard its command (GWC-29). It must still perform the one clone that keeps the
|
||||
/// overwrite and discard its command. It must still perform the one clone that keeps the
|
||||
/// worker-bound graph unaliased from the caller-owned gRPC command, and must produce the same
|
||||
/// <see cref="WorkerCommand"/> as the request overload.
|
||||
/// </summary>
|
||||
@@ -145,7 +145,7 @@ public sealed class MxAccessGrpcMapperTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies MapEvent transfers ownership of the inner MxEvent (GWC-07 / IPC-05): the
|
||||
/// Verifies MapEvent transfers ownership of the inner MxEvent: the
|
||||
/// returned reference is the same instance carried by the WorkerEvent, not a clone. The
|
||||
/// WorkerEvent is discarded after mapping and the distributor pump is its single consumer,
|
||||
/// so moving the inner event out is safe and avoids a per-event deep copy.
|
||||
|
||||
@@ -669,7 +669,7 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-27: <see cref="GatewaySession.AttachInternalEventSubscriber"/> must refuse to
|
||||
/// <see cref="GatewaySession.AttachInternalEventSubscriber"/> must refuse to
|
||||
/// attach before the session is Ready. Without the gate the attach would construct and
|
||||
/// start the distributor against a not-yet-Ready worker; the pump source throws
|
||||
/// <c>SessionNotReady</c>, every subscriber is completed with that error, and the
|
||||
@@ -720,7 +720,7 @@ public sealed class GatewaySessionTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-34, the disposal-side twin of the GWC-27 premature-attach poison: an attach that
|
||||
/// The disposal-side twin of the premature-attach poison: an attach that
|
||||
/// passed its readiness gate and then lost the CPU until the session was torn down used to
|
||||
/// find <c>_eventDistributor</c> null, read that as "not created yet" and build a
|
||||
/// REPLACEMENT distributor. Its pump could never start (the started flag is a one-way latch)
|
||||
|
||||
@@ -843,7 +843,7 @@ public sealed class SessionEventDistributorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-25: when age eviction has emptied the replay ring, a resume behind the highest
|
||||
/// When age eviction has emptied the replay ring, a resume behind the highest
|
||||
/// observed sequence still reports a gap, and the reported oldest-available sequence is
|
||||
/// the next sequence that can possibly be delivered (highest seen + 1) — never <c>0</c>,
|
||||
/// which would make the client's documented <c>oldest - 1</c> resume formula wrap to
|
||||
@@ -889,7 +889,7 @@ public sealed class SessionEventDistributorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-25: the same next-deliverable-sequence rule applies when retention is disabled
|
||||
/// The same next-deliverable-sequence rule applies when retention is disabled
|
||||
/// outright (replay capacity 0) — the ring is always empty, so a behind cursor must still
|
||||
/// get a usable resume anchor rather than <c>0</c>.
|
||||
/// </summary>
|
||||
@@ -924,7 +924,7 @@ public sealed class SessionEventDistributorTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-25 regression: a client that applies the documented
|
||||
/// Regression: a client that applies the documented
|
||||
/// <c>after_worker_sequence = oldest_available_sequence - 1</c> formula to an empty-ring
|
||||
/// ReplayGap resumes with a live watermark equal to the highest observed sequence, so the
|
||||
/// caller's live filter passes every subsequent event. Under the pre-fix <c>0</c> the
|
||||
|
||||
@@ -41,7 +41,7 @@ public sealed class SessionManagerTests
|
||||
/// Verifies the pipe name stays short enough that its Unix-domain-socket path
|
||||
/// (TMPDIR + "CoreFxPipe_" + name) fits the 104-byte macOS sun_path limit under the
|
||||
/// default per-user TMPDIR (~49 chars), and keeps the pid + session-guid uniqueness
|
||||
/// contract (NEXT-01).
|
||||
/// contract.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
@@ -110,7 +110,7 @@ public sealed class SessionManagerTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies a session inherits the owning API key's dashboard-visibility tags (SEC-25),
|
||||
/// Verifies a session inherits the owning API key's dashboard-visibility tags,
|
||||
/// compared ordinal-ignore-case so a differently cased grant still matches.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
|
||||
+4
-1
@@ -24,7 +24,10 @@ public sealed class SessionWorkerClientFactoryFakeWorkerTests : IAsyncLifetime
|
||||
|
||||
private readonly List<IWorkerTaskLauncher> _launchers = [];
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Completes immediately; no asynchronous setup is required before each test.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -36,10 +36,10 @@ public sealed class WorkerClientTests
|
||||
|
||||
/// <summary>
|
||||
/// The <c>GatewayHello</c> carries the negotiated worker-frame maximum so the worker adopts the
|
||||
/// configured limit instead of its own default (IPC-02); a regression to sending <c>0</c> means
|
||||
/// configured limit instead of its own default; a regression to sending <c>0</c> means
|
||||
/// "older gateway, use default" to the worker and would silently downgrade the negotiated limit.
|
||||
/// The adoption half is asserted only in the Windows-only worker suite, so the gateway half is
|
||||
/// pinned here, in the portable suite (TST-28). Both the default and an override are covered so
|
||||
/// pinned here, in the portable suite. Both the default and an override are covered so
|
||||
/// the assertion tracks configuration rather than a constant.
|
||||
/// </summary>
|
||||
/// <param name="maxMessageBytes">Configured worker-frame maximum to negotiate.</param>
|
||||
@@ -164,7 +164,7 @@ public sealed class WorkerClientTests
|
||||
TestTimeout,
|
||||
CancellationToken.None);
|
||||
|
||||
// The timeout also emits a WorkerCancel for the abandoned correlation (PERF-07), which sits
|
||||
// The timeout also emits a WorkerCancel for the abandoned correlation, which sits
|
||||
// ahead of the second command on the FIFO pipe; skip it rather than mistaking it for the
|
||||
// command this assertion is about.
|
||||
WorkerEnvelope secondCommand = await ReadNextCommandAsync(pipePair, timedOutCommand.CorrelationId);
|
||||
@@ -180,7 +180,7 @@ public sealed class WorkerClientTests
|
||||
/// <summary>
|
||||
/// A command timeout abandons the gateway-side wait, but the worker keeps the correlation on
|
||||
/// its single STA queue and would still run it — so the gateway forwards a <c>WorkerCancel</c>
|
||||
/// for the abandoned correlation id (PERF-07). Without it, a client that retries after a
|
||||
/// for the abandoned correlation id. Without it, a client that retries after a
|
||||
/// timeout stacks work the worker still intends to execute. Asserted on the wire because the
|
||||
/// cancel is protocol behavior the worker depends on, not an internal detail.
|
||||
/// </summary>
|
||||
@@ -223,7 +223,7 @@ public sealed class WorkerClientTests
|
||||
/// <summary>
|
||||
/// Caller cancellation abandons the correlation exactly as a gateway timeout does — a client
|
||||
/// deadline shorter than the gateway's command timeout, or a disconnect, is the common
|
||||
/// abandonment path — so it forwards the same <c>WorkerCancel</c> (IPC-34). Without it a
|
||||
/// abandonment path — so it forwards the same <c>WorkerCancel</c>. Without it a
|
||||
/// retry storm of deadline-cancelled calls leaves the worker's single STA working through
|
||||
/// commands nobody is waiting for while live callers queue behind them. Twin of
|
||||
/// <see cref="InvokeAsync_WhenCommandTimesOut_SendsWorkerCancelForThatCorrelation"/>, driven
|
||||
@@ -271,7 +271,7 @@ public sealed class WorkerClientTests
|
||||
|
||||
/// <summary>
|
||||
/// The cancel is forwarded for a caller that abandoned its command, never for the client's own
|
||||
/// teardown (IPC-34): once the stop token has fired the outbound channel is being completed and
|
||||
/// teardown: once the stop token has fired the outbound channel is being completed and
|
||||
/// every pending command is failed anyway, so a cancel would at best be pointless and at worst
|
||||
/// a write racing teardown. Asserted on the wire — the guard in the cancellation arm is the
|
||||
/// code-level expression of it, and the completed outbound channel is the second line of
|
||||
@@ -330,8 +330,7 @@ public sealed class WorkerClientTests
|
||||
/// A <c>WorkerFault</c> arriving in place of a handshake frame is the worker reporting a
|
||||
/// structured startup failure — most often MXAccess COM creation — not a protocol problem, so
|
||||
/// the gateway surfaces <see cref="WorkerClientErrorCode.WorkerFaulted"/> with the fault's
|
||||
/// category and diagnostic rather than a generic protocol violation naming only the body type
|
||||
/// (IPC-35). This is the window where the information matters most: first session open on a
|
||||
/// category and diagnostic rather than a generic protocol violation naming only the body type. This is the window where the information matters most: first session open on a
|
||||
/// freshly deployed host, where the message reaches the operator log and the client's
|
||||
/// <c>OpenFailed</c> detail.
|
||||
/// </summary>
|
||||
@@ -368,8 +367,7 @@ public sealed class WorkerClientTests
|
||||
/// <summary>
|
||||
/// The envelope <c>sequence</c> is a monotonic per-sender counter (gateway.md), so the values
|
||||
/// observed on the pipe must be strictly increasing in wire order. Stamping the sequence when
|
||||
/// the envelope is constructed lets two concurrent invokes stamp 1 and 2 but enqueue 2 then 1
|
||||
/// (GWC-28); stamping on the single-consumer write loop makes wire order and sequence order the
|
||||
/// the envelope is constructed lets two concurrent invokes stamp 1 and 2 but enqueue 2 then 1; stamping on the single-consumer write loop makes wire order and sequence order the
|
||||
/// same thing by construction.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
@@ -710,12 +708,12 @@ public sealed class WorkerClientTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-32: disposing a still-<c>Ready</c> client while the worker is emitting events must be
|
||||
/// Disposing a still-<c>Ready</c> client while the worker is emitting events must be
|
||||
/// an ordinary teardown, not a fault. <c>DisposeAsync</c> completes the same three channels
|
||||
/// <c>MarkClosed</c> and <c>SetFaulted</c> do, so it must enter a terminal state first;
|
||||
/// otherwise the read loop — already inside <c>DispatchEnvelope</c> when the staging channel
|
||||
/// is completed, or reading a stream that is torn down underneath it — misdiagnoses the
|
||||
/// completed channel as the GWC-24 sustained-backpressure overflow and raises a spurious
|
||||
/// completed channel as the sustained-backpressure overflow and raises a spurious
|
||||
/// <c>ProtocolViolation</c> fault, a <c>worker-event-staging</c> overflow metric and a
|
||||
/// redundant kill during an intentional dispose. The event channel is sized far above the
|
||||
/// burst so a genuine overflow is impossible, and <c>DisposeAsync</c> awaits its background
|
||||
@@ -726,7 +724,7 @@ public sealed class WorkerClientTests
|
||||
public async Task DisposeAsync_WhileWorkerIsEmittingEvents_ClosesWithoutFaultingOrOverflowing()
|
||||
{
|
||||
// The burst is capped at EmittedEventCeiling and the staging channel holds 2 x capacity, so
|
||||
// even if every emitted event were staged and none consumed the GWC-24 bound is unreachable:
|
||||
// even if every emitted event were staged and none consumed the staging bound is unreachable:
|
||||
// an overflow metric in this test can only be the dispose-race misdiagnosis.
|
||||
const int capacity = 2048;
|
||||
using GatewayMetrics metrics = new();
|
||||
@@ -1007,13 +1005,13 @@ public sealed class WorkerClientTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-24: the staging channel between the read loop and the event writer is bounded at
|
||||
/// The staging channel between the read loop and the event writer is bounded at
|
||||
/// 2 × <see cref="WorkerClientOptions.EventChannelCapacity"/>, so a consumer that drains
|
||||
/// slower than the worker produces faults the client at the bound instead of growing
|
||||
/// gateway memory silently. The full-mode timeout here is far longer than the test could
|
||||
/// ever wait, which proves the fault came from the staging bound and not from the timed
|
||||
/// write in <c>EnqueueWorkerEventAsync</c>. A command reply interleaved before the fault
|
||||
/// must still complete: the read loop never blocks behind events (the GWC-04 guarantee).
|
||||
/// must still complete: the read loop never blocks behind events (the read-loop guarantee).
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
@@ -1094,7 +1092,7 @@ public sealed class WorkerClientTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-24: the worker event queue-depth gauge counts staged *and* queued events, so a
|
||||
/// The worker event queue-depth gauge counts staged *and* queued events, so a
|
||||
/// backlog held in the staging channel is visible rather than invisible. Depth is
|
||||
/// incremented at staging and decremented when the consumer reads, so the single counter
|
||||
/// reports total undelivered events and returns to zero once drained.
|
||||
@@ -1148,7 +1146,7 @@ public sealed class WorkerClientTests
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GWC-33: a faulted client can remain retained for diagnostics, but its abandoned event
|
||||
/// A faulted client can remain retained for diagnostics, but its abandoned event
|
||||
/// backlog must stop contributing to the gateway-wide queue-depth gauge as soon as the
|
||||
/// client enters a terminal state rather than waiting for disposal.
|
||||
/// </summary>
|
||||
|
||||
@@ -57,7 +57,7 @@ public sealed class WorkerFrameProtocolTests
|
||||
|
||||
/// <summary>
|
||||
/// One reader instance reads many frames in sequence. The reader reuses a single length-prefix
|
||||
/// scratch buffer across calls (GWC-30), so varying the payload length frame to frame proves the
|
||||
/// scratch buffer across calls, so varying the payload length frame to frame proves the
|
||||
/// reused buffer is fully overwritten each time rather than carrying a stale prefix forward.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
|
||||
Reference in New Issue
Block a user