feat: 嚴重問題改用 Gitea 行內 review comment 標註檔案行數
每個新的嚴重問題改以行內 review comment 標註在問題所在的檔案與行數上, 留言內容為等級/審查員/建議;無法解析出行號(未標行號或一次列出多個 檔案),或行內留言失敗(該行不在 diff 範圍)時,降級為原本的一般 PR comment。 - gitea.js:新增 postPullReviewComment,呼叫 pull reviews API,以 new_position 對應新版檔案行號、commit_id 帶 PR_HEAD_SHA - comments.js:新增 parseLocation(支援 file:19 / file:70-82,取起始行)與 行內留言內容組裝;postNewCriticalComments 先試行內、失敗降級,deps 可注入 - 補 11 個測試(API payload、parseLocation 各情境、行內成功與兩種降級路徑) - README 更新流程第 7 步說明 app/ 測試 123 pass。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+79
-1
@@ -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 } from './comments.js';
|
||||
import { saveFindings, parseLocation, postNewCriticalComments } from './comments.js';
|
||||
import { FINDINGS_PATH } from './config.js';
|
||||
|
||||
describe('saveFindings', () => {
|
||||
@@ -73,3 +73,81 @@ describe('saveFindings', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseLocation', () => {
|
||||
it('parses file and single line', () => {
|
||||
assert.deepEqual(parseLocation('app/preflight.js:19'), { file: 'app/preflight.js', line: 19 });
|
||||
});
|
||||
|
||||
it('uses the start line for a line range', () => {
|
||||
assert.deepEqual(parseLocation('app/preflight.js:70-82'), { file: 'app/preflight.js', line: 70 });
|
||||
});
|
||||
|
||||
it('returns null when there is no line number', () => {
|
||||
assert.equal(parseLocation('app/preflight.test.js'), null);
|
||||
});
|
||||
|
||||
it('returns null when multiple files are listed', () => {
|
||||
assert.equal(parseLocation('Dockerfile, app/git.js, app/gitea.js'), null);
|
||||
});
|
||||
|
||||
it('returns null for non-string input', () => {
|
||||
assert.equal(parseLocation(undefined), null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('postNewCriticalComments', () => {
|
||||
const critical = { level: 'critical', role: 'Rex', location: 'app/preflight.js:19', suggestion: '修這個', is_new: true };
|
||||
|
||||
it('posts an inline review comment annotating file/line with level/role/suggestion', async () => {
|
||||
const inlineCalls = [];
|
||||
const issueCalls = [];
|
||||
await postNewCriticalComments([critical], {
|
||||
postInline: async (args) => { inlineCalls.push(args); },
|
||||
postIssue: async (body) => { issueCalls.push(body); },
|
||||
});
|
||||
assert.equal(inlineCalls.length, 1);
|
||||
assert.equal(issueCalls.length, 0);
|
||||
assert.equal(inlineCalls[0].path, 'app/preflight.js');
|
||||
assert.equal(inlineCalls[0].line, 19);
|
||||
assert.match(inlineCalls[0].body, /等級/);
|
||||
assert.match(inlineCalls[0].body, /審查員.*Rex/s);
|
||||
assert.match(inlineCalls[0].body, /建議.*修這個/s);
|
||||
});
|
||||
|
||||
it('falls back to a normal comment when the location has no line number', async () => {
|
||||
const inlineCalls = [];
|
||||
const issueCalls = [];
|
||||
await postNewCriticalComments([{ ...critical, location: 'app/preflight.js' }], {
|
||||
postInline: async (args) => { inlineCalls.push(args); },
|
||||
postIssue: async (body) => { issueCalls.push(body); },
|
||||
});
|
||||
assert.equal(inlineCalls.length, 0);
|
||||
assert.equal(issueCalls.length, 1);
|
||||
assert.match(issueCalls[0], /嚴重問題/);
|
||||
});
|
||||
|
||||
it('falls back to a normal comment when the inline post fails', async () => {
|
||||
const issueCalls = [];
|
||||
await postNewCriticalComments([critical], {
|
||||
postInline: async () => { throw new Error('line not in diff'); },
|
||||
postIssue: async (body) => { issueCalls.push(body); },
|
||||
});
|
||||
assert.equal(issueCalls.length, 1);
|
||||
assert.match(issueCalls[0], /嚴重問題/);
|
||||
});
|
||||
|
||||
it('only posts for new critical findings', async () => {
|
||||
const inlineCalls = [];
|
||||
const issueCalls = [];
|
||||
await postNewCriticalComments([
|
||||
{ ...critical, is_new: false },
|
||||
{ level: 'warning', role: 'Leo', location: 'a.js:1', suggestion: 'x', is_new: true },
|
||||
], {
|
||||
postInline: async (args) => { inlineCalls.push(args); },
|
||||
postIssue: async (body) => { issueCalls.push(body); },
|
||||
});
|
||||
assert.equal(inlineCalls.length, 0);
|
||||
assert.equal(issueCalls.length, 0);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user