From 4a0f8bcc36fda1b68432ac6c630246727cb908b5 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Tue, 23 Jun 2026 14:58:15 +0800 Subject: [PATCH] =?UTF-8?q?test(ai-review):=20=E8=A3=9C=E9=97=9C=E9=96=89?= =?UTF-8?q?=E6=89=80=E6=9C=89=20comment=E3=80=81=E9=83=A8=E5=88=86=20resol?= =?UTF-8?q?ve=20=E5=A4=B1=E6=95=97=E3=80=81=E7=99=BE=E5=88=86=E6=AF=94?= =?UTF-8?q?=E9=82=8A=E7=95=8C=E8=88=87=20usageSection=20=E6=8E=92=E7=89=88?= =?UTF-8?q?=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 | 2 ++ app/resolve.test.js | 33 +++++++++++++++++++++++++++++++++ app/usage.test.js | 7 +++++++ 3 files changed, 42 insertions(+) diff --git a/app/comments.test.js b/app/comments.test.js index e91ae63..06b3fbc 100644 --- a/app/comments.test.js +++ b/app/comments.test.js @@ -303,6 +303,8 @@ describe('postFindingsReview', () => { }); assert.doesNotMatch(reviewCalls[0].body, /AI 助理使用量/); + // usageSection 省略時,body 不應殘留多餘的尾端空白/換行 + assert.equal(reviewCalls[0].body, reviewCalls[0].body.trimEnd()); }); it('separates old and new findings in default review statistics', async () => { diff --git a/app/resolve.test.js b/app/resolve.test.js index bb1bc22..d38ef77 100644 --- a/app/resolve.test.js +++ b/app/resolve.test.js @@ -170,6 +170,39 @@ describe('reconcileConversations', () => { assert.deepEqual(result.carriedFindings, []); }); + it('resolves every unresolved comment id, not just the first per path/line group', async () => { + const closedIds = []; + const deps = baseDeps(); + deps.listComments = async () => [ + { id: 10, path: 'a.js', position: 5, body: reviewBody('🔴 嚴重', 'Assassin', 'p', 's10') }, + { id: 11, path: 'a.js', position: 5, body: reviewBody('🔴 嚴重', 'Mage', 'p', 's11') }, // 同 path|line → 同一組 + { id: 12, path: '', position: 0, body: 'no path' }, // 無 path → 不分組但仍要關 + { id: 13, path: 'b.js', position: 8, body: 'done', resolver: { login: 'dev' } }, // 已 resolve → 不關 + ]; + deps.resolveComment = async (id) => { closedIds.push(id); return { ok: true }; }; + deps.judge = async (items) => items.map(it => ({ idx: it.idx, verdict: 'open' })); + + const result = await reconcileConversations(deps); + + // 同組的 10、11 都關,無 path 的 12 也關;已 resolve 的 13 不關 + assert.deepEqual(closedIds.sort((a, b) => a - b), [10, 11, 12]); + assert.equal(result.closedCount, 3); + }); + + it('counts only successful closes when some resolve calls fail', async () => { + const deps = baseDeps(); + // a.js(id1) 關閉成功、b.js(id2) 關閉失敗(c.js 已 resolved 略過) + deps.resolveComment = async (id) => { if (id === 2) throw new Error('403'); return { ok: true }; }; + deps.judge = async (items) => items.map(it => ({ idx: it.idx, verdict: 'resolved' })); + + const result = await reconcileConversations(deps); + + assert.equal(result.closedCount, 1); // 僅 id1 成功關閉 + // findings 分流不受 resolve 成敗影響:兩個都判 resolved + assert.equal(result.resolvedCount, 2); + assert.deepEqual(result.resolvedFindings.map(f => f.location).sort(), ['a.js:10', 'b.js:20']); + }); + it('carries open-verdict conversations into findings while still closing them', async () => { const closedIds = []; const deps = baseDeps(); diff --git a/app/usage.test.js b/app/usage.test.js index 20afd4e..76d9f5d 100644 --- a/app/usage.test.js +++ b/app/usage.test.js @@ -192,6 +192,13 @@ describe('resolveRemainingPercent', () => { assert.equal(pct.percent, null); // limit > 0 守衛擋掉除以零 assert.ok(typeof pct.reason === 'string' && pct.reason.length > 0); }); + + it('reports 100% when remaining equals limit and 0% when remaining is 0', () => { + const full = resolveRemainingPercent({ available: true, used: 0, limit: 100, remaining: 100, currency: 'USD' }, null); + assert.equal(full.percent, 100); + const empty = resolveRemainingPercent({ available: true, used: 100, limit: 100, remaining: 0, currency: 'USD' }, null); + assert.equal(empty.percent, 0); + }); }); describe('formatUsageStats', () => {