// Tests for ConfigReloader.ApplyClusterConfigChanges. // Go reference: golang/nats-server/server/reload.go — routesOption.Apply, gatewayOption.Apply. using NATS.Server; using NATS.Server.Configuration; using Shouldly; namespace NATS.Server.Tests.Configuration; public class ClusterConfigReloadTests { // ─── helpers ──────────────────────────────────────────────────── private static NatsOptions WithRoutes(params string[] routes) { var opts = new NatsOptions(); opts.Cluster = new ClusterOptions { Routes = [..routes] }; return opts; } private static NatsOptions WithGatewayRemotes(params string[] urls) { var opts = new NatsOptions(); opts.Gateway = new GatewayOptions { RemoteGateways = [new RemoteGatewayOptions { Urls = [..urls] }] }; return opts; } private static NatsOptions WithLeafRemotes(params string[] urls) { var opts = new NatsOptions(); opts.LeafNode = new LeafNodeOptions { Remotes = [..urls] }; return opts; } // ─── tests ────────────────────────────────────────────────────── [Fact] public void No_changes_returns_no_changes() { // Go reference: reload.go routesOption.Apply — no-op when sets are equal var old = WithRoutes("nats://server1:6222", "nats://server2:6222"); var updated = WithRoutes("nats://server1:6222", "nats://server2:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeFalse(); result.RouteUrlsChanged.ShouldBeFalse(); result.GatewayUrlsChanged.ShouldBeFalse(); result.LeafUrlsChanged.ShouldBeFalse(); } [Fact] public void Route_url_added_detected() { // Go reference: reload.go routesOption.Apply — new route triggers reconnect var old = WithRoutes("nats://server1:6222"); var updated = WithRoutes("nats://server1:6222", "nats://server2:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.RouteUrlsChanged.ShouldBeTrue(); } [Fact] public void Route_url_removed_detected() { // Go reference: reload.go routesOption.Apply — removed route triggers disconnect var old = WithRoutes("nats://server1:6222", "nats://server2:6222"); var updated = WithRoutes("nats://server1:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.RouteUrlsChanged.ShouldBeTrue(); } [Fact] public void Gateway_url_changed_detected() { // Go reference: reload.go gatewayOption.Apply — gateway remotes reconciled on reload var old = WithGatewayRemotes("nats://gw1:7222"); var updated = WithGatewayRemotes("nats://gw2:7222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.GatewayUrlsChanged.ShouldBeTrue(); } [Fact] public void Leaf_url_changed_detected() { // Go reference: reload.go leafNodeOption.Apply — leaf remotes reconciled on reload var old = WithLeafRemotes("nats://hub:5222"); var updated = WithLeafRemotes("nats://hub2:5222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.LeafUrlsChanged.ShouldBeTrue(); } [Fact] public void Multiple_changes_detected() { // Go reference: reload.go — multiple topology changes in a single reload var old = new NatsOptions { Cluster = new ClusterOptions { Routes = ["nats://r1:6222"] }, Gateway = new GatewayOptions { RemoteGateways = [new RemoteGatewayOptions { Urls = ["nats://gw1:7222"] }] } }; var updated = new NatsOptions { Cluster = new ClusterOptions { Routes = ["nats://r1:6222", "nats://r2:6222"] }, Gateway = new GatewayOptions { RemoteGateways = [new RemoteGatewayOptions { Urls = ["nats://gw2:7222"] }] } }; var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.RouteUrlsChanged.ShouldBeTrue(); result.GatewayUrlsChanged.ShouldBeTrue(); result.LeafUrlsChanged.ShouldBeFalse(); } [Fact] public void Same_urls_different_order_no_change() { // Go reference: reload.go — order-independent URL comparison var old = WithRoutes("nats://server1:6222", "nats://server2:6222"); var updated = WithRoutes("nats://server2:6222", "nats://server1:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeFalse(); result.RouteUrlsChanged.ShouldBeFalse(); } [Fact] public void AddedRouteUrls_lists_new_routes() { // Go reference: reload.go routesOption.Apply — identifies routes to dial var old = WithRoutes("nats://server1:6222"); var updated = WithRoutes("nats://server1:6222", "nats://server2:6222", "nats://server3:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.AddedRouteUrls.Count.ShouldBe(2); result.AddedRouteUrls.ShouldContain("nats://server2:6222"); result.AddedRouteUrls.ShouldContain("nats://server3:6222"); result.RemovedRouteUrls.ShouldBeEmpty(); } [Fact] public void RemovedRouteUrls_lists_removed_routes() { // Go reference: reload.go routesOption.Apply — identifies routes to close var old = WithRoutes("nats://server1:6222", "nats://server2:6222", "nats://server3:6222"); var updated = WithRoutes("nats://server1:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.RemovedRouteUrls.Count.ShouldBe(2); result.RemovedRouteUrls.ShouldContain("nats://server2:6222"); result.RemovedRouteUrls.ShouldContain("nats://server3:6222"); result.AddedRouteUrls.ShouldBeEmpty(); } [Fact] public void Empty_to_non_empty_detected() { // Go reference: reload.go routesOption.Apply — nil→populated triggers dial var old = new NatsOptions(); // no Cluster configured var updated = WithRoutes("nats://server1:6222"); var result = ConfigReloader.ApplyClusterConfigChanges(old, updated); result.HasChanges.ShouldBeTrue(); result.RouteUrlsChanged.ShouldBeTrue(); result.AddedRouteUrls.ShouldContain("nats://server1:6222"); } }