From b54a47f3338e69f71eb12f764929e1ea01e9ceda Mon Sep 17 00:00:00 2001 From: gamer147 Date: Wed, 10 Jun 2026 08:24:24 -0400 Subject: [PATCH] test(replay): literal-prod-JSON wire-shape parity for /replay/info Per feedback_wire_shape_tests (2026-05-28): controller round-trip tests using the same DTO can't catch wire-key/wire-type drift. Asserts parsing of, snake_case emission for, and MessagePack round-trip through the captured prod frame. Co-Authored-By: Claude Opus 4.7 --- SVSim.UnitTests/Wire/ReplayInfoWireShape.cs | 98 +++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 SVSim.UnitTests/Wire/ReplayInfoWireShape.cs diff --git a/SVSim.UnitTests/Wire/ReplayInfoWireShape.cs b/SVSim.UnitTests/Wire/ReplayInfoWireShape.cs new file mode 100644 index 0000000..3227f04 --- /dev/null +++ b/SVSim.UnitTests/Wire/ReplayInfoWireShape.cs @@ -0,0 +1,98 @@ +using System.Text.Json; +using MessagePack; +using SVSim.EmulatedEntrypoint.Models.Dtos.Replay; + +namespace SVSim.UnitTests.Wire; + +/// +/// Literal-prod-JSON parity for /replay/info. Captured from +/// data_dumps/captures/traffic_prod_misc_clicking.ndjson frame 96. +/// Catches future wire-key/wire-type drift (the kind that bit card_id vs +/// cardID on the deck-code endpoint, 2026-05-28). +/// +public class ReplayInfoWireShape +{ + private const string CapturedFrame = """ + { + "replay_list": [ + { + "battle_type": "4", "two_pick_type": "0", "deck_format": "2", + "battle_id": "234471983876", "is_limit_turn": "0", + "opponent_name": "Foo", "class_id": "8", "opponent_class_id": "5", + "sub_class_id": "0", "opponent_sub_class_id": "0", + "rotation_id": "0", "opponent_rotation_id": "0", + "opponent_country_code": "", "chara_id": "8", "opponent_chara_id": "805", + "opponent_emblem_id": "721341010", "opponent_degree_id": "120023", + "is_win": "0", "battle_start_time": "2026-06-04 17:13:13", + "create_time": "2026-06-04 17:16:06" + } + ], + "feature_maintenance_list": [] + } + """; + + [Test] + public void Parses_captured_prod_frame_into_dto() + { + var resp = JsonSerializer.Deserialize(CapturedFrame); + + Assert.That(resp, Is.Not.Null); + Assert.That(resp!.ReplayList, Has.Count.EqualTo(1)); + + var item = resp.ReplayList[0]; + Assert.That(item.BattleId, Is.EqualTo("234471983876")); + Assert.That(item.OpponentName, Is.EqualTo("Foo")); + Assert.That(item.OpponentEmblemId, Is.EqualTo("721341010")); + Assert.That(item.OpponentDegreeId, Is.EqualTo("120023")); + Assert.That(item.IsWin, Is.EqualTo("0")); + Assert.That(item.BattleStartTime, Is.EqualTo("2026-06-04 17:13:13")); + } + + [Test] + public void Serialized_dto_uses_snake_case_wire_keys() + { + var dto = new ReplayInfoResponseDto + { + ReplayList = new List + { + new() + { + BattleType = "4", BattleId = "234471983876", OpponentName = "Foo", + OpponentEmblemId = "721341010", OpponentDegreeId = "120023", + IsWin = "0", BattleStartTime = "2026-06-04 17:13:13", + CreateTime = "2026-06-04 17:16:06", + }, + }, + }; + + var json = JsonSerializer.Serialize(dto, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + }); + + Assert.That(json, Does.Contain("\"battle_id\":\"234471983876\"")); + Assert.That(json, Does.Contain("\"opponent_emblem_id\":\"721341010\"")); + Assert.That(json, Does.Contain("\"battle_start_time\":\"2026-06-04 17:13:13\"")); + // Ensure no camel-case / PascalCase leakage. + Assert.That(json, Does.Not.Contain("\"BattleId\"")); + Assert.That(json, Does.Not.Contain("\"battleId\"")); + } + + [Test] + public void Round_trips_through_MessagePack_preserving_wire_keys() + { + var original = new ReplayInfoItemDto + { + BattleType = "4", BattleId = "234471983876", OpponentName = "Foo", + OpponentEmblemId = "721341010", IsWin = "1", + BattleStartTime = "2026-06-04 17:13:13", + }; + + var bytes = MessagePackSerializer.Serialize(original); + var roundTripped = MessagePackSerializer.Deserialize(bytes); + + Assert.That(roundTripped.BattleId, Is.EqualTo("234471983876")); + Assert.That(roundTripped.OpponentEmblemId, Is.EqualTo("721341010")); + Assert.That(roundTripped.IsWin, Is.EqualTo("1")); + } +}