feat(ai-review 對話收斂): 讀 PR review 留言判斷解決狀態並收斂 findings #45
@@ -32,6 +32,18 @@ describe('parseBotReviewComment', () => {
|
|||||||
assert.equal(f.suggestion, '移除硬編碼密鑰');
|
assert.equal(f.suggestion, '移除硬編碼密鑰');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('falls back to 問題 content when 建議 is absent', () => {
|
||||||
|
const body = '**審查員**:Maya\n**問題**:缺少邊界測試';
|
||||||
|
const f = parseBotReviewComment(body);
|
||||||
|
assert.equal(f.problem, '缺少邊界測試');
|
||||||
|
assert.equal(f.suggestion, '缺少邊界測試');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults level to warning when 嚴重等級/等級 is missing', () => {
|
||||||
|
const body = '**審查員**:Maya\n**問題**:p\n**建議**:s';
|
||||||
|
assert.equal(parseBotReviewComment(body).level, 'warning');
|
||||||
|
});
|
||||||
|
|
||||||
it('returns null for free-form human comments', () => {
|
it('returns null for free-form human comments', () => {
|
||||||
assert.equal(parseBotReviewComment('我覺得這段可以再想想'), null);
|
assert.equal(parseBotReviewComment('我覺得這段可以再想想'), null);
|
||||||
assert.equal(parseBotReviewComment(''), null);
|
assert.equal(parseBotReviewComment(''), null);
|
||||||
@@ -93,6 +105,23 @@ describe('judgeConversationsResolved', () => {
|
|||||||
assert.deepEqual(verdicts, [{ idx: 0, resolved: false }]);
|
assert.deepEqual(verdicts, [{ idx: 0, resolved: false }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('filters out AI results missing idx or resolved and keeps valid ones', async () => {
|
||||||
|
const items = [{ idx: 0 }, { idx: 1 }];
|
||||||
|
const chatFn = async () => [{ resolved: true }, { idx: 1, resolved: true }, { idx: 0 }];
|
||||||
|
const verdicts = await judgeConversationsResolved(items, chatFn);
|
||||||
|
assert.deepEqual(verdicts, [
|
||||||
|
{ idx: 0, resolved: false }, // {idx:0} 缺 resolved → 視為 false;缺 idx 的整筆被過濾
|
||||||
|
{ idx: 1, resolved: true },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('propagates errors thrown by chatFn to the caller', async () => {
|
||||||
|
await assert.rejects(
|
||||||
|
() => judgeConversationsResolved([{ idx: 0 }], async () => { throw new Error('LLM down'); }),
|
||||||
|
/LLM down/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns [] for no items', async () => {
|
it('returns [] for no items', async () => {
|
||||||
assert.deepEqual(await judgeConversationsResolved([]), []);
|
assert.deepEqual(await judgeConversationsResolved([]), []);
|
||||||
});
|
});
|
||||||
@@ -134,6 +163,20 @@ describe('reconcileConversations', () => {
|
|||||||
assert.equal(result.carriedFindings.length, 2);
|
assert.equal(result.carriedFindings.length, 2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('treats all conversations as unresolved when the judge throws', async () => {
|
||||||
|
const resolvedIds = [];
|
||||||
|
const deps = baseDeps();
|
||||||
|
deps.judge = async () => { throw new Error('judge boom'); };
|
||||||
|
deps.resolveComment = async (id) => { resolvedIds.push(id); return { ok: true }; };
|
||||||
|
|
||||||
|
const result = await reconcileConversations(deps);
|
||||||
|
|
||||||
|
assert.deepEqual(resolvedIds, []); // 無任何對話被 resolve
|
||||||
|
assert.equal(result.resolvedCount, 0);
|
||||||
|
assert.equal(result.carriedFindings.length, 2); // a.js + b.js 皆加回(c.js 已解決略過)
|
||||||
|
assert.deepEqual(result.carriedFindings.map(f => f.suggestion).sort(), ['fix one', 'fix two']);
|
||||||
|
});
|
||||||
|
|
||||||
it('returns empty result and does not throw when listing comments fails', async () => {
|
it('returns empty result and does not throw when listing comments fails', async () => {
|
||||||
const result = await reconcileConversations({ listComments: async () => { throw new Error('boom'); } });
|
const result = await reconcileConversations({ listComments: async () => { throw new Error('boom'); } });
|
||||||
assert.deepEqual(result, { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, unresolvedCount: 0 });
|
assert.deepEqual(result, { resolvedFindings: [], carriedFindings: [], resolvedCount: 0, unresolvedCount: 0 });
|
||||||
|
|||||||
Reference in New Issue
Block a user