import { randomUUID } from "node:crypto"; import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { ManagedStorage } from "../../apps/api/src/managed-storage.js"; import { RegistrationService } from "../../apps/api/src/registration.js"; import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; import { StickerReleaseService } from "../../apps/api/src/sticker-releases.js"; import { ProjectPurgeCleanup } from "../../apps/worker/src/project-purge-cleanup.js"; const roots: string[] = []; const closeables: Array<{ close(): void }> = []; function fixture() { const dataRoot = mkdtempSync(join(tmpdir(), "dada-wp5-06-cleanup-")); roots.push(dataRoot); mkdirSync(join(dataRoot, "db"), { recursive: true }); const databasePath = join(dataRoot, "db", "dada.sqlite3"); const registration = new RegistrationService({ challengePepper: Buffer.alloc(32, 0x61), currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath, invitePepper: Buffer.alloc(32, 0x62), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x63), }); const storage = new ManagedStorage({ dataRoot, databasePath }); const stickers = new StickerReleaseService({ databasePath, storage }); closeables.push(stickers, storage, registration); return { dataRoot, databasePath, database: registration.database, storage }; } function seedAdmin(database: RegistrationService["database"]) { const adminId = randomUUID(); database.prepare(` INSERT INTO users ( user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at ) VALUES (?, ?, 'super_admin', 'active', 0, ?, ?) `).run(adminId, `${adminId}@example.invalid`, randomUUID(), Date.now()); database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(adminId); return adminId; } async function seedHistoricalPair(test: ReturnType) { const original = await test.storage.commitBufferFixture("sticker_original", "STK2401.png", Buffer.from("original-history")); const thumbnail = await test.storage.commitBufferFixture("sticker_thumbnail", "STK2401-thumbnail.png", Buffer.from("thumbnail-history")); const insert = test.database.prepare(` INSERT INTO sticker_managed_file_history ( managed_file_id, stable_id, resource_version, file_kind, created_at ) VALUES (?, 'STK2401', 'asset-20260701.1', ?, ?) `); insert.run(original.file_id, "original", Date.now()); insert.run(thumbnail.file_id, "thumbnail", Date.now()); return { original, thumbnail }; } afterEach(() => { for (const value of closeables.splice(0).reverse()) value.close(); for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); describe("TDD-WP5-CLN-001 sticker history cleanup", () => { it("denies the whole batch when a reference appears after the candidate snapshot", async () => { const test = fixture(); const adminId = seedAdmin(test.database); const files = await seedHistoricalPair(test); const candidates = test.storage.listAssetCleanupCandidates(); expect(candidates.items).toEqual(expect.arrayContaining([ expect.objectContaining({ file_id: files.original.file_id, file_kind: "original", reference_count: 0, stable_id: "STK2401" }), expect.objectContaining({ file_id: files.thumbnail.file_id, file_kind: "thumbnail", reference_count: 0, stable_id: "STK2401" }), ])); const intent = test.storage.createAssetCleanupIntent({ actorId: adminId, fileIds: [files.original.file_id, files.thumbnail.file_id], idempotencyKey: `cleanup-${randomUUID()}-${randomUUID()}`, snapshotVersion: candidates.candidate_snapshot_version, }); test.storage.addAssetReference(files.original.file_id, "release"); expect(() => test.storage.confirmAssetCleanupIntent({ actorId: adminId, confirmationToken: intent.confirmation_token, requestId: intent.request_id, })).toThrow("ASSET_HISTORY_REFERENCE_CONFLICT"); expect(test.database.prepare("SELECT status FROM asset_cleanup_requests WHERE request_id = ?").get(intent.request_id)).toEqual({ status: "denied" }); expect(test.database.prepare("SELECT COUNT(*) AS count FROM file_cleanup_queue WHERE status = 'pending'").get()).toEqual({ count: 0 }); expect(test.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs WHERE target_ref = ? AND result = 'failed'").get(intent.request_id)).toEqual({ count: 1 }); }); it("requires the same admin, queues without reducing capacity, then remeasures after physical deletion", async () => { const test = fixture(); const adminId = seedAdmin(test.database); const otherAdminId = seedAdmin(test.database); const files = await seedHistoricalPair(test); const bytesBefore = test.storage.getState().managed_content_bytes; const candidates = test.storage.listAssetCleanupCandidates(); const intent = test.storage.createAssetCleanupIntent({ actorId: adminId, fileIds: [files.original.file_id, files.thumbnail.file_id], idempotencyKey: `cleanup-${randomUUID()}-${randomUUID()}`, snapshotVersion: candidates.candidate_snapshot_version, }); expect(() => test.storage.confirmAssetCleanupIntent({ actorId: otherAdminId, confirmationToken: intent.confirmation_token, requestId: intent.request_id, })).toThrow("ASSET_CLEANUP_CANDIDATE_STALE"); const queued = test.storage.confirmAssetCleanupIntent({ actorId: adminId, confirmationToken: intent.confirmation_token, requestId: intent.request_id, }); expect(queued).toMatchObject({ file_count: 2, status: "queued" }); expect(test.storage.getState().managed_content_bytes).toBe(bytesBefore); expect(existsSync(join(test.dataRoot, files.original.relative_path))).toBe(true); expect(test.database.prepare("SELECT COUNT(*) AS count FROM file_cleanup_queue WHERE status = 'pending'").get()).toEqual({ count: 2 }); const worker = new ProjectPurgeCleanup({ dataRoot: test.dataRoot, databasePath: test.databasePath }); const result = worker.processFileCleanup(); worker.close(); expect(result).toEqual({ completed: 2, failed: 0 }); expect(existsSync(join(test.dataRoot, files.original.relative_path))).toBe(false); expect(existsSync(join(test.dataRoot, files.thumbnail.relative_path))).toBe(false); expect(test.storage.getState()).toMatchObject({ managed_content_bytes: 0, storage_status: "active" }); expect(test.database.prepare("SELECT status FROM asset_cleanup_requests WHERE request_id = ?").get(intent.request_id)).toEqual({ status: "completed" }); expect(test.database.prepare("SELECT operation_type, result FROM admin_operation_logs WHERE target_ref = ? ORDER BY occurred_at").all(intent.request_id)).toEqual([ { operation_type: "asset_cleanup_requested", result: "succeeded" }, { operation_type: "asset_cleanup_validated", result: "succeeded" }, { operation_type: "asset_cleanup_scheduled", result: "succeeded" }, { operation_type: "asset_cleanup_physical_completed", result: "succeeded" }, ]); }); });