Files
jiantw83andClaude Opus 5 8ea6a2a8b3 test(逐項實作): 覆蓋勾選的五種危險、兩份規則正本與正本第二段
勾選的測試全部繞著「會不會改錯行」打轉,五種都是 code review 抓出來的實際缺陷:
圍欄裡長得像 checkbox 的那一行不會被改到、不同段落的同一句話靠 --section 分得開、
大寫 [X] 重跑是 no-op、方框後沒有空白照樣勾得到、沒有方框的項目給的是指路的錯誤
而不是謊報已勾過。

另外釘住抽取端與勾選端的一致性:wp-extract 交得出來的每一種 raw,--tick 都要收得下。

patchOf 收進 helpers——它先前在三個測試檔裡各有一份一模一樣的定義。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 07:39:54 +00:00

116 lines
4.1 KiB
JavaScript

/**
* 假的 Gitea:測試時用 TEA_SDLC_API_BASE 指向它,腳本便不會碰到真的伺服器。
* 路由以 "METHOD /path" 為鍵,值可以是 {status, body} 或一個接 (req) 的函式。
* 每一筆進來的請求都會被記錄,讓測試可以斷言「腳本到底發了哪些請求」。
*/
import { createServer } from 'node:http';
/**
* 啟動一台假 Gitea。
* @param {Record<string, object|Function>} routes 路由表,鍵為 "METHOD /path"
* @returns {Promise<{base: string, requests: object[], close: Function}>}
*/
export async function startStubGitea(routes = {}) {
/** 收到的請求,依序記錄;測試靠它斷言請求次數與內容 */
const requests = [];
const server = createServer((req, res) => {
const chunks = [];
req.on('data', (c) => chunks.push(c));
req.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf8');
const url = new URL(req.url, 'http://127.0.0.1');
const key = `${req.method} ${url.pathname}`;
requests.push({
method: req.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams),
authorization: req.headers.authorization ?? null,
body: raw ? safeParse(raw) : null,
});
const route = routes[key];
if (route === undefined) {
res.writeHead(404, { 'content-type': 'application/json' });
res.end(JSON.stringify({ message: `stub has no route for ${key}` }));
return;
}
const { status = 200, body = null } =
typeof route === 'function' ? route(requests.at(-1)) : route;
res.writeHead(status, { 'content-type': 'application/json' });
res.end(body === null ? '' : JSON.stringify(body));
});
});
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
const { port } = server.address();
return {
base: `http://127.0.0.1:${port}/api/v1`,
requests,
close: () => new Promise((resolve) => server.close(resolve)),
};
}
/**
* 一台「什麼都正常」的 Gitea 的路由表,測試再以 overrides 局部覆寫成想要的壞掉樣子。
* @param {string} repo 目標 repo,格式 owner/name
* @param {Record<string, object|Function>} overrides 要覆寫的路由
*/
export function healthyRoutes(repo, overrides = {}) {
return {
'GET /api/v1/user': { status: 200, body: { login: 'tester' } },
[`GET /api/v1/repos/${repo}`]: {
status: 200,
body: {
full_name: repo,
has_issues: true,
permissions: { admin: false, push: true, pull: true },
internal_tracker: { enable_time_tracker: true },
},
},
// 寫入權探針:有 issues 寫入權時,Gitea 會通過權限中介層後才發現議題不存在
[`PATCH /api/v1/repos/${repo}/issues/0`]: { status: 404, body: { message: 'issue does not exist' } },
[`GET /api/v1/repos/${repo}/labels`]: {
status: 200,
body: [
{ id: 55, name: 'ready-for-agent', color: '0e8a16', description: '可交給 agent 處理' },
{ id: 56, name: '進行中', color: 'fbca04', description: '' },
],
},
...overrides,
};
}
function safeParse(raw) {
try {
return JSON.parse(raw);
} catch {
return raw;
}
}
/**
* 啟動假 Gitea 並登記在測試結束時關掉,省去每支測試各寫一次 close。
* @param {object} t node:test 的 TestContext
* @param {Record<string, object|Function>} routes
*/
export async function withStubGitea(t, routes) {
const stub = await startStubGitea(routes);
t.after(() => stub.close());
return stub;
}
/** 把腳本指向這台假 Gitea 的環境變數 */
export const stubEnv = (stub) => ({ TEA_SDLC_API_BASE: stub.base, TEA_SDLC_TOKEN: 'stub-token' });
/**
* 找出腳本真正發出的那一個 PATCH。
* 前置檢查對 `issues/0` 的探針也是 PATCH,但它打在一顆不存在的議題上、不改動任何東西,
* 不該被當成腳本的寫入(見 lib.js 的 checkIssueWrite)。
* @returns {object|undefined} 沒發出寫入時為 undefined
*/
export function patchOf(stub) {
return stub.requests.find((r) => r.method === 'PATCH' && !r.path.endsWith('/issues/0'));
}