Files
stack/packages/cli/tests/model-proxy.test.mjs

41 lines
5.4 KiB
JavaScript

// P1: no real proxy or config. The CLI's test transport captures one fixture key.
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { main } from '../src/cli.mjs';
import { io } from './helpers.mjs';
const code=c=>e=>e.code===c;
const fakeSystem=()=>({dataRoot:join(mkdtempSync(join(tmpdir(),'p1-cli-')),'data')});
test('proxy init agent and tester refuse before prompting or transport',async()=>{for(const args of [['proxy','init','--tester'],['proxy','init']]){const x=io(args.includes('--tester')?{}:{CODEX_THREAD_ID:'fixture'});await assert.rejects(main(args,x,{promptProxyKey:()=>{throw Error('must not prompt');}}),code('human-required'));}});
test('proxy init prompts twice through no-echo seam and sends key only through human transport',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;const prompts=[],calls=[];await main(['proxy','init','--business','demo','--port','18432'],x,{system:fakeSystem(),promptProxyKey:async()=>{prompts.push('hidden');return 'p1-management';},transport:b=>async(verb,args)=>{calls.push({b,verb,args});return {port:18432,binary:'/fixture'};}});assert.equal(prompts.length,2);assert.deepEqual(calls,[{b:'demo',verb:'proxy.init',args:{key:'p1-management',port:18432}}]);assert.equal(x.out.text.includes('p1-management'),false);});
test('proxy-key-mismatch: confirmation refuses without bus call',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;let count=0;await assert.rejects(main(['proxy','init','--business','demo'],x,{system:fakeSystem(),promptProxyKey:async()=>++count===1?'p1-one':'p1-two',transport:()=>()=>{throw Error('must not call');}}),code('proxy-key-mismatch'));});
test('proxy-terminal-required: stdin redirection refuses',async()=>{await assert.rejects(main(['proxy','init'],io(),{system:fakeSystem()}),code('proxy-terminal-required'));});
test('proxy-usage: invalid verb and repeated flags refuse',async()=>{for(const args of [['proxy','unknown'],['proxy','init','--port','18432','--port','18433']])await assert.rejects(main(args,io(),{system:fakeSystem()}),code('proxy-usage'));});
test('proxy-config-invalid: port 8317 refuses before prompting',async()=>await assert.rejects(main(['proxy','init','--port','8317'],io(),{system:fakeSystem()}),code('proxy-config-invalid')));
test('proxy-key-invalid: empty prompt refuses without printing input',async()=>{const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;await assert.rejects(main(['proxy','init','--business','demo'],x,{system:fakeSystem(),promptProxyKey:async()=>'',transport:()=>()=>{throw Error('must not call');}}),code('proxy-key-invalid'));});
test('real no-echo prompt restores terminal state and never writes the key',async()=>{const {promptProxyKey}=await import('../src/model-proxy.mjs');const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;const modes=[];x.stdin.isRaw=false;x.stdin.setRawMode=value=>{modes.push(value);x.stdin.isRaw=value;};const answer=promptProxyKey(x,'Management key: ');x.stdin.write('p1-management\r');assert.equal(await answer,'p1-management');assert.deepEqual(modes,[true,false]);assert.equal(x.out.text,'');assert.equal(x.err.text.includes('p1-management'),false);});
test('real no-echo prompt ctrl-C refuses and restores echo',async()=>{const {promptProxyKey}=await import('../src/model-proxy.mjs');const x=io();x.stdin.isTTY=true;x.stdout.isTTY=true;x.stdin.isRaw=false;x.stdin.setRawMode=value=>{x.stdin.isRaw=value;};const answer=promptProxyKey(x,'Management key: ');x.stdin.write('\u0003');await assert.rejects(answer,code('proxy-key-invalid'));assert.equal(x.stdin.isRaw,false);});
test('proxy fence clear refuses without the explicit restart statement', async () => {
await assert.rejects(main(['proxy', 'fence', 'clear'], io(), { system: fakeSystem() }), code('proxy-restart-required'));
});
test('proxy fence status and flagged clear use the shared state and a loopback management read', async t => {
const fs = await import('node:fs'); const http = await import('node:http');
const { createProxyLock } = await import('../../bus/src/proxy-lock.mjs');
const root = fs.mkdtempSync(join(tmpdir(), 'cli-fence-')); t.after(() => fs.rmSync(root, { recursive: true, force: true }));
const config = join(root, 'config'); fs.mkdirSync(join(config, 'secrets'), { recursive: true, mode: 0o700 });
fs.writeFileSync(join(config, 'secrets', 'proxy-management.key'), 'fixture-management', { mode: 0o600 });
fs.mkdirSync(join(root, 'proxy'), { mode: 0o700 });
const server = http.createServer((req, res) => { assert.equal(req.headers.authorization, 'Bearer fixture-management'); res.end('["fixture-sentinel"]'); });
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); t.after(() => new Promise(resolve => server.close(resolve)));
const port = server.address().port, lock = createProxyLock({ stateDir: join(root, 'proxy'), identity: `${config}:${port}` });
await lock.run(async () => lock.fence({ org: 'alpha', op: 'issue' }));
const x = io({ MOSAIC_CONFIG: join(config, 'config.json') }); const system = { dataRoot: root, proxy: { port } };
await main(['proxy', 'fence', 'status', '--json'], x, { system });
assert.equal(JSON.parse(x.out.text).uncertain.org, 'alpha');
const y = io({ MOSAIC_CONFIG: join(config, 'config.json') });
await main(['proxy', 'fence', 'clear', '--proxy-restarted'], y, { system });
assert.equal(JSON.parse(y.out.text).cleared, true); assert.equal(lock.status(), null);
});