test(issue-link): 覆蓋兩種相依、冪等與自我相依

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 06:06:05 +00:00
co-authored by Claude Opus 5
parent b1025168c0
commit b45437e527
+165
View File
@@ -0,0 +1,165 @@
/**
* 議題相依:把工作包之間的阻擋/先決關係建到 Gitea 原生的相依上,
* 讓看板呈現真實的開發順序,而不是一堆平鋪的議題。
*/
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';
const INDEX = 12;
/** 預設情境:這顆議題目前沒有任何相依 */
function routes(overrides = {}) {
return healthyRoutes(REPO, {
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/dependencies`]: { status: 200, body: [] },
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/blocks`]: { status: 200, body: [] },
[`POST /api/v1/repos/${REPO}/issues/${INDEX}/dependencies`]: { status: 201, body: {} },
[`POST /api/v1/repos/${REPO}/issues/${INDEX}/blocks`]: { status: 201, body: {} },
...overrides,
});
}
const withStub = (t, overrides = {}) => withStubGitea(t, routes(overrides));
const run = (args, stub) =>
runScript('issue-link.js', ['--repo', REPO, '--index', String(INDEX), ...args], {
env: envFor(stub),
});
const posts = (stub, kind) =>
stub.requests.filter((r) => r.method === 'POST' && r.path.endsWith(`/${kind}`));
// ── 建立相依 ───────────────────────────────────────────────────────
test('--depends 建立「這顆被誰擋住」的相依', async (t) => {
const stub = await withStub(t);
const { code, json } = await run(['--depends', '10,11'], stub);
assert.equal(code, 0);
assert.equal(json.ok, true);
assert.deepEqual(posts(stub, 'dependencies').map((r) => r.body.index), [10, 11]);
});
test('--blocks 建立「這顆擋住誰」的相依', async (t) => {
const stub = await withStub(t);
await run(['--blocks', '13'], stub);
assert.deepEqual(posts(stub, 'blocks').map((r) => r.body.index), [13]);
});
test('請求帶上 owner 與 repo:Gitea 的相依端點少了它們會回 404', async (t) => {
const stub = await withStub(t);
await run(['--depends', '10'], stub);
const [post] = posts(stub, 'dependencies');
assert.deepEqual(post.body, { owner: 'plugins', repo: 'tea-sdlc', index: 10 });
});
test('兩種相依可以一次建立', async (t) => {
const stub = await withStub(t);
const { json } = await run(['--depends', '10', '--blocks', '13'], stub);
assert.deepEqual(json.data.added, { depends: [10], blocks: [13] });
});
test('回傳這顆議題最後的相依狀態', async (t) => {
const stub = await withStub(t);
const { json } = await run(['--depends', '10'], stub);
assert.equal(json.data.index, INDEX);
assert.equal(json.data.repo, REPO);
});
// ── 冪等 ───────────────────────────────────────────────────────────
test('已經存在的相依不重建', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/dependencies`]: {
status: 200,
body: [{ number: 10 }],
},
});
const { json } = await run(['--depends', '10,11'], stub);
assert.deepEqual(posts(stub, 'dependencies').map((r) => r.body.index), [11], '只補缺的那一個');
assert.deepEqual(json.data.added.depends, [11]);
assert.deepEqual(json.data.skipped.depends, [10]);
});
test('全部都已存在時一個請求都不發', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/dependencies`]: {
status: 200,
body: [{ number: 10 }],
},
});
const { code, json } = await run(['--depends', '10'], stub);
assert.equal(code, 0);
assert.deepEqual(posts(stub, 'dependencies'), []);
assert.deepEqual(json.data.added.depends, []);
});
// ── 參數 ───────────────────────────────────────────────────────────
test('沒給任何相依時就不必問 Gitea', async (t) => {
const stub = await withStub(t);
const { code, json } = await run([], stub);
assert.equal(code, 1);
assert.equal(json.error.code, 'NOTHING_TO_LINK');
assert.equal(stub.requests.length, 0);
});
test('相依編號不是正整數時擋在打 Gitea 之前', async (t) => {
const stub = await withStub(t);
const { json } = await run(['--depends', '10,abc'], stub);
assert.equal(json.error.code, 'BAD_INDEX');
assert.equal(stub.requests.length, 0);
});
test('不能把議題設成自己的先決', async (t) => {
const stub = await withStub(t);
const { json } = await run(['--depends', String(INDEX)], stub);
assert.equal(json.error.code, 'SELF_DEPENDENCY');
assert.equal(stub.requests.length, 0);
});
// ── --dry-run ─────────────────────────────────────────────────────
test('--dry-run 列出將建立的相依,且不發出任何寫入請求', async (t) => {
const stub = await withStub(t, {
[`GET /api/v1/repos/${REPO}/issues/${INDEX}/dependencies`]: {
status: 200,
body: [{ number: 10 }],
},
});
const { code, json } = await run(['--depends', '10,11', '--blocks', '13', '--dry-run'], stub);
assert.equal(code, 0);
assert.equal(json.data.dryRun, true);
assert.deepEqual(
json.data.requests.map((r) => `${r.method} ${r.path} ${r.body.index}`),
[
`POST /repos/${REPO}/issues/${INDEX}/dependencies 11`,
`POST /repos/${REPO}/issues/${INDEX}/blocks 13`,
],
'已存在的 10 不該出現在預覽裡',
);
assert.equal(stub.requests.some((r) => r.method !== 'GET'), false);
});