import { randomUUID } from "node:crypto"; import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { createApp } from "../../apps/api/src/app.js"; import { AssetPreviewGrantService } from "../../apps/api/src/preview-grants.js"; import { RegistrationService } from "../../apps/api/src/registration.js"; import { MockResendAdapter } from "../../apps/api/src/resend-adapter.js"; import { createAssetReleaseManifest } from "../../packages/asset-release-manifest/src/index.js"; const start = Date.parse("2026-08-04T08:00:00.000Z"); const releaseVersion = "asset-20260804.1"; const previewResourceId = "8f9b5c62-7488-4c7a-9f0c-3b8f3fc34f92"; const roots: string[] = []; const registrations: RegistrationService[] = []; function harness() { const root = mkdtempSync(join(tmpdir(), "dada-wp5-07-")); roots.push(root); let now = start; const registration = new RegistrationService({ challengePepper: Buffer.alloc(32, 0x71), clock: () => now, currentPrivacyNoticeVersion: "p0a-registration-notice-v1", databasePath: join(root, "dada.sqlite3"), invitePepper: Buffer.alloc(32, 0x72), resend: new MockResendAdapter(), sessionPepper: Buffer.alloc(32, 0x73), }); registrations.push(registration); const userId = randomUUID(); registration.database.prepare(` INSERT INTO users ( user_id, normalized_email, role, status, counts_toward_stage_limit, registration_id, created_at ) VALUES (?, ?, 'user', 'active', 1, ?, ?) `).run(userId, `${userId}@example.invalid`, randomUUID(), start); registration.database.prepare(` INSERT INTO user_profiles (user_id, creator_name, social_id) VALUES (?, 'Preview User', '@preview_user') `).run(userId); registration.database.prepare(` INSERT INTO credit_accounts (user_id, available_balance, reserved_balance, updated_at) VALUES (?, 10, 0, ?) `).run(userId, start); const adminId = randomUUID(); registration.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(), start); registration.database.prepare("INSERT INTO admin_access (user_id, allowed) VALUES (?, 1)").run(adminId); const assetReleases = createAssetReleaseManifest({ items: [{ access_class: "internal_preview_asset", content: Buffer.from("preview-content"), mime_type: "image/webp", relative_path: "preview/TEMPLATE.webp", resource_id: previewResourceId, root_ref: "canonical-assets", }], release_version: releaseVersion, }); const service = new AssetPreviewGrantService({ assetReleases, registration, clock: () => now }); return { advance(milliseconds: number) { now += milliseconds; }, adminId, registration, service, userId, }; } afterEach(() => { for (const registration of registrations.splice(0)) registration.close(); for (const root of roots.splice(0)) rmSync(root, { force: true, recursive: true }); }); describe("TASK-WP5-07 internal preview grant lifecycle", () => { it("keeps ordinary role, returns randomized manifest item IDs, and blocks revoked/expired grants", () => { const test = harness(); const batch = test.service.createBatch({ name: "WP5 preview batch", adminUserId: test.adminId, }); test.service.addBatchItems({ adminUserId: test.adminId, batchId: batch.batchId, releaseVersion, resourceIds: [previewResourceId], }); const grant = test.service.grant({ adminUserId: test.adminId, batchId: batch.batchId, expiresAt: start + 60_000, userId: test.userId, }); const firstManifest = test.service.projectManifest({ releaseVersion, userId: test.userId }); expect(firstManifest?.items).toHaveLength(1); expect(firstManifest?.items[0].resource_id).not.toBe(previewResourceId); expect(firstManifest?.items[0].url).toContain(firstManifest?.items[0].resource_id ?? ""); expect(test.service.readManifestItem({ manifestItemId: firstManifest!.items[0].resource_id, releaseVersion, userId: test.userId, })?.bytes).toEqual(Buffer.from("preview-content")); expect(test.registration.database.prepare("SELECT role FROM users WHERE user_id = ?").get(test.userId)).toEqual({ role: "user" }); test.service.revoke({ adminUserId: test.adminId, grantId: grant.grantId }); expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined(); expect(test.service.readManifestItem({ manifestItemId: firstManifest!.items[0].resource_id, releaseVersion, userId: test.userId, })).toBeUndefined(); const secondGrant = test.service.grant({ adminUserId: test.adminId, batchId: batch.batchId, expiresAt: start + 10_000, userId: test.userId, }); expect(secondGrant.status).toBe("active"); test.advance(10_001); expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined(); expect(test.registration.database.prepare("SELECT status FROM asset_preview_grants WHERE grant_id = ?").get(secondGrant.grantId)).toEqual({ status: "expired" }); test.registration.database.prepare("UPDATE users SET status = 'active' WHERE user_id = ?").run(test.userId); test.service.grant({ adminUserId: test.adminId, batchId: batch.batchId, expiresAt: start + 120_000, userId: test.userId, }); test.registration.changeUserStatus(test.userId, "suspended"); expect(test.service.projectManifest({ releaseVersion, userId: test.userId })).toBeUndefined(); expect(test.registration.database.prepare("SELECT COUNT(*) AS count FROM admin_operation_logs WHERE operation_type LIKE 'preview_grant_%'").get()).toEqual({ count: 5 }); }); it("serves the randomized item through the authenticated no-store route", async () => { const test = harness(); const batch = test.service.createBatch({ name: "WP5 route batch", adminUserId: test.adminId }); test.service.addBatchItems({ adminUserId: test.adminId, batchId: batch.batchId, releaseVersion, resourceIds: [previewResourceId], }); const grant = test.service.grant({ adminUserId: test.adminId, batchId: batch.batchId, expiresAt: start + 60_000, userId: test.userId }); const session = test.registration.issueAuthenticatedSession(test.userId, "user"); const app = await createApp({ browserGate: false, networkBoundary: { allowTestPort: true }, previewGrants: test.service, registration: test.registration, }); try { const headers = { cookie: `dada_session=${session.sessionToken}`, host: "127.0.0.1:43121", origin: "http://127.0.0.1:43121" }; const manifest = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/manifest` }); expect(manifest.statusCode).toBe(200); const itemId = manifest.json().items[0].resource_id; expect(itemId).not.toBe(previewResourceId); const asset = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${itemId}` }); expect(asset.statusCode).toBe(200); expect(asset.headers["cache-control"]).toBe("private, no-store"); expect(asset.rawPayload).toEqual(Buffer.from("preview-content")); test.service.revoke({ adminUserId: test.adminId, grantId: grant.grantId }); const revoked = await app.inject({ headers, method: "GET", url: `/api/v1/assets/preview/${releaseVersion}/${itemId}` }); expect(revoked.statusCode).toBe(404); } finally { await app.close(); } }); });