using System.Net; using System.Net.Sockets; using System.Text; using NATS.Server.LeafNodes; using NATS.Server.Subscriptions; namespace NATS.Server.LeafNodes.Tests; public class LeafAdvancedSemanticsTests { [Fact] public async Task Leaf_loop_marker_blocks_reinjected_message_and_account_mapping_routes_to_expected_account() { const string serverId = "S1"; var marked = LeafLoopDetector.Mark("orders.created", serverId); LeafLoopDetector.IsLooped(marked, serverId).ShouldBeTrue(); LeafLoopDetector.IsLooped(marked, "S2").ShouldBeFalse(); LeafLoopDetector.TryUnmark(marked, out var unmarked).ShouldBeTrue(); unmarked.ShouldBe("orders.created"); using var listener = new TcpListener(IPAddress.Loopback, 0); listener.Start(); var port = ((IPEndPoint)listener.LocalEndpoint).Port; using var remoteSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); await remoteSocket.ConnectAsync(IPAddress.Loopback, 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 TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); leaf.RemoteSubscriptionReceived = sub => { received.TrySetResult(sub); return Task.CompletedTask; }; leaf.StartLoop(timeout.Token); await WriteLineAsync(remoteSocket, "LS+ ACC_A leaf.>", timeout.Token); var lsPlus = await received.Task.WaitAsync(timeout.Token); lsPlus.Account.ShouldBe("ACC_A"); lsPlus.Subject.ShouldBe("leaf.>"); } 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) break; 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(); }