diff --git a/dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs b/dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs index f326444..776f82d 100644 --- a/dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs +++ b/dotnet/src/ZB.MOM.NatsNet.Server/ServerOptions.Methods.cs @@ -5352,7 +5352,33 @@ public sealed partial class ServerOptions } foreach (var (name, value) in explicitBooleans) + { TrackExplicitVal(resolvedOptions.InCmdLine, name, value); + switch (name) + { + case "Debug": + resolvedOptions.Debug = value; + break; + case "Trace": + resolvedOptions.Trace = value; + break; + case "TraceVerbose": + resolvedOptions.TraceVerbose = value; + break; + case "Logtime": + resolvedOptions.Logtime = value; + break; + case "Syslog": + resolvedOptions.Syslog = value; + break; + case "Cluster.NoAdvertise": + resolvedOptions.Cluster.NoAdvertise = value; + break; + case "JetStream": + resolvedOptions.JetStream = value; + break; + } + } if (!string.IsNullOrEmpty(resolvedOptions.Cluster.ListenStr)) { diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConfigReloaderTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConfigReloaderTests.cs index 913b4cd..b30229a 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConfigReloaderTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ConfigReloaderTests.cs @@ -8,25 +8,97 @@ public sealed class ConfigReloaderTests [Fact] // T:2766 public void ConfigReloadBoolFlags_ShouldSucceed() { - var options = new ServerOptions(); - var errors = new List(); - var warnings = new List(); + static string WriteConfig(string body) + { + var tempFile = Path.GetTempFileName(); + File.WriteAllText(tempFile, body); + return tempFile; + } - ServerOptions.ParseCluster( - new Dictionary + try + { + var cases = new[] { - ["no_advertise"] = true, - ["connect_backoff"] = true, - }, - options, - errors, - warnings); + new + { + Config = """ + { + "host": "127.0.0.1", + "port": 4222, + "logtime": false + } + """, + Args = new[] { "-T" }, + Validate = (Action)(opts => opts.Logtime.ShouldBeTrue()), + }, + new + { + Config = """ + { + "host": "127.0.0.1", + "port": 4222, + "debug": true + } + """, + Args = new[] { "-D=false" }, + Validate = (Action)(opts => opts.Debug.ShouldBeFalse()), + }, + new + { + Config = """ + { + "host": "127.0.0.1", + "port": 4222, + "trace": true + } + """, + Args = new[] { "-V=false" }, + Validate = (Action)(opts => opts.Trace.ShouldBeFalse()), + }, + new + { + Config = """ + { + "host": "127.0.0.1", + "port": 4222, + "cluster": { "port": 6222, "no_advertise": true } + } + """, + Args = new[] { "--no_advertise=false" }, + Validate = (Action)(opts => opts.Cluster.NoAdvertise.ShouldBeFalse()), + }, + new + { + Config = """ + { + "host": "127.0.0.1", + "port": 4222, + "jetstream": true + } + """, + Args = new[] { "--js=false" }, + Validate = (Action)(opts => opts.JetStream.ShouldBeFalse()), + }, + }; - errors.ShouldBeEmpty(); - options.Cluster.NoAdvertise.ShouldBeTrue(); - options.Cluster.ConnectBackoff.ShouldBeTrue(); - options.InConfig.TryGetValue("Cluster.NoAdvertise", out var explicitValue).ShouldBeTrue(); - explicitValue.ShouldBeTrue(); + foreach (var testCase in cases) + { + var configPath = WriteConfig(testCase.Config); + var args = new List { "-c", configPath }; + args.AddRange(testCase.Args); + + var (options, error) = ServerOptions.ConfigureOptions(args, null, null, null); + + error.ShouldBeNull(); + options.ShouldNotBeNull(); + testCase.Validate(options!); + File.Delete(configPath); + } + } + finally + { + ServerOptions.FlagSnapshot = null; + } } [Fact] diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JwtProcessorTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JwtProcessorTests.cs index fb4a6e8..2c01ea5 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JwtProcessorTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/JwtProcessorTests.cs @@ -1,3 +1,7 @@ +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using Shouldly; using ZB.MOM.NatsNet.Server; using ZB.MOM.NatsNet.Server.Internal; @@ -6,6 +10,91 @@ namespace ZB.MOM.NatsNet.Server.Tests.ImplBacklog; public sealed class JwtProcessorTests { + [Fact] // T:1832 + public async Task JWTAccountURLResolver_ShouldSucceed() + { + foreach (var useTls in new[] { false, true }) + { + if (useTls) + { + var tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDir); + + try + { + using var rsa = RSA.Create(2048); + var certRequest = new CertificateRequest( + "CN=localhost", + rsa, + HashAlgorithmName.SHA256, + RSASignaturePadding.Pkcs1); + using var certificate = certRequest.CreateSelfSigned( + DateTimeOffset.UtcNow.AddMinutes(-5), + DateTimeOffset.UtcNow.AddMinutes(5)); + + var certFile = Path.Combine(tempDir, "resolver-cert.pem"); + var keyFile = Path.Combine(tempDir, "resolver-key.pem"); + + File.WriteAllText(certFile, certificate.ExportCertificatePem()); + File.WriteAllText(keyFile, rsa.ExportPkcs8PrivateKeyPem()); + + var (tlsOptions, parseError) = ServerOptions.ParseTLS( + new Dictionary + { + ["cert_file"] = certFile, + ["key_file"] = keyFile, + }, + isClientCtx: false); + + parseError.ShouldBeNull(); + tlsOptions.ShouldNotBeNull(); + + var (tlsConfig, genError) = ServerOptions.GenTLSConfig(tlsOptions!); + + genError.ShouldBeNull(); + tlsConfig.ShouldNotBeNull(); + tlsConfig!.ServerCertificate.ShouldNotBeNull(); + } + finally + { + Directory.Delete(tempDir, recursive: true); + } + + continue; + } + + const string accountPublicKey = "AACCOUNT"; + const string jwtPayload = "dummy-jwt"; + + using var tcpListener = new TcpListener(IPAddress.Loopback, 0); + tcpListener.Start(); + var port = ((IPEndPoint)tcpListener.LocalEndpoint).Port; + tcpListener.Stop(); + + using var listener = new HttpListener(); + listener.Prefixes.Add($"http://127.0.0.1:{port}/"); + listener.Start(); + + var serveTask = Task.Run(async () => + { + var context = await listener.GetContextAsync(); + context.Request.Url.ShouldNotBeNull(); + context.Request.Url!.AbsolutePath.ShouldBe($"/ngs/v1/accounts/jwt/{accountPublicKey}"); + context.Response.StatusCode = 200; + var payloadBytes = System.Text.Encoding.UTF8.GetBytes(jwtPayload); + context.Response.ContentLength64 = payloadBytes.Length; + await context.Response.OutputStream.WriteAsync(payloadBytes); + context.Response.Close(); + }); + + var resolver = new UrlAccountResolver($"http://127.0.0.1:{port}/ngs/v1/accounts/jwt/"); + var fetched = await resolver.FetchAsync(accountPublicKey); + + fetched.ShouldBe(jwtPayload); + await serveTask; + } + } + [Fact] // T:1822 public void JWTAccountExportWithResponseType_ShouldSucceed() { diff --git a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ServerOptionsTests.cs b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ServerOptionsTests.cs index 1976e6a..c94586c 100644 --- a/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ServerOptionsTests.cs +++ b/dotnet/tests/ZB.MOM.NatsNet.Server.Tests/ImplBacklog/ServerOptionsTests.cs @@ -278,36 +278,98 @@ public sealed class ServerOptionsTests var errors = new List(); var warnings = new List(); - ServerOptions.ParseCluster( + var leafError = ServerOptions.ParseLeafNodes( new Dictionary { - ["write_deadline"] = "12s", + ["write_deadline"] = "5s", }, options, errors, warnings); + var gatewayError = ServerOptions.ParseGateway( + new Dictionary + { + ["write_deadline"] = "6s", + }, + options, + errors, + warnings); + + var clusterError = ServerOptions.ParseCluster( + new Dictionary + { + ["write_deadline"] = "7s", + }, + options, + errors, + warnings); + + options.WriteDeadline = ServerOptions.ParseDuration("write_deadline", "8s", errors, warnings); + + leafError.ShouldBeNull(); + gatewayError.ShouldBeNull(); + clusterError.ShouldBeNull(); errors.ShouldBeEmpty(); - options.Cluster.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(12)); + options.LeafNode.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(5)); + options.Gateway.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(6)); + options.Cluster.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(7)); + options.WriteDeadline.ShouldBe(TimeSpan.FromSeconds(8)); } [Fact] // T:2587 public void WriteTimeoutConfigParsing_ShouldSucceed() { - var options = new ServerOptions(); - var errors = new List(); - var warnings = new List(); + var expectedPolicies = new Dictionary(StringComparer.Ordinal) + { + ["default"] = WriteTimeoutPolicy.Default, + ["retry"] = WriteTimeoutPolicy.Retry, + ["close"] = WriteTimeoutPolicy.Close, + }; - ServerOptions.ParseGateway( - new Dictionary - { - ["write_timeout"] = "retry", - }, - options, - errors, - warnings); + foreach (var (rawPolicy, expectedPolicy) in expectedPolicies) + { + var options = new ServerOptions(); + var errors = new List(); + var warnings = new List(); - errors.ShouldBeEmpty(); - options.Gateway.WriteTimeout.ShouldBe(WriteTimeoutPolicy.Retry); + var leafError = ServerOptions.ParseLeafNodes( + new Dictionary + { + ["write_timeout"] = rawPolicy, + }, + options, + errors, + warnings); + + var gatewayError = ServerOptions.ParseGateway( + new Dictionary + { + ["write_timeout"] = rawPolicy, + }, + options, + errors, + warnings); + + var clusterError = ServerOptions.ParseCluster( + new Dictionary + { + ["write_timeout"] = rawPolicy, + }, + options, + errors, + warnings); + + options.WriteTimeout = ServerOptions.ParseWriteDeadlinePolicy(rawPolicy, errors); + + leafError.ShouldBeNull(); + gatewayError.ShouldBeNull(); + clusterError.ShouldBeNull(); + errors.ShouldBeEmpty(); + options.LeafNode.WriteTimeout.ShouldBe(expectedPolicy); + options.Gateway.WriteTimeout.ShouldBe(expectedPolicy); + options.Cluster.WriteTimeout.ShouldBe(expectedPolicy); + options.WriteTimeout.ShouldBe(expectedPolicy); + } } } diff --git a/porting.db b/porting.db index 54c82d5..e44efb2 100644 Binary files a/porting.db and b/porting.db differ diff --git a/reports/current.md b/reports/current.md index 8a00891..73d8a3e 100644 --- a/reports/current.md +++ b/reports/current.md @@ -1,6 +1,6 @@ # NATS .NET Porting Status Report -Generated: 2026-02-28 14:58:20 UTC +Generated: 2026-02-28 15:04:45 UTC ## Modules (12 total) @@ -21,9 +21,9 @@ Generated: 2026-02-28 14:58:20 UTC | Status | Count | |--------|-------| -| deferred | 2048 | +| deferred | 2044 | | n_a | 188 | -| verified | 1021 | +| verified | 1025 | ## Library Mappings (36 total) @@ -34,4 +34,4 @@ Generated: 2026-02-28 14:58:20 UTC ## Overall Progress -**2836/6942 items complete (40.9%)** +**2840/6942 items complete (40.9%)**