每支測試各自寫一次「啟動 stub、登記 close、組環境變數」已經重複三次,抽到 helpers 之後新增測試檔只要一行。各檔仍保留自己的預設路由,因為那是該檔的 情境設定,不該共用。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
163 lines
6.0 KiB
JavaScript
163 lines
6.0 KiB
JavaScript
/**
|
||
* 所有腳本共用的介面契約:具名 flag 輸入、單行 JSON 輸出、--dry-run、
|
||
* 以及「從自身檔案位置回推 plugin 根」而非依賴 cwd。
|
||
* 以 labels-list 當代表,因為契約由 lib 提供,每支腳本表現一致。
|
||
*/
|
||
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { runScript } from './helpers/run-script.js';
|
||
import { healthyRoutes, stubEnv as envFor, withStubGitea } from './helpers/stub-gitea.js';
|
||
|
||
const REPO = 'plugins/tea-sdlc';
|
||
|
||
async function withStub(t, overrides = {}) {
|
||
return withStubGitea(t, healthyRoutes(REPO, overrides));
|
||
}
|
||
|
||
|
||
// ── 輸入:具名 flag ────────────────────────────────────────────────
|
||
|
||
test('缺少必填 flag 時失敗,並指名缺的是哪一個', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { code, json } = await runScript('labels-list.js', [], { env: envFor(stub) });
|
||
|
||
assert.equal(code, 1);
|
||
assert.equal(json.ok, false);
|
||
assert.equal(json.error.code, 'MISSING_FLAG');
|
||
assert.match(json.error.message, /--repo/);
|
||
});
|
||
|
||
test('不認得的 flag 一律拒絕,不默默忽略', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { json } = await runScript('labels-list.js', ['--repo', REPO, '--verbose'], {
|
||
env: envFor(stub),
|
||
});
|
||
|
||
assert.equal(json.error.code, 'UNKNOWN_FLAG');
|
||
assert.match(json.error.message, /--verbose/);
|
||
});
|
||
|
||
test('位置參數一律拒絕:輸入只接受具名 flag', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { json } = await runScript('labels-list.js', [REPO], { env: envFor(stub) });
|
||
|
||
assert.equal(json.error.code, 'UNKNOWN_FLAG');
|
||
});
|
||
|
||
test('--repo 格式不是 owner/name 時失敗', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { json } = await runScript('labels-list.js', ['--repo', 'tea-sdlc'], { env: envFor(stub) });
|
||
|
||
assert.equal(json.error.code, 'BAD_REPO');
|
||
});
|
||
|
||
test('--key=value 與 --key value 兩種寫法等價', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const a = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
||
const b = await runScript('labels-list.js', [`--repo=${REPO}`], { env: envFor(stub) });
|
||
|
||
assert.deepEqual(a.json, b.json);
|
||
});
|
||
|
||
// ── 輸出:單行 JSON ───────────────────────────────────────────────
|
||
|
||
test('成功時輸出 {ok:true, data},且沒有 error 欄位', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
||
|
||
assert.equal(json.ok, true);
|
||
assert.ok(json.data);
|
||
assert.equal('error' in json, false);
|
||
});
|
||
|
||
test('失敗時輸出 {ok:false, error:{code,message}},且沒有 data 欄位', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { json } = await runScript('labels-list.js', [], { env: envFor(stub) });
|
||
|
||
assert.equal(json.ok, false);
|
||
assert.equal(typeof json.error.code, 'string');
|
||
assert.equal(typeof json.error.message, 'string');
|
||
assert.equal('data' in json, false);
|
||
});
|
||
|
||
test('失敗時 exit code 為 1,成功為 0', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const bad = await runScript('labels-list.js', [], { env: envFor(stub) });
|
||
const good = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
||
|
||
assert.equal(bad.code, 1);
|
||
assert.equal(good.code, 0);
|
||
});
|
||
|
||
test('例外不會以 stack trace 洩漏出去,一律收斂成 JSON', async (t) => {
|
||
// 指向一個沒有人在聽的 port,讓 fetch 直接拋連線錯誤
|
||
const { code, json, stderr } = await runScript('labels-list.js', ['--repo', REPO], {
|
||
env: { TEA_SDLC_API_BASE: 'http://127.0.0.1:1/api/v1', TEA_SDLC_TOKEN: 'x' },
|
||
});
|
||
|
||
assert.equal(code, 1);
|
||
assert.equal(json.ok, false);
|
||
assert.equal(json.error.code, 'NETWORK_ERROR');
|
||
assert.equal(stderr, '', 'stderr 必須乾淨,訊息一律走 stdout 的 JSON');
|
||
});
|
||
|
||
// ── --dry-run ─────────────────────────────────────────────────────
|
||
|
||
test('--dry-run 印出將發出的請求,且完全不碰 Gitea', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const { code, json } = await runScript('labels-list.js', ['--repo', REPO, '--dry-run'], {
|
||
env: envFor(stub),
|
||
});
|
||
|
||
assert.equal(code, 0);
|
||
assert.equal(json.ok, true);
|
||
assert.equal(json.data.dryRun, true);
|
||
assert.deepEqual(json.data.requests, [
|
||
{ method: 'GET', path: `/repos/${REPO}/labels` },
|
||
]);
|
||
assert.equal(stub.requests.length, 0, '--dry-run 不得發出任何請求,連前置檢查也不發');
|
||
});
|
||
|
||
// ── 路徑定位 ───────────────────────────────────────────────────────
|
||
|
||
test('在任意 cwd 下執行結果相同:腳本從自身位置回推 plugin 根', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const fromRoot = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
|
||
const fromSlash = await runScript('labels-list.js', ['--repo', REPO], {
|
||
env: envFor(stub),
|
||
cwd: '/',
|
||
});
|
||
const fromTmp = await runScript('labels-list.js', ['--repo', REPO], {
|
||
env: envFor(stub),
|
||
cwd: '/tmp',
|
||
});
|
||
|
||
assert.equal(fromRoot.code, 0);
|
||
assert.deepEqual(fromSlash.json, fromRoot.json);
|
||
assert.deepEqual(fromTmp.json, fromRoot.json);
|
||
});
|
||
|
||
test('--dry-run 回報的 templates/references 路徑為絕對路徑,且不隨 cwd 改變', async (t) => {
|
||
const stub = await withStub(t);
|
||
|
||
const a = await runScript('labels-list.js', ['--repo', REPO, '--dry-run'], { env: envFor(stub) });
|
||
const b = await runScript('labels-list.js', ['--repo', REPO, '--dry-run'], {
|
||
env: envFor(stub),
|
||
cwd: '/tmp',
|
||
});
|
||
|
||
assert.match(a.json.data.paths.templates, /^\/.*\/templates$/);
|
||
assert.match(a.json.data.paths.references, /^\/.*\/references$/);
|
||
assert.deepEqual(b.json.data.paths, a.json.data.paths);
|
||
});
|