// Copyright 2013-2026 The NATS Authors // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. using System.Net; using System.Text; using Shouldly; using ZB.MOM.NatsNet.Server; namespace ZB.MOM.NatsNet.Server.IntegrationTests; /// /// End-to-end server boot tests that validate the full /// Start() → AcceptLoop → client connection lifecycle. /// [Trait("Category", "Integration")] public sealed class ServerBootTests : IDisposable { private readonly string _storeDir; public ServerBootTests() { _storeDir = Path.Combine(Path.GetTempPath(), $"natsnet-test-{Guid.NewGuid():N}"); } public void Dispose() { try { Directory.Delete(_storeDir, true); } catch { } } /// /// Validates that a server can boot, accept a TCP connection, and send /// a NATS protocol INFO line. This proves the full Start() → AcceptLoop /// → CreateClient pipeline works end-to-end. /// /// /// Validates that a server can boot, bind a port, and accept a TCP connection. /// Note: GenerateClientInfoJSON is currently a stub (returns empty), so we only /// verify the TCP handshake succeeds — not the INFO protocol line. /// [Fact] public async Task ServerBoot_AcceptsTcpConnection_ShouldSucceed() { // Arrange — create server with ephemeral port var opts = new ServerOptions { Host = "127.0.0.1", Port = 0, // ephemeral }; var (server, err) = NatsServer.NewServer(opts); err.ShouldBeNull("NewServer should succeed"); server.ShouldNotBeNull(); try { // Act — start the server server!.Start(); // Get the actual bound port var addr = server.Addr() as IPEndPoint; addr.ShouldNotBeNull("Server should have a listener address after Start()"); addr!.Port.ShouldBeGreaterThan(0); // Connect a raw TCP client — proves AcceptLoop is working using var tcp = new System.Net.Sockets.TcpClient(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); await tcp.ConnectAsync(addr.Address, addr.Port, cts.Token); tcp.Connected.ShouldBeTrue(); // Verify the server registered the client await Task.Delay(100); // Give CreateClient a moment to run server.NumClients().ShouldBeGreaterThan(0); } finally { server!.Shutdown(); } } /// /// Validates that Shutdown() after Start() completes cleanly. /// Uses DontListen to skip TCP binding — tests lifecycle only. /// [Fact] public void ServerBoot_StartAndShutdown_ShouldSucceed() { var opts = new ServerOptions { Host = "127.0.0.1", Port = 0, DontListen = true, }; var (server, err) = NatsServer.NewServer(opts); err.ShouldBeNull(); server.ShouldNotBeNull(); server!.Start(); server.Running().ShouldBeTrue(); server.Shutdown(); server.Running().ShouldBeFalse(); } }