From 455e0e9572ad572365a1344cfcb17daf55e95e71 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 08:06:11 -0500 Subject: [PATCH] test(batch4-task4): port gateway and route logging-mapped tests --- .../GatewayHandlerTests.Batch4Logging.cs | 129 ++++++++++++ .../ImplBacklog/GatewayHandlerTests.cs | 2 +- .../RouteHandlerTests.Batch4Logging.cs | 189 ++++++++++++++++++ .../ImplBacklog/RouteHandlerTests.cs | 2 +- porting.db | Bin 6369280 -> 6369280 bytes 5 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.Batch4Logging.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch4Logging.cs diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.Batch4Logging.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.Batch4Logging.cs new file mode 100644 index 0000000..4910ae9 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.Batch4Logging.cs @@ -0,0 +1,129 @@ +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) + { + } + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs index f5cdacc..6ad03ed 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/GatewayHandlerTests.cs @@ -5,7 +5,7 @@ using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; -public sealed class GatewayHandlerTests +public sealed partial class GatewayHandlerTests { [Fact] // T:602 public void GatewayHeaderInfo_ShouldSucceed() diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch4Logging.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch4Logging.cs new file mode 100644 index 0000000..68aaac3 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.Batch4Logging.cs @@ -0,0 +1,189 @@ +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) + { + } + } +} diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.cs index 7cabb1c..af415bd 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/RouteHandlerTests.cs @@ -4,7 +4,7 @@ using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; -public sealed class RouteHandlerTests +public sealed partial class RouteHandlerTests { [Fact] // T:2808 public void RouteUseIPv6_ShouldSucceed() diff --git a/porting.db b/porting.db index 93e7709ed332d2c93a166b85995f41c028918876..ac3f66a8617aaedfecee32e5c97e682be3aec446 100644 GIT binary patch delta 2305 zcmajd4@{J090&0C@BMk-d+&XYgBLydbH|w?qQLP-JRs2yMWsYkP)j`woalBSATNrikx!vUIT)LS(18E7k9HxgHnyori=SQf1~3Im5sS~-fMQq{{cNp)*3Fa?w_ z0}N#(L7i7ds%hxlMmV)=8%g2m=`dJDQaJ5i+;!$lAkk94bsT?I6Ey zr!T3zkF?O{Tf%q3m%_)wdqVZY3pxi$9DFoL1oekO(xPL8y-+$tELP#qaZhHz(W93X zMI0I7*bvF(#1P#0?;+yQhSX{UT&jyPo#*QGYzR|PxqYK()jpt#{3#OCT7L?{lz$)x#c7n!iXt9mbuD&YN(g4Nav;Zr4Ff0+9%bp*CmhSmhvQ*lp;k- z7RG4EH)OM4sI9~lL&F7=>94Sdt8Xe9{_u~DJkS$ly7TxQ9gp47*wk+}p&(>N!6-!S zH!GQEV8JV9J6OL}6bSBC%y7BX5(9UyX*682GhwcJyjv+SPRAdFP)8|QCk+_}#6N`F zY!>69EA;pEX}a~=TunB0l;6m$hUFHz77qTT6hOiZc63$i4JB!8LD2OoYsSsK-m4`0 zXLZBdHPp6>;64`>0S|g{k*oDlql+~6DOuwd`KC|VJ#LY; z6Ytn8aO|`t7_z%Y&t~LdJ-Zp$)@?IGb+TP*@^)F!6cmQSQ3Q%qy425^W$BX8Hr+7^%Ptgc{f2WZ&%`RHfCSq>t3Th7=mCtGZ%>82{)hN9Fe z-rg27iSD7U8A8P*VWqxOw_E#~=3V{@*T>$0_|Av~a9ij)7;xHmj_m5)YO%oEY4-7F zdN0j>@X<+3hKm_?6O^Uf&G1XQedLcAdF$d`JHXpKD_Fe~Z~sT%G$q51rlP0NG&CJW zqZud$%|x+i7MhLXP&`UN4wQ(JP%=tEb5JUpi<~G8rK1e=G@6Gp(R{Q3J%d~*3oS&8 zP&QhOmY}652R)0PL(5PuT8{G23bYcfLiwlwtww9mTJ$`60j)#p(FS!&hP&@A?gCFJ z$n0cXkkjM-I*Kpi^Z9JvDP9ymoS&Ei@yhmYV1bk>tRwXECyG7CpG z69e2_Ny6dqDq@7Yt4IvZspUVr4>@OtP-iK5ow3UpDpeX@6;BA4^u2ms7p1MzT+~GH z<=khSh4nD!8HL_LogMz8_FEdF_wxc}W5Sn(G$bT$_mos_s4T9itPrixP6|`$B!~|! zjN}W;%8HA?7g{*mmgtOkI1>`F$OSA$PF!RI7K0`(G69Q*iHmf=qWFZOb?hCffJN|< z!nyxu>$Hc|jfW^j%i!}j@r4tvc0~S3`C~8L!RgH=+{9gMX D!3f@# delta 1440 zcmY+>e@xVM7zgn0_w)UE-}~NuKlg(#d3WF)hd($&1QE<6u>h6Oz#nL#au(3kmB^AN zkwF>ET2gJ^<7_FowaUs}bNb2KOl-DQI*ppHA69^nsaOXU6)WxGbguq*ZJ*DcKc4sV zJT1N3g%%Xumqnzp=en}myRvvhkbp!aAsH!1MHZwXE7Fk-*;!YX@dy<^CQCVJ`8+%X zzZDugghC|;n&%st?7jKMYKh+f+5%%LghqH9RFCr0*oFnho)4VINSHGQjF_?4SY?zN z(+&S&O3(4^Do^7&KNl|8X_3<=q>!50y4r?@+8V2W{0BJ(jflnw=VPZ0IyMpklK zhGqk;o@Vn?*qnOW%(K{LnkVr)V7QUyLH!7y&(=Ok+lb)h>`?YQpUb>W^zs(|ER*7N zC-3@8yQ+Psoz+fghqN~|&~|7~L3EgA!SP|Lu^)!%PL)`bVAB}&BvRbYr2j9WHIeY` zFb@hUd?hCD1^l+Upu;`m5}DyQQy&7uk}@_6++D~0Qa ziI?qG9P3quoa8PLI|tnr)D1rBs3dv|gl7<+Oze84zL4#uju5YsFUU#Zf^Y^taX9|= z31>K8V{OCwy7jd+^0o00w|_&>>va4qN(VR}_%o`~+ecjv~G(5!-{x5e)`5}(lg3><2o5QNT+9uFMd$ z{wdB|)whm%l3-yBw-#NVrjzFhZ0-rT;6Q%R*4`QOATRQv6qJf4vd);lxmXpJkrKX| z+X)pOegiuu1dj5%Isz^5>ifyJChWd!icrP}iWBYGYkZ)ChlV}kf6I>wfwJ*I+Z&|- zN@I;uaCgXK9o2>`KUpMol2VMPsu8ydT_naI=gyC}J1Dl+Zf}Cl7c<=~WoGcY1_va&=j?+fxdHC>>2glhG736=k3h znuhK~nP@u7LNib{%0V+xE}Dh%P(GTC?m~0W-DoZqZOzUtwgKPgXketg;t|AXf3K{DFxw6`^6Jd0|$js zQUqna;jdU{*jOS09SrMi+hDkDPO_$^amE^Z%${s}LVv}2P&<{l1_a8zX^i%hL4~DF IUH856FZ|l*SO5S3