From b9d7c4e97038bb431d2273ba89e82dff6f50ee02 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Fri, 26 Jun 2026 11:04:29 +0800 Subject: [PATCH] =?UTF-8?q?test(release-cleanup):=20=E8=A3=9C=E5=BC=B7?= =?UTF-8?q?=E9=80=BE=E6=99=82=E3=80=81=E9=9D=9E=E9=99=A3=E5=88=97=E3=80=81?= =?UTF-8?q?token=20=E9=82=8A=E7=95=8C=E8=88=87=20failError=20=E6=B8=AC?= =?UTF-8?q?=E8=A9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 fetchAllPages 的 AbortError 逾時與非陣列回應測試、loadConfig 的 GITEA_TOKEN 邊界測試,以及 logger.failError 測試。測試共 47 項全數通過。 Co-Authored-By: Claude Opus 4.8 (1M context) --- app/test/config.test.js | 14 ++++++++++++++ app/test/gitea-client.test.js | 22 ++++++++++++++++++++++ app/test/logger.test.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) create mode 100644 app/test/logger.test.js diff --git a/app/test/config.test.js b/app/test/config.test.js index cd1975c..4cea9e7 100644 --- a/app/test/config.test.js +++ b/app/test/config.test.js @@ -56,3 +56,17 @@ test('loadConfig 在提供 token 時保留 token 值', () => { const cfg = loadConfig({ ...base, GITEA_TOKEN: 'secret' }) assert.equal(cfg.token, 'secret') }) + +test('loadConfig 在 GITEA_TOKEN 為空字串時視為匿名(token 為 null)', () => { + const cfg = loadConfig({ ...base, GITEA_TOKEN: '' }) + assert.equal(cfg.token, null) +}) + +test('loadConfig 接受極短與極長的 GITEA_TOKEN', () => { + const short = loadConfig({ ...base, GITEA_TOKEN: 'x' }) + assert.equal(short.token, 'x') + + const longToken = 'a'.repeat(1000) + const long = loadConfig({ ...base, GITEA_TOKEN: longToken }) + assert.equal(long.token, longToken) +}) diff --git a/app/test/gitea-client.test.js b/app/test/gitea-client.test.js index c6b82c7..423ad4b 100644 --- a/app/test/gitea-client.test.js +++ b/app/test/gitea-client.test.js @@ -90,6 +90,28 @@ test('fetchAllPages 在回應非 JSON 時丟出例外', async () => { ) }) +test('fetchAllPages 在回應為非陣列 JSON 時丟出例外', async () => { + globalThis.fetch = async () => jsonResponse({ message: 'error object' }) + const client = new GiteaClient({}) + await assert.rejects( + () => client.fetchAllPages('https://example.com/api'), + /non-array JSON payload/, + ) +}) + +test('fetchAllPages 在 fetch 因逾時拋出 AbortError 時向外拋出', async () => { + globalThis.fetch = async () => { + const err = new Error('The operation was aborted due to timeout') + err.name = 'AbortError' + throw err + } + const client = new GiteaClient({}) + await assert.rejects( + () => client.fetchAllPages('https://example.com/api'), + (err) => err.name === 'AbortError', + ) +}) + test('deleteResource 回傳 HTTP 狀態碼', async () => { globalThis.fetch = async (url, opts) => { assert.equal(opts.method, 'DELETE') diff --git a/app/test/logger.test.js b/app/test/logger.test.js new file mode 100644 index 0000000..edd0bcd --- /dev/null +++ b/app/test/logger.test.js @@ -0,0 +1,35 @@ +import { test, afterEach } from 'node:test' +import assert from 'node:assert/strict' +import { failError } from '../logger.js' + +const realWrite = process.stderr.write + +afterEach(() => { + process.stderr.write = realWrite +}) + +// 攔截寫入 stderr 的內容 +function captureStderr(fn) { + const chunks = [] + process.stderr.write = (chunk) => { + chunks.push(String(chunk)) + return true + } + try { + fn() + } finally { + process.stderr.write = realWrite + } + return chunks.join('') +} + +test('failError 對 Error 物件輸出名稱、訊息與堆疊', () => { + const out = captureStderr(() => failError(new TypeError('boom'))) + assert.match(out, /\[ERR\] TypeError: boom/) + assert.match(out, /at /) // 堆疊內容 +}) + +test('failError 對非 Error 值輸出其字串形式', () => { + const out = captureStderr(() => failError('plain failure')) + assert.match(out, /\[ERR\] plain failure/) +})