Files
core2026/app/gateway-api/test/orchestratorPlan.test.ts

206 lines
8.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import path from 'node:path';
import {
buildProfileMigrationCommand,
buildProcessDefinitions,
buildWorkspaceCommands,
planProfileReconcile,
} from '../src/orchestrator/gatewayOrchestrator.js';
import type { GatewayProfileRecord } from '../src/orchestrator/profileRepository.js';
const buildProfile = (buildWorkspace?: string): GatewayProfileRecord => ({
profileName: 'che:2',
profile: 'che',
scenario: '2',
apiPort: 15003,
status: 'RUNNING',
buildStatus: 'SUCCEEDED',
buildCommitSha: '0123456789abcdef0123456789abcdef01234567',
buildWorkspace,
meta: {},
createdAt: '2026-07-25T00:00:00.000Z',
updatedAt: '2026-07-25T00:00:00.000Z',
});
describe('planProfileReconcile', () => {
it('starts missing processes for running profiles', () => {
expect(
planProfileReconcile('RUNNING', {
frontendRunning: true,
apiRunning: true,
daemonRunning: false,
auctionRunning: true,
battleSimRunning: true,
tournamentRunning: true,
})
).toEqual({ shouldStart: true, shouldStop: false });
});
it('starts processes for preopen profiles', () => {
expect(
planProfileReconcile('PREOPEN', {
frontendRunning: false,
apiRunning: false,
daemonRunning: false,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: true, shouldStop: false });
});
it('does nothing when running profile is healthy', () => {
expect(
planProfileReconcile('RUNNING', {
frontendRunning: true,
apiRunning: true,
daemonRunning: true,
auctionRunning: true,
battleSimRunning: true,
tournamentRunning: true,
})
).toEqual({ shouldStart: false, shouldStop: false });
});
it('restarts a running profile when only the auction worker is missing', () => {
expect(
planProfileReconcile('RUNNING', {
frontendRunning: true,
apiRunning: true,
daemonRunning: true,
auctionRunning: false,
battleSimRunning: true,
tournamentRunning: true,
})
).toEqual({ shouldStart: true, shouldStop: false });
});
it('stops processes for non-running profiles', () => {
expect(
planProfileReconcile('STOPPED', {
frontendRunning: false,
apiRunning: false,
daemonRunning: true,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: false, shouldStop: true });
});
it('keeps reserved profiles off', () => {
expect(
planProfileReconcile('RESERVED', {
frontendRunning: false,
apiRunning: false,
daemonRunning: false,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: false, shouldStop: false });
});
});
describe('buildProcessDefinitions', () => {
const processConfig = {
workspaceRoot: '/srv/sammo/main',
redisKeyPrefix: 'sammo:gateway',
gameTokenSecret: 'test-secret',
gatewayInternalApiUrl: 'http://127.0.0.1:13000',
};
it('runs a built profile from its commit worktree', () => {
const buildWorkspace = '/srv/sammo/worktrees/0123456789abcdef';
const definitions = buildProcessDefinitions(buildProfile(buildWorkspace), processConfig);
expect(definitions.frontend).toMatchObject({
cwd: path.join(buildWorkspace, 'app', 'game-frontend'),
script: path.join(buildWorkspace, 'node_modules', 'vite', 'bin', 'vite.js'),
args: [
'preview',
'--host',
'0.0.0.0',
'--port',
'15002',
'--outDir',
path.join(buildWorkspace, '.release-dist', 'che_2', 'game-frontend'),
],
});
expect(definitions.api.cwd).toBe(path.join(buildWorkspace, 'app', 'game-api'));
expect(definitions.api.script).toBe(path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'));
expect(definitions.api.env).toMatchObject({
GAME_PROFILE_NAME: 'che:2',
GAME_TRPC_PATH: '/che/api/trpc',
GAME_API_EVENTS_PATH: '/che/api/events',
GATEWAY_INTERNAL_API_URL: 'http://127.0.0.1:13000',
GAME_UPLOAD_PATH: '/che/api/uploads',
});
expect(definitions.daemon.cwd).toBe(path.join(buildWorkspace, 'app', 'game-engine'));
expect(definitions.daemon.script).toBe(path.join(buildWorkspace, 'app', 'game-engine', 'dist', 'index.js'));
expect(definitions.auction).toMatchObject({
cwd: path.join(buildWorkspace, 'app', 'game-api'),
script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
env: { GAME_API_ROLE: 'auction-worker' },
});
expect(definitions.battleSim).toMatchObject({
cwd: path.join(buildWorkspace, 'app', 'game-api'),
script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
env: { GAME_API_ROLE: 'battle-sim-worker' },
});
expect(definitions.tournament).toMatchObject({
cwd: path.join(buildWorkspace, 'app', 'game-api'),
script: path.join(buildWorkspace, 'app', 'game-api', 'dist', 'index.js'),
env: { GAME_API_ROLE: 'tournament-worker' },
});
});
it('keeps main as the runtime for profiles without a commit worktree', () => {
const definitions = buildProcessDefinitions(buildProfile(), processConfig);
expect(definitions.frontend.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-frontend'));
expect(definitions.api.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
expect(definitions.daemon.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-engine'));
expect(definitions.auction.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
expect(definitions.battleSim.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
expect(definitions.tournament.cwd).toBe(path.join(processConfig.workspaceRoot, 'app', 'game-api'));
});
});
describe('buildWorkspaceCommands', () => {
it('installs and builds runtime dependencies before the profile processes', () => {
const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
const commands = buildWorkspaceCommands(workspaceRoot, true);
expect(commands.map(({ args }) => args)).toEqual([
['install', '--frozen-lockfile'],
['--filter', '@sammo-ts/common', 'build'],
['--filter', '@sammo-ts/infra', 'prisma:generate'],
['--filter', '@sammo-ts/infra', 'build'],
['--filter', '@sammo-ts/logic', 'build'],
['--filter', '@sammo-ts/game-api', 'build'],
['--filter', '@sammo-ts/game-engine', 'build'],
['--filter', '@sammo-ts/gateway-api', 'build'],
]);
expect(commands.every(({ cwd }) => cwd === workspaceRoot)).toBe(true);
});
it('deploys the game schema migration after building the selected workspace', () => {
const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
const databaseUrl = 'postgresql://integration.invalid/sammo?schema=che';
const command = buildProfileMigrationCommand(workspaceRoot, databaseUrl, { NODE_ENV: 'production' });
expect(command).toEqual({
command: 'pnpm',
args: ['--filter', '@sammo-ts/infra', 'prisma:migrate:deploy:game'],
cwd: workspaceRoot,
env: {
NODE_ENV: 'production',
DATABASE_URL: databaseUrl,
},
});
});
});