Files

49 lines
3.6 KiB
JavaScript

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
const secret = 'sk-test-NOT-A-SECRET-1234';
function scratch(t) { const root = mkdtempSync(join(tmpdir(), 'r4-')); t.after(() => rmSync(root, { recursive: true, force: true })); return root; }
function safe(label, Type, props = {}, omitted = secret) { return error => {
assert.ok(error instanceof Type, error.stack);
for (const [key, value] of Object.entries(props)) assert.equal(error[key], value);
assert.ok(error.message.includes(label), error.message);
assert.doesNotMatch(error.message + error.stack, /sk-test|NOT-A-SECRET|Unexpected token/);
assert.ok(!error.message.includes(omitted), error.message);
assert.equal(error.cause, undefined);
return true;
}; }
import { spawnSync } from 'node:child_process';
import { EXIT } from '../src/runner.mjs';
const runner = join(import.meta.dirname, '../src/runner.mjs');
for (const at of ['session.json', 'stdin', 'policy.json']) test(`runner ${at} failure omits parser bytes and keeps usage exit`, t => {
const root = scratch(t), policy = join(root, 'policy.json');
writeFileSync(join(root, 'session.json'), at === 'session.json' ? secret : JSON.stringify({ bundle: { policy } }));
writeFileSync(policy, at === 'policy.json' ? secret : '{}');
const r = spawnSync(process.execPath, [runner, root], { encoding: 'utf8', input: at === 'stdin' ? secret + '\n' : JSON.stringify({ cap: 'a'.repeat(64) }) + '\n', env: { PATH: process.env.PATH, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR } });
assert.equal(r.status, EXIT.usage, r.stderr);
assert.ok(r.stderr.includes(at), r.stderr);
assert.doesNotMatch(r.stdout + r.stderr, /sk-test|NOT-A-SECRET|Unexpected token/);
});
test('runner keeps a read errno without relaying its message', t => {
const root = scratch(t); mkdirSync(join(root, 'session.json'));
const r = spawnSync(process.execPath, [runner, root], { encoding: 'utf8', input: '', env: { PATH: process.env.PATH, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR } });
assert.equal(r.status, EXIT.usage); assert.match(r.stderr, /session.json/); assert.match(r.stderr, /runner: EISDIR:/);
});
test('runner tools parser keeps SyntaxError without file bytes', t => {
const root = scratch(t), policy = join(root, 'policy.json'), tools = join(root, 'tools.json');
writeFileSync(join(root, 'session.json'), JSON.stringify({ bundle: { policy, tools }, turnEnd: 'exit', harnessEnv: {} }));
writeFileSync(policy, '{"needs":[]}'); writeFileSync(tools, secret);
// Isolate the claim boundary: this test exercises the real post-claim file parser,
// and intentionally never starts a broker, adapter or tool server.
const code = `import { Readable } from 'node:stream';
import { Client } from ${JSON.stringify(new URL('../../bus/src/client.mjs', import.meta.url).href)};
import { main } from ${JSON.stringify(new URL('../src/runner.mjs', import.meta.url).href)};
Client.prototype.call = async () => ({});
try { await main(${JSON.stringify(root)}, { stdin: Readable.from([JSON.stringify({ cap: 'a'.repeat(64) }) + '\\n']), env: {}, log() {} }); }
catch (e) { if (!(e instanceof SyntaxError)) throw e; console.error(e.stack); process.exitCode = 9; }`;
const r = spawnSync(process.execPath, ['--input-type=module', '-e', code], { encoding: 'utf8', env: { PATH: process.env.PATH, HOME: process.env.HOME, TMPDIR: process.env.TMPDIR } });
assert.equal(r.status, 9, r.stderr); assert.match(r.stderr, /tools.json/); assert.doesNotMatch(r.stderr, /sk-test|NOT-A-SECRET|Unexpected token/);
});