mirror of
https://github.com/7836246/cursor2api.git
synced 2026-09-03 07:20:02 +08:00
Tighten image path normalization, preserve multimodal request content across OpenAI-compatible endpoints, and fail fast on unsupported image_file inputs so clients get predictable behavior instead of silent degradation. Made-with: Cursor
102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
/**
|
||
* test/unit-openai-image-file.mjs
|
||
*
|
||
* 单元测试:image_file 输入应显式报错,而不是静默降级
|
||
* 运行方式:node test/unit-openai-image-file.mjs
|
||
*/
|
||
|
||
let passed = 0;
|
||
let failed = 0;
|
||
|
||
function test(name, fn) {
|
||
try {
|
||
fn();
|
||
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');
|
||
}
|
||
|
||
class OpenAIRequestError extends Error {
|
||
constructor(message, status = 400, type = 'invalid_request_error', code = 'invalid_request') {
|
||
super(message);
|
||
this.name = 'OpenAIRequestError';
|
||
this.status = status;
|
||
this.type = type;
|
||
this.code = code;
|
||
}
|
||
}
|
||
|
||
function unsupportedImageFileError(fileId) {
|
||
const suffix = fileId ? ` (file_id: ${fileId})` : '';
|
||
return new OpenAIRequestError(
|
||
`Unsupported content part: image_file${suffix}. This proxy does not support OpenAI Files API image references. Please send the image as image_url, input_image, data URI, or a local file path instead.`,
|
||
400,
|
||
'invalid_request_error',
|
||
'unsupported_content_part'
|
||
);
|
||
}
|
||
|
||
function extractOpenAIContentBlocks(msg) {
|
||
if (msg.content === null || msg.content === undefined) return '';
|
||
if (typeof msg.content === 'string') return msg.content;
|
||
if (Array.isArray(msg.content)) {
|
||
const blocks = [];
|
||
for (const p of msg.content) {
|
||
if (p.type === 'text' || p.type === 'input_text') {
|
||
if (p.text) blocks.push({ type: 'text', text: p.text });
|
||
} else if (p.type === 'image_file' && p.image_file) {
|
||
throw unsupportedImageFileError(p.image_file.file_id);
|
||
}
|
||
}
|
||
return blocks.length > 0 ? blocks : '';
|
||
}
|
||
return String(msg.content);
|
||
}
|
||
|
||
console.log('\n📦 [1] image_file 显式报错\n');
|
||
|
||
test('image_file 应抛出 OpenAIRequestError', () => {
|
||
let thrown;
|
||
try {
|
||
extractOpenAIContentBlocks({
|
||
role: 'user',
|
||
content: [
|
||
{ type: 'input_text', text: '请描述图片' },
|
||
{ type: 'image_file', image_file: { file_id: 'file_123' } },
|
||
],
|
||
});
|
||
} catch (e) {
|
||
thrown = e;
|
||
}
|
||
|
||
assert(thrown instanceof OpenAIRequestError, 'should throw OpenAIRequestError');
|
||
assert(thrown.message.includes('image_file'), 'message should mention image_file');
|
||
assert(thrown.message.includes('file_123'), 'message should include file_id');
|
||
assert(thrown.status === 400, 'status should be 400');
|
||
assert(thrown.type === 'invalid_request_error', 'type should be invalid_request_error');
|
||
assert(thrown.code === 'unsupported_content_part', 'code should be unsupported_content_part');
|
||
});
|
||
|
||
test('普通文本块仍可正常通过', () => {
|
||
const blocks = extractOpenAIContentBlocks({
|
||
role: 'user',
|
||
content: [{ type: 'input_text', text: 'hello' }],
|
||
});
|
||
assert(Array.isArray(blocks), 'blocks should be array');
|
||
assert(blocks[0].text === 'hello', 'text block should remain intact');
|
||
});
|
||
|
||
console.log('\n' + '═'.repeat(55));
|
||
console.log(` 结果: ${passed} 通过 / ${failed} 失败 / ${passed + failed} 总计`);
|
||
console.log('═'.repeat(55) + '\n');
|
||
|
||
if (failed > 0) process.exit(1);
|