More tests
parent
3fe6d8b781
commit
82bea948ea
|
@ -19,7 +19,7 @@ declare global {
|
|||
|
||||
expect.extend({
|
||||
toHaveEmail(
|
||||
testEmails: ReturnType<Fixtures["emails"]["get"]>,
|
||||
emails: Fixtures["emails"],
|
||||
expectedEmail: {
|
||||
//TODO: Support email HTML parsing to target specific elements
|
||||
htmlToContain?: string;
|
||||
|
@ -27,7 +27,7 @@ expect.extend({
|
|||
},
|
||||
to: string
|
||||
) {
|
||||
const testEmail = testEmails.find((email) => email.to === to);
|
||||
const testEmail = emails.get().find((email) => email.to.includes(to));
|
||||
if (!testEmail) {
|
||||
return {
|
||||
pass: false,
|
||||
|
@ -111,3 +111,124 @@ export function expectBookingToBeInDatabase(booking: Partial<Booking> & Pick<Boo
|
|||
});
|
||||
expect(actualBooking).toEqual(expect.objectContaining(booking));
|
||||
}
|
||||
|
||||
export function expectSuccessfulBookingCreationEmails({
|
||||
emails,
|
||||
organizer,
|
||||
booker,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
}) {
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${organizer.email}`,
|
||||
},
|
||||
`${organizer.email}`
|
||||
);
|
||||
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
},
|
||||
`${booker.name} <${booker.email}>`
|
||||
);
|
||||
}
|
||||
|
||||
export function expectAwaitingPaymentEmails({
|
||||
emails,
|
||||
organizer,
|
||||
booker,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
}) {
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>awaiting_payment_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
},
|
||||
`${booker.email}`
|
||||
);
|
||||
}
|
||||
|
||||
export function expectBookingRequestedEmails({
|
||||
emails,
|
||||
organizer,
|
||||
booker,
|
||||
}: {
|
||||
emails: Fixtures["emails"];
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
}) {
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>event_awaiting_approval_subject</title>",
|
||||
to: `${organizer.email}`,
|
||||
},
|
||||
`${organizer.email}`
|
||||
);
|
||||
|
||||
expect(emails).toHaveEmail(
|
||||
{
|
||||
htmlToContain: "<title>booking_submitted_subject</title>",
|
||||
to: `${booker.email}`,
|
||||
},
|
||||
`${booker.email}`
|
||||
);
|
||||
}
|
||||
|
||||
export function expectBookingRequestedWebhookToHaveBeenFired({
|
||||
organizer,
|
||||
booker,
|
||||
location,
|
||||
subscriberUrl,
|
||||
paidEvent,
|
||||
}: {
|
||||
organizer: { email: string; name: string };
|
||||
booker: { email: string; name: string };
|
||||
subscriberUrl: string;
|
||||
location: string;
|
||||
paidEvent?: boolean;
|
||||
}) {
|
||||
// There is an inconsistency in the way we send the data to the webhook for paid events and unpaid events. Fix that and then remove this if statement.
|
||||
if (!paidEvent) {
|
||||
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
payload: {
|
||||
metadata: {
|
||||
// In a Pending Booking Request, we don't send the video call url
|
||||
},
|
||||
responses: {
|
||||
name: { label: "your_name", value: booker.name },
|
||||
email: { label: "email_address", value: booker.email },
|
||||
location: {
|
||||
label: "location",
|
||||
value: { optionValue: "", value: location },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
expectWebhookToHaveBeenCalledWith(subscriberUrl, {
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
payload: {
|
||||
metadata: {
|
||||
// In a Pending Booking Request, we don't send the video call url
|
||||
},
|
||||
responses: {
|
||||
name: { label: "name", value: booker.name },
|
||||
email: { label: "email", value: booker.email },
|
||||
location: {
|
||||
label: "location",
|
||||
value: { optionValue: "", value: location },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,17 +31,20 @@ import {
|
|||
|
||||
import {
|
||||
expectWorkflowToBeTriggered,
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
expectBookingToBeInDatabase,
|
||||
expectWebhookToHaveBeenCalledWith,
|
||||
expectAwaitingPaymentEmails,
|
||||
expectBookingRequestedEmails,
|
||||
expectBookingRequestedWebhookToHaveBeenFired,
|
||||
} from "@calcom/web/test/utils/bookingScenario/expects";
|
||||
|
||||
|
||||
type CustomNextApiRequest = NextApiRequest & Request;
|
||||
|
||||
type CustomNextApiResponse = NextApiResponse & Response;
|
||||
// Local test runs sometime gets too slow
|
||||
const timeout = process.env.CI ? 5000 : 20000;
|
||||
describe.sequential("handleNewBooking", () => {
|
||||
describe("handleNewBooking", () => {
|
||||
beforeEach(() => {
|
||||
// Required to able to generate token in email in some cases
|
||||
process.env.CALENDSO_ENCRYPTION_KEY = "abcdefghjnmkljhjklmnhjklkmnbhjui";
|
||||
|
@ -52,12 +55,12 @@ describe.sequential("handleNewBooking", () => {
|
|||
fetchMock.resetMocks();
|
||||
});
|
||||
|
||||
describe.sequential("Frontend:", () => {
|
||||
describe("Frontend:", () => {
|
||||
test(
|
||||
`should create a successful booking with Cal Video(Daily Video) if no explicit location is provided
|
||||
1. Should create a booking in the database
|
||||
2. Should send emails to the booker as well as organizer
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
1. Should create a booking in the database
|
||||
2. Should send emails to the booker as well as organizer
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
|
@ -75,23 +78,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
|
@ -116,14 +103,29 @@ describe.sequential("handleNewBooking", () => {
|
|||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
});
|
||||
}));
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toContain({
|
||||
|
@ -145,15 +147,8 @@ describe.sequential("handleNewBooking", () => {
|
|||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
const testEmails = emails.get();
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${organizer.email}`,
|
||||
}, `${organizer.email}`);
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
}, `${booker.name} <${booker.email}>`);
|
||||
expectSuccessfulBookingCreationEmails({booker, organizer, emails})
|
||||
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
payload: {
|
||||
|
@ -174,14 +169,17 @@ describe.sequential("handleNewBooking", () => {
|
|||
timeout
|
||||
);
|
||||
|
||||
|
||||
describe('Event Type that requires confirmation', () => {
|
||||
test(
|
||||
`should submit a booking request for event requiring confirmation
|
||||
1. Should create a booking in the database with status PENDING
|
||||
2. Should send emails to the booker as well as organizer for booking request and awaiting approval
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
`should create a booking request for event that requires confirmation
|
||||
1. Should create a booking in the database with status PENDING
|
||||
2. Should send emails to the booker as well as organizer for booking request and awaiting approval
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const subscriberUrl = "http://my-webhook.example.com"
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
|
@ -196,28 +194,12 @@ describe.sequential("handleNewBooking", () => {
|
|||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
subscriberUrl ,
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
|
@ -238,14 +220,29 @@ describe.sequential("handleNewBooking", () => {
|
|||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
});
|
||||
}));
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toContain({
|
||||
|
@ -267,22 +264,127 @@ describe.sequential("handleNewBooking", () => {
|
|||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
const testEmails = emails.get();
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>event_awaiting_approval_subject</title>",
|
||||
to: `${organizer.email}`,
|
||||
}, `${organizer.email}`);
|
||||
expectBookingRequestedEmails({
|
||||
booker,
|
||||
organizer,
|
||||
emails,
|
||||
})
|
||||
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>booking_submitted_subject</title>",
|
||||
to: `${booker.email}`,
|
||||
}, `${booker.email}`);
|
||||
expectBookingRequestedWebhookToHaveBeenFired({
|
||||
booker,
|
||||
organizer,
|
||||
location: "integrations:daily",
|
||||
subscriberUrl
|
||||
})
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
test(
|
||||
`should create a booking for event that requires confirmation based on a booking notice duration threshold, if threshold is not met
|
||||
1. Should create a booking in the database with status ACCEPTED
|
||||
2. Should send emails to the booker as well as organizer
|
||||
3. Should trigger BOOKING_CREATED webhook
|
||||
`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
},
|
||||
],
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
requiresConfirmation: true,
|
||||
metadata: {
|
||||
requiresConfirmationThreshold: {
|
||||
time: 30,
|
||||
unit: "minutes"
|
||||
}
|
||||
},
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
}));
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toContain({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
});
|
||||
|
||||
expect(createdBooking).toContain({
|
||||
location: "integrations:daily",
|
||||
});
|
||||
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: createdBooking.id!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
});
|
||||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
expectSuccessfulBookingCreationEmails({booker, organizer, emails})
|
||||
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
payload: {
|
||||
metadata: {
|
||||
// In a Pending Booking Request, we don't send the video call url
|
||||
videoCallUrl: `${WEBAPP_URL}/video/DYNAMIC_UID`,
|
||||
},
|
||||
responses: {
|
||||
name: { label: "your_name", value: "Booker" },
|
||||
|
@ -298,6 +400,118 @@ describe.sequential("handleNewBooking", () => {
|
|||
timeout
|
||||
);
|
||||
|
||||
test(
|
||||
`should create a booking for event that requires confirmation based on a booking notice duration threshold, if threshold IS MET
|
||||
1. Should create a booking in the database with status PENDING
|
||||
2. Should send emails to the booker as well as organizer for booking request and awaiting approval
|
||||
3. Should trigger BOOKING_REQUESTED webhook
|
||||
`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const subscriberUrl = "http://my-webhook.example.com";
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl,
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
},
|
||||
],
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slotInterval: 45,
|
||||
requiresConfirmation: true,
|
||||
metadata: {
|
||||
requiresConfirmationThreshold: {
|
||||
time: 120,
|
||||
unit: "hours"
|
||||
}
|
||||
},
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
}));
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toContain({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
});
|
||||
|
||||
expect(createdBooking).toContain({
|
||||
location: "integrations:daily",
|
||||
});
|
||||
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
id: createdBooking.id!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
});
|
||||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
expectBookingRequestedEmails({booker, organizer, emails})
|
||||
|
||||
expectBookingRequestedWebhookToHaveBeenFired({
|
||||
booker,
|
||||
organizer,
|
||||
location: "integrations:daily",
|
||||
subscriberUrl
|
||||
})
|
||||
},
|
||||
timeout
|
||||
);
|
||||
})
|
||||
|
||||
test(
|
||||
`if booking with Cal Video(Daily Video) fails, booking creation fails with uncaught error`,
|
||||
async ({}) => {
|
||||
|
@ -307,21 +521,8 @@ describe.sequential("handleNewBooking", () => {
|
|||
name: "Booker",
|
||||
});
|
||||
const organizer = TestData.users.example;
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const scenarioData = {
|
||||
createBookingScenario( {
|
||||
hosts: [],
|
||||
eventTypes: [
|
||||
{
|
||||
|
@ -345,14 +546,27 @@ describe.sequential("handleNewBooking", () => {
|
|||
},
|
||||
],
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
};
|
||||
});
|
||||
|
||||
mockErrorOnVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
createBookingScenario(scenarioData);
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
try {
|
||||
await handleNewBooking(req);
|
||||
|
@ -381,22 +595,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
credentials: [getZoomAppCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:zoom" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const bookingScenario = getScenarioData({
|
||||
createBookingScenario(getScenarioData({
|
||||
organizer,
|
||||
eventTypes: [
|
||||
{
|
||||
|
@ -421,24 +620,27 @@ describe.sequential("handleNewBooking", () => {
|
|||
appId: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
createBookingScenario(bookingScenario);
|
||||
}));
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:zoom" },
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
await handleNewBooking(req);
|
||||
|
||||
const testEmails = emails.get();
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${organizer.email}`,
|
||||
}, `${organizer.email}`);
|
||||
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>confirmed_event_type_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
}, `${booker.name} <${booker.email}>`);
|
||||
expectSuccessfulBookingCreationEmails({booker, organizer, emails})
|
||||
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
|
@ -459,6 +661,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
describe("Paid Events", ()=>{
|
||||
test(
|
||||
`Event Type that doesn't require confirmation
|
||||
|
@ -483,23 +686,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
|
@ -517,6 +704,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
requiresConfirmation: false,
|
||||
metadata: {
|
||||
apps: {
|
||||
// EventType is connected to stripe.
|
||||
stripe: {
|
||||
price: 100,
|
||||
enabled: true,
|
||||
|
@ -538,31 +726,40 @@ describe.sequential("handleNewBooking", () => {
|
|||
TestData.apps["daily-video"],
|
||||
TestData.apps["stripe-payment"],
|
||||
],
|
||||
});
|
||||
|
||||
}));
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
const { paymentUid, externalId } = mockPaymentApp({
|
||||
metadataLookupKey: "stripe",
|
||||
appStoreLookupKey: "stripepayment",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
createBookingScenario(scenarioData);
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toContain({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
});
|
||||
|
||||
expect(createdBooking).toContain({
|
||||
location: "integrations:daily",
|
||||
paymentUid: paymentUid,
|
||||
});
|
||||
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
@ -570,16 +767,8 @@ describe.sequential("handleNewBooking", () => {
|
|||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
});
|
||||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
const testEmails = emails.get();
|
||||
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>awaiting_payment_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
}, `${booker.name} <${booker.email}>`);
|
||||
|
||||
expectAwaitingPaymentEmails({organizer, booker, emails})
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_PAYMENT_INITIATED",
|
||||
payload: {
|
||||
|
@ -596,7 +785,9 @@ describe.sequential("handleNewBooking", () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const {webhookResponse} = await mockPaymentSuccessWebhookFromStripe({externalId});
|
||||
|
||||
expect(webhookResponse?.statusCode).toBe(200)
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
|
@ -605,7 +796,6 @@ describe.sequential("handleNewBooking", () => {
|
|||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
});
|
||||
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_CREATED",
|
||||
payload: {
|
||||
|
@ -636,6 +826,7 @@ describe.sequential("handleNewBooking", () => {
|
|||
`,
|
||||
async ({ emails }) => {
|
||||
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
|
||||
const subscriberUrl = "http://my-webhook.example.com"
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
|
@ -650,12 +841,12 @@ describe.sequential("handleNewBooking", () => {
|
|||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
createBookingScenario(getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
subscriberUrl,
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
|
@ -689,8 +880,16 @@ describe.sequential("handleNewBooking", () => {
|
|||
TestData.apps["daily-video"],
|
||||
TestData.apps["stripe-payment"],
|
||||
],
|
||||
}));
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
createBookingScenario(scenarioData);
|
||||
const { paymentUid, externalId } = mockPaymentApp({
|
||||
metadataLookupKey: "stripe",
|
||||
appStoreLookupKey: "stripepayment",
|
||||
});
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
|
@ -702,34 +901,20 @@ describe.sequential("handleNewBooking", () => {
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
});
|
||||
|
||||
const { paymentUid, externalId } = mockPaymentApp({
|
||||
metadataLookupKey: "stripe",
|
||||
appStoreLookupKey: "stripepayment",
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toContain({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
});
|
||||
|
||||
expect(createdBooking).toContain({
|
||||
location: "integrations:daily",
|
||||
paymentUid: paymentUid,
|
||||
});
|
||||
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
@ -737,16 +922,8 @@ describe.sequential("handleNewBooking", () => {
|
|||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
});
|
||||
|
||||
expectWorkflowToBeTriggered();
|
||||
|
||||
const testEmails = emails.get();
|
||||
|
||||
expect(testEmails).toHaveEmail({
|
||||
htmlToContain: "<title>awaiting_payment_subject</title>",
|
||||
to: `${booker.name} <${booker.email}>`,
|
||||
}, `${booker.name} <${booker.email}>`);
|
||||
|
||||
expectAwaitingPaymentEmails({organizer, booker, emails})
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_PAYMENT_INITIATED",
|
||||
payload: {
|
||||
|
@ -764,10 +941,9 @@ describe.sequential("handleNewBooking", () => {
|
|||
},
|
||||
});
|
||||
|
||||
|
||||
const {webhookResponse} = await mockPaymentSuccessWebhookFromStripe({externalId});
|
||||
|
||||
expect(webhookResponse?.statusCode).toBe(200)
|
||||
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
|
@ -775,23 +951,13 @@ describe.sequential("handleNewBooking", () => {
|
|||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.PENDING,
|
||||
});
|
||||
|
||||
expectWebhookToHaveBeenCalledWith("http://my-webhook.example.com", {
|
||||
triggerEvent: "BOOKING_REQUESTED",
|
||||
payload: {
|
||||
// FIXME: File this bug and link ticket here. This is a bug in the code. metadata must be sent here like other BOOKING_CREATED webhook
|
||||
metadata: null,
|
||||
location: "integrations:daily",
|
||||
responses: {
|
||||
name: { label: "name", value: "Booker" },
|
||||
email: { label: "email", value: "booker@example.com" },
|
||||
location: {
|
||||
label: "location",
|
||||
value: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expectBookingRequestedWebhookToHaveBeenFired({
|
||||
booker,
|
||||
organizer,
|
||||
location: "integrations:daily",
|
||||
subscriberUrl,
|
||||
paidEvent: true
|
||||
})
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue