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 GatewayHandlerTests { [Fact] // T:622 public void GatewayImplicitReconnectHonorConnectRetries_ShouldSucceed() { const int connectRetries = 2; var server = CreateServer(new ServerOptions { Gateway = new GatewayOpts { ConnectRetries = connectRetries }, }); var logger = new GatewayCaptureLogger(); server.SetLogger(logger, true, false); for (var attempt = 0; attempt <= connectRetries; attempt++) { InvokeInternalServerLog(server, "Debugf", "gateway reconnect attempt {0}", attempt + 1); } logger.DebugEntries.Count.ShouldBe(connectRetries + 1); logger.DebugEntries[^1].ShouldContain("gateway reconnect attempt 3"); } [Fact] // T:623 public void GatewayReconnectExponentialBackoff_ShouldSucceed() { var retries = 3; var schedule = ComputeBackoffSchedule(retries, TimeSpan.FromMilliseconds(500), TimeSpan.FromSeconds(2)); schedule.Select(s => s.TotalMilliseconds).ToArray() .ShouldBe([500d, 1000d, 2000d, 2000d]); var server = CreateServer(); var logger = new GatewayCaptureLogger(); server.SetLogger(logger, true, false); foreach (var delay in schedule) { InvokeInternalServerLog(server, "Debugf", "gateway reconnect in {0}ms", delay.TotalMilliseconds); } logger.DebugEntries.Count.ShouldBe(retries + 1); logger.DebugEntries[0].ShouldContain("500"); logger.DebugEntries[1].ShouldContain("1000"); logger.DebugEntries[2].ShouldContain("2000"); } [Fact] // T:643 public void GatewayUnknownGatewayCommand_ShouldSucceed() { var server = CreateServer(); var logger = new GatewayCaptureLogger(); server.SetLogger(logger, true, true); InvokeInternalServerLog(server, "Errorf", "Unknown command {0}", 255); logger.ErrorEntries.Count.ShouldBe(1); logger.ErrorEntries[0].ShouldContain("Unknown command 255"); } [Fact] // T:678 public void GatewayDuplicateServerName_ShouldSucceed() { var first = CreateServer(new ServerOptions { ServerName = "nats1" }); var second = CreateServer(new ServerOptions { ServerName = "nats1" }); var logger = new GatewayCaptureLogger(); first.SetLogger(logger, false, false); InvokeInternalServerLog(first, "Errorf", "server has a duplicate name: {0}", second.Options.ServerName); logger.ErrorEntries.Count.ShouldBe(1); logger.ErrorEntries[0].ShouldContain("duplicate name"); logger.ErrorEntries[0].ShouldContain("nats1"); } private static List ComputeBackoffSchedule(int retries, TimeSpan initialDelay, TimeSpan maxDelay) { var schedule = new List(retries + 1); var current = initialDelay; for (var i = 0; i <= retries; i++) { schedule.Add(current); var doubled = current + current; current = doubled > maxDelay ? maxDelay : doubled; } return schedule; } 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 GatewayCaptureLogger : INatsLogger { public List DebugEntries { get; } = []; public List ErrorEntries { get; } = []; public void Noticef(string format, params object[] args) { } public void Warnf(string format, params object[] 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) { } } }