using System.Net; using System.Net.Sockets; using NATS.Server.Gateways; using Shouldly; namespace NATS.Server.Gateways.Tests.Gateways; /// /// Tests for queue group subscription tracking on GatewayConnection. /// Go reference: gateway.go — sendQueueSubsToGateway, queueSubscriptions state. /// public class QueueGroupPropagationTests : IAsyncDisposable { private readonly TcpListener _listener; private readonly Socket _clientSocket; private readonly Socket _serverSocket; private readonly GatewayConnection _gw; public QueueGroupPropagationTests() { _listener = new TcpListener(IPAddress.Loopback, 0); _listener.Start(); var port = ((IPEndPoint)_listener.LocalEndpoint).Port; _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _clientSocket.Connect(IPAddress.Loopback, port); _serverSocket = _listener.AcceptSocket(); _gw = new GatewayConnection(_serverSocket); } public async ValueTask DisposeAsync() { await _gw.DisposeAsync(); _clientSocket.Dispose(); _listener.Stop(); } // Go: gateway.go — sendQueueSubsToGateway registers queue group subscriptions per subject [Fact] public void AddQueueSubscription_registers_group() { _gw.AddQueueSubscription("orders.new", "workers"); _gw.HasQueueSubscription("orders.new", "workers").ShouldBeTrue(); } // Go: gateway.go — getQueueGroups returns all groups for a subject [Fact] public void GetQueueGroups_returns_groups_for_subject() { _gw.AddQueueSubscription("payments.>", "billing"); _gw.AddQueueSubscription("payments.>", "audit"); var groups = _gw.GetQueueGroups("payments.>"); groups.ShouldContain("billing"); groups.ShouldContain("audit"); groups.Count.ShouldBe(2); } // Go: gateway.go — removeQueueSubscription removes a specific queue group [Fact] public void RemoveQueueSubscription_removes_group() { _gw.AddQueueSubscription("events.click", "analytics"); _gw.AddQueueSubscription("events.click", "logging"); _gw.RemoveQueueSubscription("events.click", "analytics"); _gw.HasQueueSubscription("events.click", "analytics").ShouldBeFalse(); _gw.HasQueueSubscription("events.click", "logging").ShouldBeTrue(); } // Go: gateway.go — hasQueueSubscription returns true for registered subject/group pair [Fact] public void HasQueueSubscription_true_when_registered() { _gw.AddQueueSubscription("tasks.process", "pool"); _gw.HasQueueSubscription("tasks.process", "pool").ShouldBeTrue(); } // Go: gateway.go — hasQueueSubscription returns false for unknown pair [Fact] public void HasQueueSubscription_false_when_not_registered() { _gw.HasQueueSubscription("unknown.subject", "nonexistent-group").ShouldBeFalse(); } // Go: gateway.go — multiple queue groups can be registered for the same subject [Fact] public void Multiple_groups_per_subject() { _gw.AddQueueSubscription("jobs.run", "fast"); _gw.AddQueueSubscription("jobs.run", "slow"); _gw.AddQueueSubscription("jobs.run", "batch"); var groups = _gw.GetQueueGroups("jobs.run"); groups.Count.ShouldBe(3); groups.ShouldContain("fast"); groups.ShouldContain("slow"); groups.ShouldContain("batch"); } // Go: gateway.go — subscriptions for different subjects are tracked independently [Fact] public void Different_subjects_tracked_independently() { _gw.AddQueueSubscription("subject.a", "group1"); _gw.AddQueueSubscription("subject.b", "group2"); _gw.HasQueueSubscription("subject.a", "group2").ShouldBeFalse(); _gw.HasQueueSubscription("subject.b", "group1").ShouldBeFalse(); _gw.HasQueueSubscription("subject.a", "group1").ShouldBeTrue(); _gw.HasQueueSubscription("subject.b", "group2").ShouldBeTrue(); } // Go: gateway.go — QueueSubscriptionCount reflects number of distinct subjects with queue groups [Fact] public void QueueSubscriptionCount_tracks_subjects() { _gw.QueueSubscriptionCount.ShouldBe(0); _gw.AddQueueSubscription("foo", "g1"); _gw.QueueSubscriptionCount.ShouldBe(1); _gw.AddQueueSubscription("bar", "g2"); _gw.QueueSubscriptionCount.ShouldBe(2); // Adding a second group to an existing subject does not increase count _gw.AddQueueSubscription("foo", "g3"); _gw.QueueSubscriptionCount.ShouldBe(2); } // Go: gateway.go — removing a queue group that was never added is a no-op [Fact] public void RemoveQueueSubscription_no_error_for_unknown() { // Should not throw even though neither subject nor group was registered var act = () => _gw.RemoveQueueSubscription("never.registered", "ghost"); act.ShouldNotThrow(); } // Go: gateway.go — GetQueueGroups returns empty set for unknown subject [Fact] public void GetQueueGroups_empty_for_unknown_subject() { var groups = _gw.GetQueueGroups("nonexistent.subject"); groups.ShouldNotBeNull(); groups.Count.ShouldBe(0); } }