import {test,expect} from "bun:test"; import {unzipSync} from "fflate"; import {and,eq} from "drizzle-orm"; import {events,getDb,photos,photoExports,user,guests,submissions} from "@album/database"; import {getObjectBuffer,deleteObject,putObject,headObject} from "@album/storage"; import {processPhotoExport} from "./exports"; test.skipIf(process.env.EXPORT_INTEGRATION!=="1")("original ZIP bytes, approved filtering, duplicate requests and expiry",async()=>{ if (!["localhost","127.0.0.1"].includes(new URL(process.env.DATABASE_URL!).hostname) || !["localhost","127.0.0.1"].includes(new URL(process.env.S3_ENDPOINT!).hostname)) throw new Error("Local storage/database required"); const db=getDb(); const [demo]=await db.select().from(events).where(eq(events.slug,"demo")); const [owner]=await db.select({id:user.id}).from(user).limit(1); const [event]=await db.insert(events).values({groupId:demo!.groupId,title:"Export test",slug:`export-${crypto.randomUUID()}`}).returning(); const [guest]=await db.insert(guests).values({eventId:event!.id,tokenHash:crypto.randomUUID()}).returning(); const [submission]=await db.insert(submissions).values({eventId:event!.id,guestId:guest!.id}).returning(); const bytes=Buffer.from("original bytes preserved exactly"); const sourceKey=`events/${event!.id}/export-test/original`; const jobIds:string[]=[]; try { await putObject({key:sourceKey,body:bytes,contentType:"image/jpeg"}); const [photo]=await db.insert(photos).values({eventId:event!.id,submissionId:submission!.id,originalKey:sourceKey,contentType:"image/jpeg",byteSize:bytes.length,processingStatus:"ready",visibility:"public"}).returning(); await db.insert(photos).values({eventId:event!.id,submissionId:submission!.id,originalKey:sourceKey,contentType:"image/jpeg",byteSize:bytes.length,processingStatus:"ready",visibility:"private"}); for(const filter of ["approved","all"]) { const [job]=await db.insert(photoExports).values({eventId:event!.id,requestedBy:owner!.id,filter,expiresAt:new Date(Date.now()+86400000)}).returning(); jobIds.push(job!.id); expect(await db.insert(photoExports).values({eventId:event!.id,requestedBy:owner!.id,filter,expiresAt:new Date(Date.now()+86400000)}).onConflictDoNothing().returning()).toHaveLength(0); await processPhotoExport(); const [finished]=await db.select().from(photoExports).where(and(eq(photoExports.id,job!.id),eq(photoExports.eventId,event!.id))); expect(finished?.status).toBe("ready"); const zip=unzipSync(await getObjectBuffer(`exports/${event!.id}/${job!.id}.zip`)); expect(Object.keys(zip)).toHaveLength(filter==="approved"?1:2); expect(Buffer.from(zip[`${photo!.id}.jpg`]!)).toEqual(bytes); await db.update(photoExports).set({expiresAt:new Date(0)}).where(and(eq(photoExports.id,job!.id),eq(photoExports.eventId,event!.id))); await processPhotoExport(); expect(await headObject(`exports/${event!.id}/${job!.id}.zip`)).toBeNull(); } } finally { for(const id of jobIds) await deleteObject(`exports/${event!.id}/${id}.zip`); await deleteObject(sourceKey); await db.delete(events).where(eq(events.id,event!.id)); } },30000);