diff --git a/app/comments.test.js b/app/comments.test.js index 2db21fe..e91ae63 100644 --- a/app/comments.test.js +++ b/app/comments.test.js @@ -244,7 +244,7 @@ describe('postFindingsReview', () => { assert.equal(reviewSeverityLabel({ body: '**嚴重等級**:高風險' }), undefined); }); - it('posts one review with statistics and sorted line comments', async () => { + it('posts inline comments only for new findings, not old ones', async () => { const reviewCalls = []; const findings = [ { level: 'info', role: 'Maya', location: 'app/c.js:30', suggestion: 'I', is_new: true }, @@ -262,23 +262,23 @@ describe('postFindingsReview', () => { assert.match(reviewCalls[0].body, /\| 類型 \| 🔴 嚴重 \| 🟡 警告 \| 🔵 建議 \|/); assert.match(reviewCalls[0].body, /\| 舊問題 \| 1 筆 \| 0 筆 \| 0 筆 \|/); assert.match(reviewCalls[0].body, /\| 新問題 \| 0 筆 \| 1 筆 \| 1 筆 \|/); + // 舊問題 app/a.js(is_new:false)不應被行內標註,僅新問題依嚴重等級排序後標註 + assert.ok(!reviewCalls[0].comments.some(c => c.path === 'app/a.js')); assert.deepEqual( reviewCalls[0].comments.map(c => c.path), - ['app/a.js', 'app/b.js', 'app/c.js'], + ['app/b.js', 'app/c.js'], ); assert.deepEqual( reviewCalls[0].comments.map(reviewSeverityLabel), - REVIEW_SEVERITY_LABELS, + ['🟡 警告', '🔵 建議'], ); assert.deepEqual( reviewCalls[0].comments.map(c => c.new_position), - [10, 20, 30], + [20, 30], ); assert.match(reviewCalls[0].comments[0].body, /嚴重等級/); - assert.match(reviewCalls[0].comments[0].body, /審查員.*Rex/s); - assert.match(reviewCalls[0].comments[0].body, /問題.*未提供問題原因/s); - assert.doesNotMatch(reviewCalls[0].comments[0].body, /問題.*app\/a\.js:10/s); - assert.match(reviewCalls[0].comments[0].body, /建議.*C/s); + assert.match(reviewCalls[0].comments[0].body, /審查員.*Leo/s); + assert.match(reviewCalls[0].comments[0].body, /建議.*W/s); }); it('appends the usage section to the review body when provided', async () => { @@ -318,7 +318,9 @@ describe('postFindingsReview', () => { assert.equal(reviewCalls.length, 1); assert.match(reviewCalls[0].body, /\| 舊問題 \| 1 筆 \| 0 筆 \| 0 筆 \|/); assert.match(reviewCalls[0].body, /\| 新問題 \| 0 筆 \| 1 筆 \| 1 筆 \|/); - assert.equal(reviewCalls[0].comments.length, 3); + // 統計含新舊(舊問題仍計入本文),但行內 comment 只給新問題(舊 critical 不標註) + assert.equal(reviewCalls[0].comments.length, 2); + assert.ok(!reviewCalls[0].comments.some(c => c.path === 'app/a.js')); }); it('only adds comments for findings with parseable file and line', async () => { diff --git a/app/resolve.test.js b/app/resolve.test.js index 2fd755d..a93ab11 100644 --- a/app/resolve.test.js +++ b/app/resolve.test.js @@ -171,6 +171,23 @@ describe('reconcileConversations', () => { assert.deepEqual(result.carriedFindings.map(f => f.suggestion), ['fix two']); }); + it('resolves (not carries) an unresolved conversation already present in old findings', async () => { + const resolvedIds = []; + const deps = baseDeps(); + deps.judge = async (items) => items.map(it => ({ idx: it.idx, resolved: false })); // 全部未修復 + deps.resolveComment = async (id) => { resolvedIds.push(id); return { ok: true }; }; + // b.js 的問題已存在於舊問題(檔案+建議簽章相符,行號不同不影響)→ 應解決對話、不加回 + deps.oldFindings = [{ location: 'b.js:99', suggestion: 'fix two' }]; + + const result = await reconcileConversations(deps); + + assert.deepEqual(resolvedIds, [2]); // 僅 b.js(id=2) 因已存在舊問題而被 resolve + assert.equal(result.duplicateCount, 1); + assert.equal(result.resolvedCount, 0); + assert.deepEqual(result.carriedFindings.map(f => f.suggestion), ['fix one']); // a.js 不在舊問題 → 加回 + assert.deepEqual(result.resolvedFindings, []); // duplicate 不從舊問題移除 + }); + it('carries the conversation back when the resolve API call fails', async () => { const deps = baseDeps(); deps.judge = async (items) => items.map(it => ({ idx: it.idx, resolved: true }));