mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 12:16:38 +00:00
fix(test): stabilize proxy integration server ports
This commit is contained in:
1 parent
3e7ce1743b
commit
e6ac91b9c7
3 files changed
+99
-39
No files matched your search
@@ -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,8 +9,30 @@ 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();
|
||||||
|
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(): Promise<void> {
|
||||||
upstreamServer = http.createServer(async (req, res) => {
|
upstreamServer = http.createServer(async (req, res) => {
|
||||||
if (req.method !== 'POST' || req.url !== '/v1/chat/completions') {
|
if (req.method !== 'POST' || req.url !== '/v1/chat/completions') {
|
||||||
res.writeHead(404).end();
|
res.writeHead(404).end();
|
||||||
@@ -25,7 +46,10 @@ function startMockUpstream(): Promise<void> {
|
|||||||
upstreamBody = JSON.parse(body);
|
upstreamBody = JSON.parse(body);
|
||||||
const parsed = upstreamBody as {
|
const parsed = upstreamBody as {
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
messages?: Array<{ role?: string; content?: string | Array<{ type?: string; text?: string }> }>;
|
messages?: Array<{
|
||||||
|
role?: string;
|
||||||
|
content?: string | Array<{ type?: string; text?: string }>;
|
||||||
|
}>;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parsed.stream) {
|
if (parsed.stream) {
|
||||||
@@ -81,8 +105,8 @@ function startMockUpstream(): Promise<void> {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
upstreamServer.listen(upstreamPort, '127.0.0.1', () => resolve());
|
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 () => {
|
||||||
|
|||||||
@@ -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,13 +12,34 @@ 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) {
|
||||||
@@ -45,8 +65,8 @@ function startMockUpstream(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
upstreamServers.push(server);
|
upstreamServers.push(server);
|
||||||
server.listen(port, '127.0.0.1', () => resolve());
|
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',
|
||||||
|
|||||||
Reference in new issue
Block a user