Candidate527b9a1e6(gander/row196-r6-prd19, basef48725cfc), the 39 manifest paths of CANDIDATE.sha256 537d30aef70b6cd1 atec1e7fad8. Duckworth approved round 1 outside B1/B2 (rev 2973, comment 30145) and round 2 merge-scoped (rev 2986, comment 30156). Gate gander-0066 on grant sage-2145: 2334 pass, 0 fail, 1 skip; mutants 38/38 killed. BUILD-LOG lines and Duckworth's C1 DEFERRED entry follow as a doc-only append.
88 lines
6.6 KiB
JavaScript
88 lines
6.6 KiB
JavaScript
import { leasePort } from '../../../scripts/testing/lane-fixtures.mjs';
|
|
import { after as laneAfter } from 'node:test';
|
|
import { testerRequest } from '../../cli/src/tester-http.mjs';
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { spawn, spawnSync } from 'node:child_process';
|
|
import { once } from 'node:events';
|
|
import { createServer } from 'node:net';
|
|
import { request } from 'node:http';
|
|
import { testerFixture } from './tester-fixture.mjs';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, chmodSync, symlinkSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
const CLI = new URL('../src/cli.mjs', import.meta.url).pathname;
|
|
const VALUE = 'fixture_proxy_proof_0123456789abcdef_0123456789';
|
|
const PREVIEW = ['--public-host', 'console.example', '--auth-header', 'x-user', '--allow-user', 'jason'];
|
|
function fixture(t) {
|
|
const root = mkdtempSync(join(tmpdir(), 'proof-cli-')); t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
const file = join(root, 'fixture.token'); writeFileSync(file, VALUE + '\n', { mode: 0o600 });
|
|
return { root, file, env: { ...process.env, MOSAIC_CONFIG: join(root, 'missing-config.json') } };
|
|
}
|
|
test('proof startup refuses partial flags non-preview and invalid fixture file metadata or bytes', t => {
|
|
const f = fixture(t), proof = ['--proof-header', 'x-console-proof', '--proof-file', f.file];
|
|
const sets = [[...PREVIEW, '--proof-header', 'x-console-proof'], [...PREVIEW, '--proof-file', f.file], proof];
|
|
const missing = join(f.root, 'missing'), dir = join(f.root, 'directory'), link = join(f.root, 'link');
|
|
mkdirSync(dir); symlinkSync(f.file, link);
|
|
for (const file of [missing, dir, link]) sets.push([...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file]);
|
|
const cases = [
|
|
...sets.map(flags => ({ flags })),
|
|
...[['mode', VALUE, 0o644], ['short', 'short_fixture', 0o600], ['newline', VALUE.slice(0, 20) + '\n' + VALUE.slice(20), 0o600], ['bad', VALUE + '!', 0o600], ['two-final-lines', VALUE + '\n\n', 0o600]].map(([name, value, mode]) => {
|
|
const file = join(f.root, name); writeFileSync(file, value, { mode }); chmodSync(file, mode); // umask-proof (row 243)
|
|
return { flags: [...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file], value };
|
|
}),
|
|
];
|
|
// Simulate another uid via the fixture child's fstat; never chown a host file.
|
|
const hook = join(f.root, 'other-owner.mjs');
|
|
writeFileSync(hook, `import fs from 'node:fs';import {syncBuiltinESMExports} from 'node:module';const original=fs.fstatSync;fs.fstatSync=(...args)=>{const s=original(...args);s.uid=process.getuid()+1;return s;};syncBuiltinESMExports();`);
|
|
cases.push({ flags: [...PREVIEW, ...proof], env: { ...f.env, NODE_OPTIONS: `--import=${hook}` } });
|
|
for (const c of cases) {
|
|
const r = spawnSync(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', ...c.flags], { env: c.env ?? f.env, encoding: 'utf8', timeout: 5000 });
|
|
assert.equal(r.status, 2, 'invalid proof startup must refuse'); assert.match(r.stderr, /proof/i);
|
|
assert.ok(!r.stderr.includes(VALUE)); assert.ok(!r.stdout.includes(VALUE));
|
|
if (c.value) assert.ok(!r.stderr.includes(c.value));
|
|
}
|
|
});
|
|
test('serve argv carries only fixture proof path and header; startup explicitly reports proof on or off', async t => {
|
|
const f = fixture(t);
|
|
for (const on of [true, false]) {
|
|
const child = spawn(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', ...PREVIEW, ...(on ? ['--proof-header', 'x-console-proof', '--proof-file', f.file] : [])], { env: f.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let out = '', err = ''; child.stdout.on('data', c => out += c); child.stderr.on('data', c => err += c);
|
|
t.after(async () => { if (child.exitCode === null && child.signalCode === null) { child.kill('SIGTERM'); await once(child, 'close'); } });
|
|
await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(Error('fixture CLI startup timeout')), 5000);
|
|
const check = () => { if (out.includes('Ctrl-C')) { clearTimeout(timer); resolve(); } };
|
|
child.stdout.on('data', check); child.once('exit', () => { clearTimeout(timer); reject(Error('fixture CLI exited before startup')); }); check();
|
|
});
|
|
assert.match(out, new RegExp(`proxy proof ${on ? 'on' : 'off'}`));
|
|
const cmdline = readFileSync(`/proc/${child.pid}/cmdline`, 'utf8');
|
|
assert.ok(!cmdline.includes(VALUE)); assert.ok(!out.includes(VALUE)); assert.ok(!err.includes(VALUE));
|
|
if (on) { assert.ok(cmdline.includes(f.file)); assert.ok(cmdline.includes('--proof-header')); }
|
|
child.kill('SIGTERM'); await once(child, 'close');
|
|
}
|
|
});
|
|
|
|
test('CLI applies proof on the main listener and rejects that header on its tester listener', async t => {
|
|
const f = await testerFixture(t), file = join(f.root, 'proof.fixture');
|
|
writeFileSync(file, VALUE, { mode: 0o600 });
|
|
await new Promise(r=>f.web.close(r));
|
|
const child = spawn(process.execPath, [CLI, 'serve', '--port', '0', '--board', 'http://127.0.0.1:9', '--tester-socket', '--business', 'acme', ...PREVIEW, '--proof-header', 'x-console-proof', '--proof-file', file], { env: f.f.env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
let output = ''; child.stdout.on('data', c => output += c); child.stderr.resume();
|
|
t.after(async () => { if (child.exitCode === null && child.signalCode === null) { child.kill('SIGTERM'); await once(child, 'close'); } });
|
|
await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(Error('fixture tester startup timeout')), 5000);
|
|
const check = () => { if (output.includes('Ctrl-C')) { clearTimeout(timer); resolve(); } };
|
|
child.stdout.on('data', check); child.once('exit', () => { clearTimeout(timer); reject(Error('fixture tester startup refused')); }); check();
|
|
});
|
|
const port = Number(output.match(/Mosaic Console: http:\/\/127\.0\.0\.1:(\d+)/)[1]);
|
|
const get = (port, headers) => new Promise((resolve, reject) => {
|
|
const q = request({ host: '127.0.0.1', port, path: '/api/config', headers }, r => {
|
|
let text = ''; r.on('data', c => text += c); r.on('end', () => resolve({ status: r.statusCode, text }));
|
|
}); q.on('error', reject); q.end();
|
|
});
|
|
assert.equal((await get(port, { host: 'console.example', 'x-user': 'jason' })).status, 401);
|
|
const good = await get(port, { host: 'console.example', 'x-user': 'jason', 'x-console-proof': VALUE });
|
|
assert.equal(good.status, 200); assert.ok(!good.text.includes(VALUE));
|
|
assert.equal((await testerRequest({dataRoot:f.f.dataRoot,path:'/api/config',headers:{'x-console-proof':VALUE}})).status, 403);
|
|
});
|