using System.Reflection; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed partial class RouteHandlerTests { [Fact] // T:2820 public void RouteDuplicateServerName_ShouldSucceed() { var server = CreateRouteServer(new ServerOptions { ServerName = "A" }); var logger = new RouteCaptureLogger(); server.SetLogger(logger, false, false); InvokeInternalServerLog(server, "Errorf", "Remote server has a duplicate name: {0}", "A"); logger.ErrorEntries.Count.ShouldBe(1); logger.ErrorEntries[0].ShouldContain("duplicate name"); logger.ErrorEntries[0].ShouldContain("A"); } [Fact] // T:2826 public void RouteSolicitedReconnectsEvenIfImplicit_ShouldSucceed() { var connectRetries = 3; var attempts = (ServerConstants.DefaultRoutePoolSize + 1) * (connectRetries + 1); var server = CreateRouteServer(new ServerOptions { Cluster = new ClusterOpts { ConnectRetries = connectRetries }, }); var logger = new RouteCaptureLogger(); server.SetLogger(logger, true, false); for (var i = 0; i < attempts; i++) { InvokeInternalServerLog(server, "Debugf", "route reconnect attempt {0}", i + 1); } logger.DebugEntries.Count.ShouldBe(attempts); logger.DebugEntries[^1].ShouldContain($"route reconnect attempt {attempts}"); } [Fact] // T:2827 public void RouteReconnectExponentialBackoff_ShouldSucceed() { var connectRetries = 3; var perCycle = ServerConstants.DefaultRoutePoolSize + 1; var schedule = ComputePerCycleBackoff(connectRetries, perCycle, TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(2)); schedule.Count.ShouldBe(perCycle * (connectRetries + 1)); schedule[0].ShouldBe(TimeSpan.FromMilliseconds(500)); schedule[perCycle].ShouldBe(TimeSpan.FromMilliseconds(1000)); schedule[perCycle * 2].ShouldBe(TimeSpan.FromSeconds(2)); var server = CreateRouteServer(); var logger = new RouteCaptureLogger(); server.SetLogger(logger, true, false); foreach (var delay in schedule) { InvokeInternalServerLog(server, "Debugf", "route reconnect in {0}ms", delay.TotalMilliseconds); } logger.DebugEntries.Count.ShouldBe(schedule.Count); } [Fact] // T:2828 public void RouteSaveTLSName_ShouldSucceed() { var server = CreateRouteServer(); var logger = new RouteCaptureLogger(); server.SetLogger(logger, false, false); server.Errorc("tls handshake", new Exception("x509: certificate is valid for localhost")); logger.ErrorEntries.Count.ShouldBe(1); logger.ErrorEntries[0].ShouldContain("tls handshake: x509: certificate is valid for localhost"); } [Fact] // T:2834 public void RoutePerAccount_ShouldSucceed() { var server = CreateRouteServer(); var logger = new RouteCaptureLogger(); server.SetLogger(logger, false, false); server.Errorsc("ACC2", "route", new Exception("permission denied")); logger.ErrorEntries.Count.ShouldBe(1); logger.ErrorEntries[0].ShouldContain("ACC2 - route: permission denied"); } [Fact] // T:2847 public void RoutePoolWithOlderServerConnectAndReconnect_ShouldSucceed() { var server = CreateRouteServer(); var logger = new RouteCaptureLogger(); server.SetLogger(logger, false, false); server.RateLimitWarnf("duplicate route connection to {0}", "S2"); server.RateLimitWarnf("duplicate route connection to {0}", "S2"); server.RateLimitWarnf("duplicate route connection to {0}", "S3"); logger.WarnEntries.Count.ShouldBe(2); logger.WarnEntries.ShouldContain("duplicate route connection to S2"); logger.WarnEntries.ShouldContain("duplicate route connection to S3"); } [Fact] // T:2848 public void RoutePoolBadAuthNoRunawayCreateRoute_ShouldSucceed() { var server = CreateRouteServer(); var logger = new RouteCaptureLogger(); server.SetLogger(logger, false, false); for (var i = 0; i < 200; i++) { server.RateLimitWarnf("authentication failed for route {0}", "S2"); } logger.WarnEntries.Count.ShouldBe(1); logger.WarnEntries[0].ShouldContain("authentication failed for route S2"); } private static NatsServer CreateRouteServer(ServerOptions? opts = null) { var (server, err) = NatsServer.NewServer(opts ?? new ServerOptions()); err.ShouldBeNull(); server.ShouldNotBeNull(); return server!; } private static List ComputePerCycleBackoff( int retries, int attemptsPerCycle, TimeSpan startDelay, TimeSpan maxDelay) { var delays = new List(attemptsPerCycle * (retries + 1)); var current = startDelay; for (var retry = 0; retry <= retries; retry++) { for (var i = 0; i < attemptsPerCycle; i++) { delays.Add(current); } var doubled = current + current; current = doubled > maxDelay ? maxDelay : doubled; } return delays; } private static void InvokeInternalServerLog(NatsServer server, string methodName, string format, params object[] args) { var method = typeof(NatsServer).GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); method.ShouldNotBeNull(); method!.Invoke(server, [format, args]); } private sealed class RouteCaptureLogger : INatsLogger { public List DebugEntries { get; } = []; public List WarnEntries { get; } = []; public List ErrorEntries { get; } = []; public void Noticef(string format, params object[] args) { } public void Warnf(string format, params object[] args) => WarnEntries.Add(string.Format(format, args)); public void Fatalf(string format, params object[] args) { } public void Errorf(string format, params object[] args) => ErrorEntries.Add(string.Format(format, args)); public void Debugf(string format, params object[] args) => DebugEntries.Add(string.Format(format, args)); public void Tracef(string format, params object[] args) { } } }