fix(test): stabilize proxy integration server ports

This commit is contained in:
Tam Nhu Tran
2026-04-22 09:16:15 -04:00
parent 3e7ce1743b
commit e6ac91b9c7
3 changed files with 172 additions and 112 deletions
@@ -1,5 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import getPort from 'get-port';
import * as fs from 'fs'; import * as fs from 'fs';
import * as http from 'http'; import * as http from 'http';
import * as os from 'os'; import * as os from 'os';
@@ -16,6 +15,29 @@ let tempDir: string;
let originalTimeoutEnv: string | undefined; let originalTimeoutEnv: string | undefined;
let originalCcsHome: 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<number> {
if (server.listening) {
return resolveListeningPort(server);
}
return new Promise<number>((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( async function startUpstream(
handler: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> | void handler: (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void> | void
): Promise<void> { ): Promise<void> {
@@ -28,9 +50,8 @@ async function startUpstream(
upstreamSockets.delete(socket); upstreamSockets.delete(socket);
}); });
}); });
await new Promise<void>((resolve) => upstreamServer.listen(0, '127.0.0.1');
upstreamServer.listen(upstreamPort, '127.0.0.1', () => resolve()) upstreamPort = await waitForServerListening(upstreamServer);
);
} }
async function requestProxy(payload: unknown, signal?: AbortSignal): Promise<Response> { async function requestProxy(payload: unknown, signal?: AbortSignal): Promise<Response> {
@@ -46,8 +67,6 @@ async function requestProxy(payload: unknown, signal?: AbortSignal): Promise<Res
} }
beforeEach(async () => { beforeEach(async () => {
upstreamPort = await getPort();
proxyPort = await getPort();
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-edge-')); tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-edge-'));
originalTimeoutEnv = process.env.CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS; originalTimeoutEnv = process.env.CCS_OPENAI_PROXY_REQUEST_TIMEOUT_MS;
originalCcsHome = process.env.CCS_HOME; originalCcsHome = process.env.CCS_HOME;
@@ -94,9 +113,10 @@ describe('openai proxy message edge cases', () => {
}; };
proxyServer = startOpenAICompatProxyServer({ proxyServer = startOpenAICompatProxyServer({
profile, profile,
port: proxyPort, port: 0,
authToken: 'test-proxy-token', authToken: 'test-proxy-token',
}); });
proxyPort = await waitForServerListening(proxyServer);
} }
it('preserves rate-limit errors from the upstream provider', async () => { it('preserves rate-limit errors from the upstream provider', async () => {
@@ -1,5 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import getPort from 'get-port';
import * as http from 'http'; import * as http from 'http';
import { startOpenAICompatProxyServer } from '../../../src/proxy/server/proxy-server'; import { startOpenAICompatProxyServer } from '../../../src/proxy/server/proxy-server';
import type { OpenAICompatProfileConfig } from '../../../src/proxy/profile-router'; import type { OpenAICompatProfileConfig } from '../../../src/proxy/profile-router';
@@ -10,52 +9,64 @@ let upstreamBody: unknown;
let upstreamPort: number; let upstreamPort: number;
let proxyPort: number; let proxyPort: number;
function startMockUpstream(): Promise<void> { function resolveListeningPort(server: http.Server): number {
return new Promise((resolve) => { const address = server.address();
upstreamServer = http.createServer(async (req, res) => { if (!address || typeof address === 'string') {
if (req.method !== 'POST' || req.url !== '/v1/chat/completions') { throw new Error('Failed to resolve server port');
res.writeHead(404).end(); }
return; return address.port;
} }
let body = ''; async function waitForServerListening(server: http.Server): Promise<number> {
for await (const chunk of req) { if (server.listening) {
body += chunk.toString(); return resolveListeningPort(server);
} }
upstreamBody = JSON.parse(body);
const parsed = upstreamBody as {
stream?: boolean;
messages?: Array<{ role?: string; content?: string | Array<{ type?: string; text?: string }> }>;
};
if (parsed.stream) { return new Promise<number>((resolve, reject) => {
res.writeHead(200, { 'Content-Type': 'text/event-stream' }); 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') { async function startMockUpstream(): Promise<void> {
res.write( upstreamServer = http.createServer(async (req, res) => {
'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' if (req.method !== 'POST' || req.url !== '/v1/chat/completions') {
); res.writeHead(404).end();
res.write( return;
'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;
}
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( 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( 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( 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' '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<void> {
return; return;
} }
res.writeHead(200, { 'Content-Type': 'application/json' }); res.write(
res.end( 'data: {"id":"chatcmpl_1","model":"hf-model","choices":[{"index":0,"delta":{"role":"assistant","content":"Hello"}}]}\n\n'
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":{"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<Response> { async function requestProxy(payload: unknown): Promise<Response> {
@@ -98,8 +122,6 @@ async function requestProxy(payload: unknown): Promise<Response> {
} }
beforeEach(async () => { beforeEach(async () => {
upstreamPort = await getPort();
proxyPort = await getPort();
upstreamBody = undefined; upstreamBody = undefined;
await startMockUpstream(); await startMockUpstream();
const profile: OpenAICompatProfileConfig = { const profile: OpenAICompatProfileConfig = {
@@ -112,9 +134,10 @@ beforeEach(async () => {
}; };
proxyServer = startOpenAICompatProxyServer({ proxyServer = startOpenAICompatProxyServer({
profile, profile,
port: proxyPort, port: 0,
authToken: 'test-proxy-token', authToken: 'test-proxy-token',
}); });
proxyPort = await waitForServerListening(proxyServer);
}); });
afterEach(async () => { afterEach(async () => {
+63 -46
View File
@@ -1,5 +1,4 @@
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
import getPort from 'get-port';
import * as fs from 'fs'; import * as fs from 'fs';
import * as http from 'http'; import * as http from 'http';
import * as os from 'os'; import * as os from 'os';
@@ -13,40 +12,61 @@ let proxyServer: http.Server;
let upstreamServers: http.Server[] = []; let upstreamServers: http.Server[] = [];
let proxyPort: number; let proxyPort: number;
function startMockUpstream( function resolveListeningPort(server: http.Server): number {
port: 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<number> {
if (server.listening) {
return resolveListeningPort(server);
}
return new Promise<number>((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, hitLabel: string,
hits: string[], hits: string[],
bodies: Array<{ label: string; body: unknown }> bodies: Array<{ label: string; body: unknown }>
): Promise<void> { ): Promise<number> {
return new Promise((resolve) => { const server = http.createServer(async (req, res) => {
const server = http.createServer(async (req, res) => { let body = '';
let body = ''; for await (const chunk of req) {
for await (const chunk of req) { body += chunk.toString();
body += chunk.toString(); }
} hits.push(hitLabel);
hits.push(hitLabel); bodies.push({ label: hitLabel, body: JSON.parse(body) });
bodies.push({ label: hitLabel, body: JSON.parse(body) });
res.writeHead(200, { 'Content-Type': 'application/json' }); res.writeHead(200, { 'Content-Type': 'application/json' });
res.end( res.end(
JSON.stringify({ JSON.stringify({
id: `chatcmpl_${hitLabel}`, id: `chatcmpl_${hitLabel}`,
model: hitLabel, model: hitLabel,
choices: [ choices: [
{ {
index: 0, index: 0,
message: { role: 'assistant', content: `Reply from ${hitLabel}` }, message: { role: 'assistant', content: `Reply from ${hitLabel}` },
finish_reason: 'stop', finish_reason: 'stop',
}, },
], ],
usage: { prompt_tokens: 2, completion_tokens: 3 }, usage: { prompt_tokens: 2, completion_tokens: 3 },
}) })
); );
});
upstreamServers.push(server);
server.listen(port, '127.0.0.1', () => resolve());
}); });
upstreamServers.push(server);
server.listen(0, '127.0.0.1');
return waitForServerListening(server);
} }
function writeSettings(profileName: string, env: Record<string, string>): string { function writeSettings(profileName: string, env: Record<string, string>): string {
@@ -71,7 +91,7 @@ beforeEach(async () => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-routing-')); tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-proxy-routing-'));
fs.mkdirSync(path.join(tempDir, '.ccs'), { recursive: true }); fs.mkdirSync(path.join(tempDir, '.ccs'), { recursive: true });
process.env.CCS_HOME = tempDir; process.env.CCS_HOME = tempDir;
proxyPort = await getPort(); proxyPort = 0;
}); });
afterEach(async () => { afterEach(async () => {
@@ -97,12 +117,10 @@ afterEach(async () => {
describe('openai proxy request routing', () => { describe('openai proxy request routing', () => {
it('routes explicit profile:model selectors to the matching upstream profile', async () => { it('routes explicit profile:model selectors to the matching upstream profile', async () => {
const primaryPort = await getPort();
const secondaryPort = await getPort();
const hits: string[] = []; const hits: string[] = [];
const bodies: Array<{ label: string; body: unknown }> = []; const bodies: Array<{ label: string; body: unknown }> = [];
await startMockUpstream(primaryPort, 'primary', hits, bodies); const primaryPort = await startMockUpstream('primary', hits, bodies);
await startMockUpstream(secondaryPort, 'secondary', hits, bodies); const secondaryPort = await startMockUpstream('secondary', hits, bodies);
const primarySettings = writeSettings('hf', { const primarySettings = writeSettings('hf', {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`,
@@ -133,9 +151,10 @@ describe('openai proxy request routing', () => {
}; };
proxyServer = startOpenAICompatProxyServer({ proxyServer = startOpenAICompatProxyServer({
profile, profile,
port: proxyPort, port: 0,
authToken: 'test-proxy-token', authToken: 'test-proxy-token',
}); });
proxyPort = await waitForServerListening(proxyServer);
const response = await requestProxy({ const response = await requestProxy({
model: 'deepseek:deepseek-reasoner', model: 'deepseek:deepseek-reasoner',
@@ -150,12 +169,10 @@ describe('openai proxy request routing', () => {
}); });
it('routes thinking requests through the configured think scenario', async () => { it('routes thinking requests through the configured think scenario', async () => {
const primaryPort = await getPort();
const thinkPort = await getPort();
const hits: string[] = []; const hits: string[] = [];
const bodies: Array<{ label: string; body: unknown }> = []; const bodies: Array<{ label: string; body: unknown }> = [];
await startMockUpstream(primaryPort, 'primary', hits, bodies); const primaryPort = await startMockUpstream('primary', hits, bodies);
await startMockUpstream(thinkPort, 'thinker', hits, bodies); const thinkPort = await startMockUpstream('thinker', hits, bodies);
const primarySettings = writeSettings('hf', { const primarySettings = writeSettings('hf', {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`,
@@ -197,9 +214,10 @@ describe('openai proxy request routing', () => {
}; };
proxyServer = startOpenAICompatProxyServer({ proxyServer = startOpenAICompatProxyServer({
profile, profile,
port: proxyPort, port: 0,
authToken: 'test-proxy-token', authToken: 'test-proxy-token',
}); });
proxyPort = await waitForServerListening(proxyServer);
const response = await requestProxy({ const response = await requestProxy({
model: 'hf-default', model: 'hf-default',
@@ -216,12 +234,10 @@ describe('openai proxy request routing', () => {
}); });
it('routes adaptive thinking requests through the configured think scenario', async () => { it('routes adaptive thinking requests through the configured think scenario', async () => {
const primaryPort = await getPort();
const thinkPort = await getPort();
const hits: string[] = []; const hits: string[] = [];
const bodies: Array<{ label: string; body: unknown }> = []; const bodies: Array<{ label: string; body: unknown }> = [];
await startMockUpstream(primaryPort, 'primary', hits, bodies); const primaryPort = await startMockUpstream('primary', hits, bodies);
await startMockUpstream(thinkPort, 'thinker', hits, bodies); const thinkPort = await startMockUpstream('thinker', hits, bodies);
const primarySettings = writeSettings('hf', { const primarySettings = writeSettings('hf', {
ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`, ANTHROPIC_BASE_URL: `http://127.0.0.1:${primaryPort}`,
@@ -263,9 +279,10 @@ describe('openai proxy request routing', () => {
}; };
proxyServer = startOpenAICompatProxyServer({ proxyServer = startOpenAICompatProxyServer({
profile, profile,
port: proxyPort, port: 0,
authToken: 'test-proxy-token', authToken: 'test-proxy-token',
}); });
proxyPort = await waitForServerListening(proxyServer);
const response = await requestProxy({ const response = await requestProxy({
model: 'hf-default', model: 'hf-default',