Files
core2026/app/game-engine/test/hallOfFamePersistence.test.ts
T
Hide_D 4fc20de8d4 fix: 은퇴 명예 기록과 유산 수명주기 정합성을 복원한다
은퇴·사망·통일의 저장 순서와 명예의 전당 및 명장일람 판정을 Ref 흐름에 맞춘다.

유산 행동을 인증된 daemon transaction으로 통합하고 중복 지급·고유 아이템·로그·오류 경계를 회귀 테스트한다.
2026-08-24 03:38:12 +00:00

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' } });
});
});