은퇴·사망·통일의 저장 순서와 명예의 전당 및 명장일람 판정을 Ref 흐름에 맞춘다. 유산 행동을 인증된 daemon transaction으로 통합하고 중복 지급·고유 아이템·로그·오류 경계를 회귀 테스트한다.
90 lines
3.4 KiB
TypeScript
90 lines
3.4 KiB
TypeScript
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import type { GamePrisma } from '@sammo-ts/infra';
|
|
|
|
import { persistHallOfFameCandidate, resolveOfficialGameIndex } from '../src/turn/hallOfFamePersistence.js';
|
|
|
|
const candidate = {
|
|
serverId: 'hall-server',
|
|
season: 3,
|
|
scenario: 22,
|
|
generalNo: 20,
|
|
type: 'experience' as const,
|
|
value: 2_000,
|
|
owner: 'same-owner',
|
|
aux: { name: '새장수' },
|
|
};
|
|
|
|
describe('Hall of Fame persistence policy', () => {
|
|
it('preserves an owner record belonging to another general for both higher and lower new values', async () => {
|
|
const update = vi.fn(async () => undefined);
|
|
const prisma = {
|
|
hallOfFame: {
|
|
findMany: vi.fn(async () => [
|
|
{
|
|
id: 1,
|
|
serverId: candidate.serverId,
|
|
season: 3,
|
|
scenario: 22,
|
|
generalNo: 10,
|
|
type: candidate.type,
|
|
value: 1_000,
|
|
owner: candidate.owner,
|
|
aux: { name: '기존장수' },
|
|
},
|
|
]),
|
|
create: vi.fn(async () => undefined),
|
|
update,
|
|
},
|
|
} as unknown as GamePrisma.TransactionClient;
|
|
|
|
await expect(persistHallOfFameCandidate(prisma, candidate)).resolves.toBe('PRESERVED');
|
|
await expect(persistHallOfFameCandidate(prisma, { ...candidate, value: 500 })).resolves.toBe('PRESERVED');
|
|
expect(update).not.toHaveBeenCalled();
|
|
expect(prisma.hallOfFame.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('updates only value and aux for a higher same-general record, and preserves a lower value', async () => {
|
|
const existing = {
|
|
id: 2,
|
|
serverId: candidate.serverId,
|
|
season: 3,
|
|
scenario: 22,
|
|
generalNo: candidate.generalNo,
|
|
type: candidate.type,
|
|
value: 1_500,
|
|
owner: 'old-owner',
|
|
aux: { name: '기존장수' },
|
|
};
|
|
const update = vi.fn(async () => undefined);
|
|
const prisma = {
|
|
hallOfFame: {
|
|
findMany: vi.fn(async () => [existing]),
|
|
create: vi.fn(async () => undefined),
|
|
update,
|
|
},
|
|
} as unknown as GamePrisma.TransactionClient;
|
|
|
|
await expect(persistHallOfFameCandidate(prisma, candidate)).resolves.toBe('UPDATED');
|
|
expect(update).toHaveBeenCalledWith({
|
|
where: { id: existing.id },
|
|
data: { value: candidate.value, aux: candidate.aux },
|
|
});
|
|
|
|
update.mockClear();
|
|
await expect(persistHallOfFameCandidate(prisma, { ...candidate, value: 1_000 })).resolves.toBe('PRESERVED');
|
|
expect(update).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('uses persisted gameIdx and reconstructs fallback from COMPLETED games only', async () => {
|
|
const count = vi.fn(async () => 4);
|
|
const prisma = { gameHistory: { count } } as unknown as GamePrisma.TransactionClient;
|
|
|
|
await expect(resolveOfficialGameIndex(prisma, { gameIdx: 0 })).resolves.toBe(0);
|
|
expect(count).not.toHaveBeenCalled();
|
|
|
|
await expect(resolveOfficialGameIndex(prisma, { firstGameIdx: 0 })).resolves.toBe(4);
|
|
expect(count).toHaveBeenCalledWith({ where: { status: 'COMPLETED' } });
|
|
});
|
|
});
|