test(battle-node): envelope-level wire-shape regression for scripted bodies

This commit is contained in:
gamer147
2026-06-01 10:40:54 -04:00
parent 5ee270eb16
commit 19cc7980d1

View File

@@ -0,0 +1,134 @@
using System.Text.Json.Nodes;
using NUnit.Framework;
using SVSim.BattleNode.Lifecycle;
using SVSim.BattleNode.Protocol;
namespace SVSim.UnitTests.BattleNode.Lifecycle;
/// <summary>
/// Wire-shape regression tests: compare <see cref="MsgEnvelope.ToJson"/> output against
/// JSON literals derived from captured prod frames in
/// data_dumps/captures/battle-traffic_tk2_regular.ndjson. Per the feedback_wire_shape_tests
/// memory, these are literal-comparison tests — NOT self-symmetric round-trips — so they
/// catch the failure mode where a C# property is renamed and silently breaks the wire
/// contract.
/// </summary>
[TestFixture]
public class TypedBodyWireShapeTests
{
[Test]
public void BuildMatched_SerializesAllWireKeysExpectedByTheClient()
{
var env = ScriptedLifecycle.BuildMatched(
playerViewerId: 906243102, opponentViewerId: 847666884, battleId: "597830888107");
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
// Top-level envelope fields:
Assert.That(node["uri"]!.GetValue<string>(), Is.EqualTo("Matched"));
Assert.That(node["viewerId"]!.GetValue<long>(), Is.EqualTo(999_999_999L));
Assert.That(node["uuid"]!.GetValue<string>(), Is.EqualTo("node-stub"));
Assert.That(node["bid"]!.GetValue<string>(), Is.EqualTo("597830888107"));
Assert.That(node["try"]!.GetValue<int>(), Is.EqualTo(0));
Assert.That(node["cat"]!.GetValue<int>(), Is.EqualTo(1));
Assert.That(node["resultCode"]!.GetValue<int>(), Is.EqualTo(1));
// Inner selfInfo block has the wire keys the client's Parse looks up.
var selfInfo = node["selfInfo"]!.AsObject();
foreach (var key in new[] {
"country_code", "userName", "sleeveId", "emblemId", "degreeId",
"fieldId", "isOfficial", "oppoId", "seed",
})
{
Assert.That(selfInfo.ContainsKey(key), Is.True, $"selfInfo missing wire key '{key}'");
}
Assert.That(selfInfo["oppoId"]!.GetValue<long>(), Is.EqualTo(847666884L));
Assert.That(selfInfo.ContainsKey("oppoDeckCount"), Is.False, "selfInfo must NOT have oppoDeckCount");
var oppoInfo = node["oppoInfo"]!.AsObject();
Assert.That(oppoInfo["oppoDeckCount"]!.GetValue<int>(), Is.EqualTo(30));
Assert.That(oppoInfo["oppoId"]!.GetValue<long>(), Is.EqualTo(906243102L));
var selfDeck = node["selfDeck"]!.AsArray();
Assert.That(selfDeck.Count, Is.EqualTo(30));
Assert.That(selfDeck[0]!.AsObject()["idx"]!.GetValue<int>(), Is.EqualTo(1));
Assert.That(selfDeck[0]!.AsObject()["cardId"]!.GetValue<long>(), Is.EqualTo(100_011_010L));
}
[Test]
public void BuildBattleStart_SerializesAllWireKeysAndPreservesBattlePointAsymmetry()
{
var env = ScriptedLifecycle.BuildBattleStart(playerViewerId: 906243102);
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
Assert.That(node["turnState"]!.GetValue<int>(), Is.EqualTo(0));
Assert.That(node["battleType"]!.GetValue<int>(), Is.EqualTo(11));
Assert.That(node["resultCode"]!.GetValue<int>(), Is.EqualTo(1));
var selfInfo = node["selfInfo"]!.AsObject();
Assert.That(selfInfo["rank"]!.GetValue<string>(), Is.EqualTo("10"));
Assert.That(selfInfo["battlePoint"]!.GetValue<string>(), Is.EqualTo("6270")); // string on self
Assert.That(selfInfo["cardMasterName"]!.GetValue<string>(), Is.EqualTo("card_master_node_10015"));
var oppoInfo = node["oppoInfo"]!.AsObject();
Assert.That(oppoInfo["battlePoint"]!.GetValue<int>(), Is.EqualTo(0)); // int on oppo
Assert.That(oppoInfo["isMasterRank"]!.GetValue<string>(), Is.EqualTo("0"));
Assert.That(oppoInfo["masterPoint"]!.GetValue<string>(), Is.EqualTo("0"));
}
[Test]
public void BuildDeal_SerializesSelfAndOppoArraysWithPosIdxShape()
{
var env = ScriptedLifecycle.BuildDeal();
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
var self = node["self"]!.AsArray();
Assert.That(self.Count, Is.EqualTo(3));
Assert.That(self[0]!.AsObject()["pos"]!.GetValue<int>(), Is.EqualTo(0));
Assert.That(self[0]!.AsObject()["idx"]!.GetValue<int>(), Is.EqualTo(1));
var oppo = node["oppo"]!.AsArray();
Assert.That(oppo.Count, Is.EqualTo(3));
}
[Test]
public void BuildSwapResponse_SerializesSelfWithoutOppo()
{
var env = ScriptedLifecycle.BuildSwapResponse(new long[] { 1, 4, 3 });
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
Assert.That(node.ContainsKey("self"), Is.True);
Assert.That(node.ContainsKey("oppo"), Is.False);
Assert.That(node["self"]!.AsArray()[1]!.AsObject()["idx"]!.GetValue<int>(), Is.EqualTo(4));
}
[Test]
public void BuildReady_SerializesAllFieldsIncludingSeedAndSpin()
{
var env = ScriptedLifecycle.BuildReady(new long[] { 1, 4, 3 });
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
Assert.That(node["idxChangeSeed"]!.GetValue<int>(), Is.EqualTo(771_335_280));
Assert.That(node["spin"]!.GetValue<int>(), Is.EqualTo(243));
Assert.That(node["self"]!.AsArray().Count, Is.EqualTo(3));
Assert.That(node["oppo"]!.AsArray().Count, Is.EqualTo(3));
}
[Test]
public void BuildOpponentTurnStart_SerializesSpinAndResultCode()
{
var env = ScriptedLifecycle.BuildOpponentTurnStart();
var json = MsgEnvelope.ToJson(env);
var node = JsonNode.Parse(json)!.AsObject();
Assert.That(node["spin"]!.GetValue<int>(), Is.EqualTo(100));
Assert.That(node["resultCode"]!.GetValue<int>(), Is.EqualTo(1));
Assert.That(node["uri"]!.GetValue<string>(), Is.EqualTo("TurnStart"));
}
}