diff --git a/app/test/gitea-client.test.js b/app/test/gitea-client.test.js index deb6c01..460391f 100644 --- a/app/test/gitea-client.test.js +++ b/app/test/gitea-client.test.js @@ -142,6 +142,18 @@ test('fetchAllPages 的錯誤訊息會清理控制字元並限制長度(sanitize assert.ok((message.match(/x/g) || []).length <= 200) }) +test('fetchAllPages 在第 MAX_PAGES 頁回傳空陣列時正常結束(不拋例外)', async () => { + let page = 0 + globalThis.fetch = async () => { + page += 1 + // 前 999 頁有資料,第 1000 頁(MAX_PAGES)回空陣列,應正常結束 + return jsonResponse(page < 1000 ? [{ id: page }] : []) + } + const client = new GiteaClient({}) + const items = await client.fetchAllPages('https://example.com/api') + assert.equal(items.length, 999) +}) + test('deleteResource 回傳 HTTP 狀態碼', async () => { globalThis.fetch = async (url, opts) => { assert.equal(opts.method, 'DELETE') diff --git a/app/test/releases.test.js b/app/test/releases.test.js index 0f3e53b..500b671 100644 --- a/app/test/releases.test.js +++ b/app/test/releases.test.js @@ -103,6 +103,28 @@ test('cleanupReleases 對 id 進行 URL 編碼(防路徑穿越)', async () => { assert.deepEqual(client.deleted, ['http://x/releases/..%2Fevil']) }) +test('cleanupReleases 在刪除回傳非 204 時記錄錯誤至 stderr', async () => { + const client = fakeClient( + [ + { id: 1, tag_name: 'v1', name: 'n1', created_at: '2024-01-01T00:00:00Z' }, + { id: 2, tag_name: 'v2', name: 'n2', created_at: '2024-02-01T00:00:00Z' }, + ], + () => 500, + ) + const realWrite = process.stderr.write + const chunks = [] + process.stderr.write = (chunk) => { + chunks.push(String(chunk)) + return true + } + try { + await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 1 }) + } finally { + process.stderr.write = realWrite + } + assert.match(chunks.join(''), /\[ERR\]/) +}) + test('cleanupReleases 略過沒有 id 的 release', async () => { const client = fakeClient([ { id: 1, tag_name: 'v3', name: 'n3', created_at: '2024-03-01T00:00:00Z' }, diff --git a/app/test/tags.test.js b/app/test/tags.test.js index 93c1988..226c4f9 100644 --- a/app/test/tags.test.js +++ b/app/test/tags.test.js @@ -87,6 +87,33 @@ test('cleanupOrphanTags 在沒有任何 release 時刪除所有具名 tag', asyn assert.deepEqual(client.deleted, ['http://x/tags/v2.0.0', 'http://x/tags/v1.0.0']) }) +test('cleanupOrphanTags 在刪除回傳非 204 時繼續處理其餘 tag 並記錄錯誤', async () => { + const deleted = [] + const client = { + async fetchAllPages(url) { + return url.includes('/releases') ? [] : [{ name: 'v1' }, { name: 'v2' }] + }, + async deleteResource(url) { + deleted.push(url) + return 500 + }, + } + const realWrite = process.stderr.write + const chunks = [] + process.stderr.write = (chunk) => { + chunks.push(String(chunk)) + return true + } + try { + await cleanupOrphanTags(client, config) + } finally { + process.stderr.write = realWrite + } + // 兩個孤立 tag 都被嘗試刪除(失敗不中斷),且有錯誤記錄 + assert.equal(deleted.length, 2) + assert.match(chunks.join(''), /\[ERR\]/) +}) + test('cleanupOrphanTags 對含特殊字元的 tag 名稱進行 URL 編碼(防路徑穿越)', async () => { const client = fakeClient({ releases: [],