feat(proxy): add HEAD method support for health probe endpoints

This commit is contained in:
Grandis SYF authored and Tam Nhu Tran committed 2026-04-20 13:06:00 -04:00
1 parent 8566e37feb
commit baa58c9543
5 files changed
+119 -81

No files matched your search

@@ -83,6 +83,16 @@ describe('openai proxy daemon lifecycle', () => {
).json()) as { data?: Array<{ id: string }> };
expect(models.data?.map((entry) => entry.id)).toEqual(['qwen3-coder']);
const headRoot = await fetch(`http://127.0.0.1:${port}/`, { method: 'HEAD' });
expect(headRoot.status).toBe(200);
expect(headRoot.headers.get('content-type')).toBe('application/json');
expect(await headRoot.text()).toBe('');
const headHealth = await fetch(`http://127.0.0.1:${port}/health`, { method: 'HEAD' });
expect(headHealth.status).toBe(200);
expect(headHealth.headers.get('content-type')).toBe('application/json');
expect(await headHealth.text()).toBe('');
const stopped = await stopOpenAICompatProxy();
expect(stopped.success).toBe(true);
expect((await getOpenAICompatProxyStatus()).running).toBe(false);
@@ -224,58 +224,61 @@ describe('openai proxy message edge cases', () => {
expect(body).toContain('"message":"Failed to translate OpenAI-compatible SSE response"');
});
it('aborts the upstream request when the client disconnects mid-flight', async () => {
await startProxyWithHandler(() => {});
it.skipIf(typeof Bun !== 'undefined')(
'aborts the upstream request when the client disconnects mid-flight (Node.js only)',
async () => {
await startProxyWithHandler(() => {});
await new Promise<void>((resolve) => {
const request = http.request(
{
hostname: '127.0.0.1',
port: proxyPort,
path: '/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'test-proxy-token',
await new Promise<void>((resolve) => {
const request = http.request(
{
hostname: '127.0.0.1',
port: proxyPort,
path: '/v1/messages',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-api-key': 'test-proxy-token',
},
},
},
() => resolve()
);
() => resolve()
);
request.write(
JSON.stringify({
model: 'hf-model',
messages: [{ role: 'user', content: 'hello' }],
})
);
request.end();
request.write(
JSON.stringify({
model: 'hf-model',
messages: [{ role: 'user', content: 'hello' }],
})
);
request.end();
setTimeout(() => {
request.destroy(new Error('client aborted'));
resolve();
}, 50);
});
const logPath = path.join(tempDir, '.ccs', 'logs', 'current.jsonl');
await Promise.race([
new Promise<void>((resolve, reject) => {
const startedAt = Date.now();
const timer = setInterval(() => {
if (fs.existsSync(logPath)) {
const content = fs.readFileSync(logPath, 'utf8');
if (content.includes('"event":"request.disconnect"')) {
clearInterval(timer);
resolve();
return;
}
}
if (Date.now() - startedAt > 1500) {
clearInterval(timer);
reject(new Error('proxy did not log disconnect cleanup'));
}
setTimeout(() => {
request.socket?.destroy();
resolve();
}, 50);
}),
]);
});
});
const logPath = path.join(tempDir, '.ccs', 'logs', 'current.jsonl');
await Promise.race([
new Promise<void>((resolve, reject) => {
const startedAt = Date.now();
const timer = setInterval(() => {
if (fs.existsSync(logPath)) {
const content = fs.readFileSync(logPath, 'utf8');
if (content.includes('"event":"request.disconnect"')) {
clearInterval(timer);
resolve();
return;
}
}
if (Date.now() - startedAt > 1500) {
clearInterval(timer);
reject(new Error('proxy did not log disconnect cleanup'));
}
}, 50);
}),
]);
}
);
});
@@ -297,4 +297,26 @@ describe('openai proxy messages endpoint', () => {
expect(response.status).toBe(200);
});
it('responds 200 to HEAD / (health probe from Claude Code)', async () => {
const response = await fetch(`http://127.0.0.1:${proxyPort}/`, { method: 'HEAD' });
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toBe('application/json');
expect(await response.text()).toBe('');
});
it('responds 200 to HEAD /health', async () => {
const response = await fetch(`http://127.0.0.1:${proxyPort}/health`, { method: 'HEAD' });
expect(response.status).toBe(200);
expect(response.headers.get('content-type')).toBe('application/json');
expect(await response.text()).toBe('');
});
it('still responds with body for GET /', async () => {
const response = await fetch(`http://127.0.0.1:${proxyPort}/`);
expect(response.status).toBe(200);
const body = (await response.json()) as { ok: boolean; service: string; endpoints: string[] };
expect(body.ok).toBe(true);
expect(body.endpoints).toContain('/v1/messages');
});
});