diff --git a/SVSim.EmulatedEntrypoint/Controllers/CampaignController.cs b/SVSim.EmulatedEntrypoint/Controllers/CampaignController.cs
index bd19353..44c9126 100644
--- a/SVSim.EmulatedEntrypoint/Controllers/CampaignController.cs
+++ b/SVSim.EmulatedEntrypoint/Controllers/CampaignController.cs
@@ -4,6 +4,7 @@ using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.EmulatedEntrypoint.Models.Dtos.Campaign;
+using SVSim.EmulatedEntrypoint.Services;
namespace SVSim.EmulatedEntrypoint.Controllers;
@@ -41,7 +42,7 @@ public sealed class CampaignController : SVSimController
.AnyAsync(r => r.ViewerId == viewerId && r.SerialCodeId == code.Id, ct);
if (alreadyRedeemed) return Fail();
- if (code.Rewards.Any(r => !IsSupportedGiftRewardType(r.RewardType))) return Fail();
+ if (code.Rewards.Any(r => !GiftRewardTypes.IsSupported(r.RewardType))) return Fail();
try
{
@@ -82,12 +83,4 @@ public sealed class CampaignController : SVSimController
private IActionResult Fail() => Ok(new { result_code = FailureResultCode });
- ///
- /// Gift wire types per GiftController.WireRewardTypeToUserGoodsType:
- /// 1=Crystal, 4=Item, 9=Rupy. Codes with unsupported types fail-fast at redemption.
- /// Note: wire "1" means Crystal (not RedEther), following the gift wire convention
- /// rather than the enum order.
- ///
- private static bool IsSupportedGiftRewardType(int rewardType) =>
- rewardType is 1 or 4 or 9;
}
diff --git a/SVSim.UnitTests/Controllers/CampaignControllerTests.cs b/SVSim.UnitTests/Controllers/CampaignControllerTests.cs
index 3d2cbf0..0f797cb 100644
--- a/SVSim.UnitTests/Controllers/CampaignControllerTests.cs
+++ b/SVSim.UnitTests/Controllers/CampaignControllerTests.cs
@@ -182,9 +182,10 @@ public class CampaignControllerTests
{
using var factory = new SVSimTestFactory();
long viewerId = await factory.SeedViewerAsync();
- // RewardType 5 = Card; gift mapper supports only 1=Crystal, 4=Item, 9=Rupy.
+ // RewardType 11 = SpotCard; InventoryTransaction throws NotSupportedException, so the
+ // gift mapper rejects it.
var code = await SeedCodeAsync(factory, "BADTYPE",
- rewards: new[] { (5, 100L, 1) });
+ rewards: new[] { (11, 100L, 1) });
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/campaign/regist_serial_code",
@@ -227,4 +228,57 @@ public class CampaignControllerTests
using var doc = JsonDocument.Parse(raw);
Assert.That(doc.RootElement.GetProperty("result_code").GetInt32(), Is.EqualTo(4202));
}
+
+ [Test]
+ public async Task Register_with_card_reward_succeeds_and_creates_present()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ var code = await SeedCodeAsync(factory, "CARDCODE", "Free card",
+ rewards: new[] { (5, 12345L, 1) });
+
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+ var response = await client.PostAsync("/campaign/regist_serial_code",
+ JsonBody("""{"serial_code":"CARDCODE"}"""));
+ var raw = await response.Content.ReadAsStringAsync();
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), raw);
+
+ using var doc = JsonDocument.Parse(raw);
+ Assert.That(doc.RootElement.GetProperty("is_complete").GetBoolean(), Is.True);
+
+ using var verifyScope = factory.Services.CreateScope();
+ var ctx = verifyScope.ServiceProvider.GetRequiredService();
+ var presents = await ctx.ViewerPresents.AsNoTracking()
+ .Where(p => p.ViewerId == viewerId).ToListAsync();
+ Assert.That(presents, Has.Count.EqualTo(1));
+ Assert.That(presents[0].RewardType, Is.EqualTo(5));
+ Assert.That(presents[0].RewardDetailId, Is.EqualTo(12345L));
+ Assert.That(presents[0].Source, Is.EqualTo($"serial_code:{code.Id}"));
+ }
+
+ [Test]
+ public async Task Register_with_sleeve_reward_succeeds_and_creates_present()
+ {
+ using var factory = new SVSimTestFactory();
+ long viewerId = await factory.SeedViewerAsync();
+ var code = await SeedCodeAsync(factory, "SLEEVECODE", "Free sleeve",
+ rewards: new[] { (6, 700100L, 1) });
+
+ using var client = factory.CreateAuthenticatedClient(viewerId);
+ var response = await client.PostAsync("/campaign/regist_serial_code",
+ JsonBody("""{"serial_code":"SLEEVECODE"}"""));
+ var raw = await response.Content.ReadAsStringAsync();
+ Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), raw);
+
+ using var doc = JsonDocument.Parse(raw);
+ Assert.That(doc.RootElement.GetProperty("is_complete").GetBoolean(), Is.True);
+
+ using var verifyScope = factory.Services.CreateScope();
+ var ctx = verifyScope.ServiceProvider.GetRequiredService();
+ var presents = await ctx.ViewerPresents.AsNoTracking()
+ .Where(p => p.ViewerId == viewerId).ToListAsync();
+ Assert.That(presents, Has.Count.EqualTo(1));
+ Assert.That(presents[0].RewardType, Is.EqualTo(6));
+ Assert.That(presents[0].RewardDetailId, Is.EqualTo(700100L));
+ }
}