process.stdout.write 之後立刻 process.exit 會截斷輸出——stdout 接到 pipe 時 寫入是非同步的。改為等 write 的 callback 回來再退出。實測舊寫法在約 83KB 處 被切斷,新增的回歸測試以 8000 筆標籤覆蓋這條路徑。 findIssueByTitle 的翻頁原本沒有上限,Gitea 若持續回滿一頁就會無限打下去。 加上 200 頁上限,超過即以 DEDUPE_LIMIT 報錯而非無聲回 null——無聲回 null 會 讓呼叫端把既有議題再建一次,正好是冪等查重要防的事。 parseFlags 取值時不再於三元運算式內遞增迴圈變數,改為獨立敘述。 測試工具的 maxBuffer 調高到 64MB:預設 1MB 會在長輸出時砍掉子行程,那是測試 工具的限制而非腳本的問題。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
121 lines
4.1 KiB
JavaScript
121 lines
4.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { runScript } from './helpers/run-script.js';
|
|
import { startStubGitea, healthyRoutes } from './helpers/stub-gitea.js';
|
|
|
|
const REPO = 'plugins/tea-sdlc';
|
|
|
|
/** 讓每個測試都拿到一台乾淨的假 Gitea,並在結束後關掉 */
|
|
async function withStub(t, overrides = {}) {
|
|
const stub = await startStubGitea(healthyRoutes(REPO, overrides));
|
|
t.after(() => stub.close());
|
|
return stub;
|
|
}
|
|
|
|
function envFor(stub) {
|
|
return { TEA_SDLC_API_BASE: stub.base, TEA_SDLC_TOKEN: 'stub-token' };
|
|
}
|
|
|
|
test('列出目標 repo 的既有標籤,輸出單行 JSON 且 exit 0', async (t) => {
|
|
const stub = await withStub(t);
|
|
|
|
const { code, json, stdout } = await runScript('labels-list.js', ['--repo', REPO], {
|
|
env: envFor(stub),
|
|
});
|
|
|
|
assert.equal(code, 0);
|
|
assert.equal(json.ok, true);
|
|
assert.deepEqual(
|
|
json.data.labels.map((l) => l.name),
|
|
['ready-for-agent', '進行中'],
|
|
);
|
|
assert.equal(stdout.trimEnd().includes('\n'), false, '輸出必須是單行');
|
|
});
|
|
|
|
test('輸出帶上 repo 與標籤數,供下游不必再數一次', async (t) => {
|
|
const stub = await withStub(t);
|
|
|
|
const { json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
assert.equal(json.data.repo, REPO);
|
|
assert.equal(json.data.count, 2);
|
|
});
|
|
|
|
test('帶上 Authorization,且只對 Gitea 發出讀取請求', async (t) => {
|
|
const stub = await withStub(t);
|
|
|
|
await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
const labelCalls = stub.requests.filter((r) => r.path.endsWith('/labels'));
|
|
assert.equal(labelCalls.length, 1);
|
|
assert.equal(labelCalls[0].method, 'GET');
|
|
assert.equal(labelCalls[0].authorization, 'token stub-token');
|
|
});
|
|
|
|
test('不具備建立標籤的能力:沒有任何請求會寫到 labels 端點', async (t) => {
|
|
const stub = await withStub(t);
|
|
|
|
await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
const writesToLabels = stub.requests.filter(
|
|
(r) => r.path.endsWith('/labels') && r.method !== 'GET',
|
|
);
|
|
assert.deepEqual(writesToLabels, []);
|
|
});
|
|
|
|
test('不具備建立標籤的能力:看起來像要建標籤的 flag 一律被拒', async (t) => {
|
|
const stub = await withStub(t);
|
|
|
|
const { code, json } = await runScript(
|
|
'labels-list.js',
|
|
['--repo', REPO, '--create', 'needs-triage'],
|
|
{ env: envFor(stub) },
|
|
);
|
|
|
|
assert.equal(code, 1);
|
|
assert.equal(json.ok, false);
|
|
assert.equal(json.error.code, 'UNKNOWN_FLAG');
|
|
assert.equal(stub.requests.length, 0, '參數錯誤時不該碰 Gitea');
|
|
});
|
|
|
|
test('空標籤清單不是錯誤', async (t) => {
|
|
const stub = await withStub(t, { [`GET /api/v1/repos/${REPO}/labels`]: { status: 200, body: [] } });
|
|
|
|
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
assert.equal(code, 0);
|
|
assert.deepEqual(json.data.labels, []);
|
|
assert.equal(json.data.count, 0);
|
|
});
|
|
|
|
test('Gitea 回非預期狀態碼時,帶著狀態碼失敗而不是硬吞', async (t) => {
|
|
const stub = await withStub(t, {
|
|
[`GET /api/v1/repos/${REPO}/labels`]: { status: 500, body: { message: 'boom' } },
|
|
});
|
|
|
|
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
assert.equal(code, 1);
|
|
assert.equal(json.error.code, 'HTTP_ERROR');
|
|
assert.match(json.error.message, /500/);
|
|
});
|
|
|
|
test('大量標籤不會被截斷:輸出仍是完整且可解析的單行 JSON', async (t) => {
|
|
// 回歸測試:stdout 接到 pipe 時寫入是非同步的,寫完立刻 process.exit 會截斷長輸出
|
|
const many = Array.from({ length: 8000 }, (_, i) => ({
|
|
id: i,
|
|
name: `label-${i}`,
|
|
color: 'ffffff',
|
|
description: '為了把輸出撐大到足以觸發截斷的敘述文字'.repeat(2),
|
|
}));
|
|
const stub = await withStub(t, {
|
|
[`GET /api/v1/repos/${REPO}/labels`]: { status: 200, body: many },
|
|
});
|
|
|
|
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
|
|
|
assert.equal(code, 0);
|
|
assert.equal(json.data.count, 8000);
|
|
assert.equal(json.data.labels.at(-1).name, 'label-7999');
|
|
});
|