56 lines
4.4 KiB
JavaScript
56 lines
4.4 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {fork} from 'node:child_process';
|
|
import {once} from 'node:events';
|
|
import fs from 'node:fs';
|
|
import https from 'node:https';
|
|
import http from 'node:http';
|
|
import tls from 'node:tls';
|
|
import net from 'node:net';
|
|
import {fileURLToPath} from 'node:url';
|
|
const cert=fs.readFileSync(new URL('./fixtures/icap/cert.pem',import.meta.url)),key=fs.readFileSync(new URL('./fixtures/icap/key.pem',import.meta.url));
|
|
const brokerNode=fileURLToPath(new URL('../.runtime/windows/broker/node.exe',import.meta.url));
|
|
function child(t) {
|
|
assert.equal(fs.existsSync(brokerNode),true,'Verified bundled broker Node prerequisite');
|
|
const process=fork(fileURLToPath(new URL('./fixtures/broker-process-child.cjs',import.meta.url)),[],{execPath:brokerNode,execArgv:[],stdio:['ignore','ignore','ignore','ipc'],env:{SystemRoot:globalThis.process.env.SystemRoot}});
|
|
t.after(()=>{if(process.exitCode===null)process.kill();});return process;
|
|
}
|
|
function bounded(event,ms=3000){let timer;return Promise.race([event,new Promise((_,reject)=>timer=setTimeout(()=>reject(Error('test-deadline')),ms))]).finally(()=>clearTimeout(timer));}
|
|
async function request(ready,destination,body) {
|
|
return await new Promise((resolve,reject)=>{
|
|
const req=http.request({host:'127.0.0.1',port:ready.port,method:'CONNECT',path:destination,headers:{host:destination,'proxy-authorization':`Bearer ${ready.capability}`}});
|
|
req.setTimeout(2000,()=>req.destroy(Error('test-deadline')));req.on('error',reject);
|
|
req.on('connect',(res,socket)=>{
|
|
if(res.statusCode!==200){socket.destroy();reject(Error('connect-rejected'));return;}
|
|
const secure=tls.connect({socket,servername:'localhost',ca:cert,rejectUnauthorized:true});let text='';
|
|
secure.setTimeout(2000,()=>secure.destroy(Error('test-deadline')));secure.on('error',reject);secure.on('secureConnect',()=>secure.write(`POST /submit HTTP/1.1\r\nHost: ${destination}\r\nContent-Length: ${Buffer.byteLength(body)}\r\nConnection: close\r\n\r\n${body}`));secure.on('data',chunk=>text+=chunk);secure.on('end',()=>{secure.destroy();resolve(text);});
|
|
});req.end();
|
|
});
|
|
}
|
|
test('separate Node child handles inspected CONNECT and closes on private IPC disconnect',async t=>{
|
|
const received=[];let connections=0;
|
|
const target=https.createServer({key,cert},async(req,res)=>{const chunks=[];for await(const chunk of req)chunks.push(chunk);received.push(Buffer.concat(chunks).toString());res.writeHead(201);res.end('accepted');});
|
|
target.on('connection',()=>connections++);target.listen(0,'127.0.0.1');await once(target,'listening');
|
|
t.after(async()=>{target.closeAllConnections();await new Promise(r=>target.close(r));});
|
|
const destination=`localhost:${target.address().port}`,worker=child(t);
|
|
const readyEvent=bounded(once(worker,'message'));worker.send({type:'start',config:{destination}});
|
|
const [ready]=await readyEvent;assert.equal(ready.type,'ready');assert.notEqual(worker.pid,process.pid);assert.match(ready.capability,/^[a-zA-Z0-9_-]{43}$/);
|
|
assert.match(await request(ready,destination,'SECRET'),/^HTTP\/1.1 403/);assert.equal(connections,0);
|
|
assert.match(await request(ready,destination,'public'),/^HTTP\/1.1 201/);assert.deepEqual(received,['public']);
|
|
const exit=bounded(once(worker,'exit'));worker.disconnect();await exit;
|
|
const failure=await new Promise(resolve=>{const socket=net.connect(ready.port,'127.0.0.1');socket.setTimeout(1000,()=>{socket.destroy();resolve('timeout');});socket.on('connect',()=>{socket.destroy();resolve('connected');});socket.on('error',error=>resolve(error.code));});
|
|
assert.equal(failure,'ECONNREFUSED');
|
|
});
|
|
test('invalid and oversized private bootstrap reject without spawning another process',async t=>{
|
|
for(const message of [{type:'command',command:'arbitrary'},{type:'start',config:{destination:'x'.repeat(70000)}}]) {
|
|
const worker=child(t),reply=bounded(once(worker,'message')),exit=bounded(once(worker,'exit'));
|
|
worker.send(message);assert.deepEqual((await reply)[0],{type:'error',code:'invalid-message'});await exit;
|
|
}
|
|
});
|
|
test('explicit private stop closes the sole child without restart',async t=>{
|
|
const worker=child(t),ready=bounded(once(worker,'message'));
|
|
worker.send({type:'start',config:{destination:'localhost:443'}});assert.equal((await ready)[0].type,'ready');
|
|
const exit=bounded(once(worker,'exit'));worker.send({type:'stop'});await exit;
|
|
assert.equal(worker.exitCode,0);
|
|
});
|