From 0f7ec5c3db529e74bbb49c0d0032104861dc80b1 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 23 Jun 2026 15:44:44 +0800 Subject: [PATCH] =?UTF-8?q?test(ai-review):=20=E8=A3=9C=20usageSection=20?= =?UTF-8?q?=E7=B5=90=E6=A7=8B=E3=80=81resolveMissingLineNumbers=20?= =?UTF-8?q?=E4=BE=8B=E5=A4=96=E8=88=87=E8=AA=A4=E5=A0=B1=E6=B7=B7=E5=90=88?= =?UTF-8?q?=E5=B9=B3=E8=A1=8C=E6=B8=AC=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/comments.test.js | 14 ++++++++++++++ app/findings.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/app/comments.test.js b/app/comments.test.js index e1c0792..08991bc 100644 --- a/app/comments.test.js +++ b/app/comments.test.js @@ -307,6 +307,20 @@ describe('postFindingsReview', () => { assert.equal(reviewCalls[0].body, reviewCalls[0].body.trimEnd()); }); + it('appends usageSection verbatim after the stats block without altering structure', async () => { + const reviewCalls = []; + const usageSection = '## 🤖 AI 助理使用量\n\n| x | y |\n| - | - |\n| 1 | 2 |'; + await postFindingsReview([ + { level: 'warning', role: 'Leo', location: 'app/b.js:20', suggestion: 'W', is_new: true }, + ], { postReview: async (args) => { reviewCalls.push(args); }, usageSection }); + + const body = reviewCalls[0].body; + // 統計區塊在前、usageSection 原樣接在後(中間一個空行);不交錯、不被竄改 + assert.ok(body.startsWith('## AI Code Review 統計')); + assert.ok(body.endsWith(usageSection)); + assert.match(body, /## AI Code Review 統計[\s\S]*\n\n## 🤖 AI 助理使用量/); + }); + it('counts both new and old findings in the summary but only inline-comments new ones', async () => { const reviewCalls = []; await postFindingsReview([ diff --git a/app/findings.test.js b/app/findings.test.js index a15ae03..8897733 100644 --- a/app/findings.test.js +++ b/app/findings.test.js @@ -244,6 +244,23 @@ describe('findings exclusions', () => { assert.equal(result.length, 1); // 只有明確 false_positive 才剔除,其餘保守保留 }); + it('keeps failed and confirmed, drops only confirmed false positives (mixed parallel)', async () => { + const findings = [ + { level: 'warning', role: 'A', location: 'a.js:1', problem: 'p', suggestion: 'fail' }, + { level: 'warning', role: 'B', location: 'b.js:2', problem: 'p', suggestion: 'fp' }, + { level: 'warning', role: 'C', location: 'c.js:3', problem: 'p', suggestion: 'ok' }, + ]; + const chatFn = async (_sys, user) => { + const loc = JSON.parse(user).location; + if (loc === 'a.js:1') throw new Error('boom'); // 失敗 → 保守保留 + if (loc === 'b.js:2') return { verdict: 'false_positive' };// 誤報 → 剔除 + return { verdict: 'confirmed' }; // 成立 → 保留 + }; + + const result = await filterFalsePositivesWithAI(findings, [], chatFn); + assert.deepEqual(result.map(f => f.location).sort(), ['a.js:1', 'c.js:3']); + }); + it('resolveMissingLineNumbers fills missing line numbers by re-asking the role', async () => { const findings = [ { level: 'critical', role: 'Maya', location: 'app/a.js', problem: 'p', suggestion: 's' }, @@ -281,6 +298,17 @@ describe('findings exclusions', () => { assert.equal(n, 3); // 嘗試 3 次後放棄 }); + it('resolveMissingLineNumbers swallows chatFn exceptions and keeps the filename', async () => { + const findings = [{ level: 'warning', role: 'Leo', location: 'app/z.js', problem: 'p', suggestion: 's' }]; + let n = 0; + const chatFn = async () => { n += 1; throw new Error('LLM down'); }; + + await resolveMissingLineNumbers(findings, 'd', { chatFn, getRole: () => null, maxAttempts: 2 }); + + assert.equal(findings[0].location, 'app/z.js'); // 例外被吞、保留檔名、不中斷流程 + assert.equal(n, 2); // 每次嘗試仍呼叫、受上限約束 + }); + it('logs exclusions file metadata and repo state when loading exclusions', () => { const fullPath = path.join(workspace, EXCLUSIONS_PATH); fs.mkdirSync(path.dirname(fullPath), { recursive: true });