feat(gateway): 프로필별 첫 기수 번호를 관리한다
첫 기수 번호를 완료 게임 수의 기준값으로 적용하고 0기를 API와 화면에서 보존한다. 시즌 번호와 독립된 RESET 계약을 관리 패널, 테스트, 운영 문서에 반영한다.
This commit is contained in:
@@ -51,6 +51,7 @@ export interface ScenarioInstallOptions {
|
||||
autorunUser?: ScenarioAutorunOptions | null;
|
||||
preopenAt?: Date | null;
|
||||
season?: number;
|
||||
firstGameIdx?: number;
|
||||
serverId?: string;
|
||||
installOperationId?: string;
|
||||
installCommitSha?: string;
|
||||
@@ -298,6 +299,12 @@ export const seedScenarioToDatabase = async (options: ScenarioSeedOptions): Prom
|
||||
lastTurnTime: formatDateTime(now),
|
||||
};
|
||||
|
||||
const firstGameIdx =
|
||||
typeof install?.firstGameIdx === 'number' && Number.isFinite(install.firstGameIdx) && install.firstGameIdx >= 0
|
||||
? Math.floor(install.firstGameIdx)
|
||||
: 1;
|
||||
worldMeta.firstGameIdx = firstGameIdx;
|
||||
|
||||
if (typeof install?.season === 'number' && Number.isFinite(install.season)) {
|
||||
worldMeta.season = Math.floor(install.season);
|
||||
}
|
||||
@@ -390,7 +397,7 @@ export const seedScenarioToDatabase = async (options: ScenarioSeedOptions): Prom
|
||||
// Ref fixes server_cnt once during ResetHelper initialization. Keep the
|
||||
// frequently rendered game index in the same persisted read model and
|
||||
// exclude abandoned or unfinished rows from the official sequence.
|
||||
worldMeta.gameIdx = completedGameCount + 1;
|
||||
worldMeta.gameIdx = completedGameCount + firstGameIdx;
|
||||
const archivedWorldMeta = { ...worldMeta };
|
||||
delete archivedWorldMeta.hiddenSeed;
|
||||
|
||||
|
||||
@@ -171,22 +171,33 @@ describeDb('scenario database seed', () => {
|
||||
}
|
||||
});
|
||||
|
||||
test('persists the next official game index without counting cancelled or unfinished games', async () => {
|
||||
test('adds the configured first game index without counting cancelled or unfinished games', async () => {
|
||||
const marker = `scenario-seeder-game-index-${Date.now()}`;
|
||||
const connector = createGamePostgresConnector({ url: databaseUrl });
|
||||
await connector.connect();
|
||||
try {
|
||||
const completedBefore = await connector.prisma.gameHistory.count({ where: { status: 'COMPLETED' } });
|
||||
expect(completedBefore).toBe(0);
|
||||
|
||||
await seedScenarioToDatabase({
|
||||
scenarioId: 1010,
|
||||
databaseUrl,
|
||||
installOptions: { serverId: `${marker}-zero`, firstGameIdx: 0 },
|
||||
});
|
||||
const zeroWorldState = await connector.prisma.worldState.findFirstOrThrow();
|
||||
expect(zeroWorldState.meta).toMatchObject({ firstGameIdx: 0, gameIdx: 0 });
|
||||
const zeroBasedHistory = await connector.prisma.gameHistory.findUniqueOrThrow({
|
||||
where: { serverId: `${marker}-zero` },
|
||||
});
|
||||
expect(zeroBasedHistory).toMatchObject({ status: 'OPEN' });
|
||||
expect(zeroBasedHistory.env).toMatchObject({ meta: { firstGameIdx: 0, gameIdx: 0 } });
|
||||
await connector.prisma.gameHistory.update({
|
||||
where: { serverId: `${marker}-zero` },
|
||||
data: { status: 'COMPLETED' },
|
||||
});
|
||||
|
||||
await connector.prisma.gameHistory.createMany({
|
||||
data: [
|
||||
{
|
||||
serverId: `${marker}-completed`,
|
||||
date: new Date('2026-08-01T00:00:00.000Z'),
|
||||
season: 1,
|
||||
scenario: 1010,
|
||||
scenarioName: '정상 종료 fixture',
|
||||
status: 'COMPLETED',
|
||||
},
|
||||
{
|
||||
serverId: `${marker}-abandoned`,
|
||||
date: new Date('2026-08-02T00:00:00.000Z'),
|
||||
@@ -209,14 +220,26 @@ describeDb('scenario database seed', () => {
|
||||
await seedScenarioToDatabase({
|
||||
scenarioId: 1010,
|
||||
databaseUrl,
|
||||
installOptions: { serverId: marker },
|
||||
installOptions: { serverId: `${marker}-one`, firstGameIdx: 0 },
|
||||
});
|
||||
|
||||
const worldState = await connector.prisma.worldState.findFirstOrThrow();
|
||||
expect(worldState.meta).toMatchObject({ gameIdx: completedBefore + 2 });
|
||||
await expect(
|
||||
connector.prisma.gameHistory.findUniqueOrThrow({ where: { serverId: marker } })
|
||||
).resolves.toMatchObject({ status: 'OPEN' });
|
||||
expect(worldState.meta).toMatchObject({ firstGameIdx: 0, gameIdx: completedBefore + 1 });
|
||||
const oneBasedHistory = await connector.prisma.gameHistory.findUniqueOrThrow({
|
||||
where: { serverId: `${marker}-one` },
|
||||
});
|
||||
expect(oneBasedHistory).toMatchObject({ status: 'OPEN' });
|
||||
expect(oneBasedHistory.env).toMatchObject({
|
||||
meta: { firstGameIdx: 0, gameIdx: completedBefore + 1 },
|
||||
});
|
||||
|
||||
await seedScenarioToDatabase({
|
||||
scenarioId: 1010,
|
||||
databaseUrl,
|
||||
installOptions: { serverId: `${marker}-default` },
|
||||
});
|
||||
const defaultWorldState = await connector.prisma.worldState.findFirstOrThrow();
|
||||
expect(defaultWorldState.meta).toMatchObject({ firstGameIdx: 1, gameIdx: completedBefore + 2 });
|
||||
} finally {
|
||||
await connector.prisma.gameHistory.deleteMany({ where: { serverId: { startsWith: marker } } });
|
||||
await connector.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user