* refactor: apply biome formatting to packages/features (batch 1 - small subdirs) Format small subdirectories in packages/features: di, flags, holidays, oauth, settings, users, assignment-reason, selectedCalendar, hashedLink, host, form, form-builder, availability, data-table, pbac, schedules, troubleshooter, eventtypes, calendar-subscription, and root-level files. Also includes straggler apps/web BookEventForm.tsx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 2 - medium subdirs) Format medium subdirectories in packages/features: auth, credentials, calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone, tasker, and webhooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 3 - bookings + insights) Format bookings and insights subdirectories in packages/features. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 4 - ee) Format packages/features/ee subdirectory covering billing, workflows, organizations, teams, managed-event-types, round-robin, dsync, integration-attribute-sync, and payments. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1) Format booking-audit di, actions, common, dto, repository, and types subdirectories in packages/features/booking-audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2) Format booking-audit service subdirectory in packages/features/booking-audit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
289 lines
10 KiB
TypeScript
289 lines
10 KiB
TypeScript
import { describe, it, expect, beforeEach } from "vitest";
|
|
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
|
|
|
|
import type { BookingWebhookEventDTO } from "../../dto/types";
|
|
import { PayloadBuilderFactory, type PayloadBuilderSet } from "./PayloadBuilderFactory";
|
|
import * as V2021_10_20 from "./v2021-10-20";
|
|
import { WebhookVersion as WebhookVersionEnum } from "../../interface/IWebhookRepository";
|
|
|
|
describe("PayloadBuilderFactory", () => {
|
|
let factory: PayloadBuilderFactory;
|
|
let defaultBuilders: PayloadBuilderSet;
|
|
|
|
beforeEach(() => {
|
|
defaultBuilders = {
|
|
booking: new V2021_10_20.BookingPayloadBuilder(),
|
|
form: new V2021_10_20.FormPayloadBuilder(),
|
|
ooo: new V2021_10_20.OOOPayloadBuilder(),
|
|
recording: new V2021_10_20.RecordingPayloadBuilder(),
|
|
meeting: new V2021_10_20.MeetingPayloadBuilder(),
|
|
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
|
|
};
|
|
|
|
factory = new PayloadBuilderFactory(WebhookVersionEnum.V_2021_10_20, defaultBuilders);
|
|
});
|
|
|
|
describe("Constructor", () => {
|
|
it("should initialize with default version and builders", () => {
|
|
expect(factory).toBeInstanceOf(PayloadBuilderFactory);
|
|
expect(factory.getRegisteredVersions()).toContain(WebhookVersionEnum.V_2021_10_20);
|
|
});
|
|
|
|
it("should enforce required parameters at compile-time", () => {
|
|
// TypeScript ensures parameters are provided - no runtime check needed
|
|
expect(factory).toBeDefined();
|
|
expect(factory.getRegisteredVersions()).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("Version Registration", () => {
|
|
it("should register new version with complete builder set", () => {
|
|
const newVersionBuilders: PayloadBuilderSet = {
|
|
booking: new V2021_10_20.BookingPayloadBuilder(),
|
|
form: new V2021_10_20.FormPayloadBuilder(),
|
|
ooo: new V2021_10_20.OOOPayloadBuilder(),
|
|
recording: new V2021_10_20.RecordingPayloadBuilder(),
|
|
meeting: new V2021_10_20.MeetingPayloadBuilder(),
|
|
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
|
|
};
|
|
|
|
factory.registerVersion("2024-12-01", newVersionBuilders);
|
|
|
|
expect(factory.getRegisteredVersions()).toContain("2024-12-01");
|
|
expect(factory.getRegisteredVersions()).toHaveLength(2);
|
|
});
|
|
|
|
it("should allow overwriting existing version", () => {
|
|
const newBuilders: PayloadBuilderSet = {
|
|
booking: new V2021_10_20.BookingPayloadBuilder(),
|
|
form: new V2021_10_20.FormPayloadBuilder(),
|
|
ooo: new V2021_10_20.OOOPayloadBuilder(),
|
|
recording: new V2021_10_20.RecordingPayloadBuilder(),
|
|
meeting: new V2021_10_20.MeetingPayloadBuilder(),
|
|
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
|
|
};
|
|
|
|
factory.registerVersion(WebhookVersionEnum.V_2021_10_20, newBuilders);
|
|
|
|
expect(factory.getRegisteredVersions()).toHaveLength(1);
|
|
});
|
|
});
|
|
|
|
describe("Builder Routing", () => {
|
|
it("should route booking events to booking builder", () => {
|
|
const bookingTriggers = [
|
|
WebhookTriggerEvents.BOOKING_CREATED,
|
|
WebhookTriggerEvents.BOOKING_CANCELLED,
|
|
WebhookTriggerEvents.BOOKING_REQUESTED,
|
|
WebhookTriggerEvents.BOOKING_RESCHEDULED,
|
|
WebhookTriggerEvents.BOOKING_REJECTED,
|
|
WebhookTriggerEvents.BOOKING_PAID,
|
|
WebhookTriggerEvents.BOOKING_PAYMENT_INITIATED,
|
|
WebhookTriggerEvents.BOOKING_NO_SHOW_UPDATED,
|
|
];
|
|
|
|
bookingTriggers.forEach((trigger) => {
|
|
const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
|
|
expect(builder).toBe(defaultBuilders.booking);
|
|
});
|
|
});
|
|
|
|
it("should route form events to form builder", () => {
|
|
const formTriggers = [
|
|
WebhookTriggerEvents.FORM_SUBMITTED,
|
|
WebhookTriggerEvents.FORM_SUBMITTED_NO_EVENT,
|
|
];
|
|
|
|
formTriggers.forEach((trigger) => {
|
|
const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
|
|
expect(builder).toBe(defaultBuilders.form);
|
|
});
|
|
});
|
|
|
|
it("should route OOO events to OOO builder", () => {
|
|
const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, WebhookTriggerEvents.OOO_CREATED);
|
|
expect(builder).toBe(defaultBuilders.ooo);
|
|
});
|
|
|
|
it("should route recording events to recording builder", () => {
|
|
const recordingTriggers = [
|
|
WebhookTriggerEvents.RECORDING_READY,
|
|
WebhookTriggerEvents.RECORDING_TRANSCRIPTION_GENERATED,
|
|
];
|
|
|
|
recordingTriggers.forEach((trigger) => {
|
|
const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
|
|
expect(builder).toBe(defaultBuilders.recording);
|
|
});
|
|
});
|
|
|
|
it("should route meeting events to meeting builder", () => {
|
|
const meetingTriggers = [WebhookTriggerEvents.MEETING_STARTED, WebhookTriggerEvents.MEETING_ENDED];
|
|
|
|
meetingTriggers.forEach((trigger) => {
|
|
const builder = factory.getBuilder(WebhookVersionEnum.V_2021_10_20, trigger);
|
|
expect(builder).toBe(defaultBuilders.meeting);
|
|
});
|
|
});
|
|
|
|
it("should route instant meeting events to instant meeting builder", () => {
|
|
const builder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.INSTANT_MEETING
|
|
);
|
|
expect(builder).toBe(defaultBuilders.instantMeeting);
|
|
});
|
|
});
|
|
|
|
describe("Fallback Behavior", () => {
|
|
it("should fallback to default version when requested version not found", () => {
|
|
const builder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2099_99_99,
|
|
WebhookTriggerEvents.BOOKING_CREATED
|
|
);
|
|
|
|
// Should get default builder, not throw
|
|
expect(builder).toBe(defaultBuilders.booking);
|
|
});
|
|
|
|
it("should log warning when falling back to default", () => {
|
|
// Just verify it doesn't throw - logging is tested elsewhere
|
|
expect(() => {
|
|
factory.getBuilder("invalid-version", WebhookTriggerEvents.BOOKING_CREATED);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it("should never return undefined builder", () => {
|
|
const unknownVersion = WebhookVersionEnum.V_2099_99_99;
|
|
const builder = factory.getBuilder(unknownVersion, WebhookTriggerEvents.BOOKING_CREATED);
|
|
|
|
expect(builder).toBeDefined();
|
|
expect(builder).toBe(defaultBuilders.booking);
|
|
});
|
|
});
|
|
|
|
describe("Type Safety", () => {
|
|
it("should return correctly typed builder for each trigger", () => {
|
|
// These type assertions verify compile-time type safety
|
|
const bookingBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.BOOKING_CREATED
|
|
);
|
|
const formBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.FORM_SUBMITTED
|
|
);
|
|
const oooBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.OOO_CREATED
|
|
);
|
|
const recordingBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.RECORDING_READY
|
|
);
|
|
const meetingBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.MEETING_STARTED
|
|
);
|
|
const instantBuilder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.INSTANT_MEETING
|
|
);
|
|
|
|
// Runtime verification
|
|
expect(bookingBuilder).toBeDefined();
|
|
expect(formBuilder).toBeDefined();
|
|
expect(oooBuilder).toBeDefined();
|
|
expect(recordingBuilder).toBeDefined();
|
|
expect(meetingBuilder).toBeDefined();
|
|
expect(instantBuilder).toBeDefined();
|
|
});
|
|
|
|
it("should build valid payload with correctly typed DTO", () => {
|
|
const mockDTO: BookingWebhookEventDTO = {
|
|
triggerEvent: WebhookTriggerEvents.BOOKING_CREATED,
|
|
createdAt: "2024-01-15T10:00:00Z",
|
|
booking: {
|
|
id: 1,
|
|
eventTypeId: 1,
|
|
userId: 1,
|
|
smsReminderNumber: null,
|
|
},
|
|
eventType: {
|
|
eventTitle: "Test Event",
|
|
eventDescription: "Test Description",
|
|
requiresConfirmation: false,
|
|
price: 0,
|
|
currency: "USD",
|
|
length: 30,
|
|
},
|
|
evt: {
|
|
type: "test-event",
|
|
title: "Test Meeting",
|
|
description: "Meeting description",
|
|
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" },
|
|
},
|
|
],
|
|
uid: "booking-uid-123",
|
|
customInputs: {},
|
|
responses: {},
|
|
userFieldsResponses: {},
|
|
},
|
|
};
|
|
|
|
const builder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.BOOKING_CREATED
|
|
);
|
|
const payload = builder.build(mockDTO);
|
|
|
|
expect(payload).toBeDefined();
|
|
expect(payload.triggerEvent).toBe(WebhookTriggerEvents.BOOKING_CREATED);
|
|
expect(payload.payload).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe("Multiple Versions", () => {
|
|
it("should support multiple versions simultaneously", () => {
|
|
const v2Builders: PayloadBuilderSet = {
|
|
booking: new V2021_10_20.BookingPayloadBuilder(),
|
|
form: new V2021_10_20.FormPayloadBuilder(),
|
|
ooo: new V2021_10_20.OOOPayloadBuilder(),
|
|
recording: new V2021_10_20.RecordingPayloadBuilder(),
|
|
meeting: new V2021_10_20.MeetingPayloadBuilder(),
|
|
instantMeeting: new V2021_10_20.InstantMeetingBuilder(),
|
|
};
|
|
|
|
factory.registerVersion(WebhookVersionEnum.V_2024_12_01, v2Builders);
|
|
|
|
// Both versions should work
|
|
const v1Builder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2021_10_20,
|
|
WebhookTriggerEvents.BOOKING_CREATED
|
|
);
|
|
const v2Builder = factory.getBuilder(
|
|
WebhookVersionEnum.V_2024_12_01,
|
|
WebhookTriggerEvents.BOOKING_CREATED
|
|
);
|
|
|
|
expect(v1Builder).toBe(defaultBuilders.booking);
|
|
expect(v2Builder).toBe(v2Builders.booking);
|
|
expect(v1Builder).not.toBe(v2Builder); // Different instances
|
|
});
|
|
});
|
|
});
|