test(gift): claim Card/Sleeve presents; reject unsupported types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -379,4 +379,154 @@ public class GiftControllerTests
|
||||
{ "71478626", "71478627", "71478628", "71478629", "71478630" }));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GiftReceive_with_card_reward_grants_card_via_inventory_service()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
await factory.SeedGlobalsAsync();
|
||||
long viewerId = await factory.SeedViewerAsync(tutorialState: 100);
|
||||
long cardId = await factory.SeedCardAsync();
|
||||
|
||||
// Seed a single non-tutorial Card present.
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var ctx = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
ctx.ViewerPresents.Add(new ViewerPresent
|
||||
{
|
||||
ViewerId = viewerId,
|
||||
PresentId = "card-gift-001",
|
||||
Status = PresentStatus.Unclaimed,
|
||||
RewardType = 5, // UserGoodsType.Card
|
||||
RewardDetailId = cardId,
|
||||
RewardCount = 1,
|
||||
Message = "Test card grant",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Source = "test",
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var json = $$"""{"present_id_array":["card-gift-001"],"state":1,{{BaseAuthBlock}}}""";
|
||||
var response = await client.PostAsync("/gift/receive_gift",
|
||||
new StringContent(json, Encoding.UTF8, "application/json"));
|
||||
var bodyStr = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), bodyStr);
|
||||
|
||||
// Verify the card landed in the viewer's collection.
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var ctx2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var owned = await ctx2.Viewers.AsNoTracking()
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
Assert.That(owned.Cards.Any(c => c.Card.Id == cardId && c.Count > 0), Is.True,
|
||||
"Card reward_type=5 must round-trip through the gift mapper and InventoryService.GrantAsync.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GiftReceive_with_sleeve_reward_grants_sleeve_via_inventory_service()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
await factory.SeedGlobalsAsync();
|
||||
long viewerId = await factory.SeedViewerAsync(tutorialState: 100);
|
||||
|
||||
const int sleeveId = 700100;
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var ctx = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
ctx.Sleeves.Add(new SleeveEntry { Id = sleeveId });
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
ctx.ViewerPresents.Add(new ViewerPresent
|
||||
{
|
||||
ViewerId = viewerId,
|
||||
PresentId = "sleeve-gift-001",
|
||||
Status = PresentStatus.Unclaimed,
|
||||
RewardType = 6, // UserGoodsType.Sleeve
|
||||
RewardDetailId = sleeveId,
|
||||
RewardCount = 1,
|
||||
Message = "Test sleeve grant",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Source = "test",
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var json = $$"""{"present_id_array":["sleeve-gift-001"],"state":1,{{BaseAuthBlock}}}""";
|
||||
var response = await client.PostAsync("/gift/receive_gift",
|
||||
new StringContent(json, Encoding.UTF8, "application/json"));
|
||||
var bodyStr = await response.Content.ReadAsStringAsync();
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK), bodyStr);
|
||||
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var ctx2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var owned = await ctx2.Viewers.AsNoTracking()
|
||||
.Include(v => v.Sleeves)
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
Assert.That(owned.Sleeves.Any(s => s.Id == sleeveId), Is.True,
|
||||
"Sleeve reward_type=6 must round-trip through the gift mapper and InventoryService.GrantAsync.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GiftReceive_with_unsupported_reward_type_does_not_grant()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
await factory.SeedGlobalsAsync();
|
||||
long viewerId = await factory.SeedViewerAsync(tutorialState: 100);
|
||||
|
||||
// RewardType 11 = SpotCard, which GiftRewardTypes.IsSupported rejects, causing
|
||||
// WireRewardTypeToUserGoodsType to throw InvalidOperationException before CommitAsync.
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var ctx = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
ctx.ViewerPresents.Add(new ViewerPresent
|
||||
{
|
||||
ViewerId = viewerId,
|
||||
PresentId = "bad-gift-001",
|
||||
Status = PresentStatus.Unclaimed,
|
||||
RewardType = 11,
|
||||
RewardDetailId = 123,
|
||||
RewardCount = 1,
|
||||
Message = "Bad type",
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
Source = "test",
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var json = $$"""{"present_id_array":["bad-gift-001"],"state":1,{{BaseAuthBlock}}}""";
|
||||
|
||||
// TestServer propagates unhandled controller exceptions through HttpClient.PostAsync
|
||||
// (because Program.cs has no app.UseExceptionHandler middleware). Catch the exception
|
||||
// here; the critical assertion is that the DB row was never transitioned.
|
||||
bool threw = false;
|
||||
try
|
||||
{
|
||||
var response = await client.PostAsync("/gift/receive_gift",
|
||||
new StringContent(json, Encoding.UTF8, "application/json"));
|
||||
// If exception handling middleware is added later, the response will be 500 — accept both.
|
||||
Assert.That((int)response.StatusCode, Is.GreaterThanOrEqualTo(500),
|
||||
"Unsupported reward_type must not return a success status code.");
|
||||
}
|
||||
catch (Exception ex) when (ex.Message.Contains("Unsupported gift reward_type") ||
|
||||
ex.InnerException?.Message.Contains("Unsupported gift reward_type") == true)
|
||||
{
|
||||
threw = true;
|
||||
}
|
||||
|
||||
// Either path (thrown exception or 5xx) is acceptable; what matters is the DB row stayed Unclaimed.
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var ctx2 = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var present = await ctx2.ViewerPresents.AsNoTracking()
|
||||
.FirstAsync(p => p.PresentId == "bad-gift-001");
|
||||
Assert.That(present.Status, Is.EqualTo(PresentStatus.Unclaimed),
|
||||
"Failed claim must NOT transition the row — it's still claimable once the producer is fixed.");
|
||||
|
||||
// Confirm the exception did propagate (not swallowed into a silent 200).
|
||||
Assert.That(threw, Is.True,
|
||||
"InvalidOperationException for unsupported reward_type must propagate — not be silently swallowed into a 200.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user