using System.Net; using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging.Abstractions; using NATS.Server; using NATS.Server.TestUtilities; namespace NATS.Server.Core.Tests; // Tests ported from Go server_test.go: // TestRandomPorts, TestInfoServerNameDefaultsToPK, TestInfoServerNameIsSettable, // TestLameDuckModeInfo (simplified — no cluster, just ldm property/state) public class ServerConfigTests { // Ref: golang/nats-server/server/server_test.go TestRandomPorts // The Go test uses Port=-1 (their sentinel for "random"), we use Port=0 (.NET/BSD standard). // Verifies that after startup, server.Port is resolved to a non-zero ephemeral port. [Fact] public async Task Server_resolves_ephemeral_port_when_zero() { var opts = new NatsOptions { Port = 0 }; using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); _ = server.StartAsync(cts.Token); await server.WaitForReadyAsync(); try { server.Port.ShouldBeGreaterThan(0); server.Port.ShouldNotBe(4222); } finally { await cts.CancelAsync(); } } // Ref: golang/nats-server/server/server_test.go TestInfoServerNameIsSettable // Verifies that ServerName set in options is reflected in both the server property // and the INFO line sent to connecting clients. [Fact] public async Task Server_info_contains_server_name() { const string name = "my-test-server"; var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port, ServerName = name }; using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); _ = server.StartAsync(cts.Token); await server.WaitForReadyAsync(); try { // Property check server.ServerName.ShouldBe(name); // Wire check — INFO line sent on connect using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); var infoLine = await SocketTestHelper.ReadUntilAsync(sock, "INFO"); infoLine.ShouldContain("\"server_name\":\"my-test-server\""); } finally { await cts.CancelAsync(); } } // Ref: golang/nats-server/server/server_test.go TestInfoServerNameDefaultsToPK // Verifies that when no ServerName is configured, the server still populates both // server_id and server_name fields in the INFO line (name defaults to a generated value, // not null or empty). [Fact] public async Task Server_info_defaults_name_when_not_configured() { var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port }; // no ServerName set using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); _ = server.StartAsync(cts.Token); await server.WaitForReadyAsync(); try { // Both properties should be populated server.ServerId.ShouldNotBeNullOrWhiteSpace(); server.ServerName.ShouldNotBeNullOrWhiteSpace(); // Wire check — INFO line includes both fields using var sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await sock.ConnectAsync(IPAddress.Loopback, port); var infoLine = await SocketTestHelper.ReadUntilAsync(sock, "INFO"); infoLine.ShouldContain("\"server_id\":"); infoLine.ShouldContain("\"server_name\":"); } finally { await cts.CancelAsync(); } } // Ref: golang/nats-server/server/server_test.go TestLameDuckModeInfo // Simplified port: verifies that LameDuckShutdownAsync transitions the server into // lame duck mode (IsLameDuckMode becomes true) and that the server ultimately shuts // down. The full Go test requires a cluster to observe INFO updates with "ldm":true; // that aspect is not ported here because the .NET ServerInfo type does not include // an ldm/LameDuckMode field and cluster routing is out of scope for this test. [Fact] public async Task Lame_duck_mode_sets_is_lame_duck_mode_and_shuts_down() { var port = TestPortAllocator.GetFreePort(); var opts = new NatsOptions { Port = port, LameDuckGracePeriod = TimeSpan.Zero, LameDuckDuration = TimeSpan.FromMilliseconds(50), }; using var server = new NatsServer(opts, NullLoggerFactory.Instance); using var cts = new CancellationTokenSource(); _ = server.StartAsync(cts.Token); await server.WaitForReadyAsync(); server.IsLameDuckMode.ShouldBeFalse(); // Trigger lame duck — no clients connected so it should proceed straight to shutdown. await server.LameDuckShutdownAsync(); server.IsLameDuckMode.ShouldBeTrue(); server.IsShuttingDown.ShouldBeTrue(); await cts.CancelAsync(); } }