Consolidation
This commit is contained in:
@@ -79,41 +79,4 @@ public class PackRepositoryTests
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantCardsToViewer_inserts_new_and_increments_existing()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
|
||||
// Cards are not seeded by BaseDataSeeder (they come from CardImport). Insert one directly.
|
||||
const long seedCardId = 100000001L;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.Cards.Add(new ShadowverseCardEntry { Id = seedCardId, Name = "Test Card", Rarity = Rarity.Bronze });
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
long sampleCardId;
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
sampleCardId = await db.Cards.OrderBy(c => c.Id).Select(c => c.Id).FirstAsync();
|
||||
}
|
||||
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var repo = scope.ServiceProvider.GetRequiredService<IPackRepository>();
|
||||
await repo.GrantCardsToViewer(viewerId, new[] { sampleCardId, sampleCardId });
|
||||
}
|
||||
|
||||
using (var scope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var v = await db.Viewers.Include(x => x.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(x => x.Id == viewerId);
|
||||
var owned = v.Cards.Single(c => c.Card.Id == sampleCardId);
|
||||
Assert.That(owned.Count, Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ public class CardAcquisitionServiceTests
|
||||
viewer.Cards.Add(new OwnedCardEntry { Card = card, Count = count, IsProtected = false });
|
||||
}
|
||||
// Pre-seed bare Cards rows (no ownership) for any cardIds the test plans to grant via
|
||||
// the service. GrantCardsToViewer does a FirstAsync(c => c.Id == grpKey) lookup; without
|
||||
// these the production code throws "Sequence contains no elements".
|
||||
// the service. RewardGrantService.ApplyAsync does FirstOrDefaultAsync on _db.Cards;
|
||||
// without the row the grant throws InvalidOperationException("Card {id} not in catalog").
|
||||
if (grantableCardIds is not null)
|
||||
{
|
||||
foreach (var cardId in grantableCardIds)
|
||||
@@ -71,7 +71,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_NewBronzeCard_GrantsCardOnly()
|
||||
public async Task GrantManyAsync_NewBronzeCard_GrantsCardOnly()
|
||||
{
|
||||
// 101111010 is a synthetic test card (inserted ad-hoc via grantableCardIds) with no
|
||||
// CardCosmeticReward associations. Expectation: grant returns only the type=5 entry.
|
||||
@@ -79,7 +79,7 @@ public class CardAcquisitionServiceTests
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 101111010L });
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 101111010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 101111010L });
|
||||
|
||||
Assert.That(result.RewardList, Has.Count.EqualTo(1));
|
||||
Assert.That(result.RewardList[0].RewardType, Is.EqualTo(5)); // Card
|
||||
@@ -88,7 +88,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_LeaderCard_GrantsCardAndSkin()
|
||||
public async Task GrantManyAsync_LeaderCard_GrantsCardAndSkin()
|
||||
{
|
||||
// Card 704741010 (Aria leader-card variant) has 3 cosmetic associations in the seed:
|
||||
// skin 407, sleeve 704741010, emblem 704741010.
|
||||
@@ -116,7 +116,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 704741010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 704741010L });
|
||||
|
||||
var skinEntry = result.RewardList.SingleOrDefault(r => r.RewardType == 10);
|
||||
Assert.That(skinEntry, Is.Not.Null, "expected a Skin reward entry");
|
||||
@@ -134,7 +134,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_AlreadyOwnedSkin_OmitsFromRewardList()
|
||||
public async Task GrantManyAsync_AlreadyOwnedSkin_OmitsFromRewardList()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 704741010L });
|
||||
@@ -153,7 +153,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 704741010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 704741010L });
|
||||
|
||||
Assert.That(result.RewardList.Any(r => r.RewardType == 10), Is.False,
|
||||
"skin entry should be omitted since viewer already owns it");
|
||||
@@ -162,7 +162,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_FoilLeaderCard_ResolvesToNonFoilCosmetics()
|
||||
public async Task GrantManyAsync_FoilLeaderCard_ResolvesToNonFoilCosmetics()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 704741011L });
|
||||
@@ -181,7 +181,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 704741011L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 704741011L });
|
||||
|
||||
var skinEntry = result.RewardList.SingleOrDefault(r => r.RewardType == 10);
|
||||
Assert.That(skinEntry, Is.Not.Null, "expected skin entry via foil resolution");
|
||||
@@ -195,7 +195,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_MultipleCopiesOfSameLeader_GrantsCosmeticOnce()
|
||||
public async Task GrantManyAsync_MultipleCopiesOfSameLeader_GrantsCosmeticOnce()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 704741010L });
|
||||
@@ -210,7 +210,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 704741010L, 704741010L, 704741010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 704741010L, 704741010L, 704741010L });
|
||||
|
||||
Assert.That(result.RewardList.Count(r => r.RewardType == 10), Is.EqualTo(1),
|
||||
"skin should appear exactly once in reward_list");
|
||||
@@ -219,7 +219,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_RecentLeaderCard_GrantsAllFiveCosmeticTypes()
|
||||
public async Task GrantManyAsync_RecentLeaderCard_GrantsAllFiveCosmeticTypes()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 721141010L });
|
||||
@@ -250,7 +250,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 721141010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 721141010L });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
@@ -263,7 +263,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_BackfillMode_DoesNotIncrementCardCount()
|
||||
public async Task BackfillCosmeticsAsync_DoesNotIncrementCardCount()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
// Pre-seed viewer with card 704741010 count=5, no skin
|
||||
@@ -279,7 +279,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, newCardIds: null);
|
||||
var result = await service.BackfillCosmeticsAsync(viewerId);
|
||||
|
||||
using var scope2 = factory.Services.CreateScope();
|
||||
var db2 = scope2.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
@@ -295,7 +295,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_BackfillCalledTwice_SecondCallIsNoOp()
|
||||
public async Task BackfillCosmeticsAsync_CalledTwice_SecondCallIsNoOp()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { [704741010L] = 1 });
|
||||
@@ -310,15 +310,15 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var first = await service.GrantAsync(viewerId, newCardIds: null);
|
||||
var second = await service.GrantAsync(viewerId, newCardIds: null);
|
||||
var first = await service.BackfillCosmeticsAsync(viewerId);
|
||||
var second = await service.BackfillCosmeticsAsync(viewerId);
|
||||
|
||||
Assert.That(first.RewardList, Is.Not.Empty, "first call should grant cosmetics");
|
||||
Assert.That(second.RewardList, Is.Empty, "second call should be a no-op");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_LeaderCardWithMissingMapping_GrantsCardSilently()
|
||||
public async Task GrantManyAsync_LeaderCardWithMissingMapping_GrantsCardSilently()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 701141010L });
|
||||
@@ -326,7 +326,7 @@ public class CardAcquisitionServiceTests
|
||||
// NO CardCosmeticReward rows inserted for this card — simulates the 83 missing-mapping cases.
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 701141010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 701141010L });
|
||||
|
||||
Assert.That(result.RewardList.Any(r => r.RewardType == 5 && r.RewardId == 701141010L), Is.True);
|
||||
Assert.That(result.RewardList.Any(r => r.RewardType == 10), Is.False);
|
||||
@@ -334,7 +334,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GrantAsync_OrphanCosmeticReward_LogsWarningAndSkips()
|
||||
public async Task GrantManyAsync_OrphanCosmeticReward_LogsWarningAndSkips()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
var viewerId = await SeedViewerWithCards(factory, new() { }, grantableCardIds: new[] { 704741010L });
|
||||
@@ -354,7 +354,7 @@ public class CardAcquisitionServiceTests
|
||||
}
|
||||
|
||||
var service = GetService(factory);
|
||||
var result = await service.GrantAsync(viewerId, new[] { 704741010L });
|
||||
var result = await service.GrantManyAsync(viewerId, new[] { 704741010L });
|
||||
|
||||
Assert.That(result.RewardList.Any(r => r.RewardType == 5 && r.RewardId == 704741010L), Is.True);
|
||||
Assert.That(result.RewardList.Any(r => r.RewardType == 10 && r.RewardId == 407L), Is.True,
|
||||
|
||||
@@ -18,23 +18,22 @@ public class RewardGrantServiceTests
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
// Pick an Id above the seeded sleeves.csv range so this test doesn't collide with the
|
||||
// reference-CSV importer SVSimTestFactory runs at host construction.
|
||||
const int testSleeveId = 2_000_000_000;
|
||||
var sleeve = new SleeveEntry { Id = testSleeveId }; // SleeveEntry has no Name field; Id only
|
||||
var sleeve = new SleeveEntry { Id = testSleeveId };
|
||||
ctx.Sleeves.Add(sleeve);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var viewer = await ctx.Viewers.Include(v => v.Sleeves).FirstAsync(v => v.Id == viewerId);
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
|
||||
var entry = svc.Apply(viewer, UserGoodsType.Sleeve, detailId: testSleeveId, num: 1);
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Sleeve, detailId: testSleeveId, num: 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(viewer.Sleeves.Any(s => s.Id == testSleeveId), Is.True);
|
||||
Assert.That(entry.RewardType, Is.EqualTo((int)UserGoodsType.Sleeve));
|
||||
Assert.That(entry.RewardId, Is.EqualTo((long)testSleeveId));
|
||||
Assert.That(entry.RewardNum, Is.EqualTo(1));
|
||||
Assert.That(result[0].RewardType, Is.EqualTo((int)UserGoodsType.Sleeve));
|
||||
Assert.That(result[0].RewardId, Is.EqualTo((long)testSleeveId));
|
||||
Assert.That(result[0].RewardNum, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -50,12 +49,12 @@ public class RewardGrantServiceTests
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
|
||||
// Reward grants 50; final balance becomes 150 and reward_num on the wire is the new total.
|
||||
var entry = svc.Apply(viewer, UserGoodsType.Rupy, detailId: 0, num: 50);
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Rupy, detailId: 0, num: 50);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(viewer.Currency.Rupees, Is.EqualTo(150UL));
|
||||
Assert.That(entry.RewardNum, Is.EqualTo(150));
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].RewardNum, Is.EqualTo(150));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -66,8 +65,6 @@ public class RewardGrantServiceTests
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
// Pick an Id above the seeded leaderskins.csv range so this test doesn't collide with
|
||||
// the reference-CSV importer SVSimTestFactory runs at host construction.
|
||||
const int testSkinId = 9_999_999;
|
||||
ctx.LeaderSkins.Add(new LeaderSkinEntry { Id = testSkinId, Name = "Round1Reward" });
|
||||
await ctx.SaveChangesAsync();
|
||||
@@ -75,25 +72,180 @@ public class RewardGrantServiceTests
|
||||
var viewer = await ctx.Viewers.Include(v => v.LeaderSkins).FirstAsync(v => v.Id == viewerId);
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
|
||||
svc.Apply(viewer, UserGoodsType.Skin, testSkinId, 1);
|
||||
svc.Apply(viewer, UserGoodsType.Skin, testSkinId, 1); // second grant is a no-op on collection size
|
||||
await svc.ApplyAsync(viewer, UserGoodsType.Skin, testSkinId, 1);
|
||||
await svc.ApplyAsync(viewer, UserGoodsType.Skin, testSkinId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(viewer.LeaderSkins.Count(s => s.Id == testSkinId), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Card_reward_throws_NotSupported()
|
||||
public async Task Card_fresh_grant_inserts_owned_entry_and_returns_post_state_count()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
const long testCardId = 999_001_001L;
|
||||
ctx.Cards.Add(new ShadowverseCardEntry { Id = testCardId, Name = "RGSTestCard", Rarity = Rarity.Bronze });
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var viewer = await ctx.Viewers.Include(v => v.Cards).ThenInclude(c => c.Card).FirstAsync(v => v.Id == viewerId);
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Card, testCardId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].RewardType, Is.EqualTo((int)UserGoodsType.Card));
|
||||
Assert.That(result[0].RewardId, Is.EqualTo(testCardId));
|
||||
Assert.That(result[0].RewardNum, Is.EqualTo(1));
|
||||
Assert.That(viewer.Cards.Single(c => c.Card.Id == testCardId).Count, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Card_existing_grant_increments_count()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
const long testCardId = 999_001_002L;
|
||||
var card = new ShadowverseCardEntry { Id = testCardId, Name = "RGSTestCard2", Rarity = Rarity.Bronze };
|
||||
ctx.Cards.Add(card);
|
||||
var viewer = await ctx.Viewers.Include(v => v.Cards).ThenInclude(c => c.Card).FirstAsync(v => v.Id == viewerId);
|
||||
viewer.Cards.Add(new OwnedCardEntry { Card = card, Count = 2, IsProtected = false });
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Card, testCardId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].RewardNum, Is.EqualTo(3));
|
||||
Assert.That(viewer.Cards.Single(c => c.Card.Id == testCardId).Count, Is.EqualTo(3));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Card_with_cascade_rows_emits_card_plus_cosmetics()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
const long testCardId = 999_002_010L;
|
||||
const int testSkinId = 999_002_011;
|
||||
ctx.Cards.Add(new ShadowverseCardEntry { Id = testCardId, Name = "CascadeTestCard", Rarity = Rarity.Gold });
|
||||
ctx.LeaderSkins.Add(new LeaderSkinEntry { Id = testSkinId, Name = "CascadeTestSkin" });
|
||||
ctx.CardCosmeticRewards.Add(new CardCosmeticReward
|
||||
{
|
||||
CardId = testCardId, Type = CosmeticType.Skin, CosmeticId = testSkinId, Quantity = 1,
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var viewer = await ctx.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.Include(v => v.LeaderSkins)
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Card, testCardId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result, Has.Count.EqualTo(2));
|
||||
Assert.That(result.Any(r => r.RewardType == (int)UserGoodsType.Card && r.RewardId == testCardId), Is.True);
|
||||
Assert.That(result.Any(r => r.RewardType == (int)UserGoodsType.Skin && r.RewardId == testSkinId), Is.True);
|
||||
Assert.That(viewer.LeaderSkins.Any(s => s.Id == testSkinId), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Card_cascade_skips_already_owned_cosmetic()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
const long testCardId = 999_002_020L;
|
||||
const int testSkinId = 999_002_021;
|
||||
ctx.Cards.Add(new ShadowverseCardEntry { Id = testCardId, Name = "CascadeOwnedTestCard", Rarity = Rarity.Gold });
|
||||
var skin = new LeaderSkinEntry { Id = testSkinId, Name = "CascadeOwnedTestSkin" };
|
||||
ctx.LeaderSkins.Add(skin);
|
||||
ctx.CardCosmeticRewards.Add(new CardCosmeticReward
|
||||
{
|
||||
CardId = testCardId, Type = CosmeticType.Skin, CosmeticId = testSkinId, Quantity = 1,
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var viewer = await ctx.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.Include(v => v.LeaderSkins)
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
viewer.LeaderSkins.Add(skin);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Card, testCardId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result, Has.Count.EqualTo(1));
|
||||
Assert.That(result[0].RewardType, Is.EqualTo((int)UserGoodsType.Card));
|
||||
Assert.That(result[0].RewardId, Is.EqualTo(testCardId));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task Card_foil_grant_resolves_cascade_to_non_foil_id()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
|
||||
const long nonFoilId = 999_002_030L;
|
||||
const long foilId = 999_002_031L;
|
||||
const int testSkinId = 999_002_032;
|
||||
|
||||
ctx.Cards.Add(new ShadowverseCardEntry { Id = nonFoilId, Name = "FoilCascadeBase", Rarity = Rarity.Gold });
|
||||
ctx.Cards.Add(new ShadowverseCardEntry { Id = foilId, Name = "FoilCascadeFoil", Rarity = Rarity.Gold, IsFoil = true });
|
||||
ctx.LeaderSkins.Add(new LeaderSkinEntry { Id = testSkinId, Name = "FoilCascadeSkin" });
|
||||
ctx.CardCosmeticRewards.Add(new CardCosmeticReward
|
||||
{
|
||||
CardId = nonFoilId, Type = CosmeticType.Skin, CosmeticId = testSkinId, Quantity = 1,
|
||||
});
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
var viewer = await ctx.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.Include(v => v.LeaderSkins)
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
var result = await svc.ApplyAsync(viewer, UserGoodsType.Card, foilId, 1);
|
||||
await ctx.SaveChangesAsync();
|
||||
|
||||
Assert.That(result.Any(r => r.RewardType == (int)UserGoodsType.Card && r.RewardId == foilId), Is.True);
|
||||
Assert.That(result.Any(r => r.RewardType == (int)UserGoodsType.Skin && r.RewardId == testSkinId), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task SpotCard_still_throws_NotSupported()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
long viewerId = await factory.SeedViewerAsync();
|
||||
using var scope = factory.Services.CreateScope();
|
||||
var ctx = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var viewer = await ctx.Viewers.FirstAsync(v => v.Id == viewerId);
|
||||
var svc = scope.ServiceProvider.GetRequiredService<RewardGrantService>();
|
||||
|
||||
Assert.Throws<NotSupportedException>(() =>
|
||||
svc.Apply(viewer, UserGoodsType.Card, 10001001L, 1));
|
||||
Assert.ThrowsAsync<NotSupportedException>(async () =>
|
||||
await svc.ApplyAsync(viewer, UserGoodsType.SpotCard, 1L, 1));
|
||||
Assert.ThrowsAsync<NotSupportedException>(async () =>
|
||||
await svc.ApplyAsync(viewer, UserGoodsType.SpotCardOnlyLatestCardPack, 1L, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public class StoryServiceTests
|
||||
_viewer = new Mock<IViewerStoryProgressRepository>();
|
||||
// Non-reward tests never exercise the DB/reward path; use a stub InMemory context.
|
||||
var db = StoryServiceTestHelpers.NewInMemoryDb(nameof(SetUp));
|
||||
var rewards = new RewardGrantService(db);
|
||||
var rewards = new RewardGrantService(db, NullLogger<RewardGrantService>.Instance);
|
||||
_service = new StoryService(
|
||||
_master.Object, _viewer.Object,
|
||||
rewards: rewards,
|
||||
@@ -394,6 +394,137 @@ public class StoryServiceTests
|
||||
Assert.That(freshViewer.Currency.RedEther, Is.EqualTo(100UL));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FinishAsync_play_shape_first_clear_grants_card_and_cascades_cosmetic()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
|
||||
const long testCardId = 998_001_010L;
|
||||
const int testSkinId = 998_001_011;
|
||||
const int testStoryId = 998_001_500;
|
||||
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.Cards.Add(new ShadowverseCardEntry { Id = testCardId, Name = "StoryCascadeCard", Rarity = SVSim.Database.Enums.Rarity.Gold });
|
||||
db.LeaderSkins.Add(new LeaderSkinEntry { Id = testSkinId, Name = "StoryCascadeSkin" });
|
||||
db.CardCosmeticRewards.Add(new CardCosmeticReward
|
||||
{
|
||||
CardId = testCardId,
|
||||
Type = SVSim.Database.Enums.CosmeticType.Skin,
|
||||
CosmeticId = testSkinId,
|
||||
Quantity = 1,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var svc = NewServiceWithSeededViewer(factory, out var scope, out var viewerId);
|
||||
using (scope)
|
||||
{
|
||||
var chapter = Ch(testStoryId, 1, 2, "1", "2");
|
||||
chapter.Rewards.Add(new StoryChapterReward
|
||||
{
|
||||
RewardType = 5, // UserGoodsType.Card
|
||||
RewardDetailId = testCardId,
|
||||
RewardNumber = 1,
|
||||
});
|
||||
_master.Setup(m => m.GetChapterByIdAsync(testStoryId)).ReturnsAsync(chapter);
|
||||
_viewer.Setup(v => v.GetProgressForChaptersAsync(viewerId, It.IsAny<IEnumerable<int>>()))
|
||||
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
||||
|
||||
var req = new FinishRequest { StoryId = testStoryId, IsFinish = 1, ClassId = 2 };
|
||||
var resp = await svc.FinishAsync(StoryApiType.Main, req, viewerId: viewerId);
|
||||
|
||||
// reward_list (post-state) gets BOTH the Card entry AND the cascaded Skin entry.
|
||||
Assert.That(resp.RewardList.Any(r => r.RewardType == "5" && r.RewardId == testCardId.ToString()), Is.True,
|
||||
"card reward should appear in reward_list");
|
||||
Assert.That(resp.RewardList.Any(r => r.RewardType == "10" && r.RewardId == testSkinId.ToString()), Is.True,
|
||||
"cascade skin should appear in reward_list");
|
||||
|
||||
// story_reward_list (deltas) only carries the top-level chapter reward.
|
||||
Assert.That(resp.StoryRewardList.Count(r => r.RewardType == "5"), Is.EqualTo(1));
|
||||
Assert.That(resp.StoryRewardList.Any(r => r.RewardType == "10"), Is.False,
|
||||
"cascade cosmetics should not appear in story_reward_list deltas");
|
||||
}
|
||||
|
||||
// Verify viewer ownership was persisted.
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var verifyDb = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var viewer = await verifyDb.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.Include(v => v.LeaderSkins)
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
Assert.That(viewer.Cards.Any(c => c.Card.Id == testCardId), Is.True);
|
||||
Assert.That(viewer.LeaderSkins.Any(s => s.Id == testSkinId), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task FinishAsync_card_grant_for_already_owned_card_increments_not_duplicates()
|
||||
{
|
||||
using var factory = new SVSimTestFactory();
|
||||
|
||||
const long testCardId = 998_002_010L;
|
||||
const int testStoryId = 998_002_500;
|
||||
|
||||
// Pre-seed the card in the catalog AND give the viewer 2 copies of it before the story finish.
|
||||
using (var seedScope = factory.Services.CreateScope())
|
||||
{
|
||||
var db = seedScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
db.Cards.Add(new ShadowverseCardEntry
|
||||
{
|
||||
Id = testCardId,
|
||||
Name = "ExistingOwnedCard",
|
||||
Rarity = SVSim.Database.Enums.Rarity.Silver,
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var svc = NewServiceWithSeededViewer(factory, out var scope, out var viewerId);
|
||||
using (scope)
|
||||
{
|
||||
// Seed 2 owned copies of the card under the same viewer used by NewServiceWithSeededViewer.
|
||||
var scopeDb = scope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var seedViewer = await scopeDb.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
var card = await scopeDb.Cards.FirstAsync(c => c.Id == testCardId);
|
||||
seedViewer.Cards.Add(new OwnedCardEntry { Card = card, Count = 2, IsProtected = false });
|
||||
await scopeDb.SaveChangesAsync();
|
||||
|
||||
// Configure a chapter that grants 1 copy of the same card.
|
||||
var chapter = Ch(testStoryId, 1, 2, "1", "2");
|
||||
chapter.Rewards.Add(new StoryChapterReward
|
||||
{
|
||||
RewardType = 5, // UserGoodsType.Card
|
||||
RewardDetailId = testCardId,
|
||||
RewardNumber = 1,
|
||||
});
|
||||
_master.Setup(m => m.GetChapterByIdAsync(testStoryId)).ReturnsAsync(chapter);
|
||||
_viewer.Setup(v => v.GetProgressForChaptersAsync(viewerId, It.IsAny<IEnumerable<int>>()))
|
||||
.ReturnsAsync(new Dictionary<int, ViewerStoryProgress>());
|
||||
|
||||
var req = new FinishRequest { StoryId = testStoryId, IsFinish = 1, ClassId = 2 };
|
||||
var resp = await svc.FinishAsync(StoryApiType.Main, req, viewerId: viewerId);
|
||||
|
||||
// Post-state count on the wire should be 3 (2 owned + 1 granted).
|
||||
var cardEntry = resp.RewardList.SingleOrDefault(r => r.RewardType == "5" && r.RewardId == testCardId.ToString());
|
||||
Assert.That(cardEntry, Is.Not.Null, "card reward should appear in reward_list");
|
||||
Assert.That(cardEntry!.RewardNum, Is.EqualTo("3"), "post-state count should be incremented, not reset to 1");
|
||||
}
|
||||
|
||||
// Verify the viewer has exactly ONE OwnedCardEntry row for this card, with Count=3.
|
||||
using var verifyScope = factory.Services.CreateScope();
|
||||
var verifyDb = verifyScope.ServiceProvider.GetRequiredService<SVSimDbContext>();
|
||||
var viewer = await verifyDb.Viewers
|
||||
.Include(v => v.Cards).ThenInclude(c => c.Card)
|
||||
.AsSplitQuery()
|
||||
.FirstAsync(v => v.Id == viewerId);
|
||||
var ownedRows = viewer.Cards.Where(c => c.Card.Id == testCardId).ToList();
|
||||
Assert.That(ownedRows, Has.Count.EqualTo(1), "exactly one OwnedCardEntry row should exist (no duplicates)");
|
||||
Assert.That(ownedRows[0].Count, Is.EqualTo(3));
|
||||
}
|
||||
}
|
||||
|
||||
internal static class StoryServiceTestHelpers
|
||||
|
||||
Reference in New Issue
Block a user