Files
core2026/app/gateway-api/test/releaseManifest.test.ts
T
Hide_D 92ad49db45 fix: 통일 완료 뒤 턴 실행을 중단
Ref처럼 isunited 2/3에서 예약 스케줄을 멈추고 processor 직접 호출도 no-op 처리한다. 이미 기본값으로 내려간 종료 게임의 refreshLimit만 식별해 복구하는 마이그레이션과 회귀 테스트를 추가한다.
2026-08-20 06:13:28 +00:00

74 lines
3.2 KiB
TypeScript

import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { afterEach, describe, expect, it } from 'vitest';
import { readReleaseManifest, RELEASE_CONTROLLER_PROTOCOL } from '../src/orchestrator/releaseManifest.js';
const temporaryDirectories: string[] = [];
const createWorkspace = async (gatewayHead: string, gameHead: string, controllerProtocol = 1): Promise<string> => {
const workspace = await fs.mkdtemp(path.join(os.tmpdir(), 'sammo-release-manifest-'));
temporaryDirectories.push(workspace);
await fs.mkdir(path.join(workspace, 'packages/infra/prisma/gateway-migrations', gatewayHead), {
recursive: true,
});
await fs.mkdir(path.join(workspace, 'packages/infra/prisma/migrations', gameHead), { recursive: true });
await fs.writeFile(
path.join(workspace, 'release-manifest.json'),
JSON.stringify({
formatVersion: 1,
controllerProtocol,
gatewaySchemaHead: gatewayHead,
gameSchemaHead: gameHead,
components: ['gateway-api', 'gateway-frontend', 'game-api', 'game-engine', 'game-frontend'],
})
);
return workspace;
};
afterEach(async () => {
await Promise.all(temporaryDirectories.splice(0).map((directory) => fs.rm(directory, { recursive: true })));
});
describe('readReleaseManifest', () => {
it('keeps the checked-in manifest aligned with the checked-in migration heads', async () => {
const workspaceRoot = path.resolve(import.meta.dirname, '../../..');
await expect(readReleaseManifest(workspaceRoot)).resolves.toMatchObject({
controllerProtocol: RELEASE_CONTROLLER_PROTOCOL,
gatewaySchemaHead: '20260819000000_backfill_profile_release_source',
gameSchemaHead: '20260820001000_restore_united_turn_halt',
});
});
it('accepts a manifest whose schema heads match the selected workspace', async () => {
const workspace = await createWorkspace('20260801000000_gateway', '20260801000000_game');
await expect(readReleaseManifest(workspace)).resolves.toMatchObject({
gatewaySchemaHead: '20260801000000_gateway',
gameSchemaHead: '20260801000000_game',
});
});
it('rejects a stale schema head before any deployment command runs', async () => {
const workspace = await createWorkspace('20260801000000_gateway', '20260801000000_game');
await fs.mkdir(path.join(workspace, 'packages/infra/prisma/migrations/20260802000000_newer'));
await expect(readReleaseManifest(workspace)).rejects.toThrow('does not match workspace head');
});
it('allows only the explicit controller self-upgrade boundary to cross protocol versions', async () => {
const futureProtocol = RELEASE_CONTROLLER_PROTOCOL + 1;
const workspace = await createWorkspace('20260801000000_gateway', '20260801000000_game', futureProtocol);
await expect(readReleaseManifest(workspace)).rejects.toThrow(
`Release requires controller protocol ${futureProtocol}`
);
await expect(readReleaseManifest(workspace, { allowControllerUpgrade: true })).resolves.toMatchObject({
controllerProtocol: futureProtocol,
});
});
});