using System.Net; using System.Net.Sockets; using System.Text; using NATS.Server.Mqtt; namespace NATS.Server.Mqtt.Tests.Mqtt; public class MqttSessionRuntimeTests { [Fact] public async Task Qos1_publish_receives_puback_and_redelivery_on_session_reconnect_when_unacked() { await using var listener = new MqttListener("127.0.0.1", 0); listener.UseBinaryProtocol = false; using var cts = new CancellationTokenSource(); await listener.StartAsync(cts.Token); using (var first = new TcpClient()) { await first.ConnectAsync(IPAddress.Loopback, listener.Port); var firstStream = first.GetStream(); await MqttRuntimeWire.WriteLineAsync(firstStream, "CONNECT session-client clean=false"); (await MqttRuntimeWire.ReadLineAsync(firstStream, 1000)).ShouldBe("CONNACK"); await MqttRuntimeWire.WriteLineAsync(firstStream, "PUBQ1 21 sensors.temp 99"); (await MqttRuntimeWire.ReadLineAsync(firstStream, 1000)).ShouldBe("PUBACK 21"); } using var second = new TcpClient(); await second.ConnectAsync(IPAddress.Loopback, listener.Port); var secondStream = second.GetStream(); await MqttRuntimeWire.WriteLineAsync(secondStream, "CONNECT session-client clean=false"); (await MqttRuntimeWire.ReadLineAsync(secondStream, 1000)).ShouldBe("CONNACK"); (await MqttRuntimeWire.ReadLineAsync(secondStream, 1000)).ShouldBe("REDLIVER 21 sensors.temp 99"); } } internal static class MqttRuntimeWire { public static async Task WriteLineAsync(NetworkStream stream, string line) { var bytes = Encoding.UTF8.GetBytes(line + "\n"); await stream.WriteAsync(bytes); await stream.FlushAsync(); } public static async Task ReadLineAsync(NetworkStream stream, int timeoutMs) { using var timeout = new CancellationTokenSource(timeoutMs); var bytes = new List(); var one = new byte[1]; try { while (true) { var read = await stream.ReadAsync(one.AsMemory(0, 1), timeout.Token); if (read == 0) return null; if (one[0] == (byte)'\n') break; if (one[0] != (byte)'\r') bytes.Add(one[0]); } } catch (OperationCanceledException) { return null; } return Encoding.UTF8.GetString([.. bytes]); } public static async Task ReadRawAsync(NetworkStream stream, int timeoutMs) { using var timeout = new CancellationTokenSource(timeoutMs); var one = new byte[1]; try { var read = await stream.ReadAsync(one.AsMemory(0, 1), timeout.Token); if (read == 0) return null; return Encoding.UTF8.GetString(one, 0, read); } catch (OperationCanceledException) { return "__timeout__"; } } }