(run)
| 51 | } |
| 52 | |
| 53 | async function withServer(run) { |
| 54 | const traceFile = path.join(os.tmpdir(), `e2e-clients-${process.pid}-${(process.hrtime.bigint() % 1000000n).toString()}.jsonl`); |
| 55 | const saved = { |
| 56 | m: process.env.EVOMAP_PROXY_TRACE, f: process.env.EVOMAP_PROXY_TRACE_FILE, e: process.env.EVOMAP_PROXY_TRACE_ENCRYPTION, |
| 57 | k: process.env.EVOMAP_OPENAI_API_KEY, |
| 58 | }; |
| 59 | process.env.EVOMAP_PROXY_TRACE = 'full'; |
| 60 | process.env.EVOMAP_PROXY_TRACE_FILE = traceFile; |
| 61 | process.env.EVOMAP_PROXY_TRACE_ENCRYPTION = '0'; |
| 62 | process.env.EVOMAP_OPENAI_API_KEY = 'sk-openai-test'; // lets the /v1/responses handler accept codex |
| 63 | try { fs.unlinkSync(traceFile); } catch { /* fresh */ } |
| 64 | |
| 65 | const silent = { log() {}, warn() {}, error() {} }; |
| 66 | const routes = { |
| 67 | 'POST /v1/messages': buildMessagesHandler({ anthropicProxy: mockAnthropic, routerEnabled: false, logger: silent }), |
| 68 | 'POST /v1/responses': buildResponsesHandler({ openAIProxy: mockOpenAI, logger: silent }), |
| 69 | }; |
| 70 | // Bypass start() so we never write the operator's real settings.json; wire token + port by hand (read-only auth). |
| 71 | const srv = new ProxyHttpServer(routes, { port: 0, logger: silent }); |
| 72 | srv.token = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'; |
| 73 | srv.server = http.createServer((req, res) => srv._handleRequest(req, res)); |
| 74 | const port = await new Promise((resolve) => srv.server.listen(0, '127.0.0.1', () => resolve(srv.server.address().port))); |
| 75 | srv.actualPort = port; |
| 76 | |
| 77 | const call = ({ path: reqPath = '/v1/messages', headers, body }) => new Promise((resolve, reject) => { |
| 78 | const req = http.request({ host: '127.0.0.1', port, path: reqPath, method: 'POST', |
| 79 | headers: { 'content-type': 'application/json', authorization: 'Bearer aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'x-api-key': 'sk-client', ...headers } }, (res) => { |
| 80 | const chunks = []; |
| 81 | res.on('data', (d) => chunks.push(d)); |
| 82 | res.on('end', () => resolve({ status: res.statusCode, bytes: Buffer.concat(chunks) })); |
| 83 | }); |
| 84 | req.on('error', reject); |
| 85 | req.end(JSON.stringify(body)); |
| 86 | }); |
| 87 | |
| 88 | // Each test issues a non-stream + a stream call; the streamed row emits only once its SSE body is drained, |
| 89 | // so wait for the expected count (not just the first line) before asserting. |
| 90 | const readRows = async (expected = 2) => { |
| 91 | let lines = []; |
| 92 | for (let i = 0; i < 150; i++) { |
| 93 | if (fs.existsSync(traceFile)) { |
| 94 | lines = fs.readFileSync(traceFile, 'utf8').trim().split('\n').filter(Boolean); |
| 95 | if (lines.length >= expected) break; |
| 96 | } |
| 97 | await sleep(20); |
| 98 | } |
| 99 | return lines.map((l) => JSON.parse(l)); |
| 100 | }; |
| 101 | |
| 102 | try { |
| 103 | await run({ call, readRows }); |
| 104 | } finally { |
| 105 | await new Promise((r) => srv.server.close(r)); |
| 106 | try { fs.unlinkSync(traceFile); } catch { /* ignore */ } |
| 107 | process.env.EVOMAP_PROXY_TRACE = saved.m; process.env.EVOMAP_PROXY_TRACE_FILE = saved.f; |
| 108 | process.env.EVOMAP_PROXY_TRACE_ENCRYPTION = saved.e; process.env.EVOMAP_OPENAI_API_KEY = saved.k; |
| 109 | } |
| 110 | } |
no test coverage detected