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'); });