- 新增 jsp-extractor/report/method-extractor/fix-prompt/registry/provider-chat/import-service/yaml-parser 测试 - .vscode-test.mjs 加 srcDir+coverage,package.json 加 test:coverage,产出 tests/coverage/(html) - 修复 method-extractor java fallback 取错捕获组、mockDocument.getText 忽略 range - 更新 tests/test-cases.md(24 文件 179 条)与 test-execution-log.md(覆盖率记录)
102 lines
4.6 KiB
TypeScript
102 lines
4.6 KiB
TypeScript
import * as assert from 'assert';
|
|
import { OpenAICompatibleProvider } from '../src/ai/providers/openai-compatible';
|
|
import { GeminiProvider } from '../src/ai/providers/gemini';
|
|
import { ClaudeProvider } from '../src/ai/providers/claude';
|
|
import { EmptyContentError } from '../src/ai/providers/base';
|
|
|
|
const opts = { model: 'm', temperature: 0.2, maxTokens: 100, timeoutMs: 5000 };
|
|
|
|
function makeResponse(ok: boolean, status: number, jsonData?: unknown, text?: string): Response {
|
|
return {
|
|
ok,
|
|
status,
|
|
json: async () => jsonData,
|
|
text: async () => text ?? '',
|
|
} as Response;
|
|
}
|
|
|
|
suite('Provider Chat Tests', () => {
|
|
const origFetch = (globalThis as any).fetch;
|
|
let calls: Array<{ url: string; init: RequestInit }>;
|
|
|
|
setup(() => { calls = []; });
|
|
teardown(() => { (globalThis as any).fetch = origFetch; });
|
|
|
|
function stubFetch(fn: (url: string, init: RequestInit) => Response): void {
|
|
(globalThis as any).fetch = async (url: string, init: RequestInit) => {
|
|
calls.push({ url, init });
|
|
return fn(url, init);
|
|
};
|
|
}
|
|
|
|
test('openai chat returns content with bearer auth', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { choices: [{ message: { content: 'FIX' }, finish_reason: 'stop' }] }));
|
|
const p = new OpenAICompatibleProvider('key', 'https://api.x.test/v1', 'deepseek', 'DeepSeek');
|
|
assert.strictEqual(await p.chat('sys', 'user', opts), 'FIX');
|
|
assert.match(calls[0].url, /\/chat\/completions$/);
|
|
assert.strictEqual((calls[0].init.headers as Record<string, string>).Authorization, 'Bearer key');
|
|
});
|
|
|
|
test('openai 401 throws invalid key error', async () => {
|
|
stubFetch(() => makeResponse(false, 401, undefined, 'bad key'));
|
|
const p = new OpenAICompatibleProvider('key', 'https://api.x.test/v1', 'deepseek', 'DeepSeek');
|
|
await assert.rejects(() => p.chat('s', 'u', opts));
|
|
});
|
|
|
|
test('openai truncation raises EmptyContentError', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { choices: [{ message: { content: ' ' }, finish_reason: 'length' }] }));
|
|
const p = new OpenAICompatibleProvider('key', 'https://api.x.test/v1', 'deepseek', 'DeepSeek');
|
|
await assert.rejects(() => p.chat('s', 'u', opts), EmptyContentError);
|
|
});
|
|
|
|
test('openai empty content raises EmptyContentError', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { choices: [], error: { message: 'nope' } }));
|
|
const p = new OpenAICompatibleProvider('key', 'https://api.x.test/v1', 'deepseek', 'DeepSeek');
|
|
await assert.rejects(() => p.chat('s', 'u', opts), EmptyContentError);
|
|
});
|
|
|
|
test('openai other status throws', async () => {
|
|
stubFetch(() => makeResponse(false, 500, undefined, 'boom'));
|
|
const p = new OpenAICompatibleProvider('key', 'https://api.x.test/v1', 'deepseek', 'DeepSeek');
|
|
await assert.rejects(() => p.chat('s', 'u', opts));
|
|
});
|
|
|
|
test('gemini chat returns text', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { candidates: [{ content: { parts: [{ text: 'G' }] } }] }));
|
|
const p = new GeminiProvider('key', 'https://gemini.test');
|
|
assert.strictEqual(await p.chat('s', 'u', opts), 'G');
|
|
assert.match(calls[0].url, /generateContent/);
|
|
});
|
|
|
|
test('gemini empty content raises EmptyContentError', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { candidates: [], promptFeedback: { blockReason: 'safety' } }));
|
|
const p = new GeminiProvider('key', 'https://gemini.test');
|
|
await assert.rejects(() => p.chat('s', 'u', opts), EmptyContentError);
|
|
});
|
|
|
|
test('gemini non-ok throws', async () => {
|
|
stubFetch(() => makeResponse(false, 400, undefined, 'bad'));
|
|
const p = new GeminiProvider('key', 'https://gemini.test');
|
|
await assert.rejects(() => p.chat('s', 'u', opts));
|
|
});
|
|
|
|
test('claude chat returns text with api key header', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { content: [{ text: 'C' }], stop_reason: 'end_turn' }));
|
|
const p = new ClaudeProvider('key', 'https://claude.test');
|
|
assert.strictEqual(await p.chat('s', 'u', opts), 'C');
|
|
assert.strictEqual((calls[0].init.headers as Record<string, string>)['x-api-key'], 'key');
|
|
});
|
|
|
|
test('claude 401 throws', async () => {
|
|
stubFetch(() => makeResponse(false, 401, undefined, 'bad'));
|
|
const p = new ClaudeProvider('key', 'https://claude.test');
|
|
await assert.rejects(() => p.chat('s', 'u', opts));
|
|
});
|
|
|
|
test('claude empty content raises EmptyContentError', async () => {
|
|
stubFetch(() => makeResponse(true, 200, { content: [], error: { message: 'x' } }));
|
|
const p = new ClaudeProvider('key', 'https://claude.test');
|
|
await assert.rejects(() => p.chat('s', 'u', opts), EmptyContentError);
|
|
});
|
|
});
|