The socket connects with the shared-secret header, registers first, reassembles incoming frames of any size (the original dropped anything over 4 KiB) and reconnects two seconds after a failure. Incoming share_* frames go to the vital-sharing handlers; any other frame is a chat-box command for this character, run one per tick through the chat bar as if typed. Frames are written with Newtonsoft.Json defaults, the original's serializer, so the backend sees the same bytes. The secret lives in the character's settings. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
106 lines
3.6 KiB
C#
106 lines
3.6 KiB
C#
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);
|
|
}
|
|
|
|
internal static async Task<bool> WaitAsync(Func<bool> condition, int timeoutMs = 5000)
|
|
{
|
|
for (int waited = 0; waited < timeoutMs; waited += 20)
|
|
{
|
|
if (condition())
|
|
return true;
|
|
await Task.Delay(20);
|
|
}
|
|
return condition();
|
|
}
|
|
}
|