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 <noreply@anthropic.com>
This commit is contained in:
@@ -347,6 +347,39 @@ public class CardControllerTests
|
|||||||
Assert.That(body, Does.Contain("insufficient_vials"));
|
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) =>
|
private static StringContent ProtectBody(long cardId, bool isProtected) =>
|
||||||
new(
|
new(
|
||||||
$$"""{"card_id":{{cardId}},"is_protected":{{(isProtected ? "true" : "false")}},"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""",
|
$$"""{"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.
|
// controller payload, which for CardProtectResponse is an empty object.
|
||||||
Assert.That(body.Trim(), Is.EqualTo("{}"));
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user