Files
2026Technology-Competition/tests/provider-chat.test.ts
T
范智鹏 e469554691 test: 新增 8 个测试文件 63 用例 + 覆盖率设施,行覆盖 42.58%→48.24%
- 新增 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(覆盖率记录)
2026-08-27 22:54:00 +08:00

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);
});
});