using Microsoft.Extensions.Logging.Abstractions; using NATS.Server.Configuration; using NATS.Server; using NATS.Server.Gateways; using NATS.Server.Protocol; using NATS.Server.Routes; namespace NATS.Server.Clustering.Tests; // Go reference: server/route.go processImplicitRoute, server/gateway.go processImplicitGateway public class ImplicitDiscoveryTests { [Fact] public void ProcessImplicitRoute_discovers_new_peer() { // Go reference: server/route.go processImplicitRoute var mgr = RouteManagerTestHelper.Create(); var serverInfo = new ServerInfo { ServerId = "server-2", ServerName = "server-2", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = ["nats://10.0.0.2:6222", "nats://10.0.0.3:6222"], }; mgr.ProcessImplicitRoute(serverInfo); mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.2:6222"); mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.3:6222"); } [Fact] public void ProcessImplicitRoute_skips_known_peers() { // Go reference: server/route.go processImplicitRoute — skip already-known var mgr = RouteManagerTestHelper.Create(); mgr.AddKnownRoute("nats://10.0.0.2:6222"); var serverInfo = new ServerInfo { ServerId = "server-2", ServerName = "server-2", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = ["nats://10.0.0.2:6222", "nats://10.0.0.3:6222"], }; mgr.ProcessImplicitRoute(serverInfo); mgr.DiscoveredRoutes.Count.ShouldBe(1); // only 10.0.0.3 is new mgr.DiscoveredRoutes.ShouldContain("nats://10.0.0.3:6222"); } [Fact] public void ProcessImplicitRoute_with_null_urls_is_noop() { // Go reference: server/route.go processImplicitRoute — nil ConnectUrls guard var mgr = RouteManagerTestHelper.Create(); var serverInfo = new ServerInfo { ServerId = "server-2", ServerName = "server-2", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = null, }; mgr.ProcessImplicitRoute(serverInfo); mgr.DiscoveredRoutes.Count.ShouldBe(0); } [Fact] public void ProcessImplicitRoute_with_empty_urls_is_noop() { // Go reference: server/route.go processImplicitRoute — empty ConnectUrls guard var mgr = RouteManagerTestHelper.Create(); var serverInfo = new ServerInfo { ServerId = "server-2", ServerName = "server-2", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = [], }; mgr.ProcessImplicitRoute(serverInfo); mgr.DiscoveredRoutes.Count.ShouldBe(0); } [Fact] public void ProcessImplicitGateway_discovers_new_gateway() { // Go reference: server/gateway.go processImplicitGateway var mgr = GatewayManagerTestHelper.Create(); var gwInfo = new GatewayInfo { Name = "cluster-B", Urls = ["nats://10.0.1.1:7222"], }; mgr.ProcessImplicitGateway(gwInfo); mgr.DiscoveredGateways.ShouldContain("cluster-B"); } [Fact] public void ProcessImplicitGateway_with_null_throws() { // Go reference: server/gateway.go processImplicitGateway — null guard var mgr = GatewayManagerTestHelper.Create(); Should.Throw(() => mgr.ProcessImplicitGateway(null!)); } [Fact] public void ProcessImplicitGateway_deduplicates_same_cluster() { // Go reference: server/gateway.go processImplicitGateway — idempotent discovery var mgr = GatewayManagerTestHelper.Create(); var gwInfo = new GatewayInfo { Name = "cluster-B", Urls = ["nats://10.0.1.1:7222"] }; mgr.ProcessImplicitGateway(gwInfo); mgr.ProcessImplicitGateway(gwInfo); mgr.DiscoveredGateways.Count.ShouldBe(1); } [Fact] public void ForwardNewRouteInfo_invokes_event() { // Go reference: server/route.go forwardNewRouteInfoToKnownServers var mgr = RouteManagerTestHelper.Create(); var forwarded = new List(); mgr.OnForwardInfo += urls => forwarded.AddRange(urls); mgr.ForwardNewRouteInfoToKnownServers("nats://10.0.0.5:6222"); forwarded.ShouldContain("nats://10.0.0.5:6222"); } [Fact] public void ForwardNewRouteInfo_with_no_handler_does_not_throw() { // Go reference: server/route.go forwardNewRouteInfoToKnownServers — no subscribers var mgr = RouteManagerTestHelper.Create(); Should.NotThrow(() => mgr.ForwardNewRouteInfoToKnownServers("nats://10.0.0.5:6222")); } [Fact] public void AddKnownRoute_prevents_later_discovery() { // Go reference: server/route.go processImplicitRoute — pre-seeded known routes var mgr = RouteManagerTestHelper.Create(); mgr.AddKnownRoute("nats://10.0.0.9:6222"); var serverInfo = new ServerInfo { ServerId = "server-3", ServerName = "server-3", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = ["nats://10.0.0.9:6222"], }; mgr.ProcessImplicitRoute(serverInfo); mgr.DiscoveredRoutes.Count.ShouldBe(0); } [Fact] public void ConnectUrls_is_serialized_when_set() { // Go reference: server/route.go INFO message includes connect_urls var info = new ServerInfo { ServerId = "s1", ServerName = "s1", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = ["nats://10.0.0.1:4222"], }; var json = System.Text.Json.JsonSerializer.Serialize(info); json.ShouldContain("connect_urls"); json.ShouldContain("nats://10.0.0.1:4222"); } [Fact] public void ConnectUrls_is_omitted_when_null() { // Go reference: server/route.go INFO omits connect_urls when empty var info = new ServerInfo { ServerId = "s1", ServerName = "s1", Version = "0.1.0", Host = "0.0.0.0", Port = 4222, ConnectUrls = null, }; var json = System.Text.Json.JsonSerializer.Serialize(info); json.ShouldNotContain("connect_urls"); } } public static class RouteManagerTestHelper { public static RouteManager Create() { var options = new ClusterOptions { Name = "test-cluster", Host = "127.0.0.1", Port = 0 }; var stats = new ServerStats(); return new RouteManager(options, stats, "server-1", _ => { }, _ => { }, NullLogger.Instance); } } public static class GatewayManagerTestHelper { public static GatewayManager Create() { var options = new GatewayOptions { Name = "cluster-A", Host = "127.0.0.1", Port = 0 }; var stats = new ServerStats(); return new GatewayManager(options, stats, "server-1", _ => { }, _ => { }, NullLogger.Instance); } }