From e6ac91b9c716d21ba2a0493b4023efaa5e126975 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Wed, 22 Apr 2026 09:16:15 -0400 Subject: [PATCH] fix(test): stabilize proxy integration server ports --- .../proxy/messages-edge-cases.test.ts | 34 ++++- .../proxy/messages-endpoint.test.ts | 141 ++++++++++-------- .../integration/proxy/request-routing.test.ts | 109 ++++++++------ 3 files changed, 172 insertions(+), 112 deletions(-) diff --git a/tests/integration/proxy/messages-edge-cases.test.ts b/tests/integration/proxy/messages-edge-cases.test.ts index fea8ca87..faefb833 100644 --- a/tests/integration/proxy/messages-edge-cases.test.ts +++ b/tests/integration/proxy/messages-edge-cases.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; -import getPort from 'get-port'; import * as fs from 'fs'; import * as http from 'http'; import * as os from 'os'; @@ -16,6 +15,29 @@ let tempDir: string; let originalTimeoutEnv: string | undefined; let originalCcsHome: string | undefined; +function resolveListeningPort(server: http.Server): number { + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error('Failed to resolve server port'); + } + return address.port; +} + +async function waitForServerListening(server: http.Server): Promise { + if (server.listening) { + return resolveListeningPort(server); + } + + return new Promise((resolve, reject) => { + const onError = (error: Error) => reject(error); + server.once('error', onError); + server.once('listening', () => { + server.off('error', onError); + resolve(resolveListeningPort(server)); + }); + }); +} + async function startUpstream( handler: (req: http.IncomingMessage, res: http.ServerResponse) => Promise | void ): Promise { @@ -28,9 +50,8 @@ async function startUpstream( upstreamSockets.delete(socket); }); }); - await new Promise((resolve) => - upstreamServer.listen(upstreamPort, '127.0.0.1', () => resolve()) - ); + upstreamServer.listen(0, '127.0.0.1'); + upstreamPort = await waitForServerListening(upstreamServer); } async function requestProxy(payload: unknown, signal?: AbortSignal): Promise { @@ -46,8 +67,6 @@ async function requestProxy(payload: unknown, signal?: AbortSignal): Promise { - upstreamPort = await getPort(); - proxyPort = await getPort(); tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-edge-')); originalTimeoutEnv = process.env.CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS; originalCcsHome = process.env.CCS_HOME; @@ -94,9 +113,10 @@ describe('openai proxy message edge cases', () => { }; proxyServer = startOpenAICompatProxyServer({ profile, - port: proxyPort, + port: 0, authToken: 'test-proxy-token', }); + proxyPort = await waitForServerListening(proxyServer); } it('preserves rate-limit errors from the upstream provider', async () => { diff --git a/tests/integration/proxy/messages-endpoint.test.ts b/tests/integration/proxy/messages-endpoint.test.ts index 90c819eb..d8fdf275 100644 --- a/tests/integration/proxy/messages-endpoint.test.ts +++ b/tests/integration/proxy/messages-endpoint.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; -import getPort from 'get-port'; import * as http from 'http'; import { startOpenAICompatProxyServer } from '../../../src/proxy/server/proxy-server'; import type { OpenAICompatProfileConfig } from '../../../src/proxy/profile-router'; @@ -10,52 +9,64 @@ let upstreamBody: unknown; let upstreamPort: number; let proxyPort: number; -function startMockUpstream(): Promise { - return new Promise((resolve) => { - upstreamServer = http.createServer(async (req, res) => { - if (req.method !== 'POST' || req.url !== '/v1/chat/completions') { - res.writeHead(404).end(); - return; - } +function resolveListeningPort(server: http.Server): number { + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error('Failed to resolve server port'); + } + return address.port; +} - let body = ''; - for await (const chunk of req) { - body += chunk.toString(); - } - upstreamBody = JSON.parse(body); - const parsed = upstreamBody as { - stream?: boolean; - messages?: Array<{ role?: string; content?: string | Array<{ type?: string; text?: string }> }>; - }; +async function waitForServerListening(server: http.Server): Promise { + if (server.listening) { + return resolveListeningPort(server); + } - if (parsed.stream) { - res.writeHead(200, { 'Content-Type': 'text/event-stream' }); + return new Promise((resolve, reject) => { + const onError = (error: Error) => reject(error); + server.once('error', onError); + server.once('listening', () => { + server.off('error', onError); + resolve(resolveListeningPort(server)); + }); + }); +} - if (parsed.messages?.[0]?.content === 'interleaved tool fragments') { - res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"search"}}]}}]}\n\n' - ); - res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_2","type":"function","function":{"name":"open"}}]}}]}\n\n' - ); - res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\"path\\":\\"a.ts\\"}"}}]}}]}\n\n' - ); - res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\\"path\\":\\"b.ts\\"}"}}]}}]}\n\n' - ); - res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":9,"completion_tokens":4}}\n\n' - ); - res.end('data: [DONE]\n\n'); - return; - } +async function startMockUpstream(): Promise { + upstreamServer = http.createServer(async (req, res) => { + if (req.method !== 'POST' || req.url !== '/v1/chat/completions') { + res.writeHead(404).end(); + return; + } + let body = ''; + for await (const chunk of req) { + body += chunk.toString(); + } + upstreamBody = JSON.parse(body); + const parsed = upstreamBody as { + stream?: boolean; + messages?: Array<{ + role?: string; + content?: string | Array<{ type?: string; text?: string }>; + }>; + }; + + if (parsed.stream) { + res.writeHead(200, { 'Content-Type': 'text/event-stream' }); + + if (parsed.messages?.[0]?.content === 'interleaved tool fragments') { res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"}}]}\n\n' + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"search"}}]}}]}\n\n' ); res.write( - 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"search","arguments":"{\\"q\\":\\"docs\\"}"}}]}}]}\n\n' + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"id":"call_2","type":"function","function":{"name":"open"}}]}}]}\n\n' + ); + res.write( + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\"path\\":\\"a.ts\\"}"}}]}}]}\n\n' + ); + res.write( + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":1,"function":{"arguments":"{\\"path\\":\\"b.ts\\"}"}}]}}]}\n\n' ); res.write( 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":9,"completion_tokens":4}}\n\n' @@ -64,25 +75,38 @@ function startMockUpstream(): Promise { return; } - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end( - JSON.stringify({ - id: 'chatcmpl_1', - model: 'hf-model', - choices: [ - { - index: 0, - message: { role: 'assistant', content: 'Plain answer' }, - finish_reason: 'stop', - }, - ], - usage: { prompt_tokens: 2, completion_tokens: 3 }, - }) + res.write( + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"}}]}\n\n' ); - }); + res.write( + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"id":"call_1","type":"function","function":{"name":"search","arguments":"{\\"q\\":\\"docs\\"}"}}]}}]}\n\n' + ); + res.write( + 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}],"usage":{"prompt_tokens":9,"completion_tokens":4}}\n\n' + ); + res.end('data: [DONE]\n\n'); + return; + } - upstreamServer.listen(upstreamPort, '127.0.0.1', () => resolve()); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ + id: 'chatcmpl_1', + model: 'hf-model', + choices: [ + { + index: 0, + message: { role: 'assistant', content: 'Plain answer' }, + finish_reason: 'stop', + }, + ], + usage: { prompt_tokens: 2, completion_tokens: 3 }, + }) + ); }); + + upstreamServer.listen(0, '127.0.0.1'); + upstreamPort = await waitForServerListening(upstreamServer); } async function requestProxy(payload: unknown): Promise { @@ -98,8 +122,6 @@ async function requestProxy(payload: unknown): Promise { } beforeEach(async () => { - upstreamPort = await getPort(); - proxyPort = await getPort(); upstreamBody = undefined; await startMockUpstream(); const profile: OpenAICompatProfileConfig = { @@ -112,9 +134,10 @@ beforeEach(async () => { }; proxyServer = startOpenAICompatProxyServer({ profile, - port: proxyPort, + port: 0, authToken: 'test-proxy-token', }); + proxyPort = await waitForServerListening(proxyServer); }); afterEach(async () => { diff --git a/tests/integration/proxy/request-routing.test.ts b/tests/integration/proxy/request-routing.test.ts index b9f7afe8..080405b2 100644 --- a/tests/integration/proxy/request-routing.test.ts +++ b/tests/integration/proxy/request-routing.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; -import getPort from 'get-port'; import * as fs from 'fs'; import * as http from 'http'; import * as os from 'os'; @@ -13,40 +12,61 @@ let proxyServer: http.Server; let upstreamServers: http.Server[] = []; let proxyPort: number; -function startMockUpstream( - port: number, +function resolveListeningPort(server: http.Server): number { + const address = server.address(); + if (!address || typeof address === 'string') { + throw new Error('Failed to resolve server port'); + } + return address.port; +} + +async function waitForServerListening(server: http.Server): Promise { + if (server.listening) { + return resolveListeningPort(server); + } + + return new Promise((resolve, reject) => { + const onError = (error: Error) => reject(error); + server.once('error', onError); + server.once('listening', () => { + server.off('error', onError); + resolve(resolveListeningPort(server)); + }); + }); +} + +async function startMockUpstream( hitLabel: string, hits: string[], bodies: Array<{ label: string; body: unknown }> -): Promise { - return new Promise((resolve) => { - const server = http.createServer(async (req, res) => { - let body = ''; - for await (const chunk of req) { - body += chunk.toString(); - } - hits.push(hitLabel); - bodies.push({ label: hitLabel, body: JSON.parse(body) }); +): Promise { + const server = http.createServer(async (req, res) => { + let body = ''; + for await (const chunk of req) { + body += chunk.toString(); + } + hits.push(hitLabel); + bodies.push({ label: hitLabel, body: JSON.parse(body) }); - res.writeHead(200, { 'Content-Type': 'application/json' }); - res.end( - JSON.stringify({ - id: `chatcmpl_${hitLabel}`, - model: hitLabel, - choices: [ - { - index: 0, - message: { role: 'assistant', content: `Reply from ${hitLabel}` }, - finish_reason: 'stop', - }, - ], - usage: { prompt_tokens: 2, completion_tokens: 3 }, - }) - ); - }); - upstreamServers.push(server); - server.listen(port, '127.0.0.1', () => resolve()); + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end( + JSON.stringify({ + id: `chatcmpl_${hitLabel}`, + model: hitLabel, + choices: [ + { + index: 0, + message: { role: 'assistant', content: `Reply from ${hitLabel}` }, + finish_reason: 'stop', + }, + ], + usage: { prompt_tokens: 2, completion_tokens: 3 }, + }) + ); }); + upstreamServers.push(server); + server.listen(0, '127.0.0.1'); + return waitForServerListening(server); } function writeSettings(profileName: string, env: Record): string { @@ -71,7 +91,7 @@ beforeEach(async () => { tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-routing-')); fs.mkdirSync(path.join(tempDir, '.ccs'), { recursive: true }); process.env.CCS_HOME = tempDir; - proxyPort = await getPort(); + proxyPort = 0; }); afterEach(async () => { @@ -97,12 +117,10 @@ afterEach(async () => { describe('openai proxy request routing', () => { it('routes explicit profile:model selectors to the matching upstream profile', async () => { - const primaryPort = await getPort(); - const secondaryPort = await getPort(); const hits: string[] = []; const bodies: Array<{ label: string; body: unknown }> = []; - await startMockUpstream(primaryPort, 'primary', hits, bodies); - await startMockUpstream(secondaryPort, 'secondary', hits, bodies); + const primaryPort = await startMockUpstream('primary', hits, bodies); + const secondaryPort = await startMockUpstream('secondary', hits, bodies); const primarySettings = writeSettings('hf', { ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, @@ -133,9 +151,10 @@ describe('openai proxy request routing', () => { }; proxyServer = startOpenAICompatProxyServer({ profile, - port: proxyPort, + port: 0, authToken: 'test-proxy-token', }); + proxyPort = await waitForServerListening(proxyServer); const response = await requestProxy({ model: 'deepseek:deepseek-reasoner', @@ -150,12 +169,10 @@ describe('openai proxy request routing', () => { }); it('routes thinking requests through the configured think scenario', async () => { - const primaryPort = await getPort(); - const thinkPort = await getPort(); const hits: string[] = []; const bodies: Array<{ label: string; body: unknown }> = []; - await startMockUpstream(primaryPort, 'primary', hits, bodies); - await startMockUpstream(thinkPort, 'thinker', hits, bodies); + const primaryPort = await startMockUpstream('primary', hits, bodies); + const thinkPort = await startMockUpstream('thinker', hits, bodies); const primarySettings = writeSettings('hf', { ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, @@ -197,9 +214,10 @@ describe('openai proxy request routing', () => { }; proxyServer = startOpenAICompatProxyServer({ profile, - port: proxyPort, + port: 0, authToken: 'test-proxy-token', }); + proxyPort = await waitForServerListening(proxyServer); const response = await requestProxy({ model: 'hf-default', @@ -216,12 +234,10 @@ describe('openai proxy request routing', () => { }); it('routes adaptive thinking requests through the configured think scenario', async () => { - const primaryPort = await getPort(); - const thinkPort = await getPort(); const hits: string[] = []; const bodies: Array<{ label: string; body: unknown }> = []; - await startMockUpstream(primaryPort, 'primary', hits, bodies); - await startMockUpstream(thinkPort, 'thinker', hits, bodies); + const primaryPort = await startMockUpstream('primary', hits, bodies); + const thinkPort = await startMockUpstream('thinker', hits, bodies); const primarySettings = writeSettings('hf', { ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, @@ -263,9 +279,10 @@ describe('openai proxy request routing', () => { }; proxyServer = startOpenAICompatProxyServer({ profile, - port: proxyPort, + port: 0, authToken: 'test-proxy-token', }); + proxyPort = await waitForServerListening(proxyServer); const response = await requestProxy({ model: 'hf-default',