레거시 emperior 스키마와 동일하게 l12pic부터 l5pic까지 64자를 허용한다. 32자를 넘는 사용자 아이콘 경로를 포함한 통일 원자 트랜잭션 회귀 테스트와 릴리스 마이그레이션 head를 갱신한다.
74 lines
3.2 KiB
TypeScript
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: '20260820000000_widen_emperor_officer_pictures',
|
|
});
|
|
});
|
|
|
|
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,
|
|
});
|
|
});
|
|
});
|