@@ -14,6 +14,7 @@ import { FastifyInstance } from 'fastify'
1414import FormData from 'form-data'
1515import fs from 'fs'
1616import app from '../app'
17+ import { ObjectAdminDeleteAllBefore } from '../storage/events/objects/object-admin-delete-all-before'
1718import { mockQueue , useMockObject } from './common'
1819
1920describe ( 'Webhooks' , ( ) => {
@@ -385,10 +386,95 @@ describe('Webhooks', () => {
385386 } )
386387 )
387388 } )
389+
390+ it ( 'will emit webhooks for each deleted object during empty bucket operation' , async ( ) => {
391+ const emptyTestBucketName = 'bucket-empty-webhook-test'
392+ const authorization = `Bearer ${ await serviceKeyAsync } `
393+
394+ // Create a dedicated bucket for this test
395+ await appInstance . inject ( {
396+ method : 'POST' ,
397+ url : `/bucket` ,
398+ headers : {
399+ authorization,
400+ } ,
401+ payload : {
402+ name : emptyTestBucketName ,
403+ } ,
404+ } )
405+
406+ const objects = await Promise . all ( [
407+ createObject ( pg , emptyTestBucketName ) ,
408+ createObject ( pg , emptyTestBucketName ) ,
409+ createObject ( pg , emptyTestBucketName ) ,
410+ ] )
411+
412+ const response = await appInstance . inject ( {
413+ method : 'POST' ,
414+ url : `/bucket/${ emptyTestBucketName } /empty` ,
415+ headers : {
416+ authorization,
417+ } ,
418+ } )
419+
420+ expect ( response . statusCode ) . toBe ( 200 )
421+
422+ // Pass call invoked by empty on to the job handler to trigger the webhooks
423+ expect ( sendSpy ) . toHaveBeenCalledTimes ( 1 )
424+ const deleteJobCall = sendSpy . mock . calls [ 0 ] [ 0 ]
425+ expect ( deleteJobCall . name ) . toBe ( ObjectAdminDeleteAllBefore . queueName )
426+ await ObjectAdminDeleteAllBefore . handle ( deleteJobCall )
427+
428+ // Check ObjectRemoved:Delete webhooks were sent as expected
429+ expect ( sendSpy ) . toHaveBeenCalledTimes ( 1 + objects . length ) // 1 for the delete job + 3 for webhooks
430+ objects . forEach ( ( obj ) => {
431+ expect ( sendSpy ) . toHaveBeenCalledWith (
432+ expect . objectContaining ( {
433+ name : 'webhooks' ,
434+ options : expect . objectContaining ( {
435+ deadLetter : 'webhooks-dead-letter' ,
436+ expireInSeconds : expect . any ( Number ) ,
437+ } ) ,
438+ data : expect . objectContaining ( {
439+ $version : 'v1' ,
440+ event : expect . objectContaining ( {
441+ $version : 'v1' ,
442+ type : 'ObjectRemoved:Delete' ,
443+ applyTime : expect . any ( Number ) ,
444+ payload : expect . objectContaining ( {
445+ bucketId : emptyTestBucketName ,
446+ name : obj . name ,
447+ version : obj . version ,
448+ metadata : obj . metadata ,
449+ tenant : {
450+ host : undefined ,
451+ ref : 'bjhaohmqunupljrqypxz' ,
452+ } ,
453+ reqId : expect . any ( String ) ,
454+ } ) ,
455+ } ) ,
456+ tenant : {
457+ host : undefined ,
458+ ref : 'bjhaohmqunupljrqypxz' ,
459+ } ,
460+ } ) ,
461+ } )
462+ )
463+ } )
464+
465+ // Clean up: delete the bucket
466+ await appInstance . inject ( {
467+ method : 'DELETE' ,
468+ url : `/bucket/${ emptyTestBucketName } ` ,
469+ headers : {
470+ authorization,
471+ } ,
472+ } )
473+ } )
388474} )
389475
390476async function createObject ( pg : TenantConnection , bucketId : string ) {
391- const objectName = Date . now ( )
477+ const objectName = randomUUID ( )
392478 const tnx = await pg . transaction ( )
393479
394480 const [ data ] = await tnx
0 commit comments