新增 fetchAllPages 超過 MAX_PAGES 中止的測試,以及 cleanupReleases/ cleanupOrphanTags 對含 ../ 的 id/tag 名稱編碼的測試。測試共 50 項全數通過。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
116 lines
4.6 KiB
JavaScript
116 lines
4.6 KiB
JavaScript
import { test } from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { selectReleasesToDelete, cleanupReleases } from '../releases.js'
|
|
|
|
const releases = [
|
|
{ id: 1, tag_name: 'v1.0.0', created_at: '2024-01-01T00:00:00Z' },
|
|
{ id: 3, tag_name: 'v3.0.0', created_at: '2024-03-01T00:00:00Z' },
|
|
{ id: 2, tag_name: 'v2.0.0', created_at: '2024-02-01T00:00:00Z' },
|
|
]
|
|
|
|
test('selectReleasesToDelete 保留最新的 keepCount 筆,刪除其餘', () => {
|
|
const toDelete = selectReleasesToDelete(releases, 2)
|
|
assert.equal(toDelete.length, 1)
|
|
assert.equal(toDelete[0].tag_name, 'v1.0.0')
|
|
})
|
|
|
|
test('selectReleasesToDelete 在數量不超過保留數時回傳空陣列', () => {
|
|
assert.deepEqual(selectReleasesToDelete(releases, 3), [])
|
|
assert.deepEqual(selectReleasesToDelete(releases, 5), [])
|
|
})
|
|
|
|
test('selectReleasesToDelete 在 keepCount 為 0 時刪除全部(由新到舊)', () => {
|
|
const toDelete = selectReleasesToDelete(releases, 0)
|
|
assert.deepEqual(
|
|
toDelete.map((r) => r.tag_name),
|
|
['v3.0.0', 'v2.0.0', 'v1.0.0'],
|
|
)
|
|
})
|
|
|
|
test('selectReleasesToDelete 不改動原陣列', () => {
|
|
const original = [...releases]
|
|
selectReleasesToDelete(releases, 1)
|
|
assert.deepEqual(releases, original)
|
|
})
|
|
|
|
test('selectReleasesToDelete 對無效 created_at 視為最舊,不產生 NaN 排序', () => {
|
|
const list = [
|
|
{ id: 1, tag_name: 'good', created_at: '2024-05-01T00:00:00Z' },
|
|
{ id: 2, tag_name: 'bad', created_at: 'not-a-date' },
|
|
{ id: 3, tag_name: 'empty' },
|
|
]
|
|
const toDelete = selectReleasesToDelete(list, 1)
|
|
// 最新的 good 被保留;兩筆無效日期視為最舊,落入待刪除
|
|
assert.equal(toDelete.length, 2)
|
|
assert.ok(!toDelete.some((r) => r.tag_name === 'good'))
|
|
})
|
|
|
|
// 建立可記錄刪除呼叫的假 client
|
|
function fakeClient(releases, statusFor = () => 204) {
|
|
const deleted = []
|
|
return {
|
|
deleted,
|
|
async fetchAllPages() {
|
|
return releases
|
|
},
|
|
async deleteResource(url) {
|
|
deleted.push(url)
|
|
return statusFor(url)
|
|
},
|
|
}
|
|
}
|
|
|
|
test('cleanupReleases 刪除超出保留數的舊 release', 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' },
|
|
{ id: 3, tag_name: 'v3', name: 'n3', created_at: '2024-03-01T00:00:00Z' },
|
|
])
|
|
await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 1 })
|
|
// 保留最新 v3,刪除 v2 與 v1
|
|
assert.deepEqual(client.deleted, ['http://x/releases/2', 'http://x/releases/1'])
|
|
})
|
|
|
|
test('cleanupReleases 在未超過保留數時不刪除', async () => {
|
|
const client = fakeClient([
|
|
{ id: 1, tag_name: 'v1', name: 'n1', created_at: '2024-01-01T00:00:00Z' },
|
|
])
|
|
await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 2 })
|
|
assert.equal(client.deleted.length, 0)
|
|
})
|
|
|
|
test('cleanupReleases 在 deleteResource 回傳非 204 時仍繼續處理其餘項目', 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' },
|
|
{ id: 3, tag_name: 'v3', name: 'n3', created_at: '2024-03-01T00:00:00Z' },
|
|
],
|
|
(url) => (url.endsWith('/2') ? 500 : 204), // v2 刪除失敗
|
|
)
|
|
await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 1 })
|
|
// 即使 v2 失敗(500),v1 仍被嘗試刪除
|
|
assert.deepEqual(client.deleted, ['http://x/releases/2', 'http://x/releases/1'])
|
|
})
|
|
|
|
test('cleanupReleases 對 id 進行 URL 編碼(防路徑穿越)', async () => {
|
|
const client = fakeClient([
|
|
{ id: 1, tag_name: 'v2', name: 'n2', created_at: '2024-02-01T00:00:00Z' },
|
|
{ id: '../evil', tag_name: 'v1', name: 'n1', created_at: '2024-01-01T00:00:00Z' },
|
|
])
|
|
await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 1 })
|
|
// 保留最新(id=1),刪除較舊者(惡意 id);id 中的 ../ 應被編碼
|
|
assert.deepEqual(client.deleted, ['http://x/releases/..%2Fevil'])
|
|
})
|
|
|
|
test('cleanupReleases 略過沒有 id 的 release', async () => {
|
|
const client = fakeClient([
|
|
{ id: 1, tag_name: 'v3', name: 'n3', created_at: '2024-03-01T00:00:00Z' },
|
|
{ id: null, 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' },
|
|
])
|
|
await cleanupReleases(client, { releaseApiUrl: 'http://x/releases', keepCount: 1 })
|
|
// 保留最新 v3;待刪除為 v2 與 (無 id 的) v1 → 無 id 者被略過
|
|
assert.deepEqual(client.deleted, ['http://x/releases/2'])
|
|
})
|