Files
tea-sdlc/test/lib-exports.test.js
T
jiantw83andClaude Opus 5 4ae097f1e1 fix(腳本契約): 長輸出不再被截斷,查重翻頁加上上限
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>
2026-09-17 04:26:51 +00:00

128 lines
4.9 KiB
JavaScript

/**
* 這一支是唯一直接 import lib 的測試,其餘一律走子行程的 CLI 邊界。
*
* 理由:冪等查重與 git 執行點是 lib 對「其他腳本」公開的契約,但本工作包只交付
* labels-list(讀取型、不碰 git、不需查重),CLI 邊界上還沒有消費者。等 #4 的
* issue-create 與 #10 的 branch-prep 落地後,它們的 CLI 測試才是這兩件事的主場,
* 屆時這支可以縮小或移除。在那之前直接測 export,好過讓契約完全沒有測試。
*/
import test from 'node:test';
import assert from 'node:assert/strict';
import { findIssueByTitle, runGit, pluginRoot, templatesDir, referencesDir } from '../scripts/lib.js';
import { startStubGitea } from './helpers/stub-gitea.js';
import { makeTempRepo } from './helpers/temp-repo.js';
import { existsSync } from 'node:fs';
import { join } from 'node:path';
const REPO = 'plugins/tea-sdlc';
// ── 冪等查重 ───────────────────────────────────────────────────────
test('查重:標題完全相同的既有議題會被找到', async (t) => {
const stub = await startStubGitea({
[`GET /api/v1/repos/${REPO}/issues`]: {
status: 200,
body: [
{ number: 7, title: '建立工作包的抽取契約' },
{ number: 8, title: '產生圖解版總覽網頁並寫回議題' },
],
},
});
t.after(() => stub.close());
const found = await findIssueByTitle(
{ base: stub.base, token: 'stub-token' },
REPO,
'產生圖解版總覽網頁並寫回議題',
);
assert.equal(found.number, 8);
});
test('查重:沒有同名議題時回 null,讓呼叫端自行決定要不要建立', async (t) => {
const stub = await startStubGitea({
[`GET /api/v1/repos/${REPO}/issues`]: { status: 200, body: [{ number: 7, title: '別的議題' }] },
});
t.after(() => stub.close());
const found = await findIssueByTitle({ base: stub.base, token: 'x' }, REPO, '找不到的標題');
assert.equal(found, null);
});
test('查重:同時看 open 與 closed,避免把已關閉的議題再建一次', async (t) => {
const stub = await startStubGitea({
[`GET /api/v1/repos/${REPO}/issues`]: { status: 200, body: [] },
});
t.after(() => stub.close());
await findIssueByTitle({ base: stub.base, token: 'x' }, REPO, '任何標題');
assert.equal(stub.requests[0].query.state, 'all');
});
test('查重:標題只差前後空白仍視為同一顆', async (t) => {
const stub = await startStubGitea({
[`GET /api/v1/repos/${REPO}/issues`]: {
status: 200,
body: [{ number: 12, title: ' 走通腳本契約 ' }],
},
});
t.after(() => stub.close());
const found = await findIssueByTitle({ base: stub.base, token: 'x' }, REPO, '走通腳本契約');
assert.equal(found.number, 12);
});
// ── git 執行點 ─────────────────────────────────────────────────────
test('git 執行點:在臨時 repo 上跑真實 git,取得目前分支', (t) => {
const repo = makeTempRepo();
t.after(() => repo.cleanup());
const branch = runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: repo.dir });
assert.equal(branch, 'master');
});
test('git 執行點:輸出前後空白被修掉,呼叫端不必自己 trim', (t) => {
const repo = makeTempRepo();
t.after(() => repo.cleanup());
const log = runGit(['log', '--oneline'], { cwd: repo.dir });
assert.equal(log.endsWith('\n'), false);
assert.match(log, /initial$/);
});
test('git 執行點:git 失敗時拋出帶有 stderr 的錯誤,不回傳空字串', (t) => {
const repo = makeTempRepo();
t.after(() => repo.cleanup());
assert.throws(() => runGit(['rev-parse', 'no-such-ref'], { cwd: repo.dir }), /no-such-ref/);
});
// ── 路徑定位 ───────────────────────────────────────────────────────
test('plugin 根由自身檔案位置回推,templates 與 references 都在底下', () => {
assert.equal(templatesDir(), join(pluginRoot(), 'templates'));
assert.equal(referencesDir(), join(pluginRoot(), 'references'));
assert.ok(existsSync(templatesDir()));
assert.ok(existsSync(referencesDir()));
});
test('查重:翻頁有上限,查不完時報錯而不是無聲回 null', async (t) => {
// 無聲回 null 會讓呼叫端把既有議題再建一次,正好是查重要防的事
const full = Array.from({ length: 50 }, (_, i) => ({ number: i, title: `不相干的議題 ${i}` }));
const stub = await startStubGitea({
[`GET /api/v1/repos/${REPO}/issues`]: { status: 200, body: full },
});
t.after(() => stub.close());
await assert.rejects(
() => findIssueByTitle({ base: stub.base, token: 'x' }, REPO, '永遠找不到的標題'),
(error) => error.code === 'DEDUPE_LIMIT',
);
});