- providers/base.ts 引入 ApiRequestError 与 isRetryableError,按 429/5xx/超时/网络错误/401 分类 - chatWithRetry 升级为最多 3 次指数退避(1s/2s/4s),非重试错误立即上抛;新增 setRetryBaseDelayForTest 钩子 - aiFixEngine/customFixEngine 不再吞错,修复失败原因透传为 ai-error: <原因> - 扩展 ai-empty-response/customFixEngine 测试覆盖重试与分类
101 lines
3.8 KiB
TypeScript
101 lines
3.8 KiB
TypeScript
import * as assert from 'assert';
|
|
import * as vscode from 'vscode';
|
|
import { AIProvider, ChatOptions } from '../src/ai/providers/base';
|
|
import { aiFixReviewIssue } from '../src/fix/customFixEngine';
|
|
import type { ReviewIssueInput } from '../src/fix/fixPrompt';
|
|
|
|
class MockReviewFixProvider extends AIProvider {
|
|
id = 'mock';
|
|
name = 'mock';
|
|
constructor(private responses: string[]) { super('key', 'url'); }
|
|
async chat(_system: string, _user: string, _options: ChatOptions): Promise<string> {
|
|
const next = this.responses.shift();
|
|
return next ?? '{}';
|
|
}
|
|
}
|
|
|
|
const options: ChatOptions = { model: 'mock', temperature: 0, maxTokens: 100, timeoutMs: 1000 };
|
|
|
|
function reviewDiag(message = 'bad code', line = 0): ReviewIssueInput {
|
|
return { ruleId: 'custom:test-rule', line, message, suggestion: 'use GOOD instead' };
|
|
}
|
|
|
|
suite('Custom FixEngine Tests', () => {
|
|
test('aiFixReviewIssue applies AI fix and converges after verify', async () => {
|
|
const doc = await vscode.workspace.openTextDocument({
|
|
content: 'const x = BAD;\n',
|
|
language: 'javascript',
|
|
});
|
|
const provider = new MockReviewFixProvider([
|
|
'{"originalText":"BAD","newText":"GOOD"}',
|
|
'{"fixed":true}',
|
|
]);
|
|
const result = await aiFixReviewIssue(doc, reviewDiag(), 3, provider, options, true);
|
|
|
|
assert.strictEqual(result.success, true);
|
|
assert.strictEqual(result.newText, 'const x = GOOD;\n');
|
|
assert.strictEqual(result.appliedFixes.length, 1);
|
|
});
|
|
|
|
test('aiFixReviewIssue returns ai-match-failed when originalText not found', async () => {
|
|
const doc = await vscode.workspace.openTextDocument({
|
|
content: 'const x = BAD;\n',
|
|
language: 'javascript',
|
|
});
|
|
const provider = new MockReviewFixProvider(['{"originalText":"NOPE","newText":"GOOD"}']);
|
|
const result = await aiFixReviewIssue(doc, reviewDiag(), 3, provider, options, true);
|
|
|
|
assert.strictEqual(result.success, false);
|
|
assert.strictEqual(result.message, 'ai-match-failed');
|
|
});
|
|
|
|
test('aiFixReviewIssue retries until verify passes within maxIterations', async () => {
|
|
const doc = await vscode.workspace.openTextDocument({
|
|
content: 'const x = BAD;\n',
|
|
language: 'javascript',
|
|
});
|
|
const provider = new MockReviewFixProvider([
|
|
'{"originalText":"BAD","newText":"BAD2"}',
|
|
'{"fixed":false,"reason":"still bad"}',
|
|
'{"originalText":"BAD2","newText":"GOOD"}',
|
|
'{"fixed":true}',
|
|
]);
|
|
const result = await aiFixReviewIssue(doc, reviewDiag(), 3, provider, options, true);
|
|
|
|
assert.strictEqual(result.success, true);
|
|
assert.strictEqual(result.appliedFixes.length, 2);
|
|
assert.strictEqual(result.newText, 'const x = GOOD;\n');
|
|
});
|
|
|
|
test('aiFixReviewIssue accepts last fix when verify never passes', async () => {
|
|
const doc = await vscode.workspace.openTextDocument({
|
|
content: 'const x = BAD;\n',
|
|
language: 'javascript',
|
|
});
|
|
const provider = new MockReviewFixProvider([
|
|
'{"originalText":"BAD","newText":"GOOD"}',
|
|
'{"fixed":false,"reason":"still bad"}',
|
|
]);
|
|
const result = await aiFixReviewIssue(doc, reviewDiag(), 1, provider, options, true);
|
|
|
|
assert.strictEqual(result.success, true);
|
|
assert.strictEqual(result.newText, 'const x = GOOD;\n');
|
|
assert.strictEqual(result.appliedFixes.length, 1);
|
|
});
|
|
|
|
test('aiFixReviewIssue fails with ai-no-fix when AI provides empty fix', async () => {
|
|
const doc = await vscode.workspace.openTextDocument({
|
|
content: 'const x = BAD;\n',
|
|
language: 'javascript',
|
|
});
|
|
const provider = new MockReviewFixProvider([
|
|
'{"originalText":"","newText":""}',
|
|
'{"originalText":"","newText":""}',
|
|
]);
|
|
const result = await aiFixReviewIssue(doc, reviewDiag(), 3, provider, options, true);
|
|
|
|
assert.strictEqual(result.success, false);
|
|
assert.strictEqual(result.message, 'ai-no-fix');
|
|
});
|
|
});
|