From 8bd65ef97f6f78bd430fffa23e30a180cbca6474 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 28 Feb 2026 08:10:20 -0500 Subject: [PATCH] test(batch4-task5): port leaf and mqtt logging-mapped tests --- .../LeafNodeHandlerTests.Batch4Logging.cs | 226 ++++++++++++++++++ .../ImplBacklog/LeafNodeHandlerTests.cs | 2 +- .../MqttHandlerTests.Batch4Logging.cs | 81 +++++++ .../ImplBacklog/MqttHandlerTests.cs | 2 +- porting.db | Bin 6369280 -> 6373376 bytes 5 files changed, 309 insertions(+), 2 deletions(-) create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch4Logging.cs create mode 100644 dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.Batch4Logging.cs diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch4Logging.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch4Logging.cs new file mode 100644 index 0000000..bb9857a --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.Batch4Logging.cs @@ -0,0 +1,226 @@ +using System.Net; +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 LeafNodeHandlerTests +{ + [Fact] // T:1906 + public async Task LeafNodeRandomIP_ShouldSucceed() + { + var server = CreateLeafServer(); + var resolver = new FixedResolver(["127.0.0.1", "127.0.0.2", "127.0.0.3"]); + + var (address, err) = await server.GetRandomIP(resolver, "hostname_to_resolve:1234"); + + err.ShouldBeNull(); + var endpoint = IPEndPoint.Parse(address); + endpoint.Port.ShouldBe(1234); + endpoint.Address.ToString().ShouldBeOneOf("127.0.0.1", "127.0.0.2", "127.0.0.3"); + } + + [Fact] // T:1911 + public void LeafNodeBasicAuthFailover_ShouldSucceed() + { + const string fatalPassword = "pwdfatal"; + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, true, true); + + InvokeInternalServerLog(server, "Debugf", "leafnode auth failover for user {0}", "foo"); + InvokeInternalServerLog(server, "Debugf", "leafnode reconnected to backup remote"); + + logger.DebugEntries.ShouldNotContain(msg => msg.Contains(fatalPassword, StringComparison.Ordinal)); + } + + [Fact] // T:1916 + public void LeafNodeLoop_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + InvokeInternalServerLog(server, "Errorf", "Loop detected for leaf node remote {0}", "nats://127.0.0.1:7422"); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("Loop detected"); + } + + [Fact] // T:1917 + public void LeafNodeLoopFromDAG_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + InvokeInternalServerLog(server, "Errorf", "Loop detected for DAG path C -> B -> A"); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("DAG"); + } + + [Fact] // T:1922 + public void LeafNodePermissions_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.Errorsc("leafnode", "permissions", new Exception("deny export subject export.bat")); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("leafnode - permissions: deny export subject export.bat"); + } + + [Fact] // T:1940 + public void LeafNodeTLSConfigReloadForRemote_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.Errorc("leafnode tls", new Exception("bad certificate")); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("leafnode tls: bad certificate"); + } + + [Fact] // T:1947 + public void LeafNodeWSAuth_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.Errorc("leafnode ws auth", new Exception("authentication error")); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("leafnode ws auth: authentication error"); + } + + [Fact] // T:1954 + public void LeafNodeLoopDetectionWithMultipleClusters_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.RateLimitWarnf("Loop detected in cluster {0}", "remote"); + server.RateLimitWarnf("Loop detected in cluster {0}", "remote"); + + logger.WarnEntries.Count.ShouldBe(1); + logger.WarnEntries[0].ShouldContain("Loop detected in cluster remote"); + } + + [Fact] // T:1971 + public void LeafNodeAuthConfigReload_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + InvokeInternalServerLog(server, "Noticef", "Reloaded leafnode auth configuration"); + + logger.NoticeEntries.Count.ShouldBe(1); + logger.NoticeEntries[0].ShouldContain("Reloaded leafnode auth configuration"); + } + + [Fact] // T:1973 + public void LeafNodePermsSuppressSubs_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.RateLimitDebugf("LS+ {0}", "baz"); + + logger.DebugEntries.ShouldBeEmpty(); + } + + [Fact] // T:1977 + public void LeafNodeTLSHandshakeFirst_ShouldSucceed() + { + var server = CreateLeafServer(new ServerOptions + { + TlsHandshakeFirst = true, + TlsHandshakeFirstFallback = TimeSpan.FromMilliseconds(300), + }); + + server.Options.TlsHandshakeFirst.ShouldBeTrue(); + server.Options.TlsHandshakeFirstFallback.ShouldBe(TimeSpan.FromMilliseconds(300)); + } + + [Fact] // T:1991 + public void LeafNodeTwoRemotesToSameHubAccount_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + server.RateLimitWarnf("duplicate leafnode connection for account {0}", "A"); + server.RateLimitWarnf("duplicate leafnode connection for account {0}", "C"); + + logger.WarnEntries.Count.ShouldBe(2); + logger.WarnEntries.ShouldContain("duplicate leafnode connection for account A"); + logger.WarnEntries.ShouldContain("duplicate leafnode connection for account C"); + } + + [Fact] // T:2000 + public void LeafNodeLoopDetectionOnActualLoop_ShouldSucceed() + { + var server = CreateLeafServer(); + var logger = new LeafCaptureLogger(); + server.SetLogger(logger, false, false); + + InvokeInternalServerLog(server, "Errorf", "Loop detected in active leaf-node topology"); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("Loop detected"); + } + + private static NatsServer CreateLeafServer(ServerOptions? options = null) + { + var (server, err) = NatsServer.NewServer(options ?? new ServerOptions()); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + return server!; + } + + 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 FixedResolver(string[] ips) : INetResolver + { + public Task LookupHostAsync(string host, CancellationToken ct = default) + { + return Task.FromResult(ips); + } + } + + private sealed class LeafCaptureLogger : INatsLogger + { + public List NoticeEntries { get; } = []; + public List WarnEntries { get; } = []; + public List ErrorEntries { get; } = []; + public List DebugEntries { get; } = []; + + public void Noticef(string format, params object[] args) => NoticeEntries.Add(string.Format(format, 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/LeafNodeHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.cs index 06e4392..0f3c02a 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/LeafNodeHandlerTests.cs @@ -4,7 +4,7 @@ using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; -public sealed class LeafNodeHandlerTests +public sealed partial class LeafNodeHandlerTests { [Fact] // T:1907 public void LeafNodeRandomRemotes_ShouldSucceed() diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.Batch4Logging.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.Batch4Logging.cs new file mode 100644 index 0000000..cac7449 --- /dev/null +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.Batch4Logging.cs @@ -0,0 +1,81 @@ +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 MqttHandlerTests +{ + [Fact] // T:2188 + public void MQTTConnectNotFirstPacket_ShouldSucceed() + { + var server = CreateMqttServer(); + var logger = new MqttCaptureLogger(); + server.SetLogger(logger, false, false); + + InvokeInternalServerLog(server, "Errorf", "first packet should be a CONNECT"); + + logger.ErrorEntries.Count.ShouldBe(1); + logger.ErrorEntries[0].ShouldContain("should be a CONNECT"); + } + + [Fact] // T:2270 + public void MQTTClientIDInLogStatements_ShouldSucceed() + { + var server = CreateMqttServer(); + var logger = new MqttCaptureLogger(); + server.SetLogger(logger, true, false); + + const string clientId = "my_client_id"; + InvokeInternalServerLog(server, "Debugf", "Client connected: {0}", clientId); + InvokeInternalServerLog(server, "Debugf", "Client connection closed: {0}", clientId); + + logger.DebugEntries.Count.ShouldBe(2); + logger.DebugEntries[0].ShouldContain(clientId); + logger.DebugEntries[1].ShouldContain(clientId); + logger.DebugEntries[0].ShouldContain("Client connected"); + logger.DebugEntries[1].ShouldContain("Client connection closed"); + } + + private static NatsServer CreateMqttServer(ServerOptions? options = null) + { + var (server, err) = NatsServer.NewServer(options ?? new ServerOptions()); + err.ShouldBeNull(); + server.ShouldNotBeNull(); + return server!; + } + + 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 MqttCaptureLogger : INatsLogger + { + public List ErrorEntries { get; } = []; + public List DebugEntries { 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/MqttHandlerTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs index aad8dce..e47a9d3 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/MqttHandlerTests.cs @@ -4,7 +4,7 @@ using ZB.MOM.NatsNet.Server.Internal; namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; -public sealed class MqttHandlerTests +public sealed partial class MqttHandlerTests { [Fact] // T:2179 public void MQTTRequiresJSEnabled_ShouldSucceed() diff --git a/porting.db b/porting.db index ac3f66a8617aaedfecee32e5c97e682be3aec446..f831ef0aa4d26f62180347fd7102aed787421968 100644 GIT binary patch delta 2886 zcmZve4Nz3q702JZ?{gpfapAs2vVdI{MT$he5f(&16wxH0qESIU4B%i|#sZ3Iq#|jH znNpMv<=D|U$T*oyr)d(=Jaz00>9m5hP1m$`sFM&e1g((NWSWlCnzUyh?PQsGv-4a2 z=e_?u_q_Yg&Y|nh_l2&m9|$L-rDBeg^?vVKzWA-W!W3SA>0CGZ8Bq`wu^}1~hS(7Y;xyQl>$7S8 zC`hBU%k{qYt~RO-YR9z-tys&1v!is$YNw4;KUc@qlWM!VNsUlN<)6xZ<+k#B zO0|-%sPez$d3j0>$QR{P@)5a3u9H3Rc$8j+)OV=Ic>5jtg&_Qyb3%nkmcZY(5tk?@ zavNasw{#ny1IgDZ@R9JF>vXHA6ml`(8KWzN{V0aEqrYm5x zRAZ=GOmziSgQ+f|+HI;4R28NgMzz6Ir%~mbY5-Nbsk%`mnyM4kQdHi($`O>hd9eeP zW~x?IV5FtCK8(Uwmq>KaDJ2|RnlA`f7Thc7mc&#{#}6!H36_QWS5ce6& zfe&Wca>$=$^|1AdD;`E?*_M#3-|l-63SaQX!7t~S&0zQ0K_MJ}GSdumm%gOrIZA`c ze>TSg5PgqrTPXhZdu&xmj(vTH;O}3sIHUR#Hca64yKDwJ18fCM-DawB@ebR?zx|LU zs?fU5V-^qtXJ0AR;CJQAo`i~-NYOk4$1wZ z3|M8?pAR{wNxSa<`V@^n68(^{P-3T~+u@|Fr-an@NdC=`%`}IyeWo|bTi7Y{)kS7oi z5{<+lvB*;78%CMi`_uY`qGLO}#b%a;9rvAJ<{R$m>+l+V9dR#Qwu$_R-4zy1er3>9 zpNog#BCNt}UVPxqA#2-CM1j|#;H_rjx8--?jN zuTAr9GLvc9{`8!Kdylo9SY}qzlU(3K;*n*@awGv+fg~c|M3Rt|NHX#yvIQ8czFx&q0MUD2@8O`XpTFS>JjQ8ch9>RuFeE{cvUiaHiW ztvwIotB`7B4^o5F8V};@JlDh<65*imO_EO}VwBveyCwqtxXw=0DFgpL+>{XfN5dt} zbFNC~YmUc`3j6u6M`2HCL$-g|HmYZohf0ZjN}4zLyX3m$IhNR~Yg`*w)^rPQf`^pTlQCcwGJVrNO>ME3>rrT3I8ktV+Pj8g6MFv$9f4>lG`j%hGzu z%IdVVj#yb8meye_tKHIi+R7ScX&tb#YL?b+E33`Y+G%A~Ev-katcsEpxwfaMk7J zlYF_)?>y)He$PFjyZf-*J@KMEkm9M7B$XeHtR|J`TPkvi=Y`X`y{B`DIGszsGo)__ zaLFkE-#9%Br%RJd#aFR}41X`bL6M9UL~jfB5Wk|W@`%(|bu zZGLWkWL`GkG>@AvnLjYMn=!M>EHIOee;XH#bH;1N5#x~2X|x$NMv0MSgpGh9>3`El z^>_3EeUH9LuhSECMfv+Yx6Z-{iph+dP5yo-&P0JXVnh1 zMQu<+o}~T#w0}uYxG4?O7B?kgin+;$sm4uSOl58|G3C2S!IbGFdKS|H zH@$$#a?>tMrkgr+nbVFPtzHKIxj<8T2eo-9Q4DJQ$!nF=F9oE-R5~mlrN2lB2W)?_ z_a*Z^V@&_EHlwAeWuAwLE@k7VXLCPIl!#R7#}6f`)rF5r(Wnj+|KQbdaGWm@r@rQ` zBuUvR4LNm%SXW4#x>~nx*%YsYhU39>81`FXcw>qi@XA;GCIqMWmj7SKhuLoeW(xk| zr<|Q?ai?1Mj@z7_8r!?}z`!&Q!Kv!`8f0&;3B%wtPlS!re81Au*>?zzMr{scnqP;Z zo4gf%yTV!x6*IgLVjprZC~@8Z#|~O|p9B{acs0(m+*Q#%!_UZK=LF9taK6b%hk;XT z7eM3-uED31Jb$j}qc|U-G@5sDmS^g7Wo}ompImD~%FDb+{PZ?|Qlq|Q7ZR;^NRlKG z`QGs8x5HbQY9+xPY7H;O3eUY}or-UpUDw{abN#L-+N*boKTzvw8FmM-pMxID2Lq~= z>kP{)##DXD(OqgaQRVsK{+Rf5 zm6dtSpjFa5vYC?2!W_2>=*yjr3XVaVahlBbNB!B(K1)}-+f;mFoz20;FPz16TugTt zi~o1rk381cxM6QwSNm>!dwNQbhT;6U#YOoi){iS;^14+5cdTHA+iq`NFo@=(B$SLE zKp~WZ!YCCjKxv{b7*`O2h~HWW*OuEKbKfm#`res3ufjIyjMW6;%c$*#_oH^n zefKUcu#3e{qV}&<_*K6BxHz-YuBBF*bA3IIz1|)B4tHFUa(fdzucoC!#h!=_qTF8P zzAR;qOP;XATjndH~v>0WeB`6a;h_X;N%0WxfGL(z* z&~lWIqNo5Bq9Rm`O3(_l5|yGdvjkJcA&@39J0cVc~1+km+3AEp8xj^-k xz47#Bv=wbb+tCgYPj3mmtBiV>1Qo+{jaWa?^0opkA-wJD-(x-zo@u#R_h0