Files
openwork/scripts/prune-daytona-dev-snapshots.test.mjs

242 lines
7.9 KiB
JavaScript

import assert from "node:assert/strict"
import test from "node:test"
import {
pruneDaytonaDevSnapshots,
selectDevSnapshotPrunes,
} from "./prune-daytona-dev-snapshots.mjs"
const apiKey = "test-daytona-api-key"
const apiUrl = "https://app.daytona.io/api"
function jsonResponse(value, init) {
return new Response(JSON.stringify(value), init)
}
function snapshot(id, name, createdAt, state = "inactive") {
return { id, name, state, createdAt }
}
function inventoryFetch(snapshots, sandboxes = []) {
const calls = []
const fetchImpl = async (url, options) => {
calls.push({ url, options })
if (url.includes("/snapshots?")) {
return jsonResponse({ items: snapshots, total: snapshots.length })
}
if (url.includes("/sandbox?")) {
return jsonResponse(sandboxes)
}
return jsonResponse({})
}
return { calls, fetchImpl }
}
test("selection prunes only unprotected dev snapshots", () => {
const snapshots = [
snapshot("old", "openwork-dev-old", "2026-01-01T00:00:00Z"),
snapshot("stopped", "openwork-dev-stopped", "2026-01-02T00:00:00Z"),
snapshot("explicit", "openwork-dev-explicit", "2026-01-03T00:00:00Z"),
snapshot("pulling", "openwork-dev-pulling", "2026-01-04T00:00:00Z", "pulling"),
snapshot("active", "openwork-dev-active-ref", "2026-01-05T00:00:00Z"),
snapshot("recent-1", "openwork-dev-recent-1", "2026-01-06T00:00:00Z"),
snapshot("recent-2", "openwork-dev-recent-2", "2026-01-07T00:00:00Z"),
snapshot("release", "openwork-0.18.23", "2026-01-08T00:00:00Z"),
snapshot("base", "daytonaio/sandbox:0.8.0", "2026-01-09T00:00:00Z"),
snapshot("windows", "windows-small", "2026-01-10T00:00:00Z"),
]
const sandboxes = [
{ id: "started", state: "started", snapshot: "openwork-dev-active-ref" },
{ id: "stopped", state: "stopped", snapshot: "openwork-dev-stopped" },
{ id: "archived", state: "archived", snapshot: "openwork-dev-stopped" },
]
const result = selectDevSnapshotPrunes({
snapshots,
sandboxes,
nameBase: "openwork",
keepNames: ["openwork-dev-explicit"],
keepCount: 2,
})
assert.deepEqual(result.prune.map((item) => item.name), [
"openwork-dev-old",
"openwork-dev-stopped",
])
assert.deepEqual(result.keep.map((item) => [item.name, item.reason]), [
["openwork-dev-explicit", "explicit"],
["openwork-dev-pulling", "in-flight"],
["openwork-dev-active-ref", "active-ref"],
["openwork-dev-recent-1", "recent"],
["openwork-dev-recent-2", "recent"],
])
assert.equal(result.prune.some((item) => !item.name.startsWith("openwork-dev-")), false)
})
test("exactly keepCount in-scope snapshots prunes nothing", () => {
const snapshots = [
snapshot("one", "openwork-dev-one", "2026-01-01T00:00:00Z"),
snapshot("two", "openwork-dev-two", "2026-01-02T00:00:00Z"),
]
const result = selectDevSnapshotPrunes({
snapshots,
sandboxes: [],
nameBase: "openwork",
keepNames: [],
keepCount: 2,
})
assert.deepEqual(result.prune, [])
})
test("dry-run fetches inventory but sends no DELETE requests", async () => {
const snapshots = [
snapshot("old", "openwork-dev-old", "2026-01-01T00:00:00Z"),
snapshot("new", "openwork-dev-new", "2026-01-02T00:00:00Z"),
]
const { calls, fetchImpl } = inventoryFetch(snapshots)
const result = await pruneDaytonaDevSnapshots({
apiKey,
keepNames: ["openwork-dev-new"],
keepCount: 1,
dryRun: true,
fetchImpl,
log: () => {},
})
assert.deepEqual(result.prune.map((item) => item.id), ["old"])
assert.equal(calls.filter((call) => call.options.method === "DELETE").length, 0)
assert.equal(calls.filter((call) => call.options.method === "GET").length, 2)
})
test("real run deletes every selected id and no kept ids", async () => {
const snapshots = [
snapshot("old-1", "openwork-dev-old-1", "2026-01-01T00:00:00Z"),
snapshot("old-2", "openwork-dev-old-2", "2026-01-02T00:00:00Z"),
snapshot("new", "openwork-dev-new", "2026-01-03T00:00:00Z"),
]
const { calls, fetchImpl } = inventoryFetch(snapshots)
await pruneDaytonaDevSnapshots({
apiKey,
keepNames: ["openwork-dev-new"],
keepCount: 1,
fetchImpl,
log: () => {},
})
assert.deepEqual(
calls.filter((call) => call.options.method === "DELETE").map((call) => call.url),
[`${apiUrl}/snapshots/old-1`, `${apiUrl}/snapshots/old-2`],
)
})
test("pagination protects newest snapshots and reads all sandbox pages", async () => {
const firstPage = Array.from({ length: 200 }, (_, index) =>
snapshot(
`old-${index}`,
`openwork-dev-old-${String(index).padStart(3, "0")}`,
`2025-01-${String((index % 28) + 1).padStart(2, "0")}T00:00:00Z`,
),
)
const newest = snapshot("newest", "openwork-dev-newest", "2026-01-01T00:00:00Z")
const calls = []
const fetchImpl = async (url, options) => {
calls.push({ url, options })
if (url.includes("/snapshots?")) {
const page = Number(new URL(url).searchParams.get("page"))
return jsonResponse({
items: page === 1 ? firstPage : [newest],
total: 201,
})
}
if (url.includes("/sandbox?")) {
const cursor = new URL(url).searchParams.get("cursor")
return jsonResponse(
cursor
? {
items: [
{ id: "active", state: "started", snapshot: firstPage[0].name },
],
nextCursor: null,
}
: {
items: Array.from({ length: 200 }, (_, index) => ({
id: `sandbox-${index}`,
state: "stopped",
snapshot: null,
})),
nextCursor: "cursor-2",
},
)
}
return jsonResponse({})
}
const result = await pruneDaytonaDevSnapshots({
apiKey,
keepNames: [newest.name],
keepCount: 1,
fetchImpl,
log: () => {},
})
assert.equal(result.keep.some((item) => item.id === "newest"), true)
assert.equal(result.keep.some((item) => item.id === "old-0"), true)
assert.equal(result.prune.length, 199)
assert.equal(calls.some((call) => call.url.includes("/snapshots/newest")), false)
assert.equal(calls.some((call) => call.url.includes("/snapshots/old-0")), false)
assert.equal(calls.some((call) => call.url.endsWith("/snapshots/old-1")), true)
const sandboxCalls = calls.filter((call) => call.url.includes("/sandbox?"))
assert.equal(sandboxCalls.length, 2)
assert.equal(sandboxCalls[1].url.includes("cursor=cursor-2"), true)
assert.equal(sandboxCalls[1].url.includes("page="), false)
})
test("a failed DELETE does not prevent later deletes and rejects with the result", async () => {
const snapshots = [
snapshot("old-1", "openwork-dev-old-1", "2026-01-01T00:00:00Z"),
snapshot("old-2", "openwork-dev-old-2", "2026-01-02T00:00:00Z"),
snapshot("new", "openwork-dev-new", "2026-01-03T00:00:00Z"),
]
const calls = []
const messages = []
const fetchImpl = async (url, options) => {
calls.push({ url, options })
if (url.includes("/snapshots?")) {
return jsonResponse({ items: snapshots, total: snapshots.length })
}
if (url.includes("/sandbox?")) {
return jsonResponse([])
}
if (url.endsWith("/old-1")) {
return new Response("upstream exploded", { status: 500 })
}
return jsonResponse({})
}
await assert.rejects(
pruneDaytonaDevSnapshots({
apiKey,
keepNames: ["openwork-dev-new"],
keepCount: 1,
fetchImpl,
log: (message) => messages.push(message),
}),
(error) => {
assert.equal(error.result.failures.length, 1)
assert.equal(
error.result.summary,
"summary: pruned=1 kept=1 out-of-scope=0 failed=1",
)
return true
},
)
assert.deepEqual(
calls.filter((call) => call.options.method === "DELETE").map((call) => call.url),
[`${apiUrl}/snapshots/old-1`, `${apiUrl}/snapshots/old-2`],
)
assert.equal(messages.at(-1), "summary: pruned=1 kept=1 out-of-scope=0 failed=1")
})