From e3cc745a61152857e7a2321d251ee648d7c33c51 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Mon, 1 Jun 2026 12:51:33 -0400 Subject: [PATCH] test(battle-node): end-to-end drafted deck flows into Matched frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seeds a viewer + completed TK2 run, drives the WS handshake to Matched, and asserts every cardId in selfDeck matches the run's SelectedCardIdsJson. Read from RawBody (codec's wire-form deserialization) — not from MatchedBody — since the test client gets the JSON-roundtripped envelope. Co-Authored-By: Claude Opus 4.7 --- .../Integration/BattleNodeFlowTests.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs b/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs index 58216cf..46100f8 100644 --- a/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs +++ b/SVSim.UnitTests/BattleNode/Integration/BattleNodeFlowTests.cs @@ -1,9 +1,12 @@ +using System.Text.Json; 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.Database; +using SVSim.Database.Models; using SVSim.EmulatedEntrypoint; using SVSim.UnitTests.Infrastructure; @@ -73,4 +76,86 @@ public class BattleNodeFlowTests CountryCode: "KOR", UserName: "Player", SleeveId: "3000011", EmblemId: "701441011", DegreeId: "300003", FieldId: 43, IsOfficial: 0, BattleType: 11); + + /// + /// End-to-end: a viewer with a real TK2 run sees their drafted card-ids in the Matched + /// frame's selfDeck. This is the "visible win" — proves the full plumbing chain works + /// against an actual seeded viewer. + /// + [Test] + [Timeout(30000)] + public async Task Matched_frame_contains_drafted_deck_cards() + { + await using var factory = new SVSimTestFactory(); + var vid = await factory.SeedViewerAsync(); + var draftedDeck = Enumerable.Range(1, 30).Select(i => 200_000_000L + i).ToList(); + + using (var seedScope = factory.Services.CreateScope()) + { + var db = seedScope.ServiceProvider.GetRequiredService(); + db.ViewerArenaTwoPickRuns.Add(new ViewerArenaTwoPickRun + { + ViewerId = vid, + EntryId = 1, + ClassId = 1, + LeaderSkinId = 1, + SelectedCardIdsJson = JsonSerializer.Serialize(draftedDeck), + IsSelectCompleted = true, + MaxBattleCount = 5, + CandidateClassIdsJson = "[1,2,3]", + PendingPickSetsJson = "[]", + ResultListJson = "[]", + NextCandidateId = 1, + }); + await db.SaveChangesAsync(); + } + + using var scope = factory.Services.CreateScope(); + var builder = scope.ServiceProvider.GetRequiredService(); + var ctx = await builder.BuildForTwoPickAsync(vid); + var bridge = factory.Services.GetRequiredService(); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(15)); + var ct = cts.Token; + var pending = bridge.RegisterPendingBattle(viewerId: vid, context: ctx); + + var key = MakeKey(); + var encryptedVid = NodeCrypto.EncryptForNode(vid.ToString(), key); + 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); + + // InitNetwork → ack + await client.SendMsgAsync(MakeEnvelopeWith(vid, NetworkBattleUri.InitNetwork, pubSeq: 1), key, ct); + await client.ReceiveSynchronizeAsync(ct); + + // InitBattle → Matched (this is the frame we care about) + await client.SendMsgAsync(MakeEnvelopeWith(vid, NetworkBattleUri.InitBattle, pubSeq: 2), key, ct); + var matched = await client.ReceiveSynchronizeAsync(ct); + Assert.That(matched.Uri, Is.EqualTo(NetworkBattleUri.Matched)); + + // MsgEnvelope.FromJson always inflates Body as a RawBody dictionary — selfDeck is a + // List of nested dicts with int "idx" + long "cardId" keys. + var body = ((RawBody)matched.Body).Entries; + var selfDeck = (List)body["selfDeck"]!; + Assert.That(selfDeck.Count, Is.EqualTo(30)); + for (int i = 0; i < 30; i++) + { + var entry = (Dictionary)selfDeck[i]!; + Assert.That((long)entry["idx"]!, Is.EqualTo(i + 1L), + $"slot {i}: idx should be 1-based position"); + Assert.That((long)entry["cardId"]!, Is.EqualTo(draftedDeck[i]), + $"slot {i}: cardId should match the drafted card"); + } + } + + private static MsgEnvelope MakeEnvelopeWith(long vid, NetworkBattleUri uri, long pubSeq) => + new(uri, ViewerId: vid, Uuid: "udid-test", Bid: null, Try: 0, + Cat: uri == NetworkBattleUri.InitNetwork ? EmitCategory.General + : uri == NetworkBattleUri.InitBattle ? EmitCategory.Matching + : EmitCategory.Battle, + PubSeq: pubSeq, PlaySeq: null, Body: new RawBody(new Dictionary())); }