using System.Net; using System.Net.Sockets; using System.Text; using NATS.Server.LeafNodes; using NATS.Server.Subscriptions; namespace NATS.Server.LeafNodes.Tests.LeafNodes; public class LeafConnectionParityBatch4Tests { [Fact] public async Task SendLsPlus_with_queue_weight_writes_weighted_frame() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var endpoint = (IPEndPoint)listener.LocalEndpoint; using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await remoteSocket.ConnectAsync(IPAddress.Loopback, endpoint.Port); using var leafSocket = await listener.AcceptSocketAsync(); await using var leaf = new LeafConnection(leafSocket); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var handshakeTask = leaf.PerformOutboundHandshakeAsync("LOCAL", timeout.Token); (await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("LEAF LOCAL"); await WriteLineAsync(remoteSocket, "LEAF REMOTE", timeout.Token); await handshakeTask; await leaf.SendLsPlusAsync("$G", "jobs.>", "workers", queueWeight: 3, timeout.Token); (await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("LS+ $G jobs.> workers 3"); } [Fact] public async Task ReadLoop_parses_queue_weight_from_ls_plus() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var endpoint = (IPEndPoint)listener.LocalEndpoint; using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await remoteSocket.ConnectAsync(IPAddress.Loopback, endpoint.Port); using var leafSocket = await listener.AcceptSocketAsync(); await using var leaf = new LeafConnection(leafSocket); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var handshakeTask = leaf.PerformOutboundHandshakeAsync("LOCAL", timeout.Token); (await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("LEAF LOCAL"); await WriteLineAsync(remoteSocket, "LEAF REMOTE", timeout.Token); await handshakeTask; var received = new List(); leaf.RemoteSubscriptionReceived = sub => { received.Add(sub); return Task.CompletedTask; }; leaf.StartLoop(timeout.Token); await WriteLineAsync(remoteSocket, "LS+ $G jobs.> workers 7", timeout.Token); await WaitForAsync(() => received.Count >= 1, timeout.Token); received[0].Subject.ShouldBe("jobs.>"); received[0].Queue.ShouldBe("workers"); received[0].QueueWeight.ShouldBe(7); } [Fact] public async Task ReadLoop_defaults_invalid_queue_weight_to_one() { using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var endpoint = (IPEndPoint)listener.LocalEndpoint; using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await remoteSocket.ConnectAsync(IPAddress.Loopback, endpoint.Port); using var leafSocket = await listener.AcceptSocketAsync(); await using var leaf = new LeafConnection(leafSocket); using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); var handshakeTask = leaf.PerformOutboundHandshakeAsync("LOCAL", timeout.Token); (await ReadLineAsync(remoteSocket, timeout.Token)).ShouldBe("LEAF LOCAL"); await WriteLineAsync(remoteSocket, "LEAF REMOTE", timeout.Token); await handshakeTask; var received = new List(); leaf.RemoteSubscriptionReceived = sub => { received.Add(sub); return Task.CompletedTask; }; leaf.StartLoop(timeout.Token); await WriteLineAsync(remoteSocket, "LS+ $G jobs.> workers 0", timeout.Token); await WaitForAsync(() => received.Count >= 1, timeout.Token); received[0].QueueWeight.ShouldBe(1); } private static async Task ReadLineAsync(Socket socket, CancellationToken ct) { var bytes = new List(64); var single = new byte[1]; while (true) { var read = await socket.ReceiveAsync(single, SocketFlags.None, ct); if (read == 0) throw new IOException("Connection closed while reading line"); if (single[0] == (byte)'\n') break; if (single[0] != (byte)'\r') bytes.Add(single[0]); } return Encoding.ASCII.GetString([.. bytes]); } private static Task WriteLineAsync(Socket socket, string line, CancellationToken ct) => socket.SendAsync(Encoding.ASCII.GetBytes($"{line}\r\n"), SocketFlags.None, ct).AsTask(); private static async Task WaitForAsync(Func predicate, CancellationToken ct) { while (!ct.IsCancellationRequested) { if (predicate()) return; await Task.Yield(); } throw new TimeoutException("Timed out waiting for condition."); } }