qwen-code/integration-tests/fake-openai-server.test.ts
jinye 05f854b146
fix(test): Restore first-output benchmark measurement validity and correct its artifact schema (#7820)
* fix(test): restore first-output benchmark measurement validity

Anchor the post-session dwell to SSE readiness so a slow connect cannot
silently reduce a dwell scenario to an immediate-prompt run, isolate the
runner in its own serial vitest config, decide the Phase 1 prototype gate
on the paired bootstrap CI instead of a bare difference of two P50s, and
normalize every invalid timing rather than only the first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(test): correct first-output benchmark artifact schema and simplify (#7825)

Drop the bundle git commit, which resolved HEAD of whatever repository
happened to contain the bundle directory rather than the revision it was
built from; the harness commit and bundle hash already record provenance
correctly. Rename the prompt-shape config field, which held a description
of the prompt rather than the prompt itself, and bump the artifact schema
for both field changes.

Also remove an unreachable AB/BA balance check, fold a duplicated success
predicate into one, parse the comparison-only dwell after the mode check
so single mode reports the accurate error, and document the two median
definitions, the compile-cache path lifetime, and the actual buffer
overflow and cold/warm attribution semantics.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix(test): correct copyright years to 2026 (#7820)

* fix(test): reuse metricForOrdinal in coldWarmProviderDeltas (#7820)

* fix(test): strengthen benchmark test fixtures and align config with Vitest defaults (#7820)

* test(integration): cover prototype-gate input validation guards (#7820)

* fix(integration): summarize sseReadyToPromptMs metric and clarify gate error (#7820)

* test(integration): pin prototype-gate artifact shape for empty deltas (#7820)

* fix(integration): fail loudly on missing SSE timestamp; document sseReadyAt (#7820)

Replace the non-null assertion on the dwell anchor with an explicit guard so a
future path that resolves SSE readiness without recording a timestamp fails as
harness_error instead of silently degrading into an immediate-prompt run that
still reports its configured dwell. Also define sseReadyAt in the timestamp
table and note that each metric's bootstrap seed is positional, so inserting or
reordering a metric shifts later seeds and makes artifacts incomparable.

* test(integration): cover findInvalidTimings in the fast CI suite (#7820)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
2026-07-28 06:51:03 +00:00

322 lines
8.8 KiB
TypeScript

/**
* @license
* Copyright 2026 Qwen Team
* SPDX-License-Identifier: Apache-2.0
*/
import { afterEach, describe, expect, it } from 'vitest';
import {
fakeToolCall,
startFakeOpenAIServer,
type FakeOpenAIServer,
} from './fake-openai-server.js';
type StreamToolCallDelta = {
index: number;
id?: string;
type?: string;
function?: {
name?: string;
arguments?: string;
};
};
type StreamChunk = {
choices: Array<{
delta: {
tool_calls?: StreamToolCallDelta[];
};
}>;
};
let server: FakeOpenAIServer | undefined;
afterEach(async () => {
await server?.close();
server = undefined;
});
describe('fake OpenAI server', () => {
it('serves non-streaming and streaming chat completions', async () => {
server = await startFakeOpenAIServer(({ requestIndex }) =>
requestIndex === 0
? { content: 'hello from fake model' }
: {
toolCalls: [
fakeToolCall('write_file', {
file_path: '/tmp/fake.txt',
content: 'fake',
}),
],
},
);
const nonStreaming = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
messages: [{ role: 'user', content: 'hi' }],
}),
});
expect(nonStreaming.status).toBe(200);
await expect(nonStreaming.json()).resolves.toMatchObject({
choices: [
{
message: {
role: 'assistant',
content: 'hello from fake model',
},
finish_reason: 'stop',
},
],
});
const streaming = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
stream: true,
messages: [{ role: 'user', content: 'write' }],
}),
});
expect(streaming.status).toBe(200);
const streamText = await streaming.text();
expect(streamText).toContain('"tool_calls"');
expect(streamText).toContain('"write_file"');
expect(streamText).toContain('data: [DONE]');
expect(server.requests).toHaveLength(2);
});
it('can close response connections for isolated latency measurements', async () => {
server = await startFakeOpenAIServer(
() => ({ content: 'no connection reuse' }),
{ keepAlive: false },
);
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
stream: true,
messages: [{ role: 'user', content: 'hi' }],
}),
});
expect(response.status).toBe(200);
expect(response.headers.get('connection')).toBe('close');
await response.text();
});
it('closes non-streaming response connections when keepAlive is disabled', async () => {
server = await startFakeOpenAIServer(() => ({ content: 'no reuse' }), {
keepAlive: false,
});
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
stream: false,
messages: [{ role: 'user', content: 'hi' }],
}),
});
expect(response.status).toBe(200);
expect(response.headers.get('connection')).toBe('close');
await response.text();
});
it('preserves a caller-requested close for default non-streaming responses', async () => {
server = await startFakeOpenAIServer(() => ({ content: 'closed' }));
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: {
connection: 'close',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'fake-model',
stream: false,
messages: [{ role: 'user', content: 'hi' }],
}),
});
expect(response.status).toBe(200);
expect(response.headers.get('connection')).toBe('close');
await response.text();
});
it('closes idempotently', async () => {
server = await startFakeOpenAIServer(() => ({ content: 'unused' }));
const firstClose = server.close();
const secondClose = server.close();
expect(secondClose).toBe(firstClose);
await Promise.all([firstClose, secondClose]);
});
it('serves non-streaming tool calls with null content', async () => {
server = await startFakeOpenAIServer(() => ({
toolCalls: [
fakeToolCall('write_file', {
file_path: '/tmp/fake.txt',
content: 'fake',
}),
],
}));
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
messages: [{ role: 'user', content: 'use a tool' }],
}),
});
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
choices: [
{
message: {
content: null,
tool_calls: [{ function: { name: 'write_file' } }],
},
finish_reason: 'tool_calls',
},
],
});
});
it('streams tool call arguments as deltas', async () => {
server = await startFakeOpenAIServer(() => ({
toolCalls: [
fakeToolCall(
'write_file',
{
file_path: '/tmp/fake.txt',
content: 'fake',
},
'call_fixed',
),
],
}));
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
stream: true,
messages: [{ role: 'user', content: 'write' }],
}),
});
expect(response.status).toBe(200);
const toolCallDeltas = (await response.text())
.split('\n\n')
.filter((line) => line.startsWith('data: ') && line !== 'data: [DONE]')
.map((line) => JSON.parse(line.slice('data: '.length)) as StreamChunk)
.flatMap((chunk) => chunk.choices[0]?.delta.tool_calls ?? []);
expect(toolCallDeltas).toEqual([
{
index: 0,
id: 'call_fixed',
type: 'function',
function: { name: 'write_file', arguments: '' },
},
{
index: 0,
function: {
arguments: '{"file_path":"/tmp/fake.txt","content":"fake"}',
},
},
]);
});
it('returns 404 for wrong methods or paths', async () => {
server = await startFakeOpenAIServer(() => ({ content: 'unused' }));
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'GET',
});
expect(response.status).toBe(404);
});
it('returns 400 for malformed JSON bodies', async () => {
server = await startFakeOpenAIServer(() => ({ content: 'unused' }));
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: 'not json',
});
expect(response.status).toBe(400);
});
it('rejects oversized request bodies', async () => {
let handled = false;
server = await startFakeOpenAIServer(() => {
handled = true;
return { content: 'unused' };
});
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: 'x'.repeat(10 * 1024 * 1024 + 1),
});
expect(response.status).toBe(413);
expect(handled).toBe(false);
});
it('returns 500 without exposing handler error details', async () => {
server = await startFakeOpenAIServer(() => {
throw new Error('secret stack detail');
});
const response = await fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
messages: [{ role: 'user', content: 'hi' }],
}),
});
expect(response.status).toBe(500);
await expect(response.json()).resolves.toMatchObject({
error: {
message: 'fake OpenAI server handler failed',
type: 'server_error',
},
});
});
it('closes the response when streaming fails after headers are sent', async () => {
server = await startFakeOpenAIServer(() => ({
content: 1n as unknown as string,
}));
await expect(
fetch(`${server.baseUrl}/chat/completions`, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
model: 'fake-model',
stream: true,
messages: [{ role: 'user', content: 'hi' }],
}),
}),
).rejects.toThrow();
});
});