// Reference: golang/nats-server/server/route.go getRoutesExcludePool — no-pool fallback for // backward compatibility with pre-pool peers (Gap 13.6). using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Configuration; using NATS.Server.Routes; namespace NATS.Server.Tests.Routes; /// /// Tests for no-pool (legacy) route fallback — Gap 13.6. /// Verifies that , /// , and the new /// legacy-route helpers behave correctly, and that /// falls back to a legacy route /// when neither a dedicated nor a pool route exists. /// Go reference: server/route.go getRoutesExcludePool. /// public class NoPoolFallbackTests : IDisposable { private readonly List _listeners = []; private readonly List _sockets = []; public void Dispose() { foreach (var s in _sockets) s.Dispose(); foreach (var l in _listeners) l.Stop(); } // ----------------------------------------------------------------------- // Helpers // ----------------------------------------------------------------------- private static RouteManager CreateManager() => new( new ClusterOptions { Host = "127.0.0.1", Port = 0, Routes = [] }, new ServerStats(), Guid.NewGuid().ToString("N"), _ => { }, _ => { }, NullLogger.Instance); /// /// Creates a backed by a connected loopback /// socket pair so that the internal /// can be constructed without throwing. Both sockets are tracked for disposal. /// private RouteConnection MakeConnection() { var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); _listeners.Add(listener); var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _sockets.Add(client); client.Connect((IPEndPoint)listener.LocalEndpoint); var server = listener.AcceptSocket(); _sockets.Add(server); return new RouteConnection(server); } // ----------------------------------------------------------------------- // RouteConnection.SupportsPooling // ----------------------------------------------------------------------- // Go reference: server/route.go — pool-capable route: NegotiatedPoolSize > 0. [Fact] public void SupportsPooling_WhenNegotiated_ReturnsTrue() { var conn = MakeConnection(); conn.SetNegotiatedPoolSize(3); conn.SupportsPooling.ShouldBeTrue(); } // Go reference: server/route.go — default state is pre-negotiation (no pooling). [Fact] public void SupportsPooling_Default_ReturnsFalse() { var conn = MakeConnection(); // NegotiatedPoolSize defaults to 0 — pooling not yet established. conn.SupportsPooling.ShouldBeFalse(); } // ----------------------------------------------------------------------- // RouteConnection.IsLegacyRoute // ----------------------------------------------------------------------- // Go reference: server/route.go getRoutesExcludePool — NegotiatedPoolSize == 0 means legacy. [Fact] public void IsLegacyRoute_Default_ReturnsTrue() { var conn = MakeConnection(); // A freshly created connection has NegotiatedPoolSize == 0, making it legacy. conn.IsLegacyRoute.ShouldBeTrue(); } // Go reference: server/route.go — pool-negotiated connection is not legacy. [Fact] public void IsLegacyRoute_WhenNegotiated_ReturnsFalse() { var conn = MakeConnection(); conn.SetNegotiatedPoolSize(2); conn.IsLegacyRoute.ShouldBeFalse(); } // ----------------------------------------------------------------------- // RouteManager.GetLegacyRoute // ----------------------------------------------------------------------- // Go reference: server/route.go getRoutesExcludePool — returns first legacy connection. [Fact] public void GetLegacyRoute_ReturnsLegacyConnection() { var manager = CreateManager(); var legacy = MakeConnection(); // NegotiatedPoolSize == 0 by default — this is a legacy route. manager.RegisterRoute("server-legacy", legacy); var result = manager.GetLegacyRoute(); result.ShouldBeSameAs(legacy); } // Go reference: server/route.go getRoutesExcludePool — null when all routes support pooling. [Fact] public void GetLegacyRoute_NoLegacy_ReturnsNull() { var manager = CreateManager(); var pooled = MakeConnection(); pooled.SetNegotiatedPoolSize(3); manager.RegisterRoute("server-pooled", pooled); var result = manager.GetLegacyRoute(); result.ShouldBeNull(); } // ----------------------------------------------------------------------- // RouteManager.GetLegacyRoutes // ----------------------------------------------------------------------- // Go reference: server/route.go getRoutesExcludePool — returns all legacy connections. [Fact] public void GetLegacyRoutes_ReturnsAllLegacy() { var manager = CreateManager(); var legacy1 = MakeConnection(); // NegotiatedPoolSize == 0 → legacy var legacy2 = MakeConnection(); // NegotiatedPoolSize == 0 → legacy var pooled = MakeConnection(); pooled.SetNegotiatedPoolSize(3); manager.RegisterRoute("server-legacy-1", legacy1); manager.RegisterRoute("server-legacy-2", legacy2); manager.RegisterRoute("server-pooled", pooled); var result = manager.GetLegacyRoutes(); result.Count.ShouldBe(2); result.ShouldContain(legacy1); result.ShouldContain(legacy2); result.ShouldNotContain(pooled); } // ----------------------------------------------------------------------- // RouteManager.HasLegacyRoutes // ----------------------------------------------------------------------- // Go reference: server/route.go — any legacy route present returns true. [Fact] public void HasLegacyRoutes_WhenPresent_ReturnsTrue() { var manager = CreateManager(); var legacy = MakeConnection(); // IsLegacyRoute == true (default) manager.RegisterRoute("server-legacy", legacy); manager.HasLegacyRoutes.ShouldBeTrue(); } // Go reference: server/route.go — no legacy routes returns false. [Fact] public void HasLegacyRoutes_WhenAbsent_ReturnsFalse() { var manager = CreateManager(); var pooled = MakeConnection(); pooled.SetNegotiatedPoolSize(5); manager.RegisterRoute("server-pooled", pooled); manager.HasLegacyRoutes.ShouldBeFalse(); } // ----------------------------------------------------------------------- // RouteManager.GetRouteForAccount — legacy fallback (Gap 13.6) // ----------------------------------------------------------------------- // Go reference: server/route.go — when no dedicated or pool route exists, // fall back to the first legacy route for the account. [Fact] public void GetRouteForAccount_FallsBackToLegacy() { var manager = CreateManager(); // Register only a legacy route (NegotiatedPoolSize == 0). var legacy = MakeConnection(); manager.RegisterRoute("server-legacy", legacy); // No dedicated account route and no pool-capable route registered. // GetRouteForAccount must return the legacy connection. var result = manager.GetRouteForAccount("$G"); result.ShouldBeSameAs(legacy); } }