f40409a688
Tracking IDs are review bookkeeping that rots in shipped source; the rationale they tagged is kept. Comment-only change (CommentChecker 458 -> 16 false positives).
168 lines
5.7 KiB
C#
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"));
|
|
}
|
|
}
|