fix(Step9): 優先用 Gitea 狀態阻擋合併
This commit is contained in:
@@ -118,6 +118,23 @@ export async function postComment(body) {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
export async function blockPRMergeWithStatus({ sha = PR_HEAD_SHA || process.env.GITHUB_SHA, criticalCount = 0 } = {}) {
|
||||
if (!sha) throw new Error('缺少 PR head commit sha,無法建立 Gitea status');
|
||||
|
||||
const countText = criticalCount > 0 ? `${criticalCount} 個` : '';
|
||||
const resp = await axios.post(
|
||||
api(`/repos/${GITEA_REPOSITORY}/statuses/${encodeURIComponent(sha)}`),
|
||||
{
|
||||
state: 'failure',
|
||||
target_url: `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}/pulls/${PR_NUMBER}`,
|
||||
description: `AI Code Review 發現${countText}嚴重問題,請修復後再合併。`,
|
||||
context: 'ai-code-review/critical',
|
||||
},
|
||||
{ headers: headers(), timeout: 30000, httpsAgent },
|
||||
);
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 PR 指定檔案的指定行數發布行內 review comment(標註程式碼位置)。
|
||||
* 透過 Gitea 的 pull reviews API,以 new_position 對應新版檔案的行號。
|
||||
|
||||
+26
-1
@@ -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, postPullReview, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome } from './gitea.js';
|
||||
import { getPRDiff, filterDiff, postComment, postPullReviewComment, postPullReview, getCommitMessageBySha, getBranchHeadCommitMessage, shouldSkipBotCommit, getBotReviewOutcome, blockPRMergeWithStatus } from './gitea.js';
|
||||
|
||||
afterEach(() => mock.restoreAll());
|
||||
|
||||
@@ -57,6 +57,31 @@ describe('gitea', () => {
|
||||
await assert.rejects(() => postComment('test'), /api error/);
|
||||
});
|
||||
|
||||
it('blockPRMergeWithStatus creates a failure commit status on the PR head sha', async () => {
|
||||
let capturedUrl, capturedBody, capturedOpts;
|
||||
mock.method(axios, 'post', async (url, body, opts) => {
|
||||
capturedUrl = url;
|
||||
capturedBody = body;
|
||||
capturedOpts = opts;
|
||||
return { data: { id: 12 } };
|
||||
});
|
||||
|
||||
const result = await blockPRMergeWithStatus({ sha: 'abc/123', criticalCount: 3 });
|
||||
|
||||
assert.deepEqual(result, { id: 12 });
|
||||
assert.ok(capturedUrl.includes('/api/v1/repos/'));
|
||||
assert.ok(capturedUrl.endsWith('/statuses/abc%2F123'));
|
||||
assert.equal(capturedBody.state, 'failure');
|
||||
assert.equal(capturedBody.context, 'ai-code-review/critical');
|
||||
assert.ok(capturedBody.description.includes('3 個嚴重問題'));
|
||||
assert.ok(capturedBody.target_url.includes('/pulls/'));
|
||||
assert.ok(capturedOpts.headers['Authorization'].startsWith('token '));
|
||||
});
|
||||
|
||||
it('blockPRMergeWithStatus rejects when commit sha is missing', async () => {
|
||||
await assert.rejects(() => blockPRMergeWithStatus({ sha: '', criticalCount: 1 }), /缺少 PR head commit sha/);
|
||||
});
|
||||
|
||||
it('postPullReviewComment posts an inline review comment to the pulls reviews API', async () => {
|
||||
let capturedUrl, capturedBody, capturedOpts;
|
||||
mock.method(axios, 'post', async (url, body, opts) => {
|
||||
|
||||
+11
-2
@@ -1,7 +1,7 @@
|
||||
import path from 'path';
|
||||
import { GITEA_REPOSITORY, PR_NUMBER, PR_HEAD_BRANCH, PR_BASE_BRANCH, getLLMConfig, FINDINGS_PATH, EXCLUSIONS_PATH } from './config.js';
|
||||
import { loadRoles, getRoleIntro } from './roles.js';
|
||||
import { getPRDiff, postComment, getCommitMessageBySha, getBotReviewOutcome, shouldSkipBotCommit } from './gitea.js';
|
||||
import { getPRDiff, postComment, getCommitMessageBySha, getBotReviewOutcome, shouldSkipBotCommit, blockPRMergeWithStatus } from './gitea.js';
|
||||
import { analyzeWithRole, loadOldFindings, mergeFindings, sortByLevel, deduplicateWithAI, loadExclusions, applyExclusions, filterFalsePositivesWithAI } from './findings.js';
|
||||
import { saveFindings, postFindingsReview } from './comments.js';
|
||||
import { cloneRepo, commitAndPush, getRepoState } from './git.js';
|
||||
@@ -141,7 +141,16 @@ async function main() {
|
||||
step('Step9', '嚴重問題檢查');
|
||||
const criticalCount = filtered.filter(f => f.level === 'critical').length;
|
||||
if (criticalCount > 0) {
|
||||
error(`發現 ${criticalCount} 個嚴重問題,workflow 結束(exit 1)`);
|
||||
error(`發現 ${criticalCount} 個嚴重問題`);
|
||||
try {
|
||||
await blockPRMergeWithStatus({ sha: headSha, criticalCount });
|
||||
ok('已透過 Gitea API 建立 failure status 阻擋 PR 合併');
|
||||
section('Pipeline 結束');
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
warn(`透過 Gitea API 阻擋 PR 合併失敗,改用原本 workflow failure 處理: ${e.message}`);
|
||||
}
|
||||
error('workflow 結束(exit 1)');
|
||||
section('Pipeline 結束');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user