using System.Buffers.Binary; using NATS.Server.WebSocket; namespace NATS.Server.Tests.WebSocket; public class WsConnectionTests { [Fact] public async Task ReadAsync_DecodesFrameAndReturnsPayload() { var payload = "SUB test 1\r\n"u8.ToArray(); var frame = BuildUnmaskedFrame(payload); var inner = new MemoryStream(frame); var ws = new WsConnection(inner, compress: false, maskRead: false, maskWrite: false, browser: false, noCompFrag: false); var buf = new byte[256]; int n = await ws.ReadAsync(buf); n.ShouldBe(payload.Length); buf[..n].ShouldBe(payload); } [Fact] public async Task WriteAsync_FramesPayload() { var inner = new MemoryStream(); var ws = new WsConnection(inner, compress: false, maskRead: false, maskWrite: false, browser: false, noCompFrag: false); var payload = "MSG test 1 5\r\nHello\r\n"u8.ToArray(); await ws.WriteAsync(payload); await ws.FlushAsync(); inner.Position = 0; var written = inner.ToArray(); // First 2 bytes should be WS frame header (written[0] & WsConstants.FinalBit).ShouldNotBe(0); (written[0] & 0x0F).ShouldBe(WsConstants.BinaryMessage); int len = written[1] & 0x7F; len.ShouldBe(payload.Length); written[2..].ShouldBe(payload); } [Fact] public async Task WriteAsync_WithCompression_CompressesLargePayload() { var inner = new MemoryStream(); var ws = new WsConnection(inner, compress: true, maskRead: false, maskWrite: false, browser: false, noCompFrag: false); var payload = new byte[200]; Array.Fill(payload, 0x41); // 'A' repeated - very compressible await ws.WriteAsync(payload); await ws.FlushAsync(); inner.Position = 0; var written = inner.ToArray(); // RSV1 bit should be set for compressed frame (written[0] & WsConstants.Rsv1Bit).ShouldNotBe(0); // Compressed size should be less than original written.Length.ShouldBeLessThan(payload.Length + 10); } [Fact] public async Task WriteAsync_SmallPayload_NotCompressedEvenWhenEnabled() { var inner = new MemoryStream(); var ws = new WsConnection(inner, compress: true, maskRead: false, maskWrite: false, browser: false, noCompFrag: false); var payload = "Hi"u8.ToArray(); // Below CompressThreshold await ws.WriteAsync(payload); await ws.FlushAsync(); inner.Position = 0; var written = inner.ToArray(); // RSV1 bit should NOT be set for small payloads (written[0] & WsConstants.Rsv1Bit).ShouldBe(0); } [Fact] public async Task ReadAsync_DecodesMaskedFrame() { var payload = "CONNECT {}\r\n"u8.ToArray(); var (header, _) = WsFrameWriter.CreateFrameHeader( useMasking: true, compressed: false, opcode: WsConstants.BinaryMessage, payloadLength: payload.Length); var maskKey = header[^4..]; WsFrameWriter.MaskBuf(maskKey, payload); var frame = new byte[header.Length + payload.Length]; header.CopyTo(frame, 0); payload.CopyTo(frame, header.Length); var inner = new MemoryStream(frame); var ws = new WsConnection(inner, compress: false, maskRead: true, maskWrite: false, browser: false, noCompFrag: false); var buf = new byte[256]; int n = await ws.ReadAsync(buf); n.ShouldBe("CONNECT {}\r\n".Length); System.Text.Encoding.ASCII.GetString(buf, 0, n).ShouldBe("CONNECT {}\r\n"); } [Fact] public async Task ReadAsync_ReturnsZero_OnEndOfStream() { // Empty stream should return 0 (true end of stream) var inner = new MemoryStream([]); var ws = new WsConnection(inner, compress: false, maskRead: false, maskWrite: false, browser: false, noCompFrag: false); var buf = new byte[256]; int n = await ws.ReadAsync(buf); n.ShouldBe(0); } private static byte[] BuildUnmaskedFrame(byte[] payload) { var header = new byte[2]; header[0] = (byte)(WsConstants.FinalBit | WsConstants.BinaryMessage); header[1] = (byte)payload.Length; var frame = new byte[2 + payload.Length]; header.CopyTo(frame, 0); payload.CopyTo(frame, 2); return frame; } }