Files
mxaccessgw/src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/EventsHubViewerRegistryTests.cs
T
Joseph Doherty f40409a688
ci / nightly-windev (push) Has been skipped
ci / windows-x86 (push) Successful in 1m16s
ci / java (push) Successful in 2m5s
ci / portable (push) Failing after 5m9s
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).
2026-10-07 15:34:32 -04:00

168 lines
5.7 KiB
C#

using ZB.MOM.WW.MxGateway.Server.Dashboard.Hubs;
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
/// <summary>
/// Verifies <see cref="EventsHubViewerRegistry"/>, the seam
/// <see cref="DashboardEventBroadcaster"/> consults before cloning and sending
/// an event: a session is "watched" only while at least one hub connection
/// holds a subscription to it, and a dropped connection releases every
/// subscription it held.
/// </summary>
public sealed class EventsHubViewerRegistryTests
{
/// <summary>A session with no subscriber is not watched.</summary>
[Fact]
public void HasViewers_WithNoSubscribers_IsFalse()
{
EventsHubViewerRegistry registry = new();
Assert.False(registry.HasViewers("session-1"));
}
/// <summary>Adding then removing the only viewer flips the session back to unwatched.</summary>
[Fact]
public void AddViewer_ThenRemoveViewer_TogglesWatchedState()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
Assert.True(registry.HasViewers("session-1"));
registry.RemoveViewer("conn-1", "session-1");
Assert.False(registry.HasViewers("session-1"));
}
/// <summary>Each connection counts once: the session stays watched until the last one leaves.</summary>
[Fact]
public void RemoveViewer_WithOtherConnectionsStillSubscribed_KeepsSessionWatched()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
registry.AddViewer("conn-2", "session-1");
registry.RemoveViewer("conn-1", "session-1");
Assert.True(registry.HasViewers("session-1"));
registry.RemoveViewer("conn-2", "session-1");
Assert.False(registry.HasViewers("session-1"));
}
/// <summary>A repeated subscribe from the same connection is idempotent, so one unsubscribe clears it.</summary>
[Fact]
public void AddViewer_CalledTwiceForSameConnection_CountsOnce()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
registry.AddViewer("conn-1", "session-1");
registry.RemoveViewer("conn-1", "session-1");
Assert.False(registry.HasViewers("session-1"));
}
/// <summary>Viewer counts are tracked per session; unrelated sessions stay unwatched.</summary>
[Fact]
public void AddViewer_TracksSessionsIndependently()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
Assert.True(registry.HasViewers("session-1"));
Assert.False(registry.HasViewers("session-2"));
}
/// <summary>A dropped connection releases every session it held.</summary>
[Fact]
public void ReleaseConnection_ReleasesEverySessionTheConnectionHeld()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
registry.AddViewer("conn-1", "session-2");
registry.AddViewer("conn-2", "session-2");
registry.ReleaseConnection("conn-1");
Assert.False(registry.HasViewers("session-1"));
// conn-2 still watches session-2.
Assert.True(registry.HasViewers("session-2"));
}
/// <summary>Releasing a connection twice does not double-decrement another connection's subscription.</summary>
[Fact]
public void ReleaseConnection_CalledTwice_DoesNotDropOtherViewers()
{
EventsHubViewerRegistry registry = new();
registry.AddViewer("conn-1", "session-1");
registry.AddViewer("conn-2", "session-1");
registry.ReleaseConnection("conn-1");
registry.ReleaseConnection("conn-1");
Assert.True(registry.HasViewers("session-1"));
}
/// <summary>Unmatched removals cannot drive the count negative and strand a session as unwatched.</summary>
[Fact]
public void RemoveViewer_WithoutMatchingAdd_LeavesCountAtZero()
{
EventsHubViewerRegistry registry = new();
registry.RemoveViewer("conn-1", "session-1");
registry.RemoveViewer("conn-1", "session-1");
registry.ReleaseConnection("conn-1");
Assert.False(registry.HasViewers("session-1"));
// A subsequent genuine subscribe must still register as exactly one viewer.
registry.AddViewer("conn-1", "session-1");
Assert.True(registry.HasViewers("session-1"));
registry.RemoveViewer("conn-1", "session-1");
Assert.False(registry.HasViewers("session-1"));
}
/// <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")]
[InlineData("conn-1", "")]
[InlineData("conn-1", " ")]
public void AddViewer_WithBlankIdentifiers_IsIgnored(string connectionId, string sessionId)
{
EventsHubViewerRegistry registry = new();
registry.AddViewer(connectionId, sessionId);
Assert.False(registry.HasViewers(sessionId));
Assert.False(registry.HasViewers("session-1"));
}
/// <summary>Concurrent add/remove pairs settle at zero viewers, never at a stuck-on count.</summary>
[Fact]
public void AddAndRemoveViewer_UnderConcurrency_SettlesAtZero()
{
EventsHubViewerRegistry registry = new();
Parallel.For(0, 64, i =>
{
string connectionId = $"conn-{i}";
for (int pass = 0; pass < 50; pass++)
{
registry.AddViewer(connectionId, "session-1");
registry.RemoveViewer(connectionId, "session-1");
}
});
Assert.False(registry.HasViewers("session-1"));
}
}