Additional card content

This commit is contained in:
gamer147
2026-05-24 17:07:05 -04:00
parent 12fb2f4801
commit 34bcc579a5
18 changed files with 53025 additions and 16 deletions

View File

@@ -1,6 +1,11 @@
using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using SVSim.Database;
using SVSim.Database.Enums;
using SVSim.Database.Models;
using SVSim.UnitTests.Infrastructure;
namespace SVSim.UnitTests.Controllers;
@@ -295,4 +300,66 @@ public class LoadControllerTests
Assert.That(root.TryGetProperty("battle_pass_level_info", out _), Is.False,
"battle_pass_level_info optional per spec; emit null until viewer pass state is wired");
}
[Test]
public async Task LoadIndex_GrantsMissingCosmeticsForOwnedCards()
{
// Verifies the C.2 wiring: /load/index invokes ICardAcquisitionService in backfill mode,
// so a viewer who already owns a leader card but lacks its associated cosmetics gets
// them granted on the next load. Mirrors the in-flight migration story for existing
// accounts that pre-date the cosmetic-grant feature.
using var factory = new SVSimTestFactory();
var viewerId = await factory.SeedViewerAsync();
// Seed viewer with leader card 704741010 (count=1), seed mapping → skin 407, seed
// master skin row. Viewer does NOT yet own the skin.
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
var viewer = await db.Viewers.Include(v => v.Cards).FirstAsync(v => v.Id == viewerId);
var card = await db.Cards.FindAsync(704741010L);
if (card is null)
{
card = new ShadowverseCardEntry { Id = 704741010L, Name = "TestLeader", Rarity = Rarity.Legendary, IsFoil = false };
db.Cards.Add(card);
}
viewer.Cards.Add(new OwnedCardEntry { Card = card, Count = 1, IsProtected = false });
db.CardCosmeticRewards.Add(new CardCosmeticReward { CardId = 704741010L, Type = CosmeticType.Skin, CosmeticId = 407L, Quantity = 1 });
if (await db.LeaderSkins.FindAsync(407) is null)
db.LeaderSkins.Add(new LeaderSkinEntry { Id = 407, Name = "TestSkin407" });
await db.SaveChangesAsync();
}
// Call /load/index — backfill should fire as part of the action.
using var client = factory.CreateAuthenticatedClient(viewerId);
var response = await client.PostAsync("/load/index",
new StringContent(IndexRequestJson, Encoding.UTF8, "application/json"));
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
await response.Content.ReadAsStringAsync());
// Verify the response payload includes the backfilled cosmetic (not just the DB state).
// This guards against a regression where the controller serves a stale viewer snapshot
// (GetViewerByShortUdid uses .AsNoTracking() so the in-memory `viewer` reference does
// not see writes the service makes on its own tracked instance — without a post-grant
// re-fetch the first /load/index would report the skin as un-owned even though the DB
// had been updated). user_leader_skin_list always carries all master skins; the per-entry
// is_owned flag is the actual ownership signal.
using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
var skin407 = doc.RootElement.GetProperty("user_leader_skin_list").EnumerateArray()
.FirstOrDefault(e => e.GetProperty("leader_skin_id").GetInt32() == 407);
Assert.That(skin407.ValueKind, Is.Not.EqualTo(JsonValueKind.Undefined),
"response payload should include leader skin 407 entry");
Assert.That(skin407.GetProperty("is_owned").GetBoolean(), Is.True,
"response payload should mark backfilled skin 407 as owned, not just DB state");
// Verify skin 407 was actually granted by re-reading viewer state.
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
var viewer = await db.Viewers.Include(v => v.LeaderSkins).FirstAsync(v => v.Id == viewerId);
Assert.That(viewer.LeaderSkins.Any(s => s.Id == 407), Is.True,
"skin 407 should have been backfilled by /load/index");
}
}
}