Skip to content

Commit b568160

Browse files
committed
test(platform): prove a misconfigured production zone refuses to start
assertDeployableMode had only a unit test of the function, nothing showing Next stops the server when register() throws. These start the real build as a process and read its exit code and message, with a control that the same command starts.
1 parent a0e67ca commit b568160

5 files changed

Lines changed: 99 additions & 3 deletions

File tree

‎.github/scripts/http-e2e.sh‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,8 +510,8 @@ JS
510510
# The zone as deployed: flag on, /org rewritten to the Platform build. Then, on that running app,
511511
# the zone goes away, answers 503 to everything, and comes back. Then the same build with the flag
512512
# off, which must put everyone back on the legacy surface. Then that build with its flags read from
513-
# AppConfig, the real rollout path, targeting one organization by id. Every phase reports, whichever
514-
# fails.
513+
# AppConfig, the real rollout path, targeting one organization by id. Last, that zone's build
514+
# misconfigured, which must refuse to start. Every phase reports, whichever fails.
515515
platform-live-zone)
516516
platform_live_environment
517517
export SIM_PLATFORM_ORIGIN=http://127.0.0.1:3028
@@ -551,6 +551,7 @@ JS
551551
export PLATFORM_LIVE_APPCONFIG_URL=http://127.0.0.1:3033
552552
start_app platform-live-rollout 3027 'Platform main, per-organization rollout' record-http-status
553553
run_platform_live rollout platform-live-rollout || live_status=$?
554+
run_platform_live zone-boot platform-live-zone-boot || live_status=$?
554555
exit "$live_status"
555556
;;
556557

‎.github/workflows/platform-design.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ jobs:
197197
--report "$RUNNER_TEMP/e2e/platform-live-zone-recovered/report.json"
198198
--report "$RUNNER_TEMP/e2e/platform-live-flag-off/report.json"
199199
--report "$RUNNER_TEMP/e2e/platform-live-rollout/report.json"
200+
--report "$RUNNER_TEMP/e2e/platform-live-zone-boot/report.json"
200201
- name: Hold the parity ledger's flag-off rows to this run
201202
if: always() && matrix.topology == 'flag-off'
202203
run: >-

‎apps/sim/platform/checks/live/playwright.config.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ import { defineConfig } from '@playwright/test'
44
const report = process.env.PLATFORM_LIVE_REPORT_PATH ?? 'test-results/platform-live/report.json'
55

66
/** Each needs its own stack, so each is its own folder and never part of the `zone` run. */
7-
const TOPOLOGIES = ['flag-off', 'zone-down', 'zone-recovered', 'rollout', 'self-hosted'] as const
7+
const TOPOLOGIES = [
8+
'flag-off',
9+
'zone-down',
10+
'zone-recovered',
11+
'rollout',
12+
'self-hosted',
13+
'zone-boot',
14+
] as const
815

916
/**
1017
* Runs against a real main app, Platform zone and database you have already started; it starts no
@@ -17,6 +24,7 @@ const TOPOLOGIES = ['flag-off', 'zone-down', 'zone-recovered', 'rollout', 'self-
1724
* - `zone-recovered`: the flag is on and the zone has just come back.
1825
* - `rollout`: flags read from AppConfig (`support/appconfig-service.ts`), targeting one organization.
1926
* - `self-hosted`: no zone, not on a Sim hostname, billing off.
27+
* - `zone-boot`: starts its own misconfigured zones from the build; run from `apps/sim`.
2028
*/
2129
export default defineConfig({
2230
testDir: __dirname,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { type ChildProcess, spawn } from 'node:child_process'
2+
import { expect, test } from '@playwright/test'
3+
import { omit } from '@sim/utils/object'
4+
5+
/**
6+
* Starts the zone's real production build as its own process, the way a container does, under the
7+
* base path it was built with. `instrumentation.ts` runs `assertDeployableMode`, and Next exits the
8+
* server when that throws. A second zone, because the first must keep serving the suite before this.
9+
*/
10+
const PORT = 3035
11+
const ORIGIN = `http://127.0.0.1:${PORT}`
12+
13+
let running: ChildProcess | undefined
14+
15+
function startZone(env: Record<string, string>) {
16+
const output: string[] = []
17+
const zone = spawn(
18+
'node',
19+
[
20+
'../../node_modules/next/dist/bin/next',
21+
'start',
22+
'platform',
23+
'--hostname',
24+
'127.0.0.1',
25+
'--port',
26+
String(PORT),
27+
],
28+
{
29+
env: {
30+
...omit(process.env, ['SIM_PLATFORM_API_ORIGIN', 'SIM_PLATFORM_FIXTURES', 'NODE_ENV']),
31+
SIM_PLATFORM_BASE_PATH: '/org',
32+
...env,
33+
},
34+
}
35+
)
36+
running = zone
37+
zone.stdout.on('data', (chunk) => output.push(String(chunk)))
38+
zone.stderr.on('data', (chunk) => output.push(String(chunk)))
39+
const exitCode = new Promise<number | null>((resolve) => {
40+
zone.once('exit', (code) => resolve(code))
41+
})
42+
return { exitCode, output }
43+
}
44+
45+
test.afterEach(async () => {
46+
const zone = running
47+
running = undefined
48+
if (!zone || zone.exitCode !== null) return
49+
const exited = new Promise((resolve) => zone.once('exit', resolve))
50+
zone.kill('SIGTERM')
51+
await exited
52+
})
53+
54+
test('a production zone with no API origin refuses to start', async () => {
55+
const { exitCode, output } = startZone({})
56+
expect(await exitCode).toBe(1)
57+
expect(output.join('')).toContain('its data mode is "none"')
58+
})
59+
60+
test('a production zone on fixtures refuses to start', async () => {
61+
const { exitCode, output } = startZone({
62+
SIM_PLATFORM_API_ORIGIN: 'http://127.0.0.1:3027',
63+
SIM_PLATFORM_FIXTURES: '1',
64+
})
65+
expect(await exitCode).toBe(1)
66+
expect(output.join('')).toContain('its data mode is "fixture"')
67+
})
68+
69+
test('a production zone with an API origin starts under the same command', async () => {
70+
const { output } = startZone({ SIM_PLATFORM_API_ORIGIN: 'http://127.0.0.1:3027' })
71+
await expect
72+
.poll(
73+
async () => {
74+
try {
75+
// No route answers this, so the zone renders its own not-found page.
76+
return (await fetch(`${ORIGIN}/org/__availability`)).status
77+
} catch {
78+
return 'not listening yet'
79+
}
80+
},
81+
{ timeout: 60_000 }
82+
)
83+
.toBe(404)
84+
expect(output.join('')).not.toContain('its data mode is')
85+
})

‎apps/sim/platform/docs/org-parity.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,4 @@ The shell lane's rows, one per row of its audit. `shell.spec` is `checks/live/sh
378378
| Per-organization rollout: `platform-org-ui` reaches a targeted organization by id and no other, on the same deployment, through the real AppConfig client against a fixture of its service | `lib/platform/rollout.ts`, `lib/core/config/feature-flags.ts` | Done | "a targeted organization is handed to the Platform", "an organization outside the rollout stays on /o". No real AppConfig deployment has been read |
379379
| A rollback under an open tab: a client navigation keeps working, because the rollout gate lives in a layout that does not re-render, and a reload's 404 offers a way off the zone | `live-shell.tsx`, `lib/data/landing.ts` | Done | "a rollback under an open tab"; `landing.test.ts` for the root's decision. The 404's home button used to loop back to it |
380380
| Self-hosted, no zone, no flag: boots, `/o` is served by the main app, `/org` is absent | `next.config.ts`, `lib/platform/rollout.ts` | Done | "a self-hosted deployment with no zone boots", "a self-hosted organization is served by the main app", "a self-hosted deployment has no /org", in the `live (flag-off)` job |
381+
| A production zone behind a base path with no API origin, or on fixtures, refuses to start | `instrumentation.ts`, `lib/data/mode.ts` | Done | "a production zone with no API origin refuses to start", "a production zone on fixtures refuses to start", "a production zone with an API origin starts", each a real `next start` of the build |

0 commit comments

Comments
 (0)