feat/wp-schedule-and-board/main #31
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user