test(pr-comments): 議題也讀得了,不再先打 PR 端點

每個 PR 都是議題,議題不一定是 PR——原本的註解把這句話講反了,程式也照著反過來寫:
先打 /pulls/{index},對純議題回 404,於是 /sdlc-sync 在讀到第一則留言之前就斷了。

改成先讀 /issues/{index}(兩種都有),看它有沒有 pull_request 才決定要不要去翻 review。
輸出加上「類型」讓下游知道拿到的是議題還是 PR。
This commit is contained in:
2026-09-17 09:08:06 +00:00
parent fff599d0f1
commit 97e556a72e
+39 -8
View File
@@ -25,10 +25,14 @@ function routes(overrides = {}, options = {}) {
general = [],
reviews = [],
pull = { number: INDEX, title: 'feat/pr-comments/main', html_url: `https://x/${INDEX}`, state: 'open' },
isPull = true,
} = options;
const base = healthyRoutes(REPO, {
[`GET /api/v1/repos/${REPO}/pulls/${INDEX}`]: { status: 200, body: pull },
[`GET /api/v1/repos/${REPO}/issues/${INDEX}`]: {
status: 200,
body: { ...pull, ...(isPull ? { pull_request: { merged: false } } : {}) },
},
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/comments`]: {
status: 200,
body: general.map((c, i) => ({
@@ -357,18 +361,48 @@ test('帶出 PR 的識別資訊,讓回報不必再查一次', async (t) => {
assert.equal(json.data.state, 'open');
});
test('PR 不存在時回可區分的錯誤碼', async (t) => {
test('議題不存在時回可區分的錯誤碼', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}/pulls/${INDEX}`]: { status: 404, body: { message: 'not found' } },
[`GET /api/v1/repos/${REPO}/issues/${INDEX}`]: { status: 404, body: { message: 'not found' } },
}, FULL);
const { code, json } = await run([], stub);
assert.equal(code, 1);
assert.equal(json.error.code, 'PULL_NOT_FOUND');
assert.equal(json.error.code, 'ISSUE_NOT_FOUND');
assert.match(json.error.message, new RegExp(String(INDEX)));
});
test('純議題也讀得到:先讀 issue 再決定要不要翻 review', async (t) => {
// 每個 PR 都是議題,議題不一定是 PR。先打 /pulls 的話,純議題會 404,
// 而 /sdlc-sync 的輸入正是純議題——整個流程在讀到第一則留言之前就斷了
const stub = await withStub(t, {}, {
isPull: false,
general: [{ body: '這顆議題上的決策', reactions: [] }],
reviews: [],
});
const { code, json } = await run([], stub);
assert.equal(code, 0, JSON.stringify(json));
assert.equal(json.data.類型, '議題');
assert.deepEqual(json.data.留言.map((c) => c.內容), ['這顆議題上的決策']);
assert.equal(
stub.requests.some((r) => r.path.includes('/pulls/')),
false,
'純議題不該去打 PR 的端點',
);
});
test('是 PR 時類型標成 PR,並照樣讀 review', async (t) => {
const stub = await withStub(t, {}, FULL);
const { json } = await run([], stub);
assert.equal(json.data.類型, 'PR');
assert.ok(json.data.留言.some((c) => c.類型 === '總評'));
});
// ── --dry-run ─────────────────────────────────────────────────────
test('--dry-run 印出將發出的請求,且不碰 Gitea', async (t) => {
@@ -381,11 +415,8 @@ test('--dry-run 印出將發出的請求,且不碰 Gitea', async (t) => {
assert.deepEqual(
json.data.requests.map((r) => `${r.method} ${r.path}`),
[
'GET /user',
`GET /repos/${REPO}/pulls/${INDEX}`,
`GET /repos/${REPO}/issues/${INDEX}`,
`GET /repos/${REPO}/issues/${INDEX}/comments`,
`GET /repos/${REPO}/pulls/${INDEX}/reviews`,
`GET /repos/${REPO}/issues/${INDEX}/timeline`,
],
);
assert.match(json.data.note, /reaction|review/);