refactor: extract NextRequest from handle new booking (#20082)
* extract NextRequest * update api and tests * booking limits tests * fix more tests to use new approach * update more tests with new format * extract getOrgDomainConfig to not use req * extract req from loadNewUsers and pass in hostname/forcedSlug * fix instant meeting types and hostname fixes * fix handleNewBookingReq * fix type errors in tests * make hostName and forcedSlug optional * fix type err * Revert "fix type err" This reverts commit 9d5de9019d9dafe348c97b876baaa1d0675967e5. * wip fix e2e * fix: add missing headers * migrate handle recurring event and also create tests specific to fn * platform recurringbooking * fix type * hard code types on request object * bump libraries * fixup! bump libraries * fix: accessing host if headers not passed * fix: v2 recurring booking * fix: accessing host if headers not passed * chore: bump platform libraries * fix tests * push * chore: bump libraries * push lock changes * bump libraries --------- Co-authored-by: amrit <iamamrit27@gmail.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Morgan Vernay <morgan@cal.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
This commit is contained in:
co-authored by
amrit
Morgan
Morgan Vernay
supalarry
Lauris Skraucis
parent
4db3c592e5
commit
417a612aa3
@@ -213,12 +213,14 @@ import { getAccessibleUsers } from "~/lib/utils/retrieveScopedAccessibleUsers";
|
||||
* description: Authorization information is missing or invalid.
|
||||
*/
|
||||
async function handler(req: NextApiRequest) {
|
||||
const { userId, isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||
const { isSystemWideAdmin, isOrganizationOwnerOrAdmin } = req;
|
||||
let userId = req.userId;
|
||||
|
||||
req.body = {
|
||||
...req.body,
|
||||
creationSource: CreationSource.API_V1,
|
||||
};
|
||||
if (isSystemWideAdmin) req.userId = req.body.userId || userId;
|
||||
if (isSystemWideAdmin) userId = req.body.userId || userId;
|
||||
|
||||
if (isOrganizationOwnerOrAdmin) {
|
||||
const accessibleUsersIds = await getAccessibleUsers({
|
||||
@@ -226,11 +228,19 @@ async function handler(req: NextApiRequest) {
|
||||
memberUserIds: [req.body.userId || userId],
|
||||
});
|
||||
const [requestedUserId] = accessibleUsersIds;
|
||||
req.userId = requestedUserId || userId;
|
||||
userId = requestedUserId || userId;
|
||||
}
|
||||
|
||||
try {
|
||||
return await handleNewBooking(req, getBookingDataSchemaForApi);
|
||||
return await handleNewBooking(
|
||||
{
|
||||
bookingData: req.body,
|
||||
userId,
|
||||
hostname: req.headers.host || "",
|
||||
forcedSlug: req.headers["x-cal-force-slug"] as string | undefined,
|
||||
},
|
||||
getBookingDataSchemaForApi
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
const knownError = error as Error;
|
||||
if (knownError?.message === ErrorCode.NoAvailableUsersFound) {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@axiomhq/winston": "^1.2.0",
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.166",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.167",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -174,11 +174,19 @@ export class BookingsController_2024_04_15 {
|
||||
const oAuthClientId =
|
||||
clientId?.toString() || (await this.getOAuthClientIdFromEventType(body.eventTypeId));
|
||||
const { orgSlug, locationUrl } = body;
|
||||
req.headers["x-cal-force-slug"] = orgSlug;
|
||||
try {
|
||||
const booking = await handleNewBooking(
|
||||
await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed)
|
||||
);
|
||||
const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, locationUrl, isEmbed);
|
||||
const booking = await handleNewBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
forcedSlug: orgSlug,
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
if (booking.userId && booking.uid && booking.startTime) {
|
||||
void (await this.billingService.increaseUsageByUserId(booking.userId, {
|
||||
uid: booking.uid,
|
||||
@@ -284,9 +292,18 @@ export class BookingsController_2024_04_15 {
|
||||
}
|
||||
}
|
||||
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(
|
||||
await this.createNextApiRecurringBookingRequest(req, oAuthClientId, undefined, isEmbed)
|
||||
);
|
||||
const bookingRequest = await this.createNextApiBookingRequest(req, oAuthClientId, undefined, isEmbed);
|
||||
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
|
||||
createdBookings.forEach(async (booking) => {
|
||||
if (booking.userId && booking.uid && booking.startTime) {
|
||||
|
||||
@@ -149,14 +149,33 @@ export class BookingsService_2024_08_13 {
|
||||
|
||||
async createRecurringBooking(request: Request, body: CreateRecurringBookingInput_2024_08_13) {
|
||||
const bookingRequest = await this.inputService.createRecurringBookingRequest(request, body);
|
||||
const bookings = await handleNewRecurringBooking(bookingRequest);
|
||||
const bookings = await handleNewRecurringBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
noEmail: bookingRequest.noEmail,
|
||||
});
|
||||
const ids = bookings.map((booking) => booking.id || 0);
|
||||
return this.outputService.getOutputRecurringBookings(ids);
|
||||
}
|
||||
|
||||
async createRecurringSeatedBooking(request: Request, body: CreateRecurringBookingInput_2024_08_13) {
|
||||
const bookingRequest = await this.inputService.createRecurringBookingRequest(request, body);
|
||||
const bookings = await handleNewRecurringBooking(bookingRequest);
|
||||
const bookings = await handleNewRecurringBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
return this.outputService.getOutputCreateRecurringSeatedBookings(
|
||||
bookings.map((booking) => ({ uid: booking.uid || "", seatUid: booking.seatReferenceUid || "" }))
|
||||
);
|
||||
@@ -164,7 +183,16 @@ export class BookingsService_2024_08_13 {
|
||||
|
||||
async createRegularBooking(request: Request, body: CreateBookingInput_2024_08_13) {
|
||||
const bookingRequest = await this.inputService.createBookingRequest(request, body);
|
||||
const booking = await handleNewBooking(bookingRequest);
|
||||
const booking = await handleNewBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
|
||||
if (!booking.uid) {
|
||||
throw new Error("Booking missing uid");
|
||||
@@ -180,7 +208,16 @@ export class BookingsService_2024_08_13 {
|
||||
|
||||
async createSeatedBooking(request: Request, body: CreateBookingInput_2024_08_13) {
|
||||
const bookingRequest = await this.inputService.createBookingRequest(request, body);
|
||||
const booking = await handleNewBooking(bookingRequest);
|
||||
const booking = await handleNewBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
|
||||
if (!booking.uid) {
|
||||
throw new Error("Booking missing uid");
|
||||
@@ -324,7 +361,16 @@ export class BookingsService_2024_08_13 {
|
||||
bookingUid,
|
||||
body
|
||||
);
|
||||
const booking = await handleNewBooking(bookingRequest);
|
||||
const booking = await handleNewBooking({
|
||||
bookingData: bookingRequest.body,
|
||||
userId: bookingRequest.userId,
|
||||
hostname: bookingRequest.headers?.host || "",
|
||||
platformClientId: bookingRequest.platformClientId,
|
||||
platformRescheduleUrl: bookingRequest.platformRescheduleUrl,
|
||||
platformCancelUrl: bookingRequest.platformCancelUrl,
|
||||
platformBookingUrl: bookingRequest.platformBookingUrl,
|
||||
platformBookingLocation: bookingRequest.platformBookingLocation,
|
||||
});
|
||||
if (!booking.uid) {
|
||||
throw new Error("Booking missing uid");
|
||||
}
|
||||
|
||||
@@ -45,7 +45,10 @@ import {
|
||||
import { BookingInputLocation_2024_08_13 } from "@calcom/platform-types/bookings/2024-08-13/inputs/location.input";
|
||||
import { EventType } from "@calcom/prisma/client";
|
||||
|
||||
type BookingRequest = NextApiRequest & { userId: number | undefined } & OAuthRequestParams;
|
||||
type BookingRequest = NextApiRequest & {
|
||||
userId: number | undefined;
|
||||
noEmail: boolean | undefined;
|
||||
} & OAuthRequestParams;
|
||||
|
||||
type OAuthRequestParams = {
|
||||
platformClientId: string;
|
||||
@@ -223,7 +226,13 @@ export class InputBookingsService_2024_08_13 {
|
||||
creationSource: CreationSource.API_V2,
|
||||
}));
|
||||
|
||||
return newRequest as unknown as BookingRequest;
|
||||
return {
|
||||
...newRequest,
|
||||
headers: {
|
||||
hostname: request.headers["host"] || "",
|
||||
forcedSlug: request.headers["x-cal-force-slug"] as string | undefined,
|
||||
},
|
||||
} as unknown as BookingRequest;
|
||||
}
|
||||
|
||||
transformLocation(location: string | BookingInputLocation_2024_08_13): {
|
||||
|
||||
@@ -25,12 +25,17 @@ async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
|
||||
const session = await getServerSession({ req });
|
||||
/* To mimic API behavior and comply with types */
|
||||
req.userId = session?.user?.id || -1;
|
||||
req.body = {
|
||||
...req.body,
|
||||
creationSource: CreationSource.WEBAPP,
|
||||
};
|
||||
const booking = await handleNewBooking(req);
|
||||
|
||||
const booking = await handleNewBooking({
|
||||
bookingData: req.body,
|
||||
userId: session?.user?.id || -1,
|
||||
hostname: req.headers.host || "",
|
||||
forcedSlug: req.headers["x-cal-force-slug"] as string | undefined,
|
||||
});
|
||||
return booking;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,22 @@ import { defaultResponder } from "@calcom/lib/server/defaultResponder";
|
||||
|
||||
// @TODO: Didn't look at the contents of this function in order to not break old booking page.
|
||||
|
||||
async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
type PlatformParams = {
|
||||
platformClientId?: string;
|
||||
platformCancelUrl?: string;
|
||||
platformBookingUrl?: string;
|
||||
platformRescheduleUrl?: string;
|
||||
platformBookingLocation?: string;
|
||||
};
|
||||
|
||||
type RequestMeta = {
|
||||
userId?: number;
|
||||
hostname?: string;
|
||||
forcedSlug?: string;
|
||||
noEmail?: boolean;
|
||||
} & PlatformParams;
|
||||
|
||||
async function handler(req: NextApiRequest & RequestMeta) {
|
||||
const userIp = getIP(req);
|
||||
|
||||
if (process.env.NEXT_PUBLIC_CLOUDFLARE_USE_TURNSTILE_IN_BOOKER === "1") {
|
||||
@@ -26,9 +41,17 @@ async function handler(req: NextApiRequest & { userId?: number }) {
|
||||
});
|
||||
const session = await getServerSession({ req });
|
||||
/* To mimic API behavior and comply with types */
|
||||
req.userId = session?.user?.id || -1;
|
||||
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking(req);
|
||||
const createdBookings: BookingResponse[] = await handleNewRecurringBooking({
|
||||
bookingData: req.body,
|
||||
userId: session?.user?.id || -1,
|
||||
platformClientId: req.platformClientId,
|
||||
platformCancelUrl: req.platformCancelUrl,
|
||||
platformBookingUrl: req.platformBookingUrl,
|
||||
platformRescheduleUrl: req.platformRescheduleUrl,
|
||||
platformBookingLocation: req.platformBookingLocation,
|
||||
noEmail: req.noEmail,
|
||||
});
|
||||
|
||||
return createdBookings;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { AppCategories } from "@calcom/prisma/enums";
|
||||
import { type RouterOutputs } from "@calcom/trpc";
|
||||
import type { App } from "@calcom/types/App";
|
||||
import { List } from "@calcom/ui/components/list";
|
||||
import { Alert } from "@calcom/ui/components/alert";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import {
|
||||
@@ -27,6 +26,7 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui/components/dropdown";
|
||||
import { List } from "@calcom/ui/components/list";
|
||||
import { showToast } from "@calcom/ui/components/toast";
|
||||
|
||||
export type HandleDisconnect = (credentialId: number, app: App["slug"], teamId?: number) => void;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { DestinationCalendar } from "@prisma/client";
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { cloneDeep } from "lodash";
|
||||
import type { NextApiRequest } from "next";
|
||||
import short, { uuid } from "short-uuid";
|
||||
import { v5 as uuidv5 } from "uuid";
|
||||
|
||||
@@ -77,7 +76,6 @@ import {
|
||||
eventTypeAppMetadataOptionalSchema,
|
||||
eventTypeMetaDataSchemaWithTypedApps,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
import type { PlatformClientParams } from "@calcom/prisma/zod-utils";
|
||||
import { userMetadata as userMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
import { getAllWorkflowsFromEventType } from "@calcom/trpc/server/routers/viewer/workflows/util";
|
||||
import type { AdditionalInformation, AppsStatus, CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
@@ -314,33 +312,52 @@ function buildTroubleshooterData({
|
||||
return troubleshooterData;
|
||||
}
|
||||
|
||||
export type PlatformParams = {
|
||||
platformClientId?: string;
|
||||
platformCancelUrl?: string;
|
||||
platformBookingUrl?: string;
|
||||
platformRescheduleUrl?: string;
|
||||
platformBookingLocation?: string;
|
||||
};
|
||||
|
||||
export type BookingHandlerInput = {
|
||||
bookingData: Record<string, any>;
|
||||
userId?: number;
|
||||
// These used to come from headers but now we're passing them as params
|
||||
hostname?: string;
|
||||
forcedSlug?: string;
|
||||
} & PlatformParams;
|
||||
|
||||
async function handler(
|
||||
req: NextApiRequest &
|
||||
PlatformClientParams & {
|
||||
userId?: number | undefined;
|
||||
},
|
||||
input: BookingHandlerInput,
|
||||
bookingDataSchemaGetter: BookingDataSchemaGetter = getBookingDataSchema
|
||||
) {
|
||||
const {
|
||||
bookingData: rawBookingData,
|
||||
userId,
|
||||
platformClientId,
|
||||
platformCancelUrl,
|
||||
platformBookingUrl,
|
||||
platformRescheduleUrl,
|
||||
platformBookingLocation,
|
||||
} = req;
|
||||
hostname,
|
||||
forcedSlug,
|
||||
} = input;
|
||||
|
||||
const isPlatformBooking = !!platformClientId;
|
||||
|
||||
const eventType = await monitorCallbackAsync(getEventType, {
|
||||
eventTypeId: req.body.eventTypeId,
|
||||
eventTypeSlug: req.body.eventTypeSlug,
|
||||
eventTypeId: rawBookingData.eventTypeId,
|
||||
eventTypeSlug: rawBookingData.eventTypeSlug,
|
||||
});
|
||||
|
||||
const bookingDataSchema = bookingDataSchemaGetter({
|
||||
view: req.body?.rescheduleUid ? "reschedule" : "booking",
|
||||
view: rawBookingData.rescheduleUid ? "reschedule" : "booking",
|
||||
bookingFields: eventType.bookingFields,
|
||||
});
|
||||
|
||||
const bookingData = await getBookingData({
|
||||
req,
|
||||
reqBody: rawBookingData,
|
||||
eventType,
|
||||
schema: bookingDataSchema,
|
||||
});
|
||||
@@ -480,7 +497,9 @@ async function handler(
|
||||
const { qualifiedRRUsers, additionalFallbackRRUsers, fixedUsers } = await monitorCallbackAsync(
|
||||
loadAndValidateUsers,
|
||||
{
|
||||
req,
|
||||
hostname,
|
||||
forcedSlug,
|
||||
isPlatform: isPlatformBooking,
|
||||
eventType,
|
||||
eventTypeId,
|
||||
dynamicUserList,
|
||||
@@ -545,7 +564,7 @@ async function handler(
|
||||
},
|
||||
}),
|
||||
};
|
||||
if (req.body.allRecurringDates && req.body.isFirstRecurringSlot) {
|
||||
if (input.bookingData.allRecurringDates && input.bookingData.isFirstRecurringSlot) {
|
||||
const isTeamEvent =
|
||||
eventType.schedulingType === SchedulingType.COLLECTIVE ||
|
||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN;
|
||||
@@ -556,11 +575,12 @@ async function handler(
|
||||
|
||||
for (
|
||||
let i = 0;
|
||||
i < req.body.allRecurringDates.length && i < req.body.numSlotsToCheckForAvailability;
|
||||
i < input.bookingData.allRecurringDates.length &&
|
||||
i < input.bookingData.numSlotsToCheckForAvailability;
|
||||
i++
|
||||
) {
|
||||
const start = req.body.allRecurringDates[i].start;
|
||||
const end = req.body.allRecurringDates[i].end;
|
||||
const start = input.bookingData.allRecurringDates[i].start;
|
||||
const end = input.bookingData.allRecurringDates[i].end;
|
||||
if (isTeamEvent) {
|
||||
// each fixed user must be available
|
||||
for (const key in fixedUsers) {
|
||||
@@ -593,7 +613,7 @@ async function handler(
|
||||
}
|
||||
}
|
||||
|
||||
if (!req.body.allRecurringDates || req.body.isFirstRecurringSlot) {
|
||||
if (!input.bookingData.allRecurringDates || input.bookingData.isFirstRecurringSlot) {
|
||||
let availableUsers: IsFixedAwareUser[] = [];
|
||||
try {
|
||||
availableUsers = await ensureAvailableUsers(
|
||||
@@ -693,16 +713,20 @@ async function handler(
|
||||
if (!newLuckyUser) {
|
||||
break; // prevent infinite loop
|
||||
}
|
||||
if (req.body.isFirstRecurringSlot && eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
if (
|
||||
input.bookingData.isFirstRecurringSlot &&
|
||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN
|
||||
) {
|
||||
// for recurring round robin events check if lucky user is available for next slots
|
||||
try {
|
||||
for (
|
||||
let i = 0;
|
||||
i < req.body.allRecurringDates.length && i < req.body.numSlotsToCheckForAvailability;
|
||||
i < input.bookingData.allRecurringDates.length &&
|
||||
i < input.bookingData.numSlotsToCheckForAvailability;
|
||||
i++
|
||||
) {
|
||||
const start = req.body.allRecurringDates[i].start;
|
||||
const end = req.body.allRecurringDates[i].end;
|
||||
const start = input.bookingData.allRecurringDates[i].start;
|
||||
const end = input.bookingData.allRecurringDates[i].end;
|
||||
|
||||
await ensureAvailableUsers(
|
||||
{ ...eventTypeWithUsers, users: [newLuckyUser] },
|
||||
@@ -741,7 +765,10 @@ async function handler(
|
||||
fixedUsers: fixedUserPool.map((u) => u.id),
|
||||
luckyUserPool: luckyUserPool.map((u) => u.id),
|
||||
};
|
||||
} else if (req.body.allRecurringDates && eventType.schedulingType === SchedulingType.ROUND_ROBIN) {
|
||||
} else if (
|
||||
input.bookingData.allRecurringDates &&
|
||||
eventType.schedulingType === SchedulingType.ROUND_ROBIN
|
||||
) {
|
||||
// all recurring slots except the first one
|
||||
const luckyUsersFromFirstBooking = luckyUsers
|
||||
? eventTypeWithUsers.users.filter((user) => luckyUsers.find((luckyUserId) => luckyUserId === user.id))
|
||||
@@ -1024,9 +1051,9 @@ async function handler(
|
||||
})
|
||||
.build();
|
||||
|
||||
if (req.body.thirdPartyRecurringEventId) {
|
||||
if (input.bookingData.thirdPartyRecurringEventId) {
|
||||
evt = CalendarEventBuilder.fromEvent(evt)
|
||||
.withRecurringEventId(req.body.thirdPartyRecurringEventId)
|
||||
.withRecurringEventId(input.bookingData.thirdPartyRecurringEventId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -1119,7 +1146,7 @@ async function handler(
|
||||
bookerPhoneNumber,
|
||||
tAttendees,
|
||||
bookingSeat,
|
||||
reqUserId: req.userId,
|
||||
reqUserId: input.userId,
|
||||
rescheduleReason,
|
||||
reqBodyUser: reqBody.user,
|
||||
noEmail,
|
||||
@@ -1143,7 +1170,6 @@ async function handler(
|
||||
});
|
||||
|
||||
if (newBooking) {
|
||||
req.statusCode = 201;
|
||||
const bookingResponse = {
|
||||
...newBooking,
|
||||
user: {
|
||||
@@ -1229,7 +1255,7 @@ async function handler(
|
||||
},
|
||||
evt,
|
||||
originalRescheduledBooking,
|
||||
creationSource: req.body.creationSource,
|
||||
creationSource: input.bookingData.creationSource,
|
||||
tracking: reqBody.tracking,
|
||||
});
|
||||
|
||||
@@ -1269,9 +1295,9 @@ async function handler(
|
||||
if (booking && booking.id && eventType.seatsPerTimeSlot) {
|
||||
const currentAttendee = booking.attendees.find(
|
||||
(attendee) =>
|
||||
attendee.email === req.body.responses.email ||
|
||||
(req.body.responses.attendeePhoneNumber &&
|
||||
attendee.phoneNumber === req.body.responses.attendeePhoneNumber)
|
||||
attendee.email === input.bookingData.responses.email ||
|
||||
(input.bookingData.responses.attendeePhoneNumber &&
|
||||
attendee.phoneNumber === input.bookingData.responses.attendeePhoneNumber)
|
||||
);
|
||||
|
||||
// Save description to bookingSeat
|
||||
@@ -1853,7 +1879,6 @@ async function handler(
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
req.statusCode = 201;
|
||||
// TODO: Refactor better so this booking object is not passed
|
||||
// all around and instead the individual fields are sent as args.
|
||||
const bookingResponse = {
|
||||
@@ -2019,7 +2044,9 @@ async function handler(
|
||||
calendarEvent: evtWithMetadata,
|
||||
isNotConfirmed: rescheduleUid ? false : !isConfirmedByDefault,
|
||||
isRescheduleEvent: !!rescheduleUid,
|
||||
isFirstRecurringEvent: req.body.allRecurringDates ? req.body.isFirstRecurringSlot : undefined,
|
||||
isFirstRecurringEvent: input.bookingData.allRecurringDates
|
||||
? input.bookingData.isFirstRecurringSlot
|
||||
: undefined,
|
||||
hideBranding: !!eventType.owner?.hideBranding,
|
||||
seatReferenceUid: evt.attendeeSeatId,
|
||||
isDryRun,
|
||||
@@ -2044,9 +2071,6 @@ async function handler(
|
||||
loggerWithEventDetails.error("Error while scheduling no show triggers", JSON.stringify({ error }));
|
||||
}
|
||||
|
||||
// booking successful
|
||||
req.statusCode = 201;
|
||||
|
||||
// TODO: Refactor better so this booking object is not passed
|
||||
// all around and instead the individual fields are sent as args.
|
||||
const bookingResponse = {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { EventTypeCustomInput } from "@prisma/client";
|
||||
import type { NextApiRequest } from "next";
|
||||
import type z from "zod";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
@@ -14,32 +13,32 @@ import { handleCustomInputs } from "./handleCustomInputs";
|
||||
type ReqBodyWithEnd = TgetBookingDataSchema & { end: string };
|
||||
|
||||
export async function getBookingData<T extends z.ZodType>({
|
||||
req,
|
||||
reqBody,
|
||||
eventType,
|
||||
schema,
|
||||
}: {
|
||||
req: NextApiRequest;
|
||||
reqBody: Record<string, any>;
|
||||
eventType: getEventTypeResponse;
|
||||
schema: T;
|
||||
}) {
|
||||
const reqBody = await schema.parseAsync(req.body);
|
||||
const reqBodyWithEnd = (reqBody: TgetBookingDataSchema): reqBody is ReqBodyWithEnd => {
|
||||
const parsedBody = await schema.parseAsync(reqBody);
|
||||
const parsedBodyWithEnd = (body: TgetBookingDataSchema): body is ReqBodyWithEnd => {
|
||||
// Use the event length to auto-set the event end time.
|
||||
if (!Object.prototype.hasOwnProperty.call(reqBody, "end")) {
|
||||
reqBody.end = dayjs.utc(reqBody.start).add(eventType.length, "minutes").format();
|
||||
if (!Object.prototype.hasOwnProperty.call(body, "end")) {
|
||||
body.end = dayjs.utc(body.start).add(eventType.length, "minutes").format();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
if (!reqBodyWithEnd(reqBody)) {
|
||||
if (!parsedBodyWithEnd(parsedBody)) {
|
||||
throw new Error(ErrorCode.RequestBodyWithouEnd);
|
||||
}
|
||||
// reqBody.end is no longer an optional property.
|
||||
if (reqBody.customInputs) {
|
||||
// parsedBody.end is no longer an optional property.
|
||||
if (parsedBody.customInputs) {
|
||||
// Check if required custom inputs exist
|
||||
handleCustomInputs(eventType.customInputs as EventTypeCustomInput[], reqBody.customInputs);
|
||||
const reqBodyWithLegacyProps = bookingCreateSchemaLegacyPropsForApi.parse(reqBody);
|
||||
handleCustomInputs(eventType.customInputs as EventTypeCustomInput[], parsedBody.customInputs);
|
||||
const reqBodyWithLegacyProps = bookingCreateSchemaLegacyPropsForApi.parse(parsedBody);
|
||||
return {
|
||||
...reqBody,
|
||||
...parsedBody,
|
||||
name: reqBodyWithLegacyProps.name,
|
||||
email: reqBodyWithLegacyProps.email,
|
||||
guests: reqBodyWithLegacyProps.guests,
|
||||
@@ -54,10 +53,10 @@ export async function getBookingData<T extends z.ZodType>({
|
||||
attendeePhoneNumber: undefined,
|
||||
};
|
||||
}
|
||||
if (!reqBody.responses) {
|
||||
if (!parsedBody.responses) {
|
||||
throw new Error("`responses` must not be nullish");
|
||||
}
|
||||
const responses = reqBody.responses;
|
||||
const responses = parsedBody.responses;
|
||||
|
||||
const { userFieldsResponses: calEventUserFieldsResponses, responses: calEventResponses } =
|
||||
getCalEventResponses({
|
||||
@@ -65,7 +64,7 @@ export async function getBookingData<T extends z.ZodType>({
|
||||
responses,
|
||||
});
|
||||
return {
|
||||
...reqBody,
|
||||
...parsedBody,
|
||||
name: responses.name,
|
||||
email: responses.email,
|
||||
attendeePhoneNumber: responses.attendeePhoneNumber,
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
getGoogleCalendarCredential,
|
||||
Timezones,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
// TODO: we should rename this - it doesnt get a mockRequestDataForBooking - it just gets mock booking data.
|
||||
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
|
||||
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
||||
|
||||
@@ -173,13 +173,10 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req1 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingWithinLimit,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingWithinLimit,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req1);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -200,15 +197,13 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req2 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingAboveLimit,
|
||||
});
|
||||
|
||||
// this is the third team booking of this week for user 101, limit reached
|
||||
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingAboveLimit,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
|
||||
test(`Booking limits per day`, async ({}) => {
|
||||
@@ -264,13 +259,10 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req1 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingWithinLimit,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingWithinLimit,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req1);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -291,15 +283,13 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req2 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingAboveLimit,
|
||||
});
|
||||
|
||||
// this is the second team booking of this day for user 101, limit reached
|
||||
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingAboveLimit,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
|
||||
test(`Booking limits per month`, async ({}) => {
|
||||
@@ -400,13 +390,10 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req1 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingWithinLimit,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingWithinLimit,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req1);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -427,15 +414,13 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req2 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingAboveLimit,
|
||||
});
|
||||
|
||||
// this is the firth team booking (incl. managed) of this month for user 101, limit reached
|
||||
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingAboveLimit,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
|
||||
test(`Booking limits per year`, async ({}) => {
|
||||
@@ -528,13 +513,10 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req1 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingWithinLimit,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingWithinLimit,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req1);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -555,14 +537,12 @@ describe(
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req2 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingAboveLimit,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req2)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingAboveLimit,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { Prisma } from "@prisma/client";
|
||||
import type { IncomingMessage } from "http";
|
||||
import type { Logger } from "tslog";
|
||||
|
||||
import { checkIfUsersAreBlocked } from "@calcom/features/watchlist/operations/check-if-users-are-blocked.controller";
|
||||
@@ -54,7 +53,6 @@ type EventType = Pick<
|
||||
>;
|
||||
|
||||
type InputProps = {
|
||||
req: IncomingMessage;
|
||||
eventType: EventType;
|
||||
eventTypeId: number;
|
||||
dynamicUserList: string[];
|
||||
@@ -63,10 +61,12 @@ type InputProps = {
|
||||
contactOwnerEmail: string | null;
|
||||
rescheduleUid: string | null;
|
||||
routingFormResponse: RoutingFormResponse | null;
|
||||
isPlatform: boolean;
|
||||
hostname: string | undefined;
|
||||
forcedSlug: string | undefined;
|
||||
};
|
||||
|
||||
export async function loadAndValidateUsers({
|
||||
req,
|
||||
eventType,
|
||||
eventTypeId,
|
||||
dynamicUserList,
|
||||
@@ -75,6 +75,9 @@ export async function loadAndValidateUsers({
|
||||
contactOwnerEmail,
|
||||
rescheduleUid,
|
||||
routingFormResponse,
|
||||
isPlatform,
|
||||
hostname,
|
||||
forcedSlug,
|
||||
}: InputProps): Promise<{
|
||||
qualifiedRRUsers: UsersWithDelegationCredentials;
|
||||
additionalFallbackRRUsers: UsersWithDelegationCredentials;
|
||||
@@ -83,7 +86,9 @@ export async function loadAndValidateUsers({
|
||||
let users: Users = await loadUsers({
|
||||
eventType,
|
||||
dynamicUserList,
|
||||
req,
|
||||
hostname: hostname || "",
|
||||
forcedSlug,
|
||||
isPlatform,
|
||||
routedTeamMemberIds,
|
||||
contactOwnerEmail,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import type { IncomingMessage } from "http";
|
||||
|
||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import { getOrgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
||||
import {
|
||||
getRoutedUsersWithContactOwnerAndFixedUsers,
|
||||
findMatchingHostsWithEventSegment,
|
||||
@@ -33,18 +32,27 @@ type EventType = Pick<
|
||||
export const loadUsers = async ({
|
||||
eventType,
|
||||
dynamicUserList,
|
||||
req,
|
||||
hostname,
|
||||
forcedSlug,
|
||||
isPlatform,
|
||||
routedTeamMemberIds,
|
||||
contactOwnerEmail,
|
||||
}: {
|
||||
eventType: EventType;
|
||||
dynamicUserList: string[];
|
||||
req: IncomingMessage;
|
||||
routedTeamMemberIds: number[] | null;
|
||||
contactOwnerEmail: string | null;
|
||||
hostname: string;
|
||||
forcedSlug: string | undefined;
|
||||
isPlatform: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const { currentOrgDomain } = orgDomainConfig(req);
|
||||
const { currentOrgDomain } = getOrgDomainConfig({
|
||||
hostname,
|
||||
forcedSlug,
|
||||
isPlatform,
|
||||
});
|
||||
|
||||
const users = eventType.id
|
||||
? await loadUsersByEventType(eventType)
|
||||
: await loadDynamicUsers(dynamicUserList, currentOrgDomain);
|
||||
|
||||
@@ -20,7 +20,6 @@ import {
|
||||
mockSuccessfulVideoMeetingCreation,
|
||||
mockCalendarToHaveNoBusySlots,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import { expectBookingToBeInDatabase } from "@calcom/web/test/utils/bookingScenario/expects";
|
||||
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
|
||||
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
||||
@@ -116,12 +115,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("booking_limit_reached");
|
||||
|
||||
const { dateString: yearWithoutBookingsDateString } = getDate({ yearIncrement: 2 });
|
||||
|
||||
@@ -138,13 +137,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req: reqFollowingYear } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingDataFollowingYear,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingDataFollowingYear,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(reqFollowingYear);
|
||||
|
||||
expect(createdBooking.responses).toContain({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
@@ -239,16 +235,14 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
vi.spyOn(prismock, "$queryRaw").mockResolvedValue([{ totalMinutes: yearlyDurationLimit }]);
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
"duration_limit_reached"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("duration_limit_reached");
|
||||
|
||||
const { dateString: yearWithoutBookingsDateString } = getDate({ yearIncrement: 2 });
|
||||
|
||||
@@ -265,14 +259,11 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req: reqFollowingYear } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingDataFollowingYear,
|
||||
});
|
||||
|
||||
vi.spyOn(prismock, "$queryRaw").mockResolvedValue([{ totalMinutes: 0 }]);
|
||||
|
||||
const createdBooking = await handleNewBooking(reqFollowingYear);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingDataFollowingYear,
|
||||
});
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -373,12 +364,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("booking_limit_reached");
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -460,12 +451,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("booking_limit_reached");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("booking_limit_reached");
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -536,13 +527,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req: reqBookingBefore } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingBeforeData,
|
||||
});
|
||||
await expect(async () => await handleNewBooking(reqBookingBefore)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingBeforeData,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
|
||||
// 7:00 - 7:15 busy
|
||||
// 7:17 - 8:15 after event buffer
|
||||
@@ -560,13 +550,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req: reqBookingAfter } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingAfterData,
|
||||
});
|
||||
await expect(async () => await handleNewBooking(reqBookingAfter)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingAfterData,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
|
||||
test(`should throw error when booking is within a before event buffer of an existing booking
|
||||
@@ -628,13 +617,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
});
|
||||
test(`should throw error when booking is within a after event buffer of an existing booking
|
||||
@@ -696,13 +684,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
"no_available_users_found_error"
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("no_available_users_found_error");
|
||||
});
|
||||
|
||||
test(
|
||||
@@ -737,11 +724,6 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
@@ -772,9 +754,12 @@ describe("handleNewBooking", () => {
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {});
|
||||
await createBookingScenario(scenarioData);
|
||||
|
||||
await expect(() => handleNewBooking(req)).rejects.toThrowError(
|
||||
"Attempting to book a meeting in the past."
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("Attempting to book a meeting in the past.");
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -847,11 +832,6 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
@@ -867,7 +847,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(() => handleNewBooking(req)).rejects.toThrowError("booking_time_out_of_bounds_error");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("booking_time_out_of_bounds_error");
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
mockCalendarToHaveNoBusySlots,
|
||||
BookingLocations,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
expectBookingToBeInDatabase,
|
||||
@@ -157,12 +156,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
mockCalendarToHaveNoBusySlots,
|
||||
BookingLocations,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
expectBookingToBeInDatabase,
|
||||
@@ -142,12 +141,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -322,12 +319,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
createOrganization,
|
||||
buildDelegationCredential,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectWorkflowToBeTriggered,
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
@@ -160,13 +159,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -350,13 +346,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -552,13 +545,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -718,13 +708,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
|
||||
+14
-22
@@ -17,7 +17,6 @@ import {
|
||||
getScenarioData,
|
||||
mockCalendar,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import { expectBookingToBeInDatabase } from "@calcom/web/test/utils/bookingScenario/expects";
|
||||
import { getMockRequestDataForDynamicGroupBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
|
||||
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
||||
@@ -118,13 +117,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -208,14 +204,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.HostsUnavailableForBooking
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.HostsUnavailableForBooking);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -299,14 +293,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.HostsUnavailableForBooking
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.HostsUnavailableForBooking);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
mockVideoAppToCrashOnCreateMeeting,
|
||||
BookingLocations,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectWorkflowToBeTriggered,
|
||||
expectWorkflowToBeNotTriggered,
|
||||
@@ -177,13 +176,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -346,12 +342,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -512,12 +505,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -657,12 +647,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -804,12 +791,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -966,12 +951,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -1056,17 +1039,14 @@ describe("handleNewBooking", () => {
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
@@ -1104,7 +1084,9 @@ describe("handleNewBooking", () => {
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
await createBookingScenario(scenarioData);
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
location: BookingLocations.ZoomVideo,
|
||||
@@ -1150,17 +1132,14 @@ describe("handleNewBooking", () => {
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
@@ -1191,7 +1170,9 @@ describe("handleNewBooking", () => {
|
||||
apps: [TestData.apps["daily-video"]],
|
||||
});
|
||||
await createBookingScenario(scenarioData);
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
location: "Seoul",
|
||||
@@ -1239,17 +1220,14 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
@@ -1282,7 +1260,9 @@ describe("handleNewBooking", () => {
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
await createBookingScenario(scenarioData);
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
location: BookingLocations.ZoomVideo,
|
||||
@@ -1361,20 +1341,20 @@ describe("handleNewBooking", () => {
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.ZoomVideo },
|
||||
},
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.ZoomVideo },
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
|
||||
|
||||
@@ -1459,20 +1439,20 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.ZoomVideo },
|
||||
},
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.ZoomVideo },
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -1528,7 +1508,7 @@ describe("handleNewBooking", () => {
|
||||
})
|
||||
);
|
||||
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
start: `${getDate({ dateIncrement: 1 }).dateString}T05:00:00.000Z`,
|
||||
end: `${getDate({ dateIncrement: 1 }).dateString}T05:15:00.000Z`,
|
||||
@@ -1541,12 +1521,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("Invalid event length");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
})
|
||||
).rejects.toThrowError("Invalid event length");
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -1579,18 +1559,15 @@ describe("handleNewBooking", () => {
|
||||
utm_content: "content test",
|
||||
};
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
tracking,
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
}),
|
||||
tracking,
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
@@ -1629,7 +1606,9 @@ describe("handleNewBooking", () => {
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
await createBookingScenario(scenarioData);
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
expectBookingTrackingToBeInDatabase(tracking, createdBooking.uid!);
|
||||
@@ -1657,18 +1636,15 @@ describe("handleNewBooking", () => {
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
creationSource: CreationSource.WEBAPP,
|
||||
const mockedBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
},
|
||||
}),
|
||||
creationSource: CreationSource.WEBAPP,
|
||||
},
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
@@ -1707,7 +1683,9 @@ describe("handleNewBooking", () => {
|
||||
metadataLookupKey: "zoomvideo",
|
||||
});
|
||||
await createBookingScenario(scenarioData);
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockedBookingData,
|
||||
});
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
creationSource: CreationSource.WEBAPP,
|
||||
@@ -1783,14 +1761,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.NoAvailableUsersFound
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -1844,7 +1820,7 @@ describe("handleNewBooking", () => {
|
||||
})
|
||||
);
|
||||
|
||||
const _calendarMock = mockCalendar("googlecalendar", {
|
||||
mockCalendar("googlecalendar", {
|
||||
create: {
|
||||
uid: "MOCK_ID",
|
||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||
@@ -1870,14 +1846,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.NoAvailableUsersFound
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -1951,12 +1925,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -2039,14 +2010,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.NoAvailableUsersFound
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -2125,12 +2094,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -2228,12 +2194,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -2358,15 +2322,11 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
req.userId = organizer.id;
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
@@ -2484,12 +2444,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -2621,12 +2578,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -2707,22 +2661,21 @@ describe("handleNewBooking", () => {
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar");
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.CalVideo },
|
||||
},
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: BookingLocations.CalVideo },
|
||||
},
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
await handleNewBooking(req);
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
} catch (e) {
|
||||
expect(e).toBeInstanceOf(MockError);
|
||||
expect((e as { message: string }).message).toBe("Error creating Video meeting");
|
||||
@@ -2765,11 +2718,6 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const scenarioData = getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
@@ -2809,7 +2757,9 @@ describe("handleNewBooking", () => {
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {});
|
||||
await createBookingScenario(scenarioData);
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -2952,11 +2902,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -3110,11 +3058,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
@@ -3261,13 +3207,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -3325,9 +3268,12 @@ describe("handleNewBooking", () => {
|
||||
destinationEmail: organizerDestinationCalendarEmailOnEventType,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError(
|
||||
ErrorCode.NoAvailableUsersFound
|
||||
);
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
@@ -3382,12 +3328,12 @@ describe("handleNewBooking", () => {
|
||||
|
||||
await createBookingScenario(scenarioData);
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("eventTypeUser.notFound");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("eventTypeUser.notFound");
|
||||
});
|
||||
|
||||
test("username is critical in watchlist", async () => {
|
||||
@@ -3444,12 +3390,12 @@ describe("handleNewBooking", () => {
|
||||
value: organizer.username,
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("eventTypeUser.notFound");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("eventTypeUser.notFound");
|
||||
});
|
||||
|
||||
test("domain is critical in watchlist", async () => {
|
||||
@@ -3506,12 +3452,12 @@ describe("handleNewBooking", () => {
|
||||
value: "spammer.com",
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("eventTypeUser.notFound");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("eventTypeUser.notFound");
|
||||
});
|
||||
|
||||
test("domain is critical in watchlist", async () => {
|
||||
@@ -3568,12 +3514,12 @@ describe("handleNewBooking", () => {
|
||||
value: "spam@spammer.com",
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => await handleNewBooking(req)).rejects.toThrowError("eventTypeUser.notFound");
|
||||
await expect(
|
||||
async () =>
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError("eventTypeUser.notFound");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+566
@@ -0,0 +1,566 @@
|
||||
import {
|
||||
createBookingScenario,
|
||||
getBooker,
|
||||
getDate,
|
||||
getGoogleCalendarCredential,
|
||||
getOrganizer,
|
||||
getScenarioData,
|
||||
mockCalendarToHaveNoBusySlots,
|
||||
mockSuccessfulVideoMeetingCreation,
|
||||
TestData,
|
||||
Timezones,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import {
|
||||
expectBookingCreatedWebhookToHaveBeenFired,
|
||||
expectBookingToBeInDatabase,
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
expectSuccessfulCalendarEventCreationInCalendar,
|
||||
} from "@calcom/web/test/utils/bookingScenario/expects";
|
||||
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
|
||||
import { setupAndTeardown } from "@calcom/web/test/utils/bookingScenario/setupAndTeardown";
|
||||
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { describe, expect } from "vitest";
|
||||
|
||||
import { WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { ErrorCode } from "@calcom/lib/errorCodes";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
|
||||
import { test } from "@calcom/web/test/fixtures/fixtures";
|
||||
|
||||
import { handleNewRecurringBooking } from "../../handleNewRecurringBooking";
|
||||
|
||||
const DAY_IN_MS = 1000 * 60 * 60 * 24;
|
||||
|
||||
function getPlusDayDate(date: string, days: number) {
|
||||
return new Date(new Date(date).getTime() + days * DAY_IN_MS);
|
||||
}
|
||||
|
||||
// Local test runs sometime gets too slow
|
||||
const timeout = process.env.CI ? 5000 : 20000;
|
||||
describe("handleNewRecurringBooking", () => {
|
||||
setupAndTeardown();
|
||||
|
||||
describe("Recurring EventType:", () => {
|
||||
describe("User event type:", () => {
|
||||
test(
|
||||
`should create successful bookings for the number of slots requested
|
||||
1. Should create the same number of bookings as requested slots in the database
|
||||
2. Should send emails for the first booking only to the booker as well as organizer
|
||||
3. Should create a calendar event for every booking in the destination calendar
|
||||
3. Should trigger BOOKING_CREATED webhook for every booking
|
||||
`,
|
||||
async ({ emails }) => {
|
||||
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],
|
||||
destinationCalendar: {
|
||||
integration: "google_calendar",
|
||||
externalId: "organizer@google-calendar.com",
|
||||
},
|
||||
});
|
||||
|
||||
const recurrence = getRecurrence({
|
||||
type: "weekly",
|
||||
numberOfOccurrences: 3,
|
||||
});
|
||||
const plus1DateString = getDate({ dateIncrement: 1 }).dateString;
|
||||
await 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: 30,
|
||||
length: 30,
|
||||
recurringEvent: recurrence,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
destinationCalendar: {
|
||||
integration: "google_calendar",
|
||||
externalId: "event-type-1@google-calendar.com",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
videoMeetingData: {
|
||||
id: "MOCK_ID",
|
||||
password: "MOCK_PASS",
|
||||
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
||||
},
|
||||
});
|
||||
|
||||
const calendarMock = mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||
},
|
||||
});
|
||||
|
||||
const recurringCountInRequest = 2;
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
start: `${plus1DateString}T04:00:00.000Z`,
|
||||
end: `${plus1DateString}T04:30:00.000Z`,
|
||||
recurringEventId: uuidv4(),
|
||||
recurringCount: recurringCountInRequest,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const numOfSlotsToBeBooked = 4;
|
||||
|
||||
// Create an array of booking data for multiple slots
|
||||
const bookingDataArray = Array(numOfSlotsToBeBooked)
|
||||
.fill(mockBookingData)
|
||||
.map((mockBookingData, index) => {
|
||||
return {
|
||||
...mockBookingData,
|
||||
start: getPlusDayDate(mockBookingData.start, index).toISOString(),
|
||||
end: getPlusDayDate(mockBookingData.end, index).toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
// Call handleNewRecurringBooking directly instead of through API
|
||||
const createdBookings = await handleNewRecurringBooking({
|
||||
bookingData: bookingDataArray,
|
||||
userId: -1, // Simulating anonymous user like in the API test
|
||||
});
|
||||
|
||||
expect(createdBookings.length).toBe(numOfSlotsToBeBooked);
|
||||
for (const [index, createdBooking] of Object.entries(createdBookings)) {
|
||||
logger.debug("Assertion for Booking with index:", index, { createdBooking });
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
})
|
||||
);
|
||||
|
||||
expect(createdBooking).toEqual(
|
||||
expect.objectContaining({
|
||||
location: "integrations:daily",
|
||||
})
|
||||
);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
uid: createdBooking.uid!,
|
||||
eventTypeId: mockBookingData.eventTypeId,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
recurringEventId: mockBookingData.recurringEventId,
|
||||
references: [
|
||||
{
|
||||
type: "daily_video",
|
||||
uid: "MOCK_ID",
|
||||
meetingId: "MOCK_ID",
|
||||
meetingPassword: "MOCK_PASS",
|
||||
meetingUrl: "http://mock-dailyvideo.example.com/meeting-1",
|
||||
},
|
||||
{
|
||||
type: "google_calendar",
|
||||
uid: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
meetingId: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
meetingPassword: "MOCK_PASSWORD",
|
||||
meetingUrl: "https://UNUSED_URL",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expectBookingCreatedWebhookToHaveBeenFired({
|
||||
booker,
|
||||
organizer,
|
||||
location: "integrations:daily",
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
videoCallUrl: `${WEBAPP_URL}/video/${createdBookings[0].uid}`,
|
||||
});
|
||||
}
|
||||
|
||||
expectSuccessfulBookingCreationEmails({
|
||||
booker,
|
||||
booking: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
uid: createdBookings[0].uid!,
|
||||
urlOrigin: WEBSITE_URL,
|
||||
},
|
||||
organizer,
|
||||
emails,
|
||||
bookingTimeRange: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
start: createdBookings[0].startTime!,
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
end: createdBookings[0].endTime!,
|
||||
},
|
||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||
recurrence: {
|
||||
...recurrence,
|
||||
count: recurringCountInRequest,
|
||||
},
|
||||
});
|
||||
|
||||
expect(emails.get().length).toBe(2);
|
||||
|
||||
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, [
|
||||
{
|
||||
calendarId: "event-type-1@google-calendar.com",
|
||||
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
|
||||
},
|
||||
{
|
||||
calendarId: "event-type-1@google-calendar.com",
|
||||
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
|
||||
},
|
||||
{
|
||||
calendarId: "event-type-1@google-calendar.com",
|
||||
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
|
||||
},
|
||||
{
|
||||
calendarId: "event-type-1@google-calendar.com",
|
||||
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
|
||||
},
|
||||
]);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
test(
|
||||
`should fail recurring booking if second slot is already booked`,
|
||||
async ({}) => {
|
||||
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],
|
||||
destinationCalendar: {
|
||||
integration: "google_calendar",
|
||||
externalId: "organizer@google-calendar.com",
|
||||
},
|
||||
});
|
||||
|
||||
const recurrence = getRecurrence({
|
||||
type: "weekly",
|
||||
numberOfOccurrences: 3,
|
||||
});
|
||||
const plus1DateString = getDate({ dateIncrement: 1 }).dateString;
|
||||
const plus2DateString = getDate({ dateIncrement: 2 }).dateString;
|
||||
await 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: 30,
|
||||
length: 30,
|
||||
recurringEvent: recurrence,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
destinationCalendar: {
|
||||
integration: "google_calendar",
|
||||
externalId: "event-type-1@google-calendar.com",
|
||||
},
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
uid: "booking-1-uid",
|
||||
eventTypeId: 1,
|
||||
userId: organizer.id,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
startTime: `${plus2DateString}T04:00:00.000Z`,
|
||||
endTime: `${plus2DateString}T04:30:00.000Z`,
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
videoMeetingData: {
|
||||
id: "MOCK_ID",
|
||||
password: "MOCK_PASS",
|
||||
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
||||
},
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||
},
|
||||
});
|
||||
|
||||
const recurringCountInRequest = 2;
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
start: `${plus1DateString}T04:00:00.000Z`,
|
||||
end: `${plus1DateString}T04:30:00.000Z`,
|
||||
recurringEventId: uuidv4(),
|
||||
recurringCount: recurringCountInRequest,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const numOfSlotsToBeBooked = 4;
|
||||
|
||||
// Create an array of booking data for multiple slots
|
||||
const bookingDataArray = Array(numOfSlotsToBeBooked)
|
||||
.fill(mockBookingData)
|
||||
.map((mockBookingData, index) => {
|
||||
return {
|
||||
...mockBookingData,
|
||||
start: getPlusDayDate(mockBookingData.start, index).toISOString(),
|
||||
end: getPlusDayDate(mockBookingData.end, index).toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
await expect(() =>
|
||||
handleNewRecurringBooking({
|
||||
bookingData: bookingDataArray,
|
||||
userId: -1,
|
||||
})
|
||||
).rejects.toThrow(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
);
|
||||
});
|
||||
|
||||
describe("Round robin event type:", () => {
|
||||
test("should fail recurring booking if a fixed host is not available on the second slot", async () => {
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
defaultScheduleId: null,
|
||||
teams: [
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
},
|
||||
team: {
|
||||
id: 1,
|
||||
name: "Team 1",
|
||||
slug: "team-1",
|
||||
},
|
||||
},
|
||||
],
|
||||
schedules: [TestData.schedules.IstMorningShift],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
destinationCalendar: {
|
||||
integration: TestData.apps["google-calendar"].type,
|
||||
externalId: "organizer@google-calendar.com",
|
||||
},
|
||||
});
|
||||
|
||||
const otherTeamMembers = [
|
||||
{
|
||||
name: "Other Team Member 1",
|
||||
username: "other-team-member-1",
|
||||
timeZone: Timezones["+5:30"],
|
||||
defaultScheduleId: null,
|
||||
email: "other-team-member-1@example.com",
|
||||
id: 102,
|
||||
schedules: [TestData.schedules.IstMorningShift],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
destinationCalendar: {
|
||||
integration: TestData.apps["google-calendar"].type,
|
||||
externalId: "other-team-member-1@google-calendar.com",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const recurrence = getRecurrence({
|
||||
type: "weekly",
|
||||
numberOfOccurrences: 3,
|
||||
});
|
||||
|
||||
const plus1DateString = getDate({ dateIncrement: 1 }).dateString;
|
||||
const plus2DateString = getDate({ dateIncrement: 2 }).dateString;
|
||||
|
||||
await 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: 30,
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
length: 30,
|
||||
recurringEvent: recurrence,
|
||||
hosts: [
|
||||
{
|
||||
userId: 101,
|
||||
isFixed: false,
|
||||
},
|
||||
{
|
||||
userId: 102,
|
||||
isFixed: true,
|
||||
},
|
||||
],
|
||||
destinationCalendar: {
|
||||
integration: "google_calendar",
|
||||
externalId: "event-type-1@google-calendar.com",
|
||||
},
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
userId: 102,
|
||||
attendees: [
|
||||
{
|
||||
email: "IntegrationTestUser102@example.com",
|
||||
},
|
||||
],
|
||||
eventTypeId: 1,
|
||||
status: "ACCEPTED",
|
||||
startTime: `${plus2DateString}T04:00:00.000Z`,
|
||||
endTime: `${plus2DateString}T04:30:00.000Z`,
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
usersApartFromOrganizer: otherTeamMembers,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
mockSuccessfulVideoMeetingCreation({
|
||||
metadataLookupKey: "dailyvideo",
|
||||
videoMeetingData: {
|
||||
id: "MOCK_ID",
|
||||
password: "MOCK_PASS",
|
||||
url: `http://mock-dailyvideo.example.com/meeting-1`,
|
||||
},
|
||||
});
|
||||
|
||||
mockCalendarToHaveNoBusySlots("googlecalendar", {
|
||||
create: {
|
||||
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
|
||||
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
||||
},
|
||||
});
|
||||
|
||||
const recurringCountInRequest = 4;
|
||||
const mockBookingData = getMockRequestDataForBooking({
|
||||
data: {
|
||||
eventTypeId: 1,
|
||||
start: `${plus1DateString}T04:00:00.000Z`,
|
||||
end: `${plus1DateString}T04:30:00.000Z`,
|
||||
recurringEventId: uuidv4(),
|
||||
recurringCount: recurringCountInRequest,
|
||||
responses: {
|
||||
email: booker.email,
|
||||
name: booker.name,
|
||||
location: { optionValue: "", value: "integrations:daily" },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const numOfSlotsToBeBooked = 4;
|
||||
|
||||
// Create an array of booking data for multiple slots
|
||||
const bookingDataArray = Array(numOfSlotsToBeBooked)
|
||||
.fill(mockBookingData)
|
||||
.map((mockBookingData, index) => {
|
||||
return {
|
||||
...mockBookingData,
|
||||
schedulingType: SchedulingType.ROUND_ROBIN,
|
||||
start: getPlusDayDate(mockBookingData.start, index).toISOString(),
|
||||
end: getPlusDayDate(mockBookingData.end, index).toISOString(),
|
||||
};
|
||||
});
|
||||
|
||||
await expect(() =>
|
||||
handleNewRecurringBooking({
|
||||
bookingData: bookingDataArray,
|
||||
userId: -1,
|
||||
})
|
||||
).rejects.toThrow(ErrorCode.NoAvailableUsersFound);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function getRecurrence({
|
||||
type,
|
||||
numberOfOccurrences,
|
||||
}: {
|
||||
type: "weekly" | "monthly" | "yearly";
|
||||
numberOfOccurrences: number;
|
||||
}) {
|
||||
const freq = type === "yearly" ? 0 : type === "monthly" ? 1 : 2;
|
||||
return { freq, count: numberOfOccurrences, interval: 1 };
|
||||
}
|
||||
});
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
getMockPassingAppStatus,
|
||||
getDefaultBookingFields,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectWorkflowToBeTriggered,
|
||||
expectBookingToBeInDatabase,
|
||||
@@ -183,13 +182,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
uid: uidOfBookingToBeRescheduled,
|
||||
@@ -424,13 +420,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
/**
|
||||
* Booking Time should be new time
|
||||
*/
|
||||
@@ -623,13 +616,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingInDBToBeRescheduledFromTo({
|
||||
from: {
|
||||
uid: uidOfBookingToBeRescheduled,
|
||||
@@ -819,12 +809,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -1052,16 +1039,11 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
// Fake the request to be from organizer
|
||||
req.userId = organizer.id;
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
/**
|
||||
* Booking Time should be new time
|
||||
*/
|
||||
@@ -1305,16 +1287,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
// Fake the request to be from organizer
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
// Fake the request to be from organizer
|
||||
req.userId = organizer.id;
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
/**
|
||||
* Booking Time should be new time
|
||||
*/
|
||||
@@ -1524,15 +1502,11 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
// Fake the request to be from organizer
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
// Fake the request to be from organizer
|
||||
req.userId = organizer.id;
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
expect(createdBooking.responses).toEqual(
|
||||
expect.objectContaining({
|
||||
email: booker.email,
|
||||
@@ -1772,16 +1746,12 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
// Fake the request to be from organizer
|
||||
userId: previousOrganizerIdForTheBooking,
|
||||
});
|
||||
|
||||
// Fake the request to be from organizer
|
||||
req.userId = previousOrganizerIdForTheBooking;
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
/**
|
||||
* Booking Time should be new time
|
||||
*/
|
||||
@@ -1975,12 +1945,10 @@ describe("handleNewBooking", () => {
|
||||
rescheduledBy: booker.email,
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
@@ -2126,12 +2094,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
@@ -2328,12 +2294,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
@@ -2485,12 +2449,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
@@ -2646,12 +2608,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const previousBooking = await prismaMock.booking.findUnique({
|
||||
where: {
|
||||
|
||||
+29
-63
@@ -15,7 +15,6 @@ import {
|
||||
getZoomAppCredential,
|
||||
getDefaultBookingFields,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
// expectWorkflowToBeTriggered,
|
||||
expectSuccessfulBookingCreationEmails,
|
||||
@@ -174,13 +173,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -360,13 +356,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(async () => {
|
||||
await handleNewBooking(req);
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
}).rejects.toThrowError(ErrorCode.HostsUnavailableForBooking);
|
||||
},
|
||||
timeout
|
||||
@@ -483,11 +476,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -713,13 +704,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -951,13 +939,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -1194,13 +1179,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -1337,12 +1319,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
await expect(async () => {
|
||||
await handleNewBooking(req);
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
}).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
},
|
||||
timeout
|
||||
@@ -1450,11 +1430,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -1653,11 +1631,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.ZoomVideo,
|
||||
@@ -1861,11 +1837,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -2058,11 +2032,9 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
},
|
||||
});
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
await expectBookingToBeInDatabase({
|
||||
description: "",
|
||||
location: BookingLocations.CalVideo,
|
||||
@@ -2227,21 +2199,15 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req: req1 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData1,
|
||||
const createdBooking1 = await handleNewBooking({
|
||||
bookingData: mockBookingData1,
|
||||
});
|
||||
|
||||
const { req: req2 } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData2,
|
||||
});
|
||||
|
||||
const createdBooking1 = await handleNewBooking(req1);
|
||||
|
||||
expect(createdBooking1.userId).toBe(102);
|
||||
|
||||
const createdBooking2 = await handleNewBooking(req2);
|
||||
const createdBooking2 = await handleNewBooking({
|
||||
bookingData: mockBookingData2,
|
||||
});
|
||||
expect(createdBooking2.userId).toBe(102);
|
||||
});
|
||||
});
|
||||
|
||||
+12
-31
@@ -12,7 +12,6 @@ import {
|
||||
Timezones,
|
||||
createOrganization,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
|
||||
import {
|
||||
expectWorkflowToBeTriggered,
|
||||
expectSMSWorkflowToBeTriggered,
|
||||
@@ -133,13 +132,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectSMSWorkflowToBeTriggered({
|
||||
sms,
|
||||
toNumber: "000",
|
||||
@@ -232,13 +228,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectSMSWorkflowToBeNotTriggered({
|
||||
sms,
|
||||
toNumber: "000",
|
||||
@@ -379,13 +372,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectSMSWorkflowToBeTriggered({
|
||||
sms,
|
||||
toNumber: "000",
|
||||
@@ -524,13 +514,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectSMSWorkflowToBeNotTriggered({
|
||||
sms,
|
||||
toNumber: "000",
|
||||
@@ -644,13 +631,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectWorkflowToBeTriggered({
|
||||
emailsToReceive: ["booker@example.com"],
|
||||
emails,
|
||||
@@ -760,13 +744,10 @@ describe("handleNewBooking", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expectSMSWorkflowToBeTriggered({
|
||||
sms,
|
||||
toNumber: "000",
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
|
||||
import handleNewBooking from "@calcom/features/bookings/lib/handleNewBooking";
|
||||
import type { RecurringBookingCreateBody, BookingResponse } from "@calcom/features/bookings/types";
|
||||
import type { BookingResponse } from "@calcom/features/bookings/types";
|
||||
import { SchedulingType } from "@calcom/prisma/client";
|
||||
import type { AppsStatus } from "@calcom/types/Calendar";
|
||||
|
||||
export const handleNewRecurringBooking = async (
|
||||
req: NextApiRequest & {
|
||||
userId?: number | undefined;
|
||||
platformClientId?: string;
|
||||
platformRescheduleUrl?: string;
|
||||
platformCancelUrl?: string;
|
||||
platformBookingUrl?: string;
|
||||
platformBookingLocation?: string;
|
||||
noEmail?: boolean;
|
||||
}
|
||||
): Promise<BookingResponse[]> => {
|
||||
const data: RecurringBookingCreateBody[] = req.body;
|
||||
export type PlatformParams = {
|
||||
platformClientId?: string;
|
||||
platformCancelUrl?: string;
|
||||
platformBookingUrl?: string;
|
||||
platformRescheduleUrl?: string;
|
||||
platformBookingLocation?: string;
|
||||
};
|
||||
|
||||
export type BookingHandlerInput = {
|
||||
bookingData: Record<string, any>[];
|
||||
userId?: number;
|
||||
// These used to come from headers but now we're passing them as params
|
||||
hostname?: string;
|
||||
forcedSlug?: string;
|
||||
noEmail?: boolean;
|
||||
} & PlatformParams;
|
||||
|
||||
export const handleNewRecurringBooking = async (input: BookingHandlerInput): Promise<BookingResponse[]> => {
|
||||
const data = input.bookingData;
|
||||
const createdBookings: BookingResponse[] = [];
|
||||
const allRecurringDates: { start: string | undefined; end: string | undefined }[] = data.map((booking) => {
|
||||
return { start: booking.start, end: booking.end };
|
||||
@@ -33,10 +38,17 @@ export const handleNewRecurringBooking = async (
|
||||
|
||||
let luckyUsers = undefined;
|
||||
|
||||
if (isRoundRobin) {
|
||||
const recurringEventReq: NextApiRequest & { userId?: number } = req;
|
||||
const handleBookingMeta = {
|
||||
userId: input.userId,
|
||||
platformClientId: input.platformClientId,
|
||||
platformRescheduleUrl: input.platformRescheduleUrl,
|
||||
platformCancelUrl: input.platformCancelUrl,
|
||||
platformBookingUrl: input.platformBookingUrl,
|
||||
platformBookingLocation: input.platformBookingLocation,
|
||||
};
|
||||
|
||||
recurringEventReq.body = {
|
||||
if (isRoundRobin) {
|
||||
const recurringEventData = {
|
||||
...firstBooking,
|
||||
appsStatus,
|
||||
allRecurringDates,
|
||||
@@ -44,10 +56,15 @@ export const handleNewRecurringBooking = async (
|
||||
thirdPartyRecurringEventId,
|
||||
numSlotsToCheckForAvailability,
|
||||
currentRecurringIndex: 0,
|
||||
noEmail: req.noEmail !== undefined ? req.noEmail : false,
|
||||
noEmail: input.noEmail !== undefined ? input.noEmail : false,
|
||||
};
|
||||
|
||||
const firstBookingResult = await handleNewBooking(recurringEventReq);
|
||||
const firstBookingResult = await handleNewBooking({
|
||||
bookingData: recurringEventData,
|
||||
hostname: input.hostname || "",
|
||||
forcedSlug: input.forcedSlug as string | undefined,
|
||||
...handleBookingMeta,
|
||||
});
|
||||
luckyUsers = firstBookingResult.luckyUsers;
|
||||
}
|
||||
|
||||
@@ -71,9 +88,7 @@ export const handleNewRecurringBooking = async (
|
||||
// appsStatus = Object.values(calcAppsStatus);
|
||||
// }
|
||||
|
||||
const recurringEventReq: NextApiRequest & { userId?: number } = req;
|
||||
|
||||
recurringEventReq.body = {
|
||||
const recurringEventData = {
|
||||
...booking,
|
||||
appsStatus,
|
||||
allRecurringDates,
|
||||
@@ -81,12 +96,16 @@ export const handleNewRecurringBooking = async (
|
||||
thirdPartyRecurringEventId,
|
||||
numSlotsToCheckForAvailability,
|
||||
currentRecurringIndex: key,
|
||||
noEmail: req.noEmail !== undefined ? req.noEmail : key !== 0,
|
||||
noEmail: input.noEmail !== undefined ? input.noEmail : key !== 0,
|
||||
luckyUsers,
|
||||
};
|
||||
|
||||
const promiseEachRecurringBooking: ReturnType<typeof handleNewBooking> =
|
||||
handleNewBooking(recurringEventReq);
|
||||
const promiseEachRecurringBooking: ReturnType<typeof handleNewBooking> = handleNewBooking({
|
||||
hostname: input.hostname || "",
|
||||
forcedSlug: input.forcedSlug as string | undefined,
|
||||
bookingData: recurringEventData,
|
||||
...handleBookingMeta,
|
||||
});
|
||||
|
||||
const eachRecurringBooking = await promiseEachRecurringBooking;
|
||||
|
||||
|
||||
@@ -87,13 +87,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
@@ -191,7 +188,9 @@ describe("handleSeats", () => {
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
expect(createdBooking.metadata).toHaveProperty("videoCallUrl");
|
||||
|
||||
@@ -330,13 +329,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
const handleSeatsCall = spy.mock.calls[0][0];
|
||||
|
||||
expect(handleSeatsCall).toEqual(
|
||||
@@ -488,13 +484,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
const newAttendee = await prismaMock.attendee.findFirst({
|
||||
where: {
|
||||
email: booker.email,
|
||||
@@ -617,13 +610,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
const newAttendee = await prismaMock.attendee.findFirst({
|
||||
where: {
|
||||
email: booker.email,
|
||||
@@ -746,12 +736,11 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.AlreadySignedUpForBooking);
|
||||
await expect(() =>
|
||||
handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.AlreadySignedUpForBooking);
|
||||
});
|
||||
|
||||
test("If event is already full, fail", async () => {
|
||||
@@ -869,12 +858,11 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.BookingSeatsFull);
|
||||
await expect(() =>
|
||||
handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.BookingSeatsFull);
|
||||
});
|
||||
|
||||
test("Verify Seat Availability Calculation Based on Booked Seats, Not Total Attendees", async () => {
|
||||
@@ -1021,13 +1009,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
const newAttendee = await prismaMock.attendee.findFirst({
|
||||
where: {
|
||||
email: booker.email,
|
||||
@@ -1219,13 +1204,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
await handleNewBooking(req);
|
||||
|
||||
// Ensure that the attendee is no longer a part of the old booking
|
||||
const oldBookingAttendees = await prismaMock.attendee.findMany({
|
||||
where: {
|
||||
@@ -1393,7 +1375,9 @@ describe("handleSeats", () => {
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
// Ensure that the attendee is no longer a part of the old booking
|
||||
const oldBookingAttendees = await prismaMock.attendee.findMany({
|
||||
@@ -1532,13 +1516,10 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const createdBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
});
|
||||
|
||||
const createdBooking = await handleNewBooking(req);
|
||||
|
||||
// Ensure that the old booking is cancelled
|
||||
const oldBooking = await prismaMock.booking.findFirst({
|
||||
where: {
|
||||
@@ -1990,15 +1971,11 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const rescheduledBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
req.userId = organizer.id;
|
||||
|
||||
const rescheduledBooking = await handleNewBooking(req);
|
||||
|
||||
// Ensure that the booking has been moved
|
||||
expect(rescheduledBooking?.startTime).toEqual(secondBookingStartTime);
|
||||
expect(rescheduledBooking?.endTime).toEqual(secondBookingEndTime);
|
||||
@@ -2190,15 +2167,11 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
const rescheduledBooking = await handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
});
|
||||
|
||||
req.userId = organizer.id;
|
||||
|
||||
const rescheduledBooking = await handleNewBooking(req);
|
||||
|
||||
// Ensure that the booking has been moved
|
||||
expect(rescheduledBooking?.startTime).toEqual(new Date(secondBookingStartTime));
|
||||
expect(rescheduledBooking?.endTime).toEqual(new Date(secondBookingEndTime));
|
||||
@@ -2401,15 +2374,13 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
req.userId = organizer.id;
|
||||
|
||||
// const rescheduledBooking = await handleNewBooking(req);
|
||||
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.NotEnoughAvailableSeats);
|
||||
await expect(() =>
|
||||
handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NotEnoughAvailableSeats);
|
||||
});
|
||||
|
||||
test("When trying to reschedule in a non-available slot, throw an error", async () => {
|
||||
@@ -2547,14 +2518,12 @@ describe("handleSeats", () => {
|
||||
},
|
||||
});
|
||||
|
||||
const { req } = createMockNextJsRequest({
|
||||
method: "POST",
|
||||
body: mockBookingData,
|
||||
});
|
||||
|
||||
req.userId = organizer.id;
|
||||
|
||||
await expect(() => handleNewBooking(req)).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
await expect(() =>
|
||||
handleNewBooking({
|
||||
bookingData: mockBookingData,
|
||||
userId: organizer.id,
|
||||
})
|
||||
).rejects.toThrowError(ErrorCode.NoAvailableUsersFound);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -52,6 +52,34 @@ export function getOrgSlug(hostname: string, forcedSlug?: string) {
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getOrgDomainConfig({
|
||||
hostname,
|
||||
fallback,
|
||||
forcedSlug,
|
||||
isPlatform,
|
||||
}: {
|
||||
hostname: string;
|
||||
fallback?: string | string[];
|
||||
forcedSlug?: string;
|
||||
isPlatform?: boolean;
|
||||
}) {
|
||||
if (isPlatform && forcedSlug) {
|
||||
return {
|
||||
isValidOrgDomain: true,
|
||||
currentOrgDomain: forcedSlug,
|
||||
};
|
||||
}
|
||||
|
||||
return getOrgDomainConfigFromHostname({
|
||||
hostname,
|
||||
fallback,
|
||||
forcedSlug,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `getOrgDomainConfig` instead. To be removed in a future release. getOrgDomainConfig is more flexible and can be used without next request.
|
||||
*/
|
||||
export function orgDomainConfig(req: IncomingMessage | undefined, fallback?: string | string[]) {
|
||||
const forPlatform = isPlatformRequest(req);
|
||||
const forcedSlugHeader = req?.headers?.["x-cal-force-slug"];
|
||||
|
||||
@@ -177,7 +177,7 @@ async function handler(req: NextApiRequest) {
|
||||
bookingFields: eventType.bookingFields,
|
||||
});
|
||||
const reqBody = await getBookingData({
|
||||
req,
|
||||
reqBody: req.body,
|
||||
eventType,
|
||||
schema,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user