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