feat(import): import owned card collection with unknown-card skip
Extends POST /admin/import_viewer to accept owned_cards (snapshot full-replace). Unknown card_ids are skipped, counted in skipped_card_count on the response, and logged server-side. Count is clamped to OwnedCardEntry.MaxCopies (3). Also injects ILogger into AdminController and adds Cards/Items/Decks to the viewer reload graph. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -115,4 +115,84 @@ public class AdminControllerTests
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_imports_owned_cards_and_skips_unknown()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
// 10001001 is in the minimal test card set; 99999999 is not.
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = 76_561_198_111_222_333UL,
|
||||
OwnedCards = new List<ImportCard>
|
||||
{
|
||||
new() { CardId = 10001001L, Count = 2, IsProtected = true },
|
||||
new() { CardId = 99999999L, Count = 1, IsProtected = false },
|
||||
}
|
||||
});
|
||||
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
||||
await response.Content.ReadAsStringAsync());
|
||||
var body = await response.Content.ReadFromJsonAsync<ImportViewerResponse>(JsonOptions);
|
||||
Assert.That(body!.SkippedCardCount, Is.EqualTo(1), "Unknown 99999999 must be skipped and counted.");
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var stored = await db.Viewers.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(v => v.Id == body.ViewerId);
|
||||
|
||||
Assert.That(stored.Cards.Count, Is.EqualTo(1), "Only the known card should be stored.");
|
||||
var owned = stored.Cards.Single();
|
||||
Assert.That(owned.Card.Id, Is.EqualTo(10001001L));
|
||||
Assert.That(owned.Count, Is.EqualTo(2));
|
||||
Assert.That(owned.IsProtected, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_clamps_card_count_to_max_copies()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var client = factory.CreateClient();
|
||||
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = 76_561_198_111_222_334UL,
|
||||
OwnedCards = new List<ImportCard> { new() { CardId = 10001002L, Count = 5 } }
|
||||
});
|
||||
var body = await response.Content.ReadFromJsonAsync<ImportViewerResponse>(JsonOptions);
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var stored = await db.Viewers.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(v => v.Id == body!.ViewerId);
|
||||
Assert.That(stored.Cards.Single().Count, Is.EqualTo(3),
|
||||
"Count must clamp to OwnedCardEntry.MaxCopies (3).");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ImportViewer_replaces_existing_card_collection()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
const ulong steamId = 76_561_198_111_222_335UL;
|
||||
long viewerId = await factory.SeedViewerAsync(steamId: steamId);
|
||||
await factory.SeedOwnedCardAsync(viewerId, 10001001L, count: 3);
|
||||
|
||||
using var client = factory.CreateClient();
|
||||
var response = await client.PostAsJsonAsync("/admin/import_viewer", new ImportViewerRequest
|
||||
{
|
||||
SteamId = steamId,
|
||||
OwnedCards = new List<ImportCard> { new() { CardId = 10001002L, Count = 1 } }
|
||||
});
|
||||
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK),
|
||||
await response.Content.ReadAsStringAsync());
|
||||
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var stored = await db.Viewers.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
Assert.That(stored.Cards.Select(c => c.Card.Id), Is.EquivalentTo(new[] { 10001002L }),
|
||||
"Full replace: the pre-seeded 10001001 must be gone, only 10001002 present.");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user