test: 目录对齐赛道二成果物规范(tests/ 迁移+实验报告+提效测量脚本)
- src/test 迁移至根目录 tests/(16 测试 + fixtures/manual),导入改为 ../src/ - 新增 tsconfig.test.json 独立编译测试;.vscode-test.mjs / package.json / .gitignore 同步 - 新增 tests/test-execution-log.md、tests/test-cases.md(116 用例清单) - 新增 tests/measure/measure-review-time.mjs + performance-comparison.md(提效测量) - 重命名 3 个中文报告文件为 demo-*-coverage-report.md - git rm --cached 解除 vsix 跟踪;README 标注演示视频进行中
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
import * as assert from 'assert';
|
||||
import { parseJsonResponse, chatWithRetry } from '../src/ai/engine';
|
||||
import { EmptyContentError } from '../src/ai/providers/base';
|
||||
import type { AIProvider } from '../src/ai/providers/base';
|
||||
import type { ChatOptions } from '../src/ai/providers/base';
|
||||
import { OpenAICompatibleProvider } from '../src/ai/providers/openai-compatible';
|
||||
|
||||
const OPTIONS: ChatOptions = {
|
||||
model: 'test',
|
||||
temperature: 0,
|
||||
maxTokens: 1024,
|
||||
timeoutMs: 5000,
|
||||
};
|
||||
|
||||
suite('AI Empty Response Handling', () => {
|
||||
test('parseJsonResponse throws empty-response error on blank input', () => {
|
||||
assert.throws(() => parseJsonResponse(''), /空响应|empty response/);
|
||||
assert.throws(() => parseJsonResponse(' \n\t '), /空响应|empty response/);
|
||||
});
|
||||
|
||||
test('parseJsonResponse parses valid JSON normally', () => {
|
||||
const parsed = parseJsonResponse('{"findings":[]}') as { findings: unknown[] };
|
||||
assert.deepStrictEqual(parsed.findings, []);
|
||||
});
|
||||
|
||||
test('chatWithRetry retries once on EmptyContentError', async () => {
|
||||
const calls: string[] = [];
|
||||
const provider = {
|
||||
chat: async (system: string): Promise<string> => {
|
||||
calls.push(system);
|
||||
if (calls.length === 1) {
|
||||
throw new EmptyContentError('finish_reason=length');
|
||||
}
|
||||
return '{"findings":[]}';
|
||||
},
|
||||
} as unknown as AIProvider;
|
||||
|
||||
const result = await chatWithRetry(provider, 'sys', 'user', OPTIONS);
|
||||
assert.strictEqual(result, '{"findings":[]}');
|
||||
assert.strictEqual(calls.length, 2);
|
||||
});
|
||||
|
||||
test('chatWithRetry propagates error when retry also returns empty', async () => {
|
||||
const provider = {
|
||||
chat: async (): Promise<string> => {
|
||||
throw new EmptyContentError('finish_reason=length');
|
||||
},
|
||||
} as unknown as AIProvider;
|
||||
|
||||
await assert.rejects(
|
||||
() => chatWithRetry(provider, 'sys', 'user', OPTIONS),
|
||||
EmptyContentError
|
||||
);
|
||||
});
|
||||
|
||||
test('chatWithRetry does not retry on non-empty-content errors', async () => {
|
||||
let calls = 0;
|
||||
const provider = {
|
||||
chat: async (): Promise<string> => {
|
||||
calls++;
|
||||
throw new Error('boom');
|
||||
},
|
||||
} as unknown as AIProvider;
|
||||
|
||||
await assert.rejects(() => chatWithRetry(provider, 'sys', 'user', OPTIONS), /boom/);
|
||||
assert.strictEqual(calls, 1);
|
||||
});
|
||||
|
||||
test('openai-compatible reports max_tokens truncation clearly', async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = (async () => ({
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
choices: [{ message: { content: null }, finish_reason: 'length' }],
|
||||
}),
|
||||
text: async () => '',
|
||||
})) as unknown as typeof fetch;
|
||||
|
||||
try {
|
||||
const provider = new OpenAICompatibleProvider('key', 'http://localhost', 'test', 'Test');
|
||||
await assert.rejects(
|
||||
() =>
|
||||
provider.chat('sys', 'user', {
|
||||
model: 'm',
|
||||
temperature: 0,
|
||||
maxTokens: 8192,
|
||||
timeoutMs: 5000,
|
||||
}),
|
||||
(err: unknown) => {
|
||||
assert.ok(err instanceof EmptyContentError);
|
||||
assert.match((err as Error).message, /max_tokens/);
|
||||
assert.match((err as Error).message, /8192/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user