* add webhook version schema * version the code * update version from numeric to date val * migration * update schema and build factory * update string * move version picker * tooltip instead of infobadge * -- * fix type * -- * fix type * fix type * -- * fix messed up merge * improvements to payloadfactory * extract version off of DB and instead keep it in IWebhookRepository * fix webhookform * fix type safety and routing ambiguity * scalable with easier factory extensions and base definition * fix types * -- * -- * clean up prisma/client type imports * fix * type fix * type fix * cleanup * add tests and registry changes * unintended file inclusion * type-fix * select in repo * -- * explicit return type * -- * fix type * fixes * feedback 1 * feedback 2 * use enum instead of string * fixes
214 lines
7.3 KiB
TypeScript
214 lines
7.3 KiB
TypeScript
import { describe, it, expect, vi } from "vitest";
|
|
import { BookingStatus, WebhookTriggerEvents } from "@calcom/prisma/enums";
|
|
import type { CalendarEvent } from "@calcom/types/Calendar";
|
|
|
|
import type { BookingWebhookEventDTO, EventTypeInfo } from "../../dto/types";
|
|
import { BookingPayloadBuilder } from "../versioned/v2021-10-20/BookingPayloadBuilder";
|
|
|
|
vi.mock("@calcom/lib/dayjs", () => ({
|
|
getUTCOffsetByTimezone: vi.fn(() => 0),
|
|
}));
|
|
|
|
describe("BookingPayloadBuilder (v2021-10-20)", () => {
|
|
const mockEventType: EventTypeInfo = {
|
|
eventTitle: "Test Event",
|
|
eventDescription: "Test Description",
|
|
requiresConfirmation: false,
|
|
price: 0,
|
|
currency: "USD",
|
|
length: 30,
|
|
};
|
|
|
|
const mockCalendarEvent: CalendarEvent = {
|
|
type: "test-event",
|
|
title: "Test Meeting",
|
|
description: "Meeting description",
|
|
additionalNotes: "Additional notes",
|
|
startTime: "2024-01-15T10:00:00Z",
|
|
endTime: "2024-01-15T10:30:00Z",
|
|
organizer: {
|
|
id: 1,
|
|
email: "organizer@test.com",
|
|
name: "Test Organizer",
|
|
timeZone: "UTC",
|
|
language: { locale: "en" },
|
|
},
|
|
attendees: [
|
|
{
|
|
email: "attendee@test.com",
|
|
name: "Test Attendee",
|
|
timeZone: "UTC",
|
|
language: { locale: "en" },
|
|
},
|
|
],
|
|
location: "https://cal.com/video/123",
|
|
uid: "booking-uid-123",
|
|
customInputs: {},
|
|
responses: {},
|
|
userFieldsResponses: {},
|
|
};
|
|
|
|
const createMockDTO = (
|
|
triggerEvent: WebhookTriggerEvents,
|
|
extra: Partial<BookingWebhookEventDTO> = {}
|
|
): BookingWebhookEventDTO => ({
|
|
triggerEvent,
|
|
createdAt: "2024-01-15T10:00:00Z",
|
|
booking: {
|
|
id: 1,
|
|
eventTypeId: 1,
|
|
userId: 1,
|
|
smsReminderNumber: null,
|
|
},
|
|
eventType: mockEventType,
|
|
evt: mockCalendarEvent,
|
|
...extra,
|
|
});
|
|
|
|
const builder = new BookingPayloadBuilder();
|
|
|
|
describe("BOOKING_CREATED", () => {
|
|
it("should build payload with ACCEPTED status", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_CREATED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_CREATED);
|
|
expect(payload.payload.status).toBe(BookingStatus.ACCEPTED);
|
|
expect(payload.payload.bookingId).toBe(1);
|
|
expect(payload.payload.title).toBe("Test Meeting");
|
|
expect(payload.payload.organizer.email).toBe("organizer@test.com");
|
|
expect(payload.payload.attendees).toHaveLength(1);
|
|
});
|
|
|
|
it("should include eventType fields", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_CREATED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.payload.eventTitle).toBe("Test Event");
|
|
expect(payload.payload.eventDescription).toBe("Test Description");
|
|
expect(payload.payload.price).toBe(0);
|
|
expect(payload.payload.currency).toBe("USD");
|
|
expect(payload.payload.length).toBe(30);
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_CANCELLED", () => {
|
|
it("should build payload with CANCELLED status and extra fields", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_CANCELLED, {
|
|
cancelledBy: "user@test.com",
|
|
cancellationReason: "Schedule conflict",
|
|
});
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_CANCELLED);
|
|
expect(payload.payload.status).toBe(BookingStatus.CANCELLED);
|
|
expect(payload.payload.cancelledBy).toBe("user@test.com");
|
|
expect(payload.payload.cancellationReason).toBe("Schedule conflict");
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_REQUESTED", () => {
|
|
it("should build payload with PENDING status", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_REQUESTED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_REQUESTED);
|
|
expect(payload.payload.status).toBe(BookingStatus.PENDING);
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_REJECTED", () => {
|
|
it("should build payload with REJECTED status", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_REJECTED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_REJECTED);
|
|
expect(payload.payload.status).toBe(BookingStatus.REJECTED);
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_RESCHEDULED", () => {
|
|
it("should build payload with reschedule extra fields", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_RESCHEDULED, {
|
|
rescheduleId: 2,
|
|
rescheduleUid: "reschedule-uid-456",
|
|
rescheduleStartTime: "2024-01-16T10:00:00Z",
|
|
rescheduleEndTime: "2024-01-16T10:30:00Z",
|
|
rescheduledBy: "user@test.com",
|
|
});
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_RESCHEDULED);
|
|
expect(payload.payload.status).toBe(BookingStatus.ACCEPTED);
|
|
expect(payload.payload.rescheduleId).toBe(2);
|
|
expect(payload.payload.rescheduleUid).toBe("reschedule-uid-456");
|
|
expect(payload.payload.rescheduledBy).toBe("user@test.com");
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_PAID", () => {
|
|
it("should build payload with payment extra fields", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_PAID, {
|
|
paymentId: 123,
|
|
paymentData: { stripeId: "stripe_123" },
|
|
});
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_PAID);
|
|
expect(payload.payload.paymentId).toBe(123);
|
|
expect(payload.payload.paymentData).toEqual({ stripeId: "stripe_123" });
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_PAYMENT_INITIATED", () => {
|
|
it("should build payload with payment extra fields", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED, {
|
|
paymentId: 456,
|
|
paymentData: { status: "pending" },
|
|
});
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED);
|
|
expect(payload.payload.paymentId).toBe(456);
|
|
});
|
|
});
|
|
|
|
describe("BOOKING_NO_SHOW_UPDATED", () => {
|
|
it("should build no-show specific payload", () => {
|
|
const dto: BookingWebhookEventDTO = {
|
|
triggerEvent: WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED,
|
|
createdAt: "2024-01-15T10:00:00Z",
|
|
bookingUid: "booking-uid-123",
|
|
bookingId: 1,
|
|
attendees: [{ email: "attendee@test.com", noShow: true }],
|
|
message: "Attendee marked as no-show",
|
|
} as BookingWebhookEventDTO;
|
|
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED);
|
|
expect(payload.payload.bookingUid).toBe("booking-uid-123");
|
|
expect(payload.payload.bookingId).toBe(1);
|
|
expect(payload.payload.message).toBe("Attendee marked as no-show");
|
|
});
|
|
});
|
|
|
|
describe("attendee UTC offset", () => {
|
|
it("should add utcOffset to each attendee", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_CREATED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.payload.attendees[0]).toHaveProperty("utcOffset");
|
|
});
|
|
});
|
|
|
|
describe("organizer UTC offset", () => {
|
|
it("should add utcOffset to organizer", () => {
|
|
const dto = createMockDTO(WebhookTriggerEvents.BOOKING_CREATED);
|
|
const payload = builder.build(dto);
|
|
|
|
expect(payload.payload.organizer).toHaveProperty("utcOffset");
|
|
});
|
|
});
|
|
});
|