test(app): 補齊 isSafeRepoPath/extractBalancedJSON/normalizeText/repairJSONArrayWithAI/格式化函式測試

為可測性 export 三個內部函式(isSafeRepoPath、extractBalancedJSON/extractJSONText、normalizeText)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jeffery
2026-06-26 14:41:41 +08:00
co-authored by Claude Opus 4.8
parent fc10ab7266
commit 775cc575ae
8 changed files with 354 additions and 5 deletions
+84
View File
@@ -139,3 +139,87 @@ describe('json helpers', () => {
);
});
});
describe('repairJSONArrayWithAI', () => {
it('returns a clean JSON array string parseable by the caller', async () => {
const repaired = await repairJSONArrayWithAI(
'/tmp/x.json',
'.gitea/ai-review/findings.json',
'{not valid json',
async () => '[{"id":1},{"id":2}]'
);
assert.equal(repaired, '[{"id":1},{"id":2}]');
assert.deepEqual(JSON.parse(repaired), [{ id: 1 }, { id: 2 }]);
});
it('strips a fenced ```json block from the AI output', async () => {
const repaired = await repairJSONArrayWithAI(
'/tmp/x.json',
'.gitea/ai-review/exclusions.json',
'garbage',
async () => '```json\n[1, 2, 3]\n```'
);
assert.equal(repaired, '[1, 2, 3]');
assert.deepEqual(JSON.parse(repaired), [1, 2, 3]);
});
it('falls back to an empty array when the model cannot repair the content', async () => {
const repaired = await repairJSONArrayWithAI(
'/tmp/x.json',
'.gitea/ai-review/findings.json',
'totally unparseable !!! @@@',
async () => '[]'
);
assert.equal(repaired, '[]');
assert.deepEqual(JSON.parse(repaired), []);
});
it('returns garbage unchanged (no parsing/throwing) so the caller can validate', async () => {
const repaired = await repairJSONArrayWithAI(
'/tmp/x.json',
'.gitea/ai-review/findings.json',
'{broken',
async () => 'not a json array at all'
);
assert.equal(repaired, 'not a json array at all');
assert.throws(() => JSON.parse(repaired));
});
it('invokes chatFn once with the strict system prompt and JSON-encoded context', async () => {
const calls = [];
const repaired = await repairJSONArrayWithAI(
'/tmp/findings.json',
'.gitea/ai-review/findings.json',
'{broken',
async (systemPrompt, userContent) => {
calls.push({ systemPrompt, userContent });
return '[]';
}
);
assert.equal(repaired, '[]');
assert.equal(calls.length, 1);
assert.ok(calls[0].systemPrompt.includes('你是 JSON 修復器'));
assert.ok(calls[0].systemPrompt.includes('回傳 []'));
const context = JSON.parse(calls[0].userContent);
assert.deepEqual(context, {
file: '.gitea/ai-review/findings.json',
path: '/tmp/findings.json',
rawText: '{broken'
});
});
it('propagates errors thrown by chatFn', async () => {
await assert.rejects(
() => repairJSONArrayWithAI('/tmp/x.json', '.gitea/ai-review/findings.json', '{broken', async () => {
throw new Error('llm unavailable');
}),
/llm unavailable/
);
});
});