using OpenAC.MosswartMassacre.Backend; using OpenAC.MosswartMassacre.Tests.Fakes; namespace OpenAC.MosswartMassacre.Tests.Backend; public sealed class BackendClientTests { private static readonly DateTime Connected = new(2026, 9, 25, 8, 30, 15, DateTimeKind.Utc); [Fact] public async Task Connect_sends_the_secret_header_then_registers_first() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "s3cret", "Mossy", () => Connected); Assert.Equal("{\"type\":\"register\",\"player_name\":\"Mossy\"}", server.NextFrame()); Assert.True(server.Secrets.TryPeek(out string? secret)); Assert.Equal("s3cret", secret); Assert.True(await WaitAsync(() => client.TryTakeConnection(out _))); } [Fact] public async Task Frames_sent_after_connect_arrive_in_order() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); client.Send("{\"n\":1}"); client.Send("{\"n\":2}"); Assert.Equal("{\"n\":1}", server.NextFrame()); Assert.Equal("{\"n\":2}", server.NextFrame()); } [Fact] public void A_frame_sent_while_disconnected_is_dropped() { using var client = new BackendClient(new RecordingLogger()); Assert.False(client.Send("{}")); } [Fact] public async Task A_frame_larger_than_one_receive_buffer_is_reassembled() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); string big = "{\"type\":\"share_vital_update\",\"pad\":\"" + new string('x', 20000) + "\"}"; await server.PushAsync(" " + big + "\n"); string? received = null; Assert.True(await WaitAsync(() => client.TryReceive(out received))); Assert.Equal(big, received); } [Fact] public async Task A_dropped_socket_reconnects_and_registers_again() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); await server.DropAsync(); Assert.Equal("{\"type\":\"register\",\"player_name\":\"Mossy\"}", server.NextFrame(8000)); Assert.Equal(2, server.Connections); } [Fact] public async Task Stop_closes_and_stops_reconnecting() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); client.Stop(); Assert.False(client.IsRunning); Assert.False(client.IsConnected); await Task.Delay(2500); Assert.Equal(1, server.Connections); } [Fact] public async Task Stop_sends_what_was_queued_before_it_closes_without_blocking() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); string big = "{\"type\":\"full_inventory\",\"pad\":\"" + new string('x', 400_000) + "\"}"; client.Send(big); client.Send("{\"type\":\"share_unsubscribe\"}"); var watch = System.Diagnostics.Stopwatch.StartNew(); client.Stop(); Assert.True(watch.ElapsedMilliseconds < 200, $"Stop blocked for {watch.ElapsedMilliseconds} ms"); Assert.Equal(big, server.NextFrame()); Assert.Equal("{\"type\":\"share_unsubscribe\"}", server.NextFrame()); await client.LastStop.WaitAsync(TimeSpan.FromSeconds(15)); } [Fact] public async Task A_new_start_right_after_stop_hears_only_its_own_connection() { using var server = new TestSocketServer(); using var client = new BackendClient(new RecordingLogger()); client.Start(server.Uri, "", "Mossy", () => Connected); server.NextFrame(); Assert.True(await WaitAsync(() => client.IsConnected)); client.Stop(); client.Start(server.Uri, "", "Horan", () => Connected); Assert.Equal("{\"type\":\"register\",\"player_name\":\"Horan\"}", server.NextFrame(8000)); Assert.True(await WaitAsync(() => client.TryTakeConnection(out _))); Assert.False(client.TryTakeConnection(out _)); } internal static async Task WaitAsync(Func condition, int timeoutMs = 5000) { for (int waited = 0; waited < timeoutMs; waited += 20) { if (condition()) return true; await Task.Delay(20); } return condition(); } }