// Reference: golang/nats-server/server/route.go — negotiateRoutePool (pooling handshake logic) // Tests for route pool size negotiation between local and remote peers. using System.Net; using System.Net.Sockets; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Configuration; using NATS.Server.Routes; namespace NATS.Server.Clustering.Tests.Routes; /// /// Tests for route pool size negotiation (Gap 13.3). /// Covers RouteConnection.NegotiatePoolSize static method, /// RouteConnection.NegotiatedPoolSize default, RouteManager.ConfiguredPoolSize, /// and RouteManager.GetEffectivePoolSize. /// Go reference: server/route.go negotiateRoutePool. /// public class PoolSizeNegotiationTests { /// /// Creates a connected socket pair so RouteConnection can build its NetworkStream. /// Returns the RouteConnection (owns the server-side socket) and the client-side /// socket, which the caller must dispose to avoid resource leaks. /// private static (RouteConnection Route, Socket ClientSocket) CreateConnectedRoute() { var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Loopback, 0)); listener.Listen(1); var port = ((IPEndPoint)listener.LocalEndPoint!).Port; var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(IPAddress.Loopback, port); var server = listener.Accept(); listener.Dispose(); return (new RouteConnection(server), client); } // ----------------------------------------------------------------------- // RouteConnection.NegotiatePoolSize static method // ----------------------------------------------------------------------- [Fact] public void NegotiatePoolSize_BothNonZero_ReturnsMin() { // Go reference: server/route.go negotiateRoutePool — min(local, remote) var result = RouteConnection.NegotiatePoolSize(3, 5); result.ShouldBe(3); } [Fact] public void NegotiatePoolSize_Equal_ReturnsSame() { // Go reference: server/route.go negotiateRoutePool — equal sizes var result = RouteConnection.NegotiatePoolSize(3, 3); result.ShouldBe(3); } [Fact] public void NegotiatePoolSize_LocalZero_ReturnsZero() { // Go reference: server/route.go negotiateRoutePool — backward compat: // if either peer sends 0 (no pooling advertised), result is 0. var result = RouteConnection.NegotiatePoolSize(0, 5); result.ShouldBe(0); } [Fact] public void NegotiatePoolSize_RemoteZero_ReturnsZero() { // Go reference: server/route.go negotiateRoutePool — backward compat: // remote peer without pool support sends 0; result must be 0. var result = RouteConnection.NegotiatePoolSize(3, 0); result.ShouldBe(0); } [Fact] public void NegotiatePoolSize_BothZero_ReturnsZero() { // Go reference: server/route.go negotiateRoutePool — both disabled var result = RouteConnection.NegotiatePoolSize(0, 0); result.ShouldBe(0); } [Fact] public void NegotiatePoolSize_LocalLarger_ReturnsRemote() { // Go reference: server/route.go negotiateRoutePool — min(10, 3) = 3 var result = RouteConnection.NegotiatePoolSize(10, 3); result.ShouldBe(3); } [Fact] public void NegotiatePoolSize_RemoteLarger_ReturnsLocal() { // Go reference: server/route.go negotiateRoutePool — min(3, 10) = 3 var result = RouteConnection.NegotiatePoolSize(3, 10); result.ShouldBe(3); } [Fact] public void NegotiatePoolSize_OneIsOne_ReturnsOne() { // Go reference: server/route.go negotiateRoutePool — min(1, 5) = 1 var result = RouteConnection.NegotiatePoolSize(1, 5); result.ShouldBe(1); } // ----------------------------------------------------------------------- // RouteConnection.NegotiatedPoolSize default value // ----------------------------------------------------------------------- [Fact] public void NegotiatedPoolSize_Default_IsZero() { // A newly created RouteConnection (not yet handshaked) must report // NegotiatedPoolSize == 0 to signal that negotiation has not occurred. // Go reference: server/route.go — pool size is 0 until negotiateRoutePool runs. var (conn, clientSocket) = CreateConnectedRoute(); try { conn.NegotiatedPoolSize.ShouldBe(0); } finally { clientSocket.Dispose(); } } // ----------------------------------------------------------------------- // RouteManager.ComputeRoutePoolIdx determinism (regression guard) // ----------------------------------------------------------------------- [Fact] public void ComputeRoutePoolIdx_Deterministic() { // The same account name must always map to the same pool index. // Go reference: server/route.go computeRoutePoolIdx (FNV-1a 32-bit hash). const int poolSize = 5; const string account = "test-account"; var first = RouteManager.ComputeRoutePoolIdx(poolSize, account); var second = RouteManager.ComputeRoutePoolIdx(poolSize, account); var third = RouteManager.ComputeRoutePoolIdx(poolSize, account); first.ShouldBe(second); second.ShouldBe(third); first.ShouldBeGreaterThanOrEqualTo(0); first.ShouldBeLessThan(poolSize); } // ----------------------------------------------------------------------- // RouteManager.ConfiguredPoolSize and GetEffectivePoolSize // ----------------------------------------------------------------------- private static RouteManager MakeManager(int poolSize = 3) { var opts = new ClusterOptions { Host = "127.0.0.1", Port = 0, PoolSize = poolSize, }; return new RouteManager( opts, new ServerStats(), Guid.NewGuid().ToString("N"), _ => { }, _ => { }, NullLogger.Instance); } [Fact] public void ConfiguredPoolSize_ReturnsOptionsPoolSize() { // RouteManager should expose the configured pool size from ClusterOptions. // Go reference: server/route.go opts.Cluster.PoolSize default is 3. var manager = MakeManager(poolSize: 5); manager.ConfiguredPoolSize.ShouldBe(5); } [Fact] public void ConfiguredPoolSize_DefaultThreeWhenOptionsIsZero() { // When ClusterOptions.PoolSize is 0 (not explicitly set), the effective // configured pool size should fall back to 3 (Go's default). // Go reference: server/route.go default pool size of 3. var manager = MakeManager(poolSize: 0); manager.ConfiguredPoolSize.ShouldBe(3); } [Fact] public void GetEffectivePoolSize_NullRemoteId_ReturnsConfigured() { // With no remote server ID, GetEffectivePoolSize returns the configured pool size. var manager = MakeManager(poolSize: 4); manager.GetEffectivePoolSize(null).ShouldBe(4); } [Fact] public void GetEffectivePoolSize_UnknownRemoteId_ReturnsConfigured() { // For a remote server ID that has no connected (and negotiated) route, // GetEffectivePoolSize should return the configured pool size. var manager = MakeManager(poolSize: 3); manager.GetEffectivePoolSize("unknown-server-id").ShouldBe(3); } }