Spaces:
Sleeping
Sleeping
| /** | |
| * test/unit-openai-stream-usage.mjs | |
| * | |
| * 回归测试:/v1/chat/completions 流式最后一帧应携带 usage | |
| * 运行方式:npm run build && node test/unit-openai-stream-usage.mjs | |
| */ | |
| import { handleOpenAIChatCompletions } from '../dist/openai-handler.js'; | |
| let passed = 0; | |
| let failed = 0; | |
| function test(name, fn) { | |
| Promise.resolve() | |
| .then(fn) | |
| .then(() => { | |
| console.log(` ✅ ${name}`); | |
| passed++; | |
| }) | |
| .catch((e) => { | |
| console.error(` ❌ ${name}`); | |
| console.error(` ${e.message}`); | |
| failed++; | |
| }); | |
| } | |
| function assert(condition, msg) { | |
| if (!condition) throw new Error(msg || 'Assertion failed'); | |
| } | |
| function assertEqual(a, b, msg) { | |
| const as = JSON.stringify(a), bs = JSON.stringify(b); | |
| if (as !== bs) throw new Error(msg || `Expected ${bs}, got ${as}`); | |
| } | |
| function createCursorSseResponse(deltas) { | |
| const encoder = new TextEncoder(); | |
| const stream = new ReadableStream({ | |
| start(controller) { | |
| for (const delta of deltas) { | |
| controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'text-delta', delta })}\n\n`)); | |
| } | |
| controller.close(); | |
| }, | |
| }); | |
| return new Response(stream, { | |
| status: 200, | |
| headers: { 'Content-Type': 'text/event-stream' }, | |
| }); | |
| } | |
| class MockResponse { | |
| constructor() { | |
| this.statusCode = 200; | |
| this.headers = {}; | |
| this.body = ''; | |
| this.ended = false; | |
| } | |
| writeHead(statusCode, headers) { | |
| this.statusCode = statusCode; | |
| this.headers = { ...this.headers, ...headers }; | |
| } | |
| write(chunk) { | |
| this.body += String(chunk); | |
| return true; | |
| } | |
| end(chunk = '') { | |
| this.body += String(chunk); | |
| this.ended = true; | |
| } | |
| } | |
| function extractDataChunks(sseText) { | |
| return sseText | |
| .split('\n\n') | |
| .map(part => part.trim()) | |
| .filter(Boolean) | |
| .filter(part => part.startsWith('data: ')) | |
| .map(part => part.slice(6)) | |
| .filter(part => part !== '[DONE]') | |
| .map(part => JSON.parse(part)); | |
| } | |
| console.log('\n📦 [1] OpenAI Chat Completions 流式 usage 回归\n'); | |
| const pending = []; | |
| pending.push((async () => { | |
| const originalFetch = global.fetch; | |
| try { | |
| global.fetch = async () => createCursorSseResponse(['Hello', ' world from Cursor']); | |
| const req = { | |
| method: 'POST', | |
| path: '/v1/chat/completions', | |
| body: { | |
| model: 'gpt-4.1', | |
| stream: true, | |
| messages: [ | |
| { role: 'user', content: 'Write a short greeting in English.' }, | |
| ], | |
| }, | |
| }; | |
| const res = new MockResponse(); | |
| await handleOpenAIChatCompletions(req, res); | |
| assertEqual(res.statusCode, 200, 'statusCode 应为 200'); | |
| assert(res.ended, '响应应结束'); | |
| const chunks = extractDataChunks(res.body); | |
| assert(chunks.length >= 2, '至少应包含 role chunk 和完成 chunk'); | |
| const lastChunk = chunks[chunks.length - 1]; | |
| assertEqual(lastChunk.object, 'chat.completion.chunk'); | |
| assert(lastChunk.usage, '最后一帧应包含 usage'); | |
| assert(typeof lastChunk.usage.prompt_tokens === 'number' && lastChunk.usage.prompt_tokens > 0, 'prompt_tokens 应为正数'); | |
| assert(typeof lastChunk.usage.completion_tokens === 'number' && lastChunk.usage.completion_tokens > 0, 'completion_tokens 应为正数'); | |
| assertEqual( | |
| lastChunk.usage.total_tokens, | |
| lastChunk.usage.prompt_tokens + lastChunk.usage.completion_tokens, | |
| 'total_tokens 应等于 prompt_tokens + completion_tokens' | |
| ); | |
| assertEqual(lastChunk.choices[0].finish_reason, 'stop', '最后一帧 finish_reason 应为 stop'); | |
| const contentChunks = chunks.filter(chunk => chunk.choices?.[0]?.delta?.content); | |
| assert(contentChunks.length > 0, '应输出至少一个 content chunk'); | |
| } finally { | |
| global.fetch = originalFetch; | |
| } | |
| })().then(() => { | |
| console.log(' ✅ 流式最后一帧携带 usage'); | |
| passed++; | |
| }).catch((e) => { | |
| console.error(' ❌ 流式最后一帧携带 usage'); | |
| console.error(` ${e.message}`); | |
| failed++; | |
| })); | |
| await Promise.all(pending); | |
| console.log('\n' + '═'.repeat(55)); | |
| console.log(` 结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`); | |
| console.log('═'.repeat(55) + '\n'); | |
| if (failed > 0) process.exit(1); | |