From 995a62e04d176136ddc27dbe2983d96cf9ee55ae Mon Sep 17 00:00:00 2001 From: Jeffery Date: Mon, 22 Jun 2026 10:01:24 +0000 Subject: [PATCH] =?UTF-8?q?test(ai-review=20review):=20=E8=A3=9C=E4=B8=8A?= =?UTF-8?q?=20Review=20=E7=99=BC=E5=B8=83=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 | 65 +++++++++++++++++++++++++++++++++++++++++++- app/gitea.test.js | 28 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/app/comments.test.js b/app/comments.test.js index 78b0e0f..7e3b88a 100644 --- a/app/comments.test.js +++ b/app/comments.test.js @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; -import { saveFindings, parseLocation, postNewCriticalComments } from './comments.js'; +import { saveFindings, parseLocation, postNewCriticalComments, postFindingsReview } from './comments.js'; import { FINDINGS_PATH } from './config.js'; describe('saveFindings', () => { @@ -185,3 +185,66 @@ describe('postNewCriticalComments', () => { assert.ok(issueCalls.every(b => criticalCommentPattern.test(b))); }); }); + +describe('postFindingsReview', () => { + it('posts one review with statistics and sorted line comments', async () => { + const reviewCalls = []; + const findings = [ + { level: 'info', role: 'Maya', location: 'app/c.js:30', suggestion: 'I', is_new: true }, + { level: 'critical', role: 'Rex', location: 'app/a.js:10', suggestion: 'C', is_new: false }, + { level: 'warning', role: 'Leo', location: 'app/b.js:20', suggestion: 'W', is_new: true }, + ]; + + await postFindingsReview(findings, { + postReview: async (args) => { reviewCalls.push(args); }, + }); + + assert.equal(reviewCalls.length, 1); + assert.match(reviewCalls[0].body, /總問題:3 筆/); + assert.match(reviewCalls[0].body, /可標註檔案與行數:3 筆/); + assert.match(reviewCalls[0].body, /嚴重:1 筆/); + assert.match(reviewCalls[0].body, /警告:1 筆/); + assert.match(reviewCalls[0].body, /建議:1 筆/); + assert.deepEqual( + reviewCalls[0].comments.map(c => c.path), + ['app/a.js', 'app/b.js', 'app/c.js'], + ); + assert.deepEqual( + reviewCalls[0].comments.map(c => c.new_position), + [10, 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, /問題.*app\/a\.js:10/s); + assert.match(reviewCalls[0].comments[0].body, /建議.*C/s); + }); + + it('only adds comments for findings with parseable file and line', async () => { + const reviewCalls = []; + await postFindingsReview([ + { level: 'critical', role: 'Rex', location: 'app/a.js', suggestion: 'missing line', is_new: true }, + { level: 'warning', role: 'Leo', location: 'app/b.js:20', suggestion: 'line', is_new: true }, + ], { + postReview: async (args) => { reviewCalls.push(args); }, + }); + + assert.equal(reviewCalls.length, 1); + assert.match(reviewCalls[0].body, /總問題:2 筆/); + assert.match(reviewCalls[0].body, /可標註檔案與行數:1 筆/); + assert.match(reviewCalls[0].body, /無法標註檔案與行數:1 筆/); + assert.equal(reviewCalls[0].comments.length, 1); + assert.equal(reviewCalls[0].comments[0].path, 'app/b.js'); + }); + + it('uses an explicit problem field when present', async () => { + const reviewCalls = []; + await postFindingsReview([ + { level: 'warning', role: 'Leo', location: 'app/a.js:5', problem: '命名不清楚', suggestion: '改成具體名稱' }, + ], { + postReview: async (args) => { reviewCalls.push(args); }, + }); + + assert.match(reviewCalls[0].comments[0].body, /問題.*命名不清楚/s); + assert.match(reviewCalls[0].comments[0].body, /建議.*改成具體名稱/s); + }); +}); diff --git a/app/gitea.test.js b/app/gitea.test.js index 89c5a3a..de76d51 100644 --- a/app/gitea.test.js +++ b/app/gitea.test.js @@ -1,7 +1,7 @@ import { describe, it, afterEach, mock } from 'node:test'; import assert from 'node:assert/strict'; import axios from 'axios'; -import { getPRDiff, filterDiff, postComment, postPullReviewComment, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome } from './gitea.js'; +import { getPRDiff, filterDiff, postComment, postPullReviewComment, postPullReview, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome } from './gitea.js'; afterEach(() => mock.restoreAll()); @@ -82,6 +82,32 @@ describe('gitea', () => { await assert.rejects(() => postPullReviewComment({ path: 'a.js', line: 1, body: 'x' }), /not in diff/); }); + it('postPullReview posts one review with multiple comments', async () => { + let capturedUrl, capturedBody, capturedOpts; + mock.method(axios, 'post', async (url, body, opts) => { + capturedUrl = url; + capturedBody = body; + capturedOpts = opts; + return { data: { id: 9 } }; + }); + + const result = await postPullReview({ + body: 'summary', + comments: [{ path: 'app/a.js', new_position: 10, body: 'comment' }], + }); + + assert.deepEqual(result, { id: 9 }); + assert.ok(capturedUrl.includes('/api/v1/repos/')); + assert.ok(capturedUrl.endsWith('/reviews')); + assert.equal(capturedBody.event, 'COMMENT'); + assert.equal(capturedBody.body, 'summary'); + assert.equal(capturedBody.comments.length, 1); + assert.equal(capturedBody.comments[0].path, 'app/a.js'); + assert.equal(capturedBody.comments[0].new_position, 10); + assert.equal(capturedBody.comments[0].body, 'comment'); + assert.ok(capturedOpts.headers['Authorization'].startsWith('token ')); + }); + it('getCommitMessageBySha reads commit message from Gitea API', async () => { let capturedUrl; mock.method(axios, 'get', async (url) => {