diff --git a/src/modules/webhooks/webhook.controllers.test.ts b/src/modules/webhooks/webhook.controllers.test.ts index ecf192d..2424cd0 100644 --- a/src/modules/webhooks/webhook.controllers.test.ts +++ b/src/modules/webhooks/webhook.controllers.test.ts @@ -25,6 +25,7 @@ function createMockResponse() { res.status = jest.fn().mockReturnValue(res); res.json = jest.fn().mockReturnValue(res); res.setHeader = jest.fn().mockReturnValue(res); + res.end = jest.fn().mockReturnValue(res); return res as Response; } @@ -120,7 +121,7 @@ describe('deleteWebhookHandler', () => { expect(res.status).toHaveBeenCalledWith(404); }); - it('returns 200 on successful deletion', async () => { + it('returns 204 on successful deletion', async () => { mockService.deleteWebhook.mockResolvedValue({ id: 'wh-1' }); const req = createMockSignedRequest('creator-1', { webhookId: 'wh-1' }); @@ -128,7 +129,7 @@ describe('deleteWebhookHandler', () => { await deleteWebhookHandler(req, res); - expect(res.status).toHaveBeenCalledWith(200); + expect(res.status).toHaveBeenCalledWith(204); expect(mockService.deleteWebhook).toHaveBeenCalledWith('wh-1', 'creator-1'); }); }); diff --git a/src/modules/webhooks/webhook.controllers.ts b/src/modules/webhooks/webhook.controllers.ts index fb3ce05..fee1547 100644 --- a/src/modules/webhooks/webhook.controllers.ts +++ b/src/modules/webhooks/webhook.controllers.ts @@ -83,7 +83,7 @@ export async function deleteWebhookHandler( sendNotFound(res, 'Webhook'); return; } - sendSuccess(res, result, 200, 'Webhook deleted successfully'); + res.status(204).end(); } catch { sendError(res, 500, ErrorCode.INTERNAL_ERROR, 'Failed to delete webhook'); } diff --git a/src/modules/webhooks/webhook.integration.test.ts b/src/modules/webhooks/webhook.integration.test.ts index adb04c6..32ff626 100644 --- a/src/modules/webhooks/webhook.integration.test.ts +++ b/src/modules/webhooks/webhook.integration.test.ts @@ -177,8 +177,7 @@ describe('DELETE /api/v1/creators/:id/webhooks/:webhookId', () => { .delete(`/api/v1/creators/${creatorId}/webhooks/${webhookId}`) .set(authHeaders('DELETE', `/api/v1/creators/${creatorId}/webhooks/${webhookId}`, creatorId)); - expect(deleteRes.status).toBe(200); - expect(deleteRes.body.success).toBe(true); + expect(deleteRes.status).toBe(204); const verifyRes = await supertest(app) .get(`/api/v1/creators/${creatorId}/webhooks`) @@ -195,6 +194,48 @@ describe('DELETE /api/v1/creators/:id/webhooks/:webhookId', () => { expect(res.status).toBe(404); }); + + it('stops future deliveries when a webhook is deleted (#506)', async () => { + // Register a webhook and confirm it exists + const webhook = await prisma.webhook.create({ + data: { + id: 'webhook-deletion-test-506', + creatorId, + callbackUrl: 'https://example.com/deleted-hook', + events: { set: ['BUY', 'SELL'] }, + }, + }); + + // Delete the webhook and assert the response is 204 + const deleteRes = await supertest(app) + .delete(`/api/v1/creators/${creatorId}/webhooks/${webhook.id}`) + .set(authHeaders('DELETE', `/api/v1/creators/${creatorId}/webhooks/${webhook.id}`, creatorId)); + + expect(deleteRes.status).toBe(204); + + // Webhook record no longer exists after deletion + const deletedWebhook = await prisma.webhook.findUnique({ where: { id: webhook.id } }); + expect(deletedWebhook).toBeNull(); + + // Simulate a trade event after deletion + const { dispatchWebhookEvent } = await import('./webhook.service'); + await dispatchWebhookEvent({ + type: 'buy', + creatorId, + buyerOrSellerAddress: 'GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF', + amount: '100', + price: '10.5', + feePaid: '0.5', + timestamp: new Date().toISOString(), + }); + + // Assert no delivery attempt was made for the deleted webhook + const events = await prisma.webhookEvent.findMany({ + where: { webhookId: webhook.id }, + }); + + expect(events.length).toBe(0); + }); }); describe('webhook dispatch', () => {