Merge branch 'main' into feature/general-icon-names

# Conflicts:
#	app/game-frontend/src/views/CurrentCityView.vue
This commit is contained in:
2026-07-26 06:01:04 +00:00
111 changed files with 14430 additions and 2960 deletions
@@ -88,6 +88,8 @@ const createHarness = (
? [
{ name: 'sammo:che:2:game-api', status: 'online' },
{ name: 'sammo:che:2:turn-daemon', status: 'online' },
{ name: 'sammo:che:2:auction-worker', status: 'online' },
{ name: 'sammo:che:2:battle-sim-worker', status: 'online' },
{ name: 'sammo:che:2:tournament-worker', status: 'online' },
]
: [],
@@ -138,7 +140,7 @@ const createHarness = (
};
describe('GatewayOrchestrator first-class operations', () => {
it('starts both profile processes and records success', async () => {
it('starts every profile process and records success', async () => {
const harness = createHarness(buildOperation('START'));
await harness.orchestrator.runOperationsNow();
@@ -147,12 +149,14 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.started.map((definition) => definition.name)).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.completions).toEqual(['SUCCEEDED']);
});
it('stops both profile processes and records success', async () => {
it('stops every profile process and records success', async () => {
const harness = createHarness(buildOperation('STOP'));
await harness.orchestrator.runOperationsNow();
@@ -161,11 +165,15 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.stopped).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.deleted).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.completions).toEqual(['SUCCEEDED']);
@@ -190,6 +198,8 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.deleted).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.completions).toEqual(['SUCCEEDED']);
@@ -203,7 +213,7 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.completions).toEqual(['FAILED']);
});
it('attempts to stop both roles before reporting a partial PM2 failure', async () => {
it('attempts to stop every role before reporting a partial PM2 failure', async () => {
const harness = createHarness(buildOperation('STOP'), false, true);
await harness.orchestrator.runOperationsNow();
@@ -211,11 +221,15 @@ describe('GatewayOrchestrator first-class operations', () => {
expect(harness.stopped).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.deleted).toEqual([
'sammo:che:2:game-api',
'sammo:che:2:turn-daemon',
'sammo:che:2:auction-worker',
'sammo:che:2:battle-sim-worker',
'sammo:che:2:tournament-worker',
]);
expect(harness.completions).toEqual(['FAILED']);
@@ -29,6 +29,8 @@ describe('planProfileReconcile', () => {
planProfileReconcile('RUNNING', {
apiRunning: true,
daemonRunning: false,
auctionRunning: true,
battleSimRunning: true,
tournamentRunning: true,
})
).toEqual({ shouldStart: true, shouldStop: false });
@@ -39,6 +41,8 @@ describe('planProfileReconcile', () => {
planProfileReconcile('PREOPEN', {
apiRunning: false,
daemonRunning: false,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: true, shouldStop: false });
@@ -49,16 +53,32 @@ describe('planProfileReconcile', () => {
planProfileReconcile('RUNNING', {
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', {
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', {
apiRunning: false,
daemonRunning: true,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: false, shouldStop: true });
@@ -69,6 +89,8 @@ describe('planProfileReconcile', () => {
planProfileReconcile('RESERVED', {
apiRunning: false,
daemonRunning: false,
auctionRunning: false,
battleSimRunning: false,
tournamentRunning: false,
})
).toEqual({ shouldStart: false, shouldStop: false });
@@ -96,6 +118,16 @@ describe('buildProcessDefinitions', () => {
});
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'),
@@ -108,6 +140,8 @@ describe('buildProcessDefinitions', () => {
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'));
});
});