test(battle-node): end-to-end drafted deck flows into Matched frame

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 <noreply@anthropic.com>
This commit is contained in:
gamer147
2026-06-01 12:51:33 -04:00
parent b0488e3f2e
commit e3cc745a61

View File

@@ -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);
/// <summary>
/// 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.
/// </summary>
[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<SVSimDbContext>();
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<SVSim.EmulatedEntrypoint.Services.IMatchContextBuilder>();
var ctx = await builder.BuildForTwoPickAsync(vid);
var bridge = factory.Services.GetRequiredService<IMatchingBridge>();
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<object?> of nested dicts with int "idx" + long "cardId" keys.
var body = ((RawBody)matched.Body).Entries;
var selfDeck = (List<object?>)body["selfDeck"]!;
Assert.That(selfDeck.Count, Is.EqualTo(30));
for (int i = 0; i < 30; i++)
{
var entry = (Dictionary<string, object?>)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<string, object?>()));
}