using OpenAC.MosswartMassacre.Tests.Fakes; namespace OpenAC.MosswartMassacre.Tests.Backend; /// /// The socket's status and error lines reach the player's chat, worded as the /// original worded them, and only through the tick: the socket thread never /// writes to chat itself. /// public sealed class BackendChatStatusTests { private const string P = "[Mosswart Massacre] "; [Fact] public async Task Enable_shows_connecting_then_connected_and_registered_on_the_next_tick() { using var server = new TestSocketServer(); using var harness = new PluginHarness("Mossy"); harness.Login(); harness.Context.Settings.Current.BackendUrl = server.Uri.ToString(); harness.Context.Commands.Dispatch("ws enable"); Assert.Equal([P + "[WebSocket] connecting…", P + "WS streaming ENABLED."], Tail(harness, 2)); server.NextFrame(); Assert.True(await BackendClientTests.WaitAsync(() => harness.Context.Backend.IsConnected)); Assert.DoesNotContain(P + "[WebSocket] CONNECTED", harness.ChatLines); int before = harness.ChatLines.Count; int tickThread = Environment.CurrentManagedThreadId; harness.Tick(0.01); Assert.Equal( [P + "[WebSocket] CONNECTED", P + "[WebSocket] REGISTERED", P + "[WebSocket] Starting telemetry loop"], Tail(harness, 3)); AssertPostedOn(harness, before, tickThread); } [Fact] public async Task Streaming_saved_on_connects_at_login_and_says_so() { using var server = new TestSocketServer(); using var harness = new PluginHarness("Mossy"); harness.Context.Settings.Current.WebSocketEnabled = true; harness.Context.Settings.Current.BackendUrl = server.Uri.ToString(); harness.Context.Settings.Save(); harness.Host.Automation.Character.IsInWorld = true; harness.Plugin.OnLogin(); Assert.Contains(P + "[WebSocket] connecting…", harness.ChatLines); Assert.Equal("{\"type\":\"register\",\"player_name\":\"Mossy\"}", server.NextFrame()); Assert.True(await BackendClientTests.WaitAsync(() => harness.Context.Backend.IsConnected)); harness.Tick(0.01); Assert.Contains(P + "[WebSocket] REGISTERED", harness.ChatLines); } [Fact] public async Task A_failed_connect_shows_the_error_cleanup_and_reconnect_on_the_next_tick() { using var harness = new PluginHarness("Mossy"); harness.Login(); // Nothing listens on port 1: the connect is refused. harness.Context.Settings.Current.BackendUrl = "ws://127.0.0.1:1/websocket/"; var log = (RecordingLogger)harness.Host.Log; harness.Context.Commands.Dispatch("ws enable"); Assert.True(await BackendClientTests.WaitAsync( () => log.Any(line => line.Contains("[WebSocket] Reconnecting in 2 seconds...")), 15000)); Assert.DoesNotContain(harness.ChatLines, line => line.Contains("Connection error")); int before = harness.ChatLines.Count; int tickThread = Environment.CurrentManagedThreadId; harness.Tick(0.01); string[] tail = Tail(harness, 3); Assert.StartsWith(P + "[WebSocket] Connection error: ", tail[0]); Assert.StartsWith(P + "[WebSocket] Cleaning up connection - Final state: ", tail[1]); Assert.Equal(P + "[WebSocket] Reconnecting in 2 seconds...", tail[2]); AssertPostedOn(harness, before, tickThread); } [Fact] public async Task Disable_shows_disabled_before_the_command_reply() { using var server = new TestSocketServer(); using var harness = new PluginHarness("Mossy"); harness.Login(); harness.Context.Settings.Current.BackendUrl = server.Uri.ToString(); harness.Context.Commands.Dispatch("ws enable"); server.NextFrame(); Assert.True(await BackendClientTests.WaitAsync(() => harness.Context.Backend.IsConnected)); harness.Tick(0.01); harness.Context.Commands.Dispatch("ws disable"); Assert.Equal([P + "[WebSocket] DISABLED", P + "WS streaming DISABLED."], Tail(harness, 2)); } [Fact] public void A_reconnect_with_sharing_on_says_it_re_subscribed_even_without_verbose_logging() { using var harness = new PluginHarness("Mossy"); harness.Login(); harness.Context.Settings.Current.VitalSharingEnabled = true; harness.Context.Settings.Current.VerboseLogging = false; harness.SimulateConnected(); Assert.Equal( [P + "[WebSocket] Vital sharing re-subscribed", P + "[WebSocket] Starting telemetry loop"], Tail(harness, 2)); } /// Every chat line from on was written on , the thread that ran the tick. private static void AssertPostedOn(PluginHarness harness, int from, int threadId) => Assert.All(harness.Host.Automation.Chat.PostedThreadIds.Skip(from), id => Assert.Equal(threadId, id)); private static string[] Tail(PluginHarness harness, int count) => [.. harness.ChatLines.Skip(Math.Max(0, harness.ChatLines.Count - count))]; }