补齐金币商店测试覆盖

This commit is contained in:
Wang Zhuoxuan 2026-05-13 17:45:58 +08:00
parent 6bf9db9820
commit 7aa53657fc
3 changed files with 28 additions and 1 deletions

View File

@ -78,7 +78,7 @@
| G3-4 | 实现商店商品和购买接口 | [x] | 商品价格符合设计:提示羽毛 80、爱心补给 150、双倍 XP 250、连胜护盾 400、装扮 800-3000 |
| G3-5 | 实现道具使用接口 | [x] | 爱心补给恢复满心,双倍 XP 药水 15 分钟生效,提示羽毛返回可排除选项,连胜护盾可保护断签 |
| G3-6 | 更新 bootstrap/shop DTO | [x] | 客户端能拿到金币、库存、可购买商品、广告商品、订阅权益 |
| G3-7 | 添加金币/商店测试 | [ ] | 覆盖余额不足、重复购买、使用道具、药水时效、库存扣减和流水记录 |
| G3-7 | 添加金币/商店测试 | [x] | 覆盖余额不足、重复购买、使用道具、药水时效、库存扣减和流水记录 |
验证记录2026-05-13G3-1 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/gamification/coin-service.test.ts src/__tests__/services/learning/challenge-service.test.ts` 仍在启动阶段被 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
验证记录2026-05-13G3-2 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/gamification/chest-service.test.ts src/__tests__/services/gamification/coin-service.test.ts src/__tests__/services/gamification-rules.test.ts` 仍在启动阶段被同一个 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
@ -86,6 +86,7 @@
验证记录2026-05-13G3-4 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/shop/shop-service.test.ts src/__tests__/services/gamification/coin-service.test.ts src/__tests__/services/gamification/inventory-service.test.ts` 仍在启动阶段被同一个 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
验证记录2026-05-13G3-5 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/gamification/item-use-service.test.ts src/__tests__/services/gamification/inventory-service.test.ts` 仍在启动阶段被同一个 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
验证记录2026-05-13G3-6 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/app/bootstrap-service.test.ts src/__tests__/services/gamification/inventory-service.test.ts src/__tests__/services/shop/shop-service.test.ts` 仍在启动阶段被同一个 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
验证记录2026-05-13G3-7 已通过 `./node_modules/.bin/tsc --noEmit`、`./node_modules/.bin/eslint .` 和 `git diff --check`;定向运行 `./node_modules/.bin/vitest run src/__tests__/services/gamification/coin-service.test.ts src/__tests__/services/shop/shop-service.test.ts src/__tests__/services/gamification/inventory-service.test.ts src/__tests__/services/gamification/item-use-service.test.ts` 仍在启动阶段被同一个 `@rolldown/binding-darwin-x64` 原生 binding 未签名问题阻塞。
## Phase G4广告恢复与订阅权益对齐

View File

@ -89,10 +89,13 @@ describe('item-use-service', () => {
clientRequestId: 'use-xp-1',
});
const activeUntilMs = Date.parse(result.effect.activeUntil ?? '');
const expectedMs = Date.now() + 15 * 60 * 1000;
expect(result.itemId).toBe('double_xp_potion');
expect(result.quantityRemaining).toBe(0);
expect(result.effect.type).toBe('double_xp');
expect(result.effect.activeUntil).toEqual(expect.any(String));
expect(Math.abs(activeUntilMs - expectedMs)).toBeLessThan(2_000);
expect(updateSet).toHaveBeenCalledWith(expect.objectContaining({
activeUntil: expect.any(Date),
metadata: {

View File

@ -106,6 +106,29 @@ describe('shop-service', () => {
}));
});
it('does not spend coins or grant inventory twice for duplicate purchases', async () => {
mockSelectQueue([
[{ id: 'coin-tx-1' }], // spendCoins: existing spend transaction
[{ coinsBalance: 220 }],
[{ id: 'item-tx-1' }], // grantInventoryItem: existing item grant
[{ itemId: 'hint_feather', quantity: 1, activeUntil: null, metadata: null }],
]);
const result = await purchaseShopProduct('user-1', 'hint-feather', 'request-1');
expect(result.product.id).toBe('hint-feather');
expect(result.coinsSpent).toBe(0);
expect(result.coinsBalance).toBe(220);
expect(result.item).toEqual({
itemId: 'hint_feather',
quantity: 1,
activeUntil: null,
metadata: null,
});
expect(db.insert).not.toHaveBeenCalled();
expect(db.update).not.toHaveBeenCalled();
});
it('throws when the user does not have enough coins', async () => {
mockSelectQueue([
[], // no existing spend