using System.Text.Json; using NucleicBroker; namespace NucleicBroker.Tests; public sealed class OutboundWriterTests { [Fact] public async Task PreservesEnqueueOrderAcrossKinds() { var captured = new StringWriter(); var writer = new OutboundWriter(captured); writer.EnqueueJson("""{"a":1}"""); writer.EnqueueProcOutput(1, stderr: false, "one"u8); writer.EnqueueProcOutput(1, stderr: false, "two"u8); writer.EnqueueJson("""{"b":2}"""); await writer.CompleteAsync(); var lines = captured.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries); Assert.Equal("""{"a":1}""", lines[0]); Assert.Equal("""{"b":2}""", lines[^1]); // The two same-stream chunks arrive between the json lines, concatenated in order — // as ONE notification when the drain saw both queued, as two otherwise. Either way // the decoded byte stream is identical. var decoded = string.Concat(lines[1..^1].Select(l => { var note = JsonDocument.Parse(l).RootElement; Assert.Equal("proc.stdout", note.GetProperty("method").GetString()); return System.Text.Encoding.UTF8.GetString( Convert.FromBase64String(note.GetProperty("params").GetProperty("b64").GetString()!)); })); Assert.Equal("onetwo", decoded); } [Fact] public async Task FramesLinesWithBareLineFeed() { // The wire is LF-framed on every host (docs/WINDOWS_PORT.md §3.3). TextWriter.NewLine // would otherwise be CRLF on Windows, hiding a stray \r inside every line hostd parses. // Forcing CRLF on the sink reproduces the Windows default on any platform, so this // pins the framing even when CI runs the suite on Linux. var captured = new StringWriter { NewLine = "\r\n" }; var writer = new OutboundWriter(captured); writer.EnqueueJson("""{"a":1}"""); writer.EnqueueProcOutput(1, stderr: false, "one"u8); await writer.CompleteAsync(); var text = captured.ToString(); Assert.DoesNotContain('\r', text); Assert.EndsWith("\n", text); } [Fact] public async Task StderrAndStdoutChunksNeverMerge() { var captured = new StringWriter(); var writer = new OutboundWriter(captured); writer.EnqueueProcOutput(1, stderr: false, "out"u8); writer.EnqueueProcOutput(1, stderr: true, "err"u8); await writer.CompleteAsync(); var methods = captured.ToString().Split('\n', StringSplitOptions.RemoveEmptyEntries) .Select(l => JsonDocument.Parse(l).RootElement.GetProperty("method").GetString()) .ToList(); Assert.Equal(["proc.stdout", "proc.stderr"], methods); } }