import { WebhookTriggerEvents } from "@calcom/prisma/enums"; import { beforeEach, describe, expect, it, vi } from "vitest"; import type { ILogger } from "../../interface/infrastructure"; import type { WebhookTasker } from "../../tasker/WebhookTasker"; import { WebhookTaskerProducerService } from "../WebhookTaskerProducerService"; /** * Unit Tests for WebhookTaskerProducerService * * Tests the lightweight Producer service for queueing webhook delivery tasks. */ describe("WebhookTaskerProducerService", () => { let producer: WebhookTaskerProducerService; let mockWebhookTasker: WebhookTasker; let mockLogger: ILogger; beforeEach(() => { // Mock WebhookTasker mockWebhookTasker = { deliverWebhook: vi.fn().mockResolvedValue({ taskId: "task-id-123" }), } as unknown as WebhookTasker; // Mock Logger mockLogger = { debug: vi.fn(), info: vi.fn(), warn: vi.fn(), error: vi.fn(), getSubLogger: vi.fn().mockReturnThis(), } as unknown as ILogger; producer = new WebhookTaskerProducerService({ webhookTasker: mockWebhookTasker, logger: mockLogger, }); }); describe("Constructor & Dependencies", () => { it("should be instantiable with Tasker and Logger", () => { expect(producer).toBeInstanceOf(WebhookTaskerProducerService); }); it("should create sub-logger with prefix", () => { expect(mockLogger.getSubLogger).toHaveBeenCalledWith({ prefix: ["[WebhookTaskerProducerService]"], }); }); }); describe("queueBookingCreatedWebhook", () => { it("should queue a BOOKING_CREATED webhook task", async () => { await producer.queueBookingCreatedWebhook({ triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, bookingUid: "booking-123", eventTypeId: 456, userId: 789, }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, bookingUid: "booking-123", eventTypeId: 456, userId: 789, operationId: expect.any(String), timestamp: expect.any(String), }) ); }); it("should generate operationId if not provided", async () => { await producer.queueBookingCreatedWebhook({ triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, bookingUid: "booking-123", }); const callArgs = vi.mocked(mockWebhookTasker.deliverWebhook).mock.calls[0]; const payload = callArgs[0]; expect(payload).toHaveProperty("operationId"); expect(payload.operationId).toMatch(/^[0-9a-f-]{36}$/); // UUID format }); it("should use provided operationId", async () => { await producer.queueBookingCreatedWebhook({ triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, bookingUid: "booking-123", operationId: "custom-op-id", }); const callArgs = vi.mocked(mockWebhookTasker.deliverWebhook).mock.calls[0]; const payload = callArgs[0]; expect(payload.operationId).toBe("custom-op-id"); }); it("should log debug messages", async () => { await producer.queueBookingCreatedWebhook({ bookingUid: "booking-123", }); expect(mockLogger.debug).toHaveBeenCalledWith( "Queueing booking webhook task", expect.objectContaining({ operationId: expect.any(String), triggerEvent: WebhookTriggerEvents.BOOKING_CREATED, bookingUid: "booking-123", }) ); expect(mockLogger.debug).toHaveBeenCalledWith( "Webhook delivery task queued", expect.objectContaining({ operationId: expect.any(String), taskId: "task-id-123", }) ); }); }); describe("queueBookingCancelledWebhook", () => { it("should queue a BOOKING_CANCELLED webhook task", async () => { await producer.queueBookingCancelledWebhook({ bookingUid: "booking-456", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_CANCELLED, bookingUid: "booking-456", }) ); }); }); describe("Metadata Support", () => { it("should include metadata if provided", async () => { await producer.queueBookingCreatedWebhook({ bookingUid: "booking-123", metadata: { customField: "value" }, }); const callArgs = vi.mocked(mockWebhookTasker.deliverWebhook).mock.calls[0]; const payload = callArgs[0]; expect(payload.metadata).toEqual({ customField: "value" }); }); }); describe("Error Handling", () => { it("should log and rethrow error if WebhookTasker fails", async () => { const error = new Error("WebhookTasker failed"); vi.mocked(mockWebhookTasker.deliverWebhook).mockRejectedValueOnce(error); await expect( producer.queueBookingCreatedWebhook({ bookingUid: "booking-123", }) ).rejects.toThrow("WebhookTasker failed"); expect(mockLogger.error).toHaveBeenCalledWith( "Failed to queue webhook delivery task", expect.objectContaining({ error: "WebhookTasker failed", }) ); }); }); describe("All Event-Specific Methods", () => { it("should have queueBookingCreatedWebhook", async () => { await producer.queueBookingCreatedWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_CREATED }) ); }); it("should have queueBookingCancelledWebhook", async () => { await producer.queueBookingCancelledWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_CANCELLED }) ); }); it("should have queueBookingRescheduledWebhook", async () => { await producer.queueBookingRescheduledWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_RESCHEDULED }) ); }); it("should have queueBookingRequestedWebhook", async () => { await producer.queueBookingRequestedWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_REQUESTED }) ); }); it("should have queueBookingRejectedWebhook", async () => { await producer.queueBookingRejectedWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_REJECTED }) ); }); it("should have queueBookingPaymentInitiatedWebhook", async () => { await producer.queueBookingPaymentInitiatedWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED }) ); }); it("should have queueBookingPaidWebhook", async () => { await producer.queueBookingPaidWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_PAID }) ); }); it("should have queueBookingNoShowUpdatedWebhook", async () => { await producer.queueBookingNoShowUpdatedWebhook({ bookingUid: "test-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED }) ); }); it("should have queueFormSubmittedWebhook", async () => { await producer.queueFormSubmittedWebhook({ formId: "form-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.FORM_SUBMITTED }) ); }); it("should have queueRecordingReadyWebhook", async () => { await producer.queueRecordingReadyWebhook({ recordingId: "rec-123", bookingUid: "booking-123", }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.RECORDING_READY }) ); }); it("should have queueOOOCreatedWebhook", async () => { await producer.queueOOOCreatedWebhook({ oooEntryId: 123, userId: 456, }); expect(mockWebhookTasker.deliverWebhook).toHaveBeenCalledWith( expect.objectContaining({ triggerEvent: WebhookTriggerEvents.OOO_CREATED }) ); }); }); });