using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.DependencyInjection; using NUnit.Framework; using SVSim.BattleNode.Bridge; using SVSim.BattleNode.Protocol; using SVSim.BattleNode.Wire; using SVSim.EmulatedEntrypoint; using SVSim.UnitTests.Infrastructure; namespace SVSim.UnitTests.BattleNode.Integration; [TestFixture] public class BattleNodeFlowTests { /// /// End-to-end smoke for the v1 scripted lifecycle. Boots the EmulatedEntrypoint via /// SVSimTestFactory (in-memory SQLite + reference-data CSV import), mints a battle /// through IMatchingBridge, opens a raw Socket.IO v2 client against the in-process /// TestServer, and drives InitNetwork → Loaded → Swap, asserting the right scripted /// frames come back in order. /// [Test] [Timeout(30000)] public async Task ClientWalksHandshakeToReady_ReceivesAllScriptedFrames() { await using var factory = new SVSimTestFactory(); var bridge = factory.Services.GetRequiredService(); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); var ct = cts.Token; var pending = bridge.RegisterPendingBattle(viewerId: 906243102); var key = MakeKey(); var encryptedVid = NodeCrypto.EncryptForNode("906243102", key); // TestServer ignores the host portion of the URI — only the path + query route. var wsUri = new Uri($"ws://localhost/socket.io/?BattleId={pending.BattleId}&viewerId={Uri.EscapeDataString(encryptedVid)}&EIO=3&transport=websocket"); var wsClient = factory.Server.CreateWebSocketClient(); var ws = await wsClient.ConnectAsync(wsUri, ct); await using var client = new RawSocketIoTestClient(ws); await client.ConsumeHandshakeAsync(ct); // 1. InitNetwork → expect InitNetwork ack push only. await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.InitNetwork, pubSeq: 1), key, ct); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.InitNetwork)); // 2. InitBattle → expect Matched (handler is now subscribed on the client side). await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.InitBattle, pubSeq: 2), key, ct); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Matched)); // 3. Loaded → expect BattleStart + Deal. await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.Loaded, pubSeq: 3), key, ct); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.BattleStart)); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Deal)); // 4. Swap with empty idxList → expect Swap response + Ready. await client.SendMsgAsync(MakeEnvelope(NetworkBattleUri.Swap, pubSeq: 4, body: new Dictionary { ["idxList"] = new List() }), key, ct); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Swap)); Assert.That((await client.ReceiveSynchronizeAsync(ct)).Uri, Is.EqualTo(NetworkBattleUri.Ready)); } private static MsgEnvelope MakeEnvelope(NetworkBattleUri uri, long pubSeq, Dictionary? body = null) => new(uri, ViewerId: 906243102, Uuid: "udid-test", Bid: null, Try: 0, // EmitMsgPack: InitNetwork → general(99); other matching URIs → matching(2); else battle(1). Cat: uri == NetworkBattleUri.InitNetwork ? EmitCategory.General : uri == NetworkBattleUri.InitBattle ? EmitCategory.Matching : EmitCategory.Battle, PubSeq: pubSeq, PlaySeq: null, Body: new RawBody(body ?? new Dictionary())); private static string MakeKey() { var seq = 0; return NodeCrypto.GenerateKey(() => (seq++ * 13) % 16); } }