diff --git a/containers/api-proxy/server.anthropic-beta.test.js b/containers/api-proxy/server.anthropic-beta.test.js index aaf7283a7..e536082cb 100644 --- a/containers/api-proxy/server.anthropic-beta.test.js +++ b/containers/api-proxy/server.anthropic-beta.test.js @@ -6,7 +6,13 @@ */ const https = require('https'); -const { EventEmitter } = require('events'); +const { + makeReq: makeReqFactory, + makeRes, + makeProxyReq, + makeProxyRes, + getStructuredLogs, +} = require('./test-helpers/server-mock-factories'); const originalHttpsProxy = process.env.HTTPS_PROXY; let proxyRequest; @@ -34,52 +40,7 @@ afterAll(() => { describe('proxyRequest anthropic deprecated beta handling', () => { function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/messages'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; - } - - function makeRes() { - const res = { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(() => { - res.headersSent = true; - }), - end: jest.fn(), - destroy: jest.fn(), - }; - return res; - } - - function makeProxyReq() { - const proxyReq = new EventEmitter(); - proxyReq.end = jest.fn(); - proxyReq.write = jest.fn(); - proxyReq.destroy = jest.fn(); - return proxyReq; - } - - function makeProxyRes(statusCode, headers = { 'content-type': 'application/json' }) { - const proxyRes = new EventEmitter(); - proxyRes.statusCode = statusCode; - proxyRes.headers = headers; - proxyRes.pipe = jest.fn(); - return proxyRes; - } - - function getStructuredLogs(writeSpy, eventName) { - return writeSpy.mock.calls - .map(([line]) => { - try { - return JSON.parse(line); - } catch { - return null; - } - }) - .filter(entry => entry && entry.event === eventName); + return makeReqFactory('/v1/messages', headers); } afterEach(() => { diff --git a/containers/api-proxy/server.error-handling.test.js b/containers/api-proxy/server.error-handling.test.js index 8e105b792..3676ea96b 100644 --- a/containers/api-proxy/server.error-handling.test.js +++ b/containers/api-proxy/server.error-handling.test.js @@ -7,6 +7,11 @@ const https = require('https'); const { EventEmitter } = require('events'); +const { + makeReq: makeReqFactory, + makeRes, + getStructuredLogs, +} = require('./test-helpers/server-mock-factories'); const originalHttpsProxy = process.env.HTTPS_PROXY; let proxyRequest; @@ -29,36 +34,11 @@ afterAll(() => { describe('proxyRequest error handling', () => { function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/chat/completions'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; - } - - function makeRes() { - const res = { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(() => { - res.headersSent = true; - }), - end: jest.fn(), - destroy: jest.fn(), - }; - return res; + return makeReqFactory('/v1/chat/completions', headers); } function getRequestErrorLog(writeSpy) { - for (const [line] of writeSpy.mock.calls) { - try { - const parsed = JSON.parse(line); - if (parsed.event === 'request_error') return parsed; - } catch { - // ignore non-JSON writes - } - } - return null; + return getStructuredLogs(writeSpy, 'request_error')[0] || null; } let stdoutWriteSpy; diff --git a/containers/api-proxy/server.model-not-supported.test.js b/containers/api-proxy/server.model-not-supported.test.js index 1c7a7e8cb..19f6b40c8 100644 --- a/containers/api-proxy/server.model-not-supported.test.js +++ b/containers/api-proxy/server.model-not-supported.test.js @@ -7,7 +7,13 @@ */ const https = require('https'); -const { EventEmitter } = require('events'); +const { + makeReq: makeReqFactory, + makeRes, + makeProxyReq, + makeProxyRes, + getStructuredLogs, +} = require('./test-helpers/server-mock-factories'); const originalHttpsProxy = process.env.HTTPS_PROXY; let proxyRequest; @@ -36,38 +42,7 @@ afterAll(() => { // ── helpers ─────────────────────────────────────────────────────────────────── function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/chat/completions'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; -} - -function makeRes() { - const res = { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(() => { res.headersSent = true; }), - end: jest.fn(), - destroy: jest.fn(), - }; - return res; -} - -function makeProxyReq() { - const proxyReq = new EventEmitter(); - proxyReq.end = jest.fn(); - proxyReq.write = jest.fn(); - proxyReq.destroy = jest.fn(); - return proxyReq; -} - -function makeProxyRes(statusCode, headers = { 'content-type': 'application/json' }) { - const proxyRes = new EventEmitter(); - proxyRes.statusCode = statusCode; - proxyRes.headers = headers; - proxyRes.pipe = jest.fn(); - return proxyRes; + return makeReqFactory('/v1/chat/completions', headers); } /** Flush all pending microtasks/promises so async retry callbacks can run. */ @@ -75,12 +50,6 @@ function flushPromises() { return new Promise(resolve => setImmediate(resolve)); } -function getStructuredLogs(writeSpy, eventName) { - return writeSpy.mock.calls - .map(([line]) => { try { return JSON.parse(line); } catch { return null; } }) - .filter(entry => entry && entry.event === eventName); -} - // ── tests ───────────────────────────────────────────────────────────────────── describe('proxyRequest copilot model-not-supported retry', () => { diff --git a/containers/api-proxy/server.proxy-headers.test.js b/containers/api-proxy/server.proxy-headers.test.js index b69ed5631..3397b2951 100644 --- a/containers/api-proxy/server.proxy-headers.test.js +++ b/containers/api-proxy/server.proxy-headers.test.js @@ -5,7 +5,11 @@ */ const https = require('https'); -const { EventEmitter } = require('events'); +const { + makeReq: makeReqFactory, + makeRes, + makeProxyReq, +} = require('./test-helpers/server-mock-factories'); const originalHttpsProxy = process.env.HTTPS_PROXY; let proxyRequest; @@ -28,21 +32,7 @@ afterAll(() => { describe('proxyRequest X-Initiator injection', () => { /** Minimal mock for http.IncomingMessage backed by EventEmitter. */ function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/chat/completions'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; - } - - /** Minimal mock for http.ServerResponse. */ - function makeRes() { - return { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(), - end: jest.fn(), - }; + return makeReqFactory('/v1/chat/completions', headers); } afterEach(() => { @@ -58,10 +48,7 @@ describe('proxyRequest X-Initiator injection', () => { let capturedProxyReq; jest.spyOn(https, 'request').mockImplementation((options) => { capturedOptions = options; - const proxyReq = new EventEmitter(); - proxyReq.end = jest.fn(); - proxyReq.write = jest.fn(); - proxyReq.destroy = jest.fn(); + const proxyReq = makeProxyReq(); capturedProxyReq = proxyReq; return proxyReq; }); diff --git a/containers/api-proxy/server.token-guards.test.js b/containers/api-proxy/server.token-guards.test.js index 9a358a087..58e7b45a9 100644 --- a/containers/api-proxy/server.token-guards.test.js +++ b/containers/api-proxy/server.token-guards.test.js @@ -7,6 +7,7 @@ const https = require('https'); const { EventEmitter } = require('events'); +const { makeReq: makeReqFactory, makeRes } = require('./test-helpers/server-mock-factories'); const originalHttpsProxy = process.env.HTTPS_PROXY; let proxyRequest; @@ -34,20 +35,7 @@ afterAll(() => { describe('proxyRequest effective token guard', () => { function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/chat/completions'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; - } - - function makeRes() { - return { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(), - end: jest.fn(), - }; + return makeReqFactory('/v1/chat/completions', headers); } beforeEach(() => { @@ -113,20 +101,7 @@ describe('proxyRequest effective token guard', () => { describe('proxyRequest max-runs guard', () => { function makeReq(headers = {}) { - const req = new EventEmitter(); - req.url = '/v1/chat/completions'; - req.method = 'POST'; - req.headers = { 'content-type': 'application/json', ...headers }; - return req; - } - - function makeRes() { - return { - headersSent: false, - setHeader: jest.fn(), - writeHead: jest.fn(), - end: jest.fn(), - }; + return makeReqFactory('/v1/chat/completions', headers); } beforeEach(() => { diff --git a/containers/api-proxy/test-helpers/server-mock-factories.js b/containers/api-proxy/test-helpers/server-mock-factories.js new file mode 100644 index 000000000..4a050acb0 --- /dev/null +++ b/containers/api-proxy/test-helpers/server-mock-factories.js @@ -0,0 +1,54 @@ +'use strict'; + +const { EventEmitter } = require('events'); + +function makeReq(url, headers = {}) { + const req = new EventEmitter(); + req.url = url; + req.method = 'POST'; + req.headers = { 'content-type': 'application/json', ...headers }; + return req; +} + +function makeRes() { + const res = { + headersSent: false, + setHeader: jest.fn(), + writeHead: jest.fn(() => { + res.headersSent = true; + }), + end: jest.fn(), + destroy: jest.fn(), + }; + return res; +} + +function makeProxyReq() { + const proxyReq = new EventEmitter(); + proxyReq.end = jest.fn(); + proxyReq.write = jest.fn(); + proxyReq.destroy = jest.fn(); + return proxyReq; +} + +function makeProxyRes(statusCode, headers = { 'content-type': 'application/json' }) { + const proxyRes = new EventEmitter(); + proxyRes.statusCode = statusCode; + proxyRes.headers = headers; + proxyRes.pipe = jest.fn(); + return proxyRes; +} + +function getStructuredLogs(writeSpy, eventName) { + return writeSpy.mock.calls + .map(([line]) => { + try { + return JSON.parse(line); + } catch { + return null; + } + }) + .filter(entry => entry && entry.event === eventName); +} + +module.exports = { makeReq, makeRes, makeProxyReq, makeProxyRes, getStructuredLogs };