Files
tea-sdlc/test/preflight.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

232 lines
8.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { runScript, pathWithOnly } 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));
}
// ── 第一層:執行環境 ────────────────────────────────────────────────
test('第一層:PATH 上沒有 git 時中止,錯誤訊息指名 git', async (t) => {
const stub = await withStub(t);
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], {
env: envFor(stub),
path: pathWithOnly(['tea']),
});
assert.equal(code, 1);
assert.equal(json.error.code, 'ENV_MISSING');
assert.match(json.error.message, /git/);
assert.equal(stub.requests.length, 0, '環境不通過就不該發請求');
});
test('第一層:PATH 上沒有 tea 時中止,錯誤訊息指名 tea 並給出安裝指引', async (t) => {
const stub = await withStub(t);
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], {
env: envFor(stub),
path: pathWithOnly(['git']),
});
assert.equal(code, 1);
assert.equal(json.error.code, 'ENV_MISSING');
assert.match(json.error.message, /tea/);
});
// ── 第二層:Gitea 登入 ─────────────────────────────────────────────
test('第二層:token 無效時中止,訊息指向 tea login', async (t) => {
const stub = await withStub(t, {
'GET /api/v1/user': { status: 401, body: { message: 'unauthorized' } },
});
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(code, 1);
assert.equal(json.error.code, 'LOGIN_INVALID');
assert.match(json.error.message, /tea login/);
});
test('第二層:找不到任何登入資訊時中止', async (t) => {
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], {
env: { TEA_SDLC_API_BASE: '', TEA_SDLC_TOKEN: '', TEA_SDLC_CONFIG: '/nonexistent/tea.yml' },
});
assert.equal(code, 1);
assert.equal(json.error.code, 'LOGIN_INVALID');
});
// ── 第三層:issues unit 寫入權 ─────────────────────────────────────
test('第三層:repo 不存在或讀不到時,錯誤碼與權限問題分得開', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}`]: { status: 404, body: { message: 'not found' } },
});
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(code, 1);
assert.equal(json.error.code, 'REPO_NOT_FOUND');
});
test('第三層:repo 關閉議題功能時中止,訊息指出開啟位置', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}`]: {
status: 200,
body: {
has_issues: false,
permissions: { admin: false, push: true, pull: true },
internal_tracker: { enable_time_tracker: true },
},
},
});
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(code, 1);
assert.equal(json.error.code, 'ISSUES_UNIT_OFF');
assert.match(json.error.message, /Settings/);
});
test('第三層:對 issues unit 實測寫入權,被擋下時中止', async (t) => {
const stub = await withStub(t, {
[`PATCH /api/v1/repos/${REPO}/issues/0`]: { status: 403, body: { message: 'forbidden' } },
});
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(code, 1);
assert.equal(json.error.code, 'NO_ISSUE_WRITE');
assert.match(json.error.message, /issues/i);
});
test('第三層:不能只看 permissions.push——push 為真但 issues unit 被擋,仍須失敗', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}`]: {
status: 200,
body: {
has_issues: true,
// team 的 unit 權限可獨立於 repo 的 push 權限,所以這裡是真的也不算數
permissions: { admin: false, push: true, pull: true },
internal_tracker: { enable_time_tracker: true },
},
},
[`PATCH /api/v1/repos/${REPO}/issues/0`]: { status: 403, body: { message: 'forbidden' } },
});
const { json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(json.error.code, 'NO_ISSUE_WRITE');
});
test('第三層:探針打在不存在的議題 index 0,確保無副作用', async (t) => {
const stub = await withStub(t);
await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
const probes = stub.requests.filter((r) => r.method === 'PATCH');
assert.equal(probes.length, 1);
assert.equal(probes[0].path, `/api/v1/repos/${REPO}/issues/0`);
assert.deepEqual(probes[0].body, {}, '探針不得挾帶任何要寫入的欄位');
});
// ── 第四層:時間追蹤 ───────────────────────────────────────────────
test('第四層:時間追蹤未開啟時中止,訊息指出 Enable Time Tracker 的位置', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}`]: {
status: 200,
body: {
has_issues: true,
permissions: { admin: false, push: true, pull: true },
internal_tracker: { enable_time_tracker: false },
},
},
});
const { code, json } = await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.equal(code, 1);
assert.equal(json.error.code, 'TIME_TRACKER_OFF');
assert.match(json.error.message, /Settings → Advanced Settings → Enable Time Tracker/);
});
// ── 四層之間 ───────────────────────────────────────────────────────
test('四層的錯誤碼兩兩相異,呼叫端分得出是哪一層壞了', async (t) => {
const cases = [
{
code: 'ENV_MISSING',
run: async () => {
const stub = await withStub(t);
return runScript('labels-list.js', ['--repo', REPO], {
env: envFor(stub),
path: pathWithOnly(['git']),
});
},
},
{
code: 'LOGIN_INVALID',
run: async () => {
const stub = await withStub(t, {
'GET /api/v1/user': { status: 401, body: {} },
});
return runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
},
},
{
code: 'NO_ISSUE_WRITE',
run: async () => {
const stub = await withStub(t, {
[`PATCH /api/v1/repos/${REPO}/issues/0`]: { status: 403, body: {} },
});
return runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
},
},
{
code: 'TIME_TRACKER_OFF',
run: async () => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}`]: {
status: 200,
body: {
has_issues: true,
permissions: { admin: true, push: true, pull: true },
internal_tracker: { enable_time_tracker: false },
},
},
});
return runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
},
},
];
const seen = [];
for (const { code, run } of cases) {
const { json } = await run();
assert.equal(json.error.code, code);
seen.push(json.error.code);
}
assert.equal(new Set(seen).size, seen.length);
});
test('前一層失敗就停手,不會再往下打', async (t) => {
const stub = await withStub(t, {
'GET /api/v1/user': { status: 401, body: {} },
});
await runScript('labels-list.js', ['--repo', REPO], { env: envFor(stub) });
assert.deepEqual(
stub.requests.map((r) => r.path),
['/api/v1/user'],
'登入檢查沒過就不該再查 repo 或標籤',
);
});