fix: make cutover restart argv upgrade-safe and tests platform-honest
- cutover-orchestrator: restart via 'node --import tsx' (matching install.sh) instead of a hardcoded .pnpm/tsx@4.22.4 loader path that breaks on tsx bumps - production-gateway: use process.execPath for the legacy-start fixture; POSIX-only 0600 mode assertion now win32-guarded - cli-runtime + installer suites: skip where they require POSIX-only process-group SIGTERM, lsof, or /bin/sh - fixture-safety: fall back to config.example.json when config.json is absent on a fresh clone
This commit is contained in:
@@ -131,7 +131,9 @@ afterEach(async () => {
|
||||
await Promise.all(cleanup.splice(0).map((fn) => fn()));
|
||||
});
|
||||
|
||||
describe.sequential('executable package runtimes', () => {
|
||||
// The runtime lifecycle under test is macOS/POSIX-only: process-group SIGTERM,
|
||||
// lsof port inspection, and corepack as a direct spawn target.
|
||||
describe.sequential('executable package runtimes', { skip: process.platform === 'win32' }, () => {
|
||||
it('makes the exact production gateway executable attempt 8788 only with acknowledgement', async () => {
|
||||
await expectPortFree(8788);
|
||||
const fixture = await listenOn(8788);
|
||||
|
||||
@@ -23,7 +23,7 @@ const STATE_FILE = 'cutover-state.json';
|
||||
const GATEWAY_ARGV = [
|
||||
process.execPath,
|
||||
'--import',
|
||||
resolve(process.cwd(), 'node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/dist/loader.mjs'),
|
||||
'tsx',
|
||||
resolve(process.cwd(), 'apps/api/src/production-gateway-cli.ts'),
|
||||
] as const;
|
||||
const DIGEST = /^[a-f0-9]{64}$/u;
|
||||
|
||||
@@ -45,9 +45,9 @@ async function fixture(name = 'cutover'): Promise<CutoverPlan> {
|
||||
drillEvidence: drill,
|
||||
drillEvidenceSha256: createHash('sha256').update(drillContent).digest('hex'),
|
||||
legacyPid: 41,
|
||||
legacyCommand: '/usr/local/bin/node /absolute/legacy/server.js',
|
||||
legacyCommand: `${process.execPath} /absolute/legacy/server.js`,
|
||||
legacyIdentity: 'legacy-start-1',
|
||||
legacyStart: ['/usr/local/bin/node', '/absolute/legacy/server.js'],
|
||||
legacyStart: [process.execPath, '/absolute/legacy/server.js'],
|
||||
stateDir: join(root, 'state'),
|
||||
};
|
||||
}
|
||||
@@ -130,11 +130,12 @@ describe('cutover validation and reversible orchestration', () => {
|
||||
expect(signals).toEqual([41]);
|
||||
expect(starts[0]?.environment?.MULTI_SIMADMIN_GATEWAY_TOKEN).toBe(token);
|
||||
const statePath = join(plan.stateDir, 'cutover-state.json');
|
||||
if (process.platform !== 'win32')
|
||||
expect((await lstat(statePath)).mode & 0o777).toBe(0o600);
|
||||
expect(await readFile(statePath, 'utf8')).not.toContain(token);
|
||||
expect(await rollback(plan, system)).toBe(43);
|
||||
expect(signals).toEqual([41, 42]);
|
||||
expect(starts[1]?.argv).toEqual(['/usr/local/bin/node', '/absolute/legacy/server.js']);
|
||||
expect(starts[1]?.argv).toEqual([process.execPath, '/absolute/legacy/server.js']);
|
||||
});
|
||||
|
||||
it('automatically restarts legacy if the gateway fails to own authenticated 8788', async () => {
|
||||
@@ -146,10 +147,10 @@ describe('cutover validation and reversible orchestration', () => {
|
||||
[
|
||||
process.execPath,
|
||||
'--import',
|
||||
resolve(process.cwd(), 'node_modules/.pnpm/tsx@4.22.4/node_modules/tsx/dist/loader.mjs'),
|
||||
'tsx',
|
||||
resolve(process.cwd(), 'apps/api/src/production-gateway-cli.ts'),
|
||||
],
|
||||
['/usr/local/bin/node', '/absolute/legacy/server.js'],
|
||||
[process.execPath, '/absolute/legacy/server.js'],
|
||||
]);
|
||||
const state = JSON.parse(await readFile(join(plan.stateDir, 'cutover-state.json'), 'utf8')) as {
|
||||
phase: string;
|
||||
|
||||
@@ -221,7 +221,14 @@ test('real fixtures satisfy envelope, registry, leak scan, and manifest integrit
|
||||
const files = (await jsonFiles(fixtureRoot)).filter((x) => !x.includes('/synthetic-errors/'));
|
||||
const srcEntries = await readdir(path.join(root, 'src'));
|
||||
assert.ok(!srcEntries.includes('synthetic-errors'));
|
||||
const config = JSON.parse(await readFile(path.resolve(root, '../../config.json'), 'utf8'));
|
||||
// config.json is gitignored; on a fresh clone scan the committed example config
|
||||
// instead so the leak scan still exercises real instance-shaped data.
|
||||
const configPath = path.resolve(root, '../../config.json');
|
||||
const configText = await readFile(configPath, 'utf8').catch((error) => {
|
||||
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
||||
return readFile(path.resolve(root, '../../config.example.json'), 'utf8');
|
||||
});
|
||||
const config = JSON.parse(configText);
|
||||
const privateValues = stringLeaves(config);
|
||||
const expected = selectReadonlyOperations(upstream58e2204Operations).selected;
|
||||
assert.equal(files.length, expected.length * 2);
|
||||
|
||||
@@ -6,7 +6,7 @@ import { test } from 'node:test';
|
||||
const installer = new URL('../scripts/install.sh', import.meta.url);
|
||||
const source = readFileSync(installer, 'utf8');
|
||||
|
||||
test('one-click installer has safe lifecycle commands and pinned production defaults', () => {
|
||||
test('one-click installer has safe lifecycle commands and pinned production defaults', { skip: process.platform === 'win32' && 'runs /bin/sh; installer targets macOS' }, () => {
|
||||
execFileSync('/bin/sh', ['-n', installer.pathname]);
|
||||
const help = execFileSync('/bin/sh', [installer.pathname, 'help'], { encoding: 'utf8' });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user