test(delete-utils): 新增重試、狀態分流與併發上限測試
涵蓋 isRetryableStatus、deleteWithRetry(成功/暫時性重試/永久性即止/網路例外用盡) 與 runWithConcurrency(順序、併發上限、空陣列)。測試共 75 項全數通過。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9e88f5a9d5
commit
a0ef76d9d2
@@ -0,0 +1,97 @@
|
|||||||
|
import { test } from 'node:test'
|
||||||
|
import assert from 'node:assert/strict'
|
||||||
|
import {
|
||||||
|
isRetryableStatus,
|
||||||
|
deleteWithRetry,
|
||||||
|
runWithConcurrency,
|
||||||
|
} from '../delete-utils.js'
|
||||||
|
|
||||||
|
const noSleep = () => Promise.resolve()
|
||||||
|
|
||||||
|
test('isRetryableStatus 僅對暫時性狀態碼回傳 true', () => {
|
||||||
|
for (const s of [429, 502, 503, 504]) assert.equal(isRetryableStatus(s), true)
|
||||||
|
for (const s of [200, 204, 400, 401, 403, 404, 500]) {
|
||||||
|
assert.equal(isRetryableStatus(s), false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deleteWithRetry 成功(204)時不重試', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const client = {
|
||||||
|
async deleteResource() {
|
||||||
|
calls += 1
|
||||||
|
return 204
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = await deleteWithRetry(client, 'u', { sleep: noSleep })
|
||||||
|
assert.equal(result.status, 204)
|
||||||
|
assert.equal(calls, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deleteWithRetry 對暫時性錯誤重試後成功', async () => {
|
||||||
|
const statuses = [503, 503, 204]
|
||||||
|
let i = 0
|
||||||
|
const client = {
|
||||||
|
async deleteResource() {
|
||||||
|
return statuses[i++]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = await deleteWithRetry(client, 'u', { sleep: noSleep })
|
||||||
|
assert.equal(result.status, 204)
|
||||||
|
assert.equal(result.attempts, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deleteWithRetry 對永久性錯誤(403)立即停止重試', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const client = {
|
||||||
|
async deleteResource() {
|
||||||
|
calls += 1
|
||||||
|
return 403
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = await deleteWithRetry(client, 'u', { sleep: noSleep })
|
||||||
|
assert.equal(result.status, 403)
|
||||||
|
assert.equal(calls, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('deleteWithRetry 對網路例外重試至次數用盡並回傳 error', async () => {
|
||||||
|
let calls = 0
|
||||||
|
const client = {
|
||||||
|
async deleteResource() {
|
||||||
|
calls += 1
|
||||||
|
throw new Error('network down')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const result = await deleteWithRetry(client, 'u', { attempts: 3, sleep: noSleep })
|
||||||
|
assert.equal(result.status, 0)
|
||||||
|
assert.match(result.error.message, /network down/)
|
||||||
|
assert.equal(calls, 3)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('runWithConcurrency 回傳對應輸入順序的結果', async () => {
|
||||||
|
const items = [1, 2, 3, 4, 5]
|
||||||
|
const out = await runWithConcurrency(items, async (n) => n * 2, 2)
|
||||||
|
assert.deepEqual(out, [2, 4, 6, 8, 10])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('runWithConcurrency 不超過併發上限', async () => {
|
||||||
|
let active = 0
|
||||||
|
let maxActive = 0
|
||||||
|
const items = Array.from({ length: 10 }, (_, i) => i)
|
||||||
|
await runWithConcurrency(
|
||||||
|
items,
|
||||||
|
async () => {
|
||||||
|
active += 1
|
||||||
|
maxActive = Math.max(maxActive, active)
|
||||||
|
await Promise.resolve()
|
||||||
|
active -= 1
|
||||||
|
},
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
assert.ok(maxActive <= 3, `maxActive=${maxActive} 應 <= 3`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('runWithConcurrency 對空陣列回傳空陣列', async () => {
|
||||||
|
const out = await runWithConcurrency([], async () => 1, 4)
|
||||||
|
assert.deepEqual(out, [])
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user