From 71b0c66631de8f6a4c9e835ba80c3a035bc17dd2 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Thu, 28 May 2026 02:21:57 -0400 Subject: [PATCH] test(card): snapshot-mismatch + protect-load round-trip Add two spec-prescribed tests that the implementation plan missed: - Create_proceeds_when_client_possession_snapshot_disagrees_with_server - Protect_then_load_index_emits_is_protected_one Co-Authored-By: Claude Sonnet 4.6 --- .../Controllers/CardControllerTests.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/SVSim.UnitTests/Controllers/CardControllerTests.cs b/SVSim.UnitTests/Controllers/CardControllerTests.cs index d988750..7f48ddc 100644 --- a/SVSim.UnitTests/Controllers/CardControllerTests.cs +++ b/SVSim.UnitTests/Controllers/CardControllerTests.cs @@ -347,6 +347,39 @@ public class CardControllerTests Assert.That(body, Does.Contain("insufficient_vials")); } + [Test] + public async Task Create_proceeds_when_client_possession_snapshot_disagrees_with_server() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + // Server has 0 owned; client thinks it has 5 (stale snapshot). + await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 0, craftCost: 200); + await factory.SetRedEtherAsync(viewerId, 1_000UL); + using var client = factory.CreateAuthenticatedClient(viewerId); + + // Inner JSON: create 1, client snapshot=5 (disagrees with server count=0). + // Spec: snapshot mismatch is warn-log only, never blocks the request. + var response = await client.PostAsync("/card/create", + CreateBody("{\"10001001\":\"1,5\"}")); + var body = await response.Content.ReadAsStringAsync(); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), body); + + var entries = JsonDocument.Parse(body).RootElement + .GetProperty("reward_list") + .EnumerateArray() + .Select(e => (Type: e.GetProperty("reward_type").GetInt32(), + Id: e.GetProperty("reward_id").GetInt64(), + Num: e.GetProperty("reward_num").GetInt32())) + .ToList(); + + // RedEther and card count based on actual server state, not client snapshot. + Assert.That(entries, Has.Member((Type: 1, Id: 0L, Num: 800)), + "RedEther post-state total = 1000 - 200 = 800"); + Assert.That(entries, Has.Member((Type: 5, Id: 10001001L, Num: 1)), + "Card post-state owned count = 0 + 1 = 1"); + } + private static StringContent ProtectBody(long cardId, bool isProtected) => new( $$"""{"card_id":{{cardId}},"is_protected":{{(isProtected ? "true" : "false")}},"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""", @@ -431,4 +464,39 @@ public class CardControllerTests // controller payload, which for CardProtectResponse is an empty object. Assert.That(body.Trim(), Is.EqualTo("{}")); } + + [Test] + public async Task Protect_then_load_index_emits_is_protected_one() + { + // Spec: /load/index user_card_list[].is_protected is an int wire value (0 or 1), + // not a bool. Protect a card then verify /load/index round-trips the flag correctly. + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(); + await factory.SeedOwnedCardAsync(viewerId, cardId: 10001001L, count: 2); + using var client = factory.CreateAuthenticatedClient(viewerId); + + // Set the protect flag. + var protectResponse = await client.PostAsync("/card/protect", + ProtectBody(10001001L, isProtected: true)); + Assert.That(protectResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK), + await protectResponse.Content.ReadAsStringAsync()); + + // Call /load/index and parse user_card_list. + const string IndexRequestJson = + """{"viewer_id":"0","steam_id":0,"steam_session_ticket":"","carrier":"web","card_master_hash":""}"""; + var loadResponse = await client.PostAsync("/load/index", + new StringContent(IndexRequestJson, Encoding.UTF8, "application/json")); + var loadBody = await loadResponse.Content.ReadAsStringAsync(); + Assert.That(loadResponse.StatusCode, Is.EqualTo(HttpStatusCode.OK), loadBody); + + var cardEntry = JsonDocument.Parse(loadBody).RootElement + .GetProperty("user_card_list") + .EnumerateArray() + .FirstOrDefault(e => e.GetProperty("card_id").GetInt64() == 10001001L); + + Assert.That(cardEntry.ValueKind, Is.Not.EqualTo(JsonValueKind.Undefined), + "Expected card 10001001 in user_card_list"); + Assert.That(cardEntry.GetProperty("is_protected").GetInt32(), Is.EqualTo(1), + "is_protected wire value must be 1 (int) after protect call"); + } }