diff --git a/packages/sdk/src/__tests__/integration/delete-snapshots.integration.test.ts b/packages/sdk/src/__tests__/integration/delete-snapshots.integration.test.ts index cec8066f..0cb0be75 100644 --- a/packages/sdk/src/__tests__/integration/delete-snapshots.integration.test.ts +++ b/packages/sdk/src/__tests__/integration/delete-snapshots.integration.test.ts @@ -1,58 +1,86 @@ import { describe, it, expect, afterAll } from "vitest"; import { Box, EphemeralBox } from "../../index.js"; -import { UPSTASH_BOX_API_KEY, UPSTASH_BOX_BASE_URL } from "./setup.js"; +import { ALLOW_ACCOUNT_WIDE_TESTS, UPSTASH_BOX_API_KEY, UPSTASH_BOX_BASE_URL } from "./setup.js"; const conn = { apiKey: UPSTASH_BOX_API_KEY!, baseUrl: UPSTASH_BOX_BASE_URL }; describe.skipIf(!UPSTASH_BOX_API_KEY)("Box.deleteSnapshots (static)", () => { let box: EphemeralBox | undefined; + // Vitest runs test files in parallel and nine other files depend on their own + // snapshots surviving, so this one only ever deletes ids it created. + const created: string[] = []; afterAll(async () => { - try { - await Box.deleteSnapshots(conn); - } catch {} + if (created.length > 0) { + try { + await Box.deleteSnapshots({ ...conn, snapshotIds: created }); + } catch { + // cleanup best-effort + } + } try { await box?.delete(); - } catch {} + } catch { + // cleanup best-effort + } }, 30000); + const snapshot = async (name: string) => { + box ??= await EphemeralBox.create({ ...conn, ttl: 300 }); + const snap = await box.snapshot({ name }); + created.push(snap.id); + return snap; + }; + it("deletes a specific snapshot by ID", async () => { - box = await EphemeralBox.create({ ...conn, ttl: 300 }); - const snap = await box.snapshot({ name: "integ-delete-single" }); + const snap = await snapshot("integ-delete-single"); const r1 = await Box.deleteSnapshots({ ...conn, snapshotIds: snap.id }); expect(r1.deleted).toBe(1); - const remaining = await box.listSnapshots(); + const remaining = await box!.listSnapshots(); expect(remaining.find((s) => s.id === snap.id)).toBeUndefined(); }, 120000); it("deletes multiple snapshots by ID", async () => { - box ??= await EphemeralBox.create({ ...conn, ttl: 300 }); const [s1, s2] = await Promise.all([ - box.snapshot({ name: "integ-delete-multi-1" }), - box.snapshot({ name: "integ-delete-multi-2" }), + snapshot("integ-delete-multi-1"), + snapshot("integ-delete-multi-2"), ]); const r2 = await Box.deleteSnapshots({ ...conn, snapshotIds: [s1.id, s2.id] }); expect(r2.deleted).toBe(2); - const remaining = await box.listSnapshots(); + const remaining = await box!.listSnapshots(); expect(remaining.find((s) => s.id === s1.id)).toBeUndefined(); expect(remaining.find((s) => s.id === s2.id)).toBeUndefined(); }, 120000); - it("deletes all snapshots when no snapshotIds provided", async () => { - box ??= await EphemeralBox.create({ ...conn, ttl: 300 }); - await Promise.all([ - box.snapshot({ name: "integ-delete-all-1" }), - box.snapshot({ name: "integ-delete-all-2" }), - ]); + it("rejects an empty id list rather than deleting everything", async () => { + await expect(Box.deleteSnapshots({ ...conn, snapshotIds: [] })).rejects.toThrow(); + }); +}); - const r3 = await Box.deleteSnapshots(conn); - expect(r3.deleted).toBeGreaterThanOrEqual(2); +// Deleting every snapshot on the account cannot share a key with anything else, +// including a second CI run on another pull request, so it is opt-in. The +// request shape for this call is covered by box-delete-snapshots.test.ts. +describe.skipIf(!UPSTASH_BOX_API_KEY || !ALLOW_ACCOUNT_WIDE_TESTS)( + "Box.deleteSnapshots (account-wide)", + () => { + it("deletes all snapshots when no snapshotIds are provided", async () => { + const box = await EphemeralBox.create({ ...conn, ttl: 300 }); + try { + await Promise.all([ + box.snapshot({ name: "integ-delete-all-1" }), + box.snapshot({ name: "integ-delete-all-2" }), + ]); - const remaining = await box.listSnapshots(); - expect(remaining).toHaveLength(0); - }, 120000); -}); + const result = await Box.deleteSnapshots(conn); + expect(result.deleted).toBeGreaterThanOrEqual(2); + expect(await box.listSnapshots()).toHaveLength(0); + } finally { + await box.delete().catch(() => {}); + } + }, 120000); + }, +); diff --git a/packages/sdk/src/__tests__/integration/setup.ts b/packages/sdk/src/__tests__/integration/setup.ts index a52f4257..965b4ef5 100644 --- a/packages/sdk/src/__tests__/integration/setup.ts +++ b/packages/sdk/src/__tests__/integration/setup.ts @@ -8,3 +8,10 @@ dotenv.config({ path: resolve(__dirname, "../../../../../.env") }); export const UPSTASH_BOX_API_KEY = process.env.UPSTASH_BOX_API_KEY; export const UPSTASH_BOX_BASE_URL = process.env.UPSTASH_BOX_BASE_URL; export const CONTEXT7_API_KEY = process.env.CONTEXT7_API_KEY; + +/** + * Opt-in for tests that act on every resource in the account rather than only + * the ones they created. They cannot run beside anything else on a shared key, + * so CI leaves this unset. Set it when running against a scratch account. + */ +export const ALLOW_ACCOUNT_WIDE_TESTS = process.env.UPSTASH_BOX_ALLOW_ACCOUNT_WIDE === "1";