diff --git a/SVSim.UnitTests/Controllers/GiftControllerTests.cs b/SVSim.UnitTests/Controllers/GiftControllerTests.cs index d3f7fb2..e9df66b 100644 --- a/SVSim.UnitTests/Controllers/GiftControllerTests.cs +++ b/SVSim.UnitTests/Controllers/GiftControllerTests.cs @@ -247,4 +247,93 @@ public class GiftControllerTests Assert.That(finalPost.Crystals, Is.EqualTo(midPost.Crystals), "Second claim of same present_ids must not re-grant."); Assert.That(finalPost.Rupees, Is.EqualTo(midPost.Rupees)); } + + [Test] + public async Task GiftTop_prod_route_returns_same_content_as_tutorial_route() + { + using var factory = new SVSimTestFactory(); + long viewerId = await factory.SeedViewerAsync(tutorialState: 31); + await factory.SeedTutorialPresentsAsync(viewerId); + using var client = factory.CreateAuthenticatedClient(viewerId); + + var tutorial = await client.PostAsync("/tutorial/gift_top", + new StringContent($$"""{"page":1,{{BaseAuthBlock}}}""", Encoding.UTF8, "application/json")); + var prod = await client.PostAsync("/gift/top", + new StringContent($$"""{"page":1,{{BaseAuthBlock}}}""", Encoding.UTF8, "application/json")); + + Assert.That(tutorial.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + Assert.That(prod.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + + using var tDoc = JsonDocument.Parse(await tutorial.Content.ReadAsStringAsync()); + using var pDoc = JsonDocument.Parse(await prod.Content.ReadAsStringAsync()); + + // The two routes are pure aliases — present_list under both contains the same five + // PresentIds (compare as a set; CreatedAt-tied ordering may differ). + var tIds = tDoc.RootElement.GetProperty("present_list").EnumerateArray() + .Select(p => p.GetProperty("present_id").GetString()).ToHashSet(); + var pIds = pDoc.RootElement.GetProperty("present_list").EnumerateArray() + .Select(p => p.GetProperty("present_id").GetString()).ToHashSet(); + Assert.That(pIds, Is.EquivalentTo(tIds)); + Assert.That(pIds.Count, Is.EqualTo(5)); + } + + [Test] + public async Task GiftReceive_prod_route_does_not_advance_tutorial_state() + { + using var factory = new SVSimTestFactory(); + await factory.SeedGlobalsAsync(); + long viewerId = await factory.SeedViewerAsync(tutorialState: 31); + await factory.SeedTutorialPresentsAsync(viewerId); + using var client = factory.CreateAuthenticatedClient(viewerId); + + var json = $$"""{"present_id_array":["71478626"],"state":1,{{BaseAuthBlock}}}"""; + var response = await client.PostAsync("/gift/receive_gift", + new StringContent(json, Encoding.UTF8, "application/json")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + + // Prod route NEVER advances tutorial. tutorial_step echoes persisted state (still 31). + Assert.That(doc.RootElement.GetProperty("tutorial_step").GetInt32(), Is.EqualTo(31)); + Assert.That(await factory.GetViewerTutorialStateAsync(viewerId), Is.EqualTo(31)); + } + + [Test] + public async Task GiftReceive_state_3_deletes_without_grant_and_without_history() + { + using var factory = new SVSimTestFactory(); + await factory.SeedGlobalsAsync(); + long viewerId = await factory.SeedViewerAsync(tutorialState: 31); + await factory.SeedTutorialPresentsAsync(viewerId); + using var client = factory.CreateAuthenticatedClient(viewerId); + + var preCurrency = await factory.GetViewerCurrencyAsync(viewerId); + + // Delete the crystal gift (71478626 grants +400 crystals on state=1). + var json = $$"""{"present_id_array":["71478626"],"state":3,{{BaseAuthBlock}}}"""; + var response = await client.PostAsync("/gift/receive_gift", + new StringContent(json, Encoding.UTF8, "application/json")); + + Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); + var root = doc.RootElement; + + // No currency granted. + var postCurrency = await factory.GetViewerCurrencyAsync(viewerId); + Assert.That(postCurrency.Crystals, Is.EqualTo(preCurrency.Crystals), + "state=3 (MAIL_DELETE) must not grant."); + + // No reward_list / total_receive_count_list entries. + Assert.That(root.GetProperty("reward_list").GetArrayLength(), Is.EqualTo(0)); + Assert.That(root.GetProperty("total_receive_count_list").GetArrayLength(), Is.EqualTo(0)); + + // received_ids still reflects what was processed (the row transitioned to Deleted), + // so the client knows the gift is gone from its inbox. + Assert.That(root.GetProperty("received_ids").GetArrayLength(), Is.EqualTo(1)); + + // The deleted gift does NOT appear in present_history_list — it's tombstoned, not archived. + Assert.That(root.GetProperty("present_history_list").GetArrayLength(), Is.EqualTo(0)); + // ... and present_list now has 4 remaining unclaimed gifts. + Assert.That(root.GetProperty("present_list").GetArrayLength(), Is.EqualTo(4)); + } }