Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions apps/web/test/utils/bookingScenario/bookingScenario.ts
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not focussing on fixing eslint errors for test files as this is a bit urgent PR.

Original file line number Diff line number Diff line change
Expand Up @@ -1697,6 +1697,41 @@ export function enableEmailFeature() {
});
}

// Helper function to enable email validation feature for a team
export async function enableEmailValidationForTeam(teamId: number) {
// Create the feature if it doesn't exist
await prismock.feature.upsert({
where: { slug: "booking-email-validation" },
update: {
enabled: true,
type: "OPERATIONAL",
},
create: {
slug: "booking-email-validation",
enabled: true,
type: "OPERATIONAL",
description:
"Enable email validation during booking process using ZeroBounce API - Prevents bookings with invalid, spam, or abusive email addresses.",
},
});

// Associate the feature with the team
await prismock.teamFeatures.upsert({
where: {
teamId_featureId: {
teamId,
featureId: "booking-email-validation",
},
},
update: {},
create: {
teamId,
featureId: "booking-email-validation",
assignedBy: "test-setup",
},
});
}

export function mockNoTranslations() {
log.silly("Mocking i18n.getTranslation to return identity function");
i18nMock.getTranslation.mockImplementation(() => {
Expand Down Expand Up @@ -1796,6 +1831,7 @@ export async function mockCalendar(
creationCrash?: boolean;
updationCrash?: boolean;
getAvailabilityCrash?: boolean;
getAvailabilitySlowDownTime?: number;
}
): Promise<CalendarServiceMethodMock> {
const appStoreLookupKey = metadataLookupKey;
Expand Down Expand Up @@ -1958,6 +1994,9 @@ export async function mockCalendar(
if (calendarData?.getAvailabilityCrash) {
throw new Error("MockCalendarService.getAvailability fake error");
}
if (calendarData?.getAvailabilitySlowDownTime) {
await new Promise((resolve) => setTimeout(resolve, calendarData.getAvailabilitySlowDownTime));
}
getAvailabilityCalls.push({
args: {
dateFrom,
Expand Down
24 changes: 19 additions & 5 deletions packages/features/bookings/lib/handleNewBooking.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import short, { uuid } from "short-uuid";
import { v5 as uuidv5 } from "uuid";

Expand All @@ -25,6 +24,9 @@ import { handlePayment } from "@calcom/features/bookings/lib/handlePayment";
import { handleWebhookTrigger } from "@calcom/features/bookings/lib/handleWebhookTrigger";
import { isEventTypeLoggingEnabled } from "@calcom/features/bookings/lib/isEventTypeLoggingEnabled";
import type { CacheService } from "@calcom/features/calendar-cache/lib/getShouldServeCache";
import { getCheckBookingAndDurationLimitsService } from "@calcom/features/di/containers/BookingLimits";
import { getCacheService } from "@calcom/features/di/containers/Cache";
import { getLuckyUserService } from "@calcom/features/di/containers/LuckyUser";
import AssignmentReasonRecorder from "@calcom/features/ee/round-robin/assignmentReason/AssignmentReasonRecorder";
import { getUsernameList } from "@calcom/features/eventtypes/lib/defaultEvents";
import { getEventName, updateHostInEventName } from "@calcom/features/eventtypes/lib/eventNaming";
Expand All @@ -47,9 +49,6 @@ import {
enrichHostsWithDelegationCredentials,
getFirstDelegationConferencingCredentialAppLocation,
} from "@calcom/lib/delegationCredential/server";
import { getCheckBookingAndDurationLimitsService } from "@calcom/features/di/containers/BookingLimits";
import { getCacheService } from "@calcom/features/di/containers/Cache";
import { getLuckyUserService } from "@calcom/features/di/containers/LuckyUser";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import { extractBaseEmail } from "@calcom/lib/extract-base-email";
Expand Down Expand Up @@ -89,6 +88,7 @@ import type {
import type { CredentialForCalendarService } from "@calcom/types/Credential";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";

import { validateBookingEmail } from "../../emailValidation/lib/validateBookingEmail";
import type { EventPayloadType, EventTypeInfo } from "../../webhooks/lib/sendPayload";
import { BookingActionMap, BookingEmailSmsHandler } from "./BookingEmailSmsHandler";
import { getAllCredentialsIncludeServiceAccountKey } from "./getAllCredentialsForUsersOnEvent/getAllCredentials";
Expand Down Expand Up @@ -497,6 +497,16 @@ async function handler(

await checkIfBookerEmailIsBlocked({ loggedInUserId: userId, bookerEmail });

// Email validation - Fast checks (cache + Cal.com)
const emailValidationResult = await validateBookingEmail({
email: bookerEmail,
teamId: eventType.teamId ?? eventType.parent?.teamId ?? null,
logger: loggerWithEventDetails,
});

// We don't await fullValidation here as we want it to progress along with other slow parallel operations(like availability check)
emailValidationResult?.startProviderValidation();

if (!rawBookingData.rescheduleUid) {
await checkActiveBookingsLimitForBooker({
eventTypeId,
Expand Down Expand Up @@ -1395,6 +1405,9 @@ async function handler(
organizerUser.id
);

// This is a good place to wait as availability loading and other things happen in parallel before it and bookings are created only after it.
await emailValidationResult?.waitForProviderValidation();

// For seats, if the booking already exists then we want to add the new attendee to the existing booking
if (eventType.seatsPerTimeSlot) {
const newBooking = await handleSeats({
Expand Down Expand Up @@ -1480,7 +1493,8 @@ async function handler(

const changedOrganizer =
!!originalRescheduledBooking &&
(eventType.schedulingType === SchedulingType.ROUND_ROBIN || eventType.schedulingType === SchedulingType.COLLECTIVE) &&
(eventType.schedulingType === SchedulingType.ROUND_ROBIN ||
eventType.schedulingType === SchedulingType.COLLECTIVE) &&
originalRescheduledBooking.userId !== evt.organizer.id;

const skipDeleteEventsAndMeetings = changedOrganizer;
Expand Down
Loading
Loading