6.3 KiB
PM2-Orchestrated E2E Test (Gateway Orchestrator)
This document describes how to run a full end-to-end test using the gateway
orchestrator (PM2) so that game-api and game-engine run as real processes
and communicate over Redis.
Goal
Validate the full process boundary:
- gateway orchestrator starts/stops
game-apiandturn-daemonvia PM2 game-apitalks to the daemon over Redis streams- daemon publishes realtime events over Redis pub/sub
- optional mutation commands flow from API to engine and back
Where to Implement
Use the existing integration test package and add a PM2-backed test.
- Test file:
tools/integration-tests/test/orchestrator.e2e.test.ts - Test helpers:
tools/integration-tests/src/orchestratorHarness.ts - Reuse existing integration setup in
docs/integration-tests.md
Core integration points:
- Orchestrator entrypoint:
app/gateway-api/src/orchestrator/orchestratorServer.ts - PM2 manager:
app/gateway-api/src/orchestrator/pm2ProcessManager.ts - Process names:
app/gateway-api/src/orchestrator/gatewayOrchestrator.ts:244 - Redis stream keys:
app/game-api/src/daemon/streamKeys.ts,app/game-engine/src/lifecycle/redisCommandStream.ts - Turn daemon status endpoint:
app/game-api/src/router/turnDaemon/index.ts
Prerequisites
- PM2 is installed and available on PATH.
pnpm --filter @sammo-ts/game-api buildpnpm --filter @sammo-ts/game-engine buildpnpm --filter @sammo-ts/gateway-api build(to run orchestrator in dist)- Postgres and Redis are running (use a dedicated DB/schema and Redis DB).
Minimum environment variables for the test:
DATABASE_URLorPOSTGRES_*(bothgame-apiandgame-enginerely on this)GATEWAY_DB_SCHEMA(gateway profile DB schema)REDIS_URLGAME_TOKEN_SECRETGATEWAY_REDIS_PREFIX(optional but recommended to isolate keys)GATEWAY_WORKSPACE_ROOT(workspace root with builtdist/)GATEWAY_WORKTREE_ROOT(worktree root for builds; can be temp)GATEWAY_TRPC_PATH(e.g./gateway/api/trpc)GAME_TRPC_PATH(e.g./che/api/trpc)GAME_API_EVENTS_PATH(e.g./che/api/events)
For profile alignment:
PROFILEandSCENARIOfor game server processesTURN_PROFILE_NAMEshould match${PROFILE}:${SCENARIO}
Procedure
-
Reset state
- Truncate the game schema and gateway schema.
- Flush Redis (or use a dedicated Redis DB index).
-
Seed the game DB
- Use
seedProfileDatabaseto seed a scenario and create an admin general. - Use a fixed
INTEGRATION_WORLD_SEEDto keep deterministic RNG.
- Use
-
Create/Update the gateway profile record
- Upsert a profile with status
RUNNING(orPREOPEN) and a knownapiPort. - Ensure
profileNameis${profile}:${scenario}.
- Upsert a profile with status
-
Start the orchestrator
- Run
app/gateway-api/dist/index.jswithGATEWAY_ROLE=orchestrator. - Or instantiate
GatewayOrchestratordirectly and callstart(). - Wait for PM2 to report the processes as
online.
- Run
-
Verify API <-> daemon
- Exchange a gateway session carrying
superuser,admin, oradmin.profiles.manage[:<profile>]for a game access token. - Call
turnDaemon.statuswith that administrator token and expect a non-null status. - Send a mutation command that expects a result (e.g.,
troop.join) and verifycommandResultis received.
- Exchange a gateway session carrying
-
Verify realtime events
- Trigger a run via
turnDaemon.runwith the administrator token. - Subscribe to
sammo:${profileName}:realtime:eventsand wait forturnCompleted.
- Trigger a run via
-
Cleanup
- Stop orchestrator, then
pm2 deletethe processes by name:sammo:${profileName}:game-apiandsammo:${profileName}:turn-daemon. - Flush Redis and drop the test schemas.
- Stop orchestrator, then
What to Check
Mandatory checks:
- PM2 reports both processes as online.
- Authenticated profile administration is required for every
turnDaemon.run/pause/resume/statuscall. turnDaemon.statusresponds within the timeout for the administrator token.- A command with
commandResultreturns a success response. - A
turnCompletedrealtime event is observed after aruncommand.
Optional checks:
worldState.lastTurnTimeadvances after a run.- A mutation command changes DB state (e.g., troop join/exit).
Notes
turnDaemon.runis fire-and-forget; rely on status or realtime events for confirmation.- PM2 is global; use unique
profileNameper test run to avoid collisions. - Orchestrator starts binaries from
dist/underGATEWAY_WORKSPACE_ROOT. If build artifacts are missing, PM2 will start and immediately exit.
HWE GUI lifecycle
app/gateway-frontend/e2e/hwe-lifecycle.spec.ts verifies the operational
browser flow with one administrator and two independent user contexts:
- The administrator logs in, selects
hwe:2, loads scenario 2 from a fixed commit, requests a reset, and waits for that exact operation to succeed. - The administrator returns to the gateway main page and sees the open HWE action.
- Each user logs in with a separate Chromium context, sees the same open HWE row, creates a general, and reaches the HWE main dashboard without an error.
The test uses an ignored secret directory. It reads admin_password,
user_a_password, and user_b_password from
SAMMO_LIFECYCLE_SECRET_ROOT; passwords must not be passed on the command
line. SAMMO_LIFECYCLE_SOURCE_COMMIT must be a full commit SHA.
Build the gateway frontend with the public prefix contract before starting its preview server:
VITE_APP_BASE_PATH=/gateway \
VITE_GATEWAY_API_URL=/gateway/api/trpc \
VITE_GAME_API_URL_TEMPLATE='/{profile}/api/trpc' \
VITE_GAME_WEB_URL_TEMPLATE='/{profile}/' \
pnpm --filter @sammo-ts/gateway-frontend build
Build the HWE frontend with VITE_APP_BASE_PATH=/hwe,
VITE_GAME_API_URL=/hwe/api/trpc, and
VITE_GAME_SSE_URL=/hwe/api/events. Start the isolated gateway API,
orchestrator, both previews, Postgres, and Redis, then run:
SAMMO_LIFECYCLE_SECRET_ROOT=/path/to/ignored/secrets \
SAMMO_LIFECYCLE_SOURCE_COMMIT="$(git rev-parse HEAD)" \
pnpm --filter @sammo-ts/gateway-frontend test:e2e:hwe-lifecycle
The Playwright web server is a local prefix-preserving proxy on port 15140.
It mirrors the Caddy route contract while keeping all navigation on one
origin. Passing the test means the actual reset/build/seed/PM2 process path and
both user creation flows completed; it is not a mocked API test.