Skip to content

Commit 191868c

Browse files
committed
test(openai-bridge): cover upstream /responses preference
1 parent 9ad6d0b commit 191868c

1 file changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import test from 'node:test';
2+
import assert from 'node:assert/strict';
3+
import http from 'node:http';
4+
import { once } from 'node:events';
5+
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
6+
import os from 'node:os';
7+
import path from 'node:path';
8+
import { createRequire } from 'node:module';
9+
10+
const require = createRequire(import.meta.url);
11+
const { createOpenaiBridgeHttpHandler } = require('../../cli/openai-bridge.js');
12+
13+
function listen(server) {
14+
server.listen(0, '127.0.0.1');
15+
return once(server, 'listening').then(() => {
16+
const addr = server.address();
17+
return { port: addr.port, host: addr.address };
18+
});
19+
}
20+
21+
async function requestText(url, { method = 'GET', headers = {}, body } = {}) {
22+
return new Promise((resolve, reject) => {
23+
const u = new URL(url);
24+
const req = http.request({
25+
hostname: u.hostname,
26+
port: u.port,
27+
path: `${u.pathname}${u.search}`,
28+
method,
29+
headers
30+
}, (res) => {
31+
const chunks = [];
32+
res.on('data', (c) => chunks.push(c));
33+
res.on('end', () => resolve({
34+
status: res.statusCode || 0,
35+
headers: res.headers || {},
36+
text: Buffer.concat(chunks).toString('utf-8')
37+
}));
38+
});
39+
req.on('error', reject);
40+
if (body !== undefined) {
41+
req.write(typeof body === 'string' ? body : JSON.stringify(body));
42+
}
43+
req.end();
44+
});
45+
}
46+
47+
test('openai-bridge prefers upstream /responses and rewraps SSE when stream requested', async () => {
48+
const upstream = http.createServer((req, res) => {
49+
if (req.url === '/v1/responses' && req.method === 'POST') {
50+
res.writeHead(200, { 'Content-Type': 'application/json' });
51+
res.end(JSON.stringify({
52+
id: 'resp_upstream',
53+
model: 'gpt-test',
54+
output: [{
55+
type: 'message',
56+
role: 'assistant',
57+
content: [{ type: 'output_text', text: 'hello-from-upstream' }]
58+
}]
59+
}));
60+
return;
61+
}
62+
if (req.url === '/v1/models' && req.method === 'GET') {
63+
res.writeHead(200, { 'Content-Type': 'application/json' });
64+
res.end(JSON.stringify({ object: 'list', data: [] }));
65+
return;
66+
}
67+
res.writeHead(404, { 'Content-Type': 'application/json' });
68+
res.end(JSON.stringify({ error: 'not found' }));
69+
});
70+
const { port: upstreamPort } = await listen(upstream);
71+
72+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'codexmate-bridge-test-'));
73+
const settingsFile = path.join(tmpDir, 'bridge.json');
74+
await writeFile(settingsFile, JSON.stringify({
75+
version: 1,
76+
providers: {
77+
test: { baseUrl: `http://127.0.0.1:${upstreamPort}/v1`, apiKey: 'sk-upstream' }
78+
}
79+
}), 'utf-8');
80+
81+
const handler = createOpenaiBridgeHttpHandler({ settingsFile, expectedToken: 'codexmate' });
82+
const bridge = http.createServer((req, res) => {
83+
if (!handler(req, res)) {
84+
res.statusCode = 404;
85+
res.end('not handled');
86+
}
87+
});
88+
const { port: bridgePort } = await listen(bridge);
89+
90+
const base = `http://127.0.0.1:${bridgePort}/bridge/openai/test/v1/responses`;
91+
const sse = await requestText(base, {
92+
method: 'POST',
93+
headers: {
94+
'Content-Type': 'application/json',
95+
'Authorization': 'Bearer codexmate'
96+
},
97+
body: {
98+
model: 'gpt-test',
99+
input: 'ping',
100+
stream: true
101+
}
102+
});
103+
assert.equal(sse.status, 200);
104+
assert.match(sse.headers['content-type'], /text\/event-stream/i);
105+
assert.match(sse.text, /event: response\.completed/);
106+
assert.match(sse.text, /data: \[DONE\]/);
107+
108+
await bridge.close();
109+
await upstream.close();
110+
await rm(tmpDir, { recursive: true, force: true });
111+
});
112+
113+
test('openai-bridge falls back to upstream /chat/completions when /responses is not supported', async () => {
114+
const upstream = http.createServer((req, res) => {
115+
if (req.url === '/v1/responses') {
116+
res.writeHead(405, { 'Content-Type': 'application/json' });
117+
res.end(JSON.stringify({ error: 'Method Not Allowed' }));
118+
return;
119+
}
120+
if (req.url === '/v1/chat/completions' && req.method === 'POST') {
121+
res.writeHead(200, { 'Content-Type': 'application/json' });
122+
res.end(JSON.stringify({
123+
id: 'chatcmpl_x',
124+
model: 'gpt-test',
125+
choices: [{ message: { role: 'assistant', content: 'hello-from-chat' } }]
126+
}));
127+
return;
128+
}
129+
res.writeHead(404, { 'Content-Type': 'application/json' });
130+
res.end(JSON.stringify({ error: 'not found' }));
131+
});
132+
const { port: upstreamPort } = await listen(upstream);
133+
134+
const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'codexmate-bridge-test-'));
135+
const settingsFile = path.join(tmpDir, 'bridge.json');
136+
await writeFile(settingsFile, JSON.stringify({
137+
version: 1,
138+
providers: {
139+
test: { baseUrl: `http://127.0.0.1:${upstreamPort}/v1`, apiKey: 'sk-upstream' }
140+
}
141+
}), 'utf-8');
142+
143+
const handler = createOpenaiBridgeHttpHandler({ settingsFile, expectedToken: 'codexmate' });
144+
const bridge = http.createServer((req, res) => {
145+
if (!handler(req, res)) {
146+
res.statusCode = 404;
147+
res.end('not handled');
148+
}
149+
});
150+
const { port: bridgePort } = await listen(bridge);
151+
152+
const url = `http://127.0.0.1:${bridgePort}/bridge/openai/test/v1/responses`;
153+
const resp = await requestText(url, {
154+
method: 'POST',
155+
headers: {
156+
'Content-Type': 'application/json',
157+
'Authorization': 'Bearer codexmate'
158+
},
159+
body: {
160+
model: 'gpt-test',
161+
input: 'ping',
162+
stream: false
163+
}
164+
});
165+
assert.equal(resp.status, 200);
166+
const parsed = JSON.parse(resp.text);
167+
assert.equal(parsed.object, 'response');
168+
assert.equal(parsed.model, 'gpt-test');
169+
assert.ok(Array.isArray(parsed.output));
170+
171+
await bridge.close();
172+
await upstream.close();
173+
await rm(tmpDir, { recursive: true, force: true });
174+
});
175+

0 commit comments

Comments
 (0)