Files
calendar/packages/features/webhooks/lib/factory/versioned/PayloadBuilderFactory.test.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* 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>
2026-02-11 15:47:14 +01:00

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
});
});
});