Files
tea-sdlc/test/script-contract.test.js
T
jiantw83andClaude Opus 5 213c09eaa6 refactor(測試): 共用假 Gitea 的啟動與環境變數
每支測試各自寫一次「啟動 stub、登記 close、組環境變數」已經重複三次,抽到
helpers 之後新增測試檔只要一行。各檔仍保留自己的預設路由,因為那是該檔的
情境設定,不該共用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 12:44:23 +08:00

163 lines
6.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 所有腳本共用的介面契約:具名 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);
});