diff --git a/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs b/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs index fe89173..0903f5c 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/GiftController.cs @@ -8,6 +8,7 @@ using SVSim.Database.Services.Inventory; using SVSim.EmulatedEntrypoint.Mapping; using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.Gift; using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.Gift; +using SVSim.EmulatedEntrypoint.Services; namespace SVSim.EmulatedEntrypoint.Controllers; @@ -87,10 +88,6 @@ public class GiftController : SVSimController { if (state == 1) { - // Wire reward_type on the gift endpoint follows a gift-specific scheme that - // diverges from UserGoodsType for currencies: wire "1" means Crystal (enum=2), - // wire "9" means Rupy (enum=9), wire "4" means Item (enum=4). A naked cast would - // resolve wire 1 -> UserGoodsType.RedEther and silently grant the wrong wallet. var granted = await tx.GrantAsync( WireRewardTypeToUserGoodsType(p.RewardType), p.RewardDetailId, @@ -168,13 +165,17 @@ public class GiftController : SVSimController }; } - private static UserGoodsType WireRewardTypeToUserGoodsType(int wireType) => wireType switch + /// + /// Gift wire's reward_type is a literal integer — the + /// client's Wizard/RewardBase.cs:245 casts it directly to UserGoods.Type. + /// Mirror that cast, validated against . + /// + private static UserGoodsType WireRewardTypeToUserGoodsType(int wireType) { - 1 => UserGoodsType.Crystal, - 4 => UserGoodsType.Item, - 9 => UserGoodsType.Rupy, - _ => throw new InvalidOperationException($"Unmapped gift wire reward_type {wireType}"), - }; + if (!GiftRewardTypes.IsSupported(wireType)) + throw new InvalidOperationException($"Unsupported gift reward_type {wireType}"); + return (UserGoodsType)wireType; + } private async Task<(List Unclaimed, List History)> ReadTopWindowAsync( long viewerId, int page) diff --git a/SVSim.UnitTests/Controllers/GiftControllerTests.cs b/SVSim.UnitTests/Controllers/GiftControllerTests.cs index 75952de..5f29068 100644 --- a/SVSim.UnitTests/Controllers/GiftControllerTests.cs +++ b/SVSim.UnitTests/Controllers/GiftControllerTests.cs @@ -84,18 +84,19 @@ public class GiftControllerTests Assert.That(root.GetProperty("present_list").GetArrayLength(), Is.EqualTo(0)); Assert.That(root.GetProperty("present_history_list").GetArrayLength(), Is.EqualTo(5)); - // Currency credited: +400 crystals, +100 rupees. + // Currency credited: +400 red ether, +100 rupees. + // Tutorial gift 71478626 has reward_type=1 — that's RedEther per UserGoods.Type, not Crystal. var post = await factory.GetViewerCurrencyAsync(viewerId); - Assert.That(post.Crystals - pre.Crystals, Is.EqualTo(400UL)); + Assert.That(post.RedEther - pre.RedEther, Is.EqualTo(400UL)); Assert.That(post.Rupees - pre.Rupees, Is.EqualTo(100UL)); // reward_list carries post-state TOTALS, not deltas, per project_wire_reward_list_post_state. // After claiming gifts, the crystal/rupy entries in reward_list should equal viewer's post-grant totals. var rewardList = root.GetProperty("reward_list").EnumerateArray().ToList(); - var crystalEntry = rewardList.First(e => e.GetProperty("reward_type").GetString() == "1"); - var rupyEntry = rewardList.First(e => e.GetProperty("reward_type").GetString() == "9"); - Assert.That(crystalEntry.GetProperty("reward_num").GetString(), - Is.EqualTo(post.Crystals.ToString()), + var redEtherEntry = rewardList.First(e => e.GetProperty("reward_type").GetString() == "1"); + var rupyEntry = rewardList.First(e => e.GetProperty("reward_type").GetString() == "9"); + Assert.That(redEtherEntry.GetProperty("reward_num").GetString(), + Is.EqualTo(post.RedEther.ToString()), "reward_list currency entries must carry POST-STATE TOTALS, not gift deltas (client does direct assignment)."); Assert.That(rupyEntry.GetProperty("reward_num").GetString(), Is.EqualTo(post.Rupees.ToString())); @@ -243,13 +244,13 @@ public class GiftControllerTests await client.PostAsync("/tutorial/gift_receive", new StringContent(json, Encoding.UTF8, "application/json")); var midPost = await factory.GetViewerCurrencyAsync(viewerId); - Assert.That(midPost.Crystals - preFirst.Crystals, Is.EqualTo(400UL)); + Assert.That(midPost.RedEther - preFirst.RedEther, Is.EqualTo(400UL)); Assert.That(midPost.Rupees - preFirst.Rupees, Is.EqualTo(100UL)); var second = await client.PostAsync("/tutorial/gift_receive", new StringContent(json, Encoding.UTF8, "application/json")); Assert.That(second.StatusCode, Is.EqualTo(HttpStatusCode.OK)); var finalPost = await factory.GetViewerCurrencyAsync(viewerId); - Assert.That(finalPost.Crystals, Is.EqualTo(midPost.Crystals), "Second claim of same present_ids must not re-grant."); + Assert.That(finalPost.RedEther, Is.EqualTo(midPost.RedEther), "Second claim of same present_ids must not re-grant."); Assert.That(finalPost.Rupees, Is.EqualTo(midPost.Rupees)); } @@ -314,7 +315,7 @@ public class GiftControllerTests var preCurrency = await factory.GetViewerCurrencyAsync(viewerId); - // Delete the crystal gift (71478626 grants +400 crystals on state=1). + // Delete the red-ether gift (71478626 grants +400 RedEther 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")); @@ -325,7 +326,7 @@ public class GiftControllerTests // No currency granted. var postCurrency = await factory.GetViewerCurrencyAsync(viewerId); - Assert.That(postCurrency.Crystals, Is.EqualTo(preCurrency.Crystals), + Assert.That(postCurrency.RedEther, Is.EqualTo(preCurrency.RedEther), "state=3 (MAIL_DELETE) must not grant."); // No reward_list / total_receive_count_list entries. diff --git a/SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs b/SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs index fd66747..add29da 100644 --- a/SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs +++ b/SVSim.UnitTests/Controllers/TutorialFlowEndToEndTests.cs @@ -85,7 +85,8 @@ public class TutorialFlowEndToEndTests Assert.That(receiveResp.StatusCode, Is.EqualTo(HttpStatusCode.OK)); var midCurrency = await factory.GetViewerCurrencyAsync(viewerId); - Assert.That(midCurrency.Crystals - preCurrency.Crystals, Is.EqualTo(400UL)); + // Tutorial gift 71478626 has reward_type=1 — that's RedEther per UserGoods.Type, not Crystal. + Assert.That(midCurrency.RedEther - preCurrency.RedEther, Is.EqualTo(400UL)); Assert.That(midCurrency.Rupees - preCurrency.Rupees, Is.EqualTo(100UL)); // gift_receive should also have advanced the tutorial step to 41 server-side.