// Go: gateway.go — per-account subscription routing state on outbound gateway connections. // Gap 11.3: Account-specific gateway routes. using System.Net; using System.Net.Sockets; using NATS.Server.Gateways; using Shouldly; namespace NATS.Server.Tests.Gateways; /// /// Unit tests for account-specific subscription tracking on GatewayConnection. /// Each test constructs a GatewayConnection using a connected socket pair so the /// constructor succeeds; the subscription tracking methods are pure in-memory operations /// that do not require the network handshake to have completed. /// Go reference: gateway.go — account-scoped subscription propagation on outbound routes. /// public class AccountGatewayRoutesTests : IAsyncDisposable { private readonly TcpListener _listener; private readonly Socket _clientSocket; private readonly Socket _serverSocket; private readonly GatewayConnection _conn; public AccountGatewayRoutesTests() { _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(); _conn = new GatewayConnection(_serverSocket); } public async ValueTask DisposeAsync() { await _conn.DisposeAsync(); _clientSocket.Dispose(); _listener.Stop(); } // Go: gateway.go — AddAccountSubscription records the subject under the account key. [Fact] public void AddAccountSubscription_adds_subject() { _conn.AddAccountSubscription("ACCT_A", "orders.created"); _conn.AccountSubscriptionCount("ACCT_A").ShouldBe(1); } // Go: gateway.go — GetAccountSubscriptions returns a snapshot of tracked subjects. [Fact] public void GetAccountSubscriptions_returns_subjects() { _conn.AddAccountSubscription("ACCT_B", "payments.processed"); _conn.AddAccountSubscription("ACCT_B", "payments.failed"); var subs = _conn.GetAccountSubscriptions("ACCT_B"); subs.ShouldContain("payments.processed"); subs.ShouldContain("payments.failed"); subs.Count.ShouldBe(2); } // Go: gateway.go — RemoveAccountSubscription removes a specific subject. [Fact] public void RemoveAccountSubscription_removes_subject() { _conn.AddAccountSubscription("ACCT_C", "foo.bar"); _conn.AddAccountSubscription("ACCT_C", "foo.baz"); _conn.RemoveAccountSubscription("ACCT_C", "foo.bar"); _conn.GetAccountSubscriptions("ACCT_C").ShouldNotContain("foo.bar"); _conn.GetAccountSubscriptions("ACCT_C").ShouldContain("foo.baz"); } // Go: gateway.go — AccountSubscriptionCount reflects current tracked count. [Fact] public void AccountSubscriptionCount_tracks_count() { _conn.AddAccountSubscription("ACCT_D", "s1"); _conn.AddAccountSubscription("ACCT_D", "s2"); _conn.AddAccountSubscription("ACCT_D", "s3"); _conn.RemoveAccountSubscription("ACCT_D", "s2"); _conn.AccountSubscriptionCount("ACCT_D").ShouldBe(2); } // Go: gateway.go — each account maintains its own isolated subscription set. [Fact] public void Different_accounts_tracked_independently() { _conn.AddAccountSubscription("ACC_X", "shared.subject"); _conn.AddAccountSubscription("ACC_Y", "other.subject"); _conn.GetAccountSubscriptions("ACC_X").ShouldContain("shared.subject"); _conn.GetAccountSubscriptions("ACC_X").ShouldNotContain("other.subject"); _conn.GetAccountSubscriptions("ACC_Y").ShouldContain("other.subject"); _conn.GetAccountSubscriptions("ACC_Y").ShouldNotContain("shared.subject"); } // Go: gateway.go — GetAccountSubscriptions returns empty set for a never-seen account. [Fact] public void GetAccountSubscriptions_returns_empty_for_unknown() { var subs = _conn.GetAccountSubscriptions("UNKNOWN_ACCOUNT"); subs.ShouldBeEmpty(); } // Go: gateway.go — duplicate AddAccountSubscription calls are idempotent (set semantics). [Fact] public void AddAccountSubscription_deduplicates() { _conn.AddAccountSubscription("ACCT_E", "orders.>"); _conn.AddAccountSubscription("ACCT_E", "orders.>"); _conn.AddAccountSubscription("ACCT_E", "orders.>"); _conn.AccountSubscriptionCount("ACCT_E").ShouldBe(1); } // Go: gateway.go — removing a subject that was never added is a no-op (no exception). [Fact] public void RemoveAccountSubscription_no_error_for_unknown() { // Neither the account nor the subject has ever been added. var act = () => _conn.RemoveAccountSubscription("NEVER_ADDED", "some.subject"); act.ShouldNotThrow(); } // Go: gateway.go — an account can track many subjects simultaneously. [Fact] public void Multiple_subjects_per_account() { var subjects = new[] { "a.b", "c.d", "e.f.>", "g.*", "h" }; foreach (var s in subjects) _conn.AddAccountSubscription("ACCT_F", s); var result = _conn.GetAccountSubscriptions("ACCT_F"); result.Count.ShouldBe(subjects.Length); foreach (var s in subjects) result.ShouldContain(s); } // Go: gateway.go — AccountSubscriptionCount returns 0 for an account with no entries. [Fact] public void AccountSubscriptionCount_zero_for_unknown() { _conn.AccountSubscriptionCount("COMPLETELY_NEW_ACCOUNT").ShouldBe(0); } }