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 ac3f66a..f831ef0 100644 Binary files a/porting.db and b/porting.db differ