feat(packs): wire PackDrawTableImporter; retire ICardPoolProvider
Bootstrap Program.cs now calls PackDrawTableImporter after PackImporter. Delete DbCardPoolProvider, ICardPoolProvider, and the DbCardPoolProvider tests — the new IPackDrawTableRepository covers what GachaPointService needed (legendary-tier card_ids per pack) and PackOpenService takes the draw table directly. GachaPointService now resolves the legendary catalog from PackDrawTable.CardWeights filtered by Tier==Legendary, instead of ICardPoolProvider.GetPool then a rarity filter. Same end set, no DB pool walk. Test fallout: tests that fabricate custom card sets for gacha-point tests now call factory.SeedPackDrawTableFromSetAsync(packId, setId) to install a matching legendary-tier stub. Full suite: 647/647 green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -44,6 +44,7 @@ public class PackControllerGachaPointTests
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var body = JsonBody("""{"odds_gacha_id":10008,"parent_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
@@ -95,6 +96,7 @@ public class PackControllerGachaPointTests
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var body = JsonBody("""{"odds_gacha_id":10008,"parent_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
@@ -145,6 +147,7 @@ public class PackControllerGachaPointTests
|
||||
viewer.GachaPointBalances.Add(new ViewerGachaPointBalance { PackId = 10008, Points = 500 });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var body = JsonBody("""{"card_id":108041010,"parent_gacha_id":10008,"odds_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
@@ -213,6 +216,7 @@ public class PackControllerGachaPointTests
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
using var client = factory.CreateAuthenticatedClient(viewerId);
|
||||
var body = JsonBody("""{"card_id":108041010,"parent_gacha_id":10008,"odds_gacha_id":10008,"viewer_id":"0","steam_id":0,"steam_session_ticket":""}""");
|
||||
|
||||
@@ -276,7 +276,32 @@ internal sealed class SVSimTestFactory : WebApplicationFactory<Program>
|
||||
/// at 100% rate; slot 1-7 and slot 8 both draw from the same pool. Use for tests that need
|
||||
/// /pack/open to succeed against a custom seeded card pool.
|
||||
/// </summary>
|
||||
public async Task SeedPackDrawTableAsync(int packId, params long[] cardIds)
|
||||
public Task SeedPackDrawTableAsync(int packId, params long[] cardIds)
|
||||
=> SeedPackDrawTableAsync(packId, DrawTier.Bronze, cardIds);
|
||||
|
||||
/// <summary>
|
||||
/// Convenience for gacha-point tests: picks Legendary cards from <paramref name="cardSetId"/>
|
||||
/// (skipping foils) and seeds them as the draw table's Legendary tier for <paramref name="packId"/>.
|
||||
/// </summary>
|
||||
public async Task SeedPackDrawTableFromSetAsync(int packId, int cardSetId)
|
||||
{
|
||||
using var scope = Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
var legendaryIds = await db.CardSets
|
||||
.Where(s => s.Id == cardSetId)
|
||||
.SelectMany(s => s.Cards)
|
||||
.Where(c => c.Rarity == SVSim.Database.Enums.Rarity.Legendary && !c.IsFoil)
|
||||
.Select(c => c.Id)
|
||||
.ToListAsync();
|
||||
|
||||
if (legendaryIds.Count > 0)
|
||||
{
|
||||
await SeedPackDrawTableAsync(packId, DrawTier.Legendary, legendaryIds.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SeedPackDrawTableAsync(int packId, DrawTier tier, params long[] cardIds)
|
||||
{
|
||||
using var scope = Services.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
@@ -284,12 +309,12 @@ internal sealed class SVSimTestFactory : WebApplicationFactory<Program>
|
||||
if (await db.PackDrawConfigs.AnyAsync(c => c.Id == packId)) return;
|
||||
|
||||
db.PackDrawConfigs.Add(new PackDrawConfigEntry { Id = packId, AnimationRatePct = 0 });
|
||||
db.PackDrawSlotRates.Add(new PackDrawSlotRateEntry { PackId = packId, Slot = DrawSlot.General, Tier = DrawTier.Bronze, RatePct = 100 });
|
||||
db.PackDrawSlotRates.Add(new PackDrawSlotRateEntry { PackId = packId, Slot = DrawSlot.Eighth, Tier = DrawTier.Bronze, RatePct = 100 });
|
||||
db.PackDrawSlotRates.Add(new PackDrawSlotRateEntry { PackId = packId, Slot = DrawSlot.General, Tier = tier, RatePct = 100 });
|
||||
db.PackDrawSlotRates.Add(new PackDrawSlotRateEntry { PackId = packId, Slot = DrawSlot.Eighth, Tier = tier, RatePct = 100 });
|
||||
foreach (var cid in cardIds)
|
||||
{
|
||||
db.PackDrawCardWeights.Add(new PackDrawCardWeightEntry { PackId = packId, Slot = DrawSlot.General, Tier = DrawTier.Bronze, CardId = cid, RatePct = 100.0 / cardIds.Length });
|
||||
db.PackDrawCardWeights.Add(new PackDrawCardWeightEntry { PackId = packId, Slot = DrawSlot.Eighth, Tier = DrawTier.Bronze, CardId = cid, RatePct = 100.0 / cardIds.Length });
|
||||
db.PackDrawCardWeights.Add(new PackDrawCardWeightEntry { PackId = packId, Slot = DrawSlot.General, Tier = tier, CardId = cid, RatePct = 100.0 / cardIds.Length });
|
||||
db.PackDrawCardWeights.Add(new PackDrawCardWeightEntry { PackId = packId, Slot = DrawSlot.Eighth, Tier = tier, CardId = cid, RatePct = 100.0 / cardIds.Length });
|
||||
}
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using SVSim.Database;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Models;
|
||||
using SVSim.EmulatedEntrypoint.Services;
|
||||
using SVSim.UnitTests.Infrastructure;
|
||||
|
||||
namespace SVSim.UnitTests.Services;
|
||||
|
||||
public class DbCardPoolProviderTests
|
||||
{
|
||||
[Test]
|
||||
public async Task GetPool_for_standard_pack_returns_cards_of_matching_set()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long anyCardId;
|
||||
int setId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var setWithCards = await db.CardSets.Include(s => s.Cards)
|
||||
.FirstAsync(s => s.Cards.Count > 0);
|
||||
setId = setWithCards.Id;
|
||||
anyCardId = setWithCards.Cards.First().Id;
|
||||
}
|
||||
|
||||
using var scope2 = factory.Services.CreateScope();
|
||||
var provider = scope2.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
var pool = provider.GetPool(new PackConfigEntry
|
||||
{
|
||||
Id = setId, BasePackId = setId, PackCategory = PackCategory.None
|
||||
});
|
||||
|
||||
Assert.That(pool.Any(c => c.Id == anyCardId), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPool_for_legendary_special_returns_cards_from_rotation_sets()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var provider = scope.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
|
||||
var pool = provider.GetPool(new PackConfigEntry
|
||||
{
|
||||
Id = 92001, BasePackId = 90001, PackCategory = PackCategory.SpecialCardPack
|
||||
});
|
||||
|
||||
Assert.That(pool.Count, Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPool_for_skin_pack_returns_empty()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var provider = scope.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
|
||||
var pool = provider.GetPool(new PackConfigEntry
|
||||
{
|
||||
Id = 70001, BasePackId = 70001, PackCategory = PackCategory.LeaderSkinPack
|
||||
});
|
||||
|
||||
Assert.That(pool, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetPool_excludes_foil_cards()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long nonFoilId, foilId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
// Pick the highest-Id card so that id+1 is guaranteed unoccupied.
|
||||
nonFoilId = await db.Cards.OrderByDescending(c => c.Id).Select(c => c.Id).FirstAsync();
|
||||
foilId = nonFoilId + 1;
|
||||
var foilCard = new ShadowverseCardEntry
|
||||
{
|
||||
Id = foilId, Name = $"Card {foilId}", Rarity = Rarity.Bronze, IsFoil = true,
|
||||
};
|
||||
// Add directly to the Cards DbSet and set the FK via shadow property,
|
||||
// avoiding nav-collection tracker conflicts.
|
||||
db.Cards.Add(foilCard);
|
||||
db.Entry(foilCard).Property("ShadowverseCardSetEntryId").CurrentValue = 10001;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var scope2 = factory.Services.CreateScope();
|
||||
var provider = scope2.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
var pool = provider.GetPool(new PackConfigEntry
|
||||
{
|
||||
Id = 10001, BasePackId = 10001,
|
||||
PackCategory = PackCategory.None,
|
||||
});
|
||||
|
||||
Assert.That(pool.Any(c => c.Id == nonFoilId), Is.True, "non-foil must be in the pool");
|
||||
Assert.That(pool.Any(c => c.Id == foilId), Is.False, "foil must be excluded from the pool");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TryGetFoilTwin_returns_the_id_plus_one_foil_when_present()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long nonFoilId, foilId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
// Pick the highest-Id card so that id+1 is guaranteed unoccupied.
|
||||
nonFoilId = await db.Cards.OrderByDescending(c => c.Id).Select(c => c.Id).FirstAsync();
|
||||
foilId = nonFoilId + 1;
|
||||
var foilCard = new ShadowverseCardEntry
|
||||
{
|
||||
Id = foilId, Name = $"Card {foilId}", Rarity = Rarity.Bronze, IsFoil = true,
|
||||
};
|
||||
db.Cards.Add(foilCard);
|
||||
db.Entry(foilCard).Property("ShadowverseCardSetEntryId").CurrentValue = 10001;
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
using var scope2 = factory.Services.CreateScope();
|
||||
var provider = scope2.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
|
||||
var twin = provider.TryGetFoilTwin(nonFoilId);
|
||||
Assert.That(twin, Is.Not.Null);
|
||||
Assert.That(twin!.Id, Is.EqualTo(foilId));
|
||||
Assert.That(twin.IsFoil, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TryGetFoilTwin_returns_null_when_no_foil_at_id_plus_one()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long anyCardId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
anyCardId = await db.Cards.OrderBy(c => c.Id).Select(c => c.Id).FirstAsync();
|
||||
}
|
||||
|
||||
using var scope2 = factory.Services.CreateScope();
|
||||
var provider = scope2.ServiceProvider.GetRequiredService<ICardPoolProvider>();
|
||||
|
||||
Assert.That(provider.TryGetFoilTwin(anyCardId), Is.Null,
|
||||
"no foil seeded at anyCardId+1, so TryGetFoilTwin must return null");
|
||||
}
|
||||
}
|
||||
@@ -78,6 +78,7 @@ public class GachaPointServiceTests
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10008, viewerId);
|
||||
@@ -125,6 +126,7 @@ public class GachaPointServiceTests
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10008, viewerId);
|
||||
@@ -173,6 +175,7 @@ public class GachaPointServiceTests
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10008, viewerId);
|
||||
@@ -219,6 +222,7 @@ public class GachaPointServiceTests
|
||||
PackId = 10008, CardId = 108041010, ReceivedAt = DateTime.UtcNow,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10008, viewerId);
|
||||
@@ -515,6 +519,7 @@ public class GachaPointServiceTests
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10008, 10008);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10008, viewerId);
|
||||
@@ -555,6 +560,7 @@ public class GachaPointServiceTests
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = 400, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
await factory.SeedPackDrawTableFromSetAsync(10099, 10099);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<IGachaPointService>();
|
||||
var result = await svc.GetRewardsAsync(10099, viewerId);
|
||||
@@ -586,6 +592,14 @@ public class GachaPointServiceTests
|
||||
CommenceDate = DateTime.UtcNow.AddDays(-1), CompleteDate = DateTime.UtcNow.AddDays(30),
|
||||
GachaPointConfig = new PackGachaPointConfig { ExchangeablePoint = threshold, IncreaseGachaPoint = 1 },
|
||||
});
|
||||
// Draw table pointing at the seeded legendary so IPackDrawTableRepository.GetAsync
|
||||
// surfaces it for GachaPointService.GetRewardsAsync / TryExchangeAsync.
|
||||
db.PackDrawConfigs.Add(new PackDrawConfigEntry { Id = packId, AnimationRatePct = 0 });
|
||||
db.PackDrawSlotRates.Add(new PackDrawSlotRateEntry { PackId = packId, Slot = DrawSlot.General, Tier = DrawTier.Legendary, RatePct = 100 });
|
||||
db.PackDrawCardWeights.Add(new PackDrawCardWeightEntry
|
||||
{
|
||||
PackId = packId, Slot = DrawSlot.General, Tier = DrawTier.Legendary, CardId = 108041010, RatePct = 100,
|
||||
});
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user