test: add unit tests for sms sending (#14737)

* add tests for workflow sms

* fix type error

* improvements

* add tests for team workflow

* rename test

* add emailsToReceive everywhere

* fix type errors

* code clean up

* fix fresh-booking.test.ts

* add destination email to emailsToReceive

* remove unused webhooks

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
Carina Wollendorfer
2024-05-02 10:42:38 -04:00
committed by GitHub
co-authored by CarinaWolli Udit Takkar Hariom
parent c18407d75a
commit 0391bfe887
10 changed files with 674 additions and 46 deletions
+11
View File
@@ -2,15 +2,20 @@
import { test as base } from "vitest";
import { getTestEmails } from "@calcom/lib/testEmails";
import { getTestSMS } from "@calcom/lib/testSMS";
export interface Fixtures {
emails: ReturnType<typeof getEmailsFixture>;
sms: ReturnType<typeof getSMSFixture>;
}
export const test = base.extend<Fixtures>({
emails: async ({}, use) => {
await use(getEmailsFixture());
},
sms: async ({}, use) => {
await use(getSMSFixture());
},
});
function getEmailsFixture() {
@@ -18,3 +23,9 @@ function getEmailsFixture() {
get: getTestEmails,
};
}
function getSMSFixture() {
return {
get: getTestSMS,
};
}
@@ -19,7 +19,7 @@ import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
import type { WorkflowActions, WorkflowTemplates, WorkflowTriggerEvents } from "@calcom/prisma/client";
import type { SchedulingType } from "@calcom/prisma/enums";
import type { SchedulingType, SMSLockState } from "@calcom/prisma/enums";
import type { BookingStatus } from "@calcom/prisma/enums";
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import type { userMetadataType } from "@calcom/prisma/zod-utils";
@@ -952,6 +952,7 @@ export function getOrganizer({
teams,
organizationId,
metadata,
smsLockState,
}: {
name: string;
email: string;
@@ -965,6 +966,7 @@ export function getOrganizer({
weekStart?: WeekDays;
teams?: InputUser["teams"];
metadata?: userMetadataType;
smsLockState?: SMSLockState;
}) {
return {
...TestData.users.example,
@@ -981,6 +983,7 @@ export function getOrganizer({
organizationId,
profiles: [],
metadata,
smsLockState,
};
}
+51 -17
View File
@@ -307,38 +307,72 @@ export function expectWebhookToHaveBeenCalledWith(
export function expectWorkflowToBeTriggered({
emails,
organizer,
destinationEmail,
emailsToReceive,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
destinationEmail?: string;
emailsToReceive: string[];
}) {
const subjectPattern = /^Reminder: /i;
expect(emails.get()).toEqual(
emailsToReceive.forEach((email) => {
expect(emails.get()).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: email,
}),
])
);
});
}
export function expectWorkflowToBeNotTriggered({
emails,
emailsToReceive,
}: {
emails: Fixtures["emails"];
emailsToReceive: string[];
}) {
const subjectPattern = /^Reminder: /i;
emailsToReceive.forEach((email) => {
expect(emails.get()).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: email,
}),
])
);
});
}
export function expectSMSWorkflowToBeTriggered({
sms,
toNumber,
}: {
sms: Fixtures["sms"];
toNumber: string;
}) {
expect(sms.get()).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: destinationEmail ?? organizer.email,
to: toNumber,
}),
])
);
}
export function expectWorkflowToBeNotTriggered({
emails,
organizer,
export function expectSMSWorkflowToBeNotTriggered({
sms,
toNumber,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
sms: Fixtures["sms"];
toNumber: string;
}) {
const subjectPattern = /^Reminder: /i;
expect(emails.get()).not.toEqual(
expect(sms.get()).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: organizer.email,
to: toNumber,
}),
])
);
@@ -30,6 +30,7 @@ export function getMockRequestDataForBooking({
email: string;
name: string;
location: { optionValue: ""; value: string };
smsReminderNumber?: string;
};
};
}) {
+4 -2
View File
@@ -15,7 +15,7 @@ const _testWithAndWithoutOrg = (
const t = mode === "only" ? test.only : mode === "skip" ? test.skip : test;
t(
`${description} - With org`,
async ({ emails, meta, task, onTestFailed, expect, skip }) => {
async ({ emails, sms, meta, task, onTestFailed, expect, skip }) => {
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
@@ -27,6 +27,7 @@ const _testWithAndWithoutOrg = (
onTestFailed,
expect,
emails,
sms,
skip,
org: {
organization: org,
@@ -39,9 +40,10 @@ const _testWithAndWithoutOrg = (
t(
`${description}`,
async ({ emails, meta, task, onTestFailed, expect, skip }) => {
async ({ emails, sms, meta, task, onTestFailed, expect, skip }) => {
await fn({
emails,
sms,
meta,
task,
onTestFailed,
@@ -42,6 +42,7 @@ import {
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
import {
expectWorkflowToBeTriggered,
expectWorkflowToBeNotTriggered,
expectSuccessfulBookingCreationEmails,
expectBookingToBeInDatabase,
expectAwaitingPaymentEmails,
@@ -51,7 +52,6 @@ import {
expectBookingPaymentIntiatedWebhookToHaveBeenFired,
expectBrokenIntegrationEmails,
expectSuccessfulCalendarEventCreationInCalendar,
expectWorkflowToBeNotTriggered,
expectICalUIDAsString,
} from "@calcom/web/test/utils/bookingScenario/expects";
import { getMockRequestDataForBooking } from "@calcom/web/test/utils/bookingScenario/getMockRequestDataForBooking";
@@ -215,9 +215,8 @@ describe("handleNewBooking", () => {
});
expectWorkflowToBeTriggered({
organizer,
emailsToReceive: [organizerDestinationCalendarEmailOnEventType],
emails,
destinationEmail: organizerDestinationCalendarEmailOnEventType,
});
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
calendarId: "event-type-1@google-calendar.com",
@@ -379,7 +378,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
// We won't be sending evt.destinationCalendar in this case.
@@ -541,7 +540,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
calendarId: "organizer@google-calendar.com",
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
@@ -675,7 +674,7 @@ describe("handleNewBooking", () => {
],
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
// FIXME: We should send Broken Integration emails on calendar event creation failure
// expectCalendarEventCreationFailureEmails({ booker, organizer, emails });
@@ -825,7 +824,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
calendarId: "organizer@google-calendar.com",
@@ -982,7 +981,7 @@ describe("handleNewBooking", () => {
],
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
calendarId: "organizer@google-calendar.com",
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
@@ -1511,7 +1510,7 @@ describe("handleNewBooking", () => {
status: BookingStatus.PENDING,
});
expectWorkflowToBeNotTriggered({ organizer, emails });
expectWorkflowToBeNotTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingRequestedEmails({
booker,
@@ -1636,7 +1635,7 @@ describe("handleNewBooking", () => {
}),
});
expectWorkflowToBeNotTriggered({ organizer, emails });
expectWorkflowToBeNotTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingRequestedEmails({
booker,
@@ -1764,7 +1763,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
@@ -1897,7 +1896,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeNotTriggered({ organizer, emails });
expectWorkflowToBeNotTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingRequestedEmails({ booker, organizer, emails });
@@ -2076,7 +2075,7 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
@@ -2220,7 +2219,7 @@ describe("handleNewBooking", () => {
}),
});
expectWorkflowToBeNotTriggered({ organizer, emails });
expectWorkflowToBeNotTriggered({ emailsToReceive: [organizer.email], emails });
expectAwaitingPaymentEmails({ organizer, booker, emails });
@@ -2244,7 +2243,7 @@ describe("handleNewBooking", () => {
status: BookingStatus.ACCEPTED,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingCreatedWebhookToHaveBeenFired({
booker,
@@ -2374,7 +2373,7 @@ describe("handleNewBooking", () => {
status: BookingStatus.PENDING,
});
expectWorkflowToBeNotTriggered({ organizer, emails });
expectWorkflowToBeNotTriggered({ emailsToReceive: [organizer.email], emails });
expectAwaitingPaymentEmails({ organizer, booker, emails });
expectBookingPaymentIntiatedWebhookToHaveBeenFired({
@@ -240,7 +240,7 @@ describe("handleNewBooking", () => {
],
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, {
calEvent: {
@@ -459,7 +459,7 @@ describe("handleNewBooking", () => {
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, {
calEvent: {
@@ -656,7 +656,7 @@ describe("handleNewBooking", () => {
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
// FIXME: We should send Broken Integration emails on calendar event updation failure
// expectBrokenIntegrationEmails({ booker, organizer, emails });
@@ -850,7 +850,7 @@ describe("handleNewBooking", () => {
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingRequestedEmails({
booker,
@@ -1085,7 +1085,7 @@ describe("handleNewBooking", () => {
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, {
calEvent: {
@@ -1312,7 +1312,7 @@ describe("handleNewBooking", () => {
},
});
//expectWorkflowToBeTriggered({emails, organizer});
//expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectBookingRequestedEmails({
booker,
@@ -1559,7 +1559,7 @@ describe("handleNewBooking", () => {
},
});
expectWorkflowToBeTriggered({ emails, organizer });
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulVideoMeetingUpdationInCalendar(videoMock, {
calEvent: {
@@ -0,0 +1,541 @@
import { describe, beforeEach } from "vitest";
import { resetTestSMS } from "@calcom/lib/testSMS";
import { SMSLockState, SchedulingType } from "@calcom/prisma/enums";
import { test } from "@calcom/web/test/fixtures/fixtures";
import {
createBookingScenario,
getGoogleCalendarCredential,
TestData,
getOrganizer,
getBooker,
getScenarioData,
mockSuccessfulVideoMeetingCreation,
mockCalendarToHaveNoBusySlots,
BookingLocations,
getDate,
Timezones,
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
import { createMockNextJsRequest } from "@calcom/web/test/utils/bookingScenario/createMockNextJsRequest";
import {
expectWorkflowToBeTriggered,
expectSMSWorkflowToBeTriggered,
expectSMSWorkflowToBeNotTriggered,
} 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";
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
describe("handleNewBooking", () => {
setupAndTeardown();
beforeEach(() => {
resetTestSMS();
});
describe("User Workflows", () => {
test(
"should send workflow email and sms when booking is created",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
});
const organizerOtherEmail = "organizer2@example.com";
const organizerDestinationCalendarEmailOnEventType = "organizerEventTypeEmail@example.com";
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",
primaryEmail: organizerOtherEmail,
},
});
await createBookingScenario(
getScenarioData({
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeEventTypeId: 1,
},
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "SMS_ATTENDEE",
template: "REMINDER",
activeEventTypeId: 1,
},
],
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
useEventTypeDestinationCalendarEmail: true,
users: [
{
id: 101,
},
],
destinationCalendar: {
integration: "google_calendar",
externalId: "event-type-1@google-calendar.com",
primaryEmail: organizerDestinationCalendarEmailOnEventType,
},
},
],
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",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
smsReminderNumber: "000",
},
},
});
const { req } = createMockNextJsRequest({
method: "POST",
body: mockBookingData,
});
await handleNewBooking(req);
expectSMSWorkflowToBeTriggered({
sms,
toNumber: "000",
});
expectWorkflowToBeTriggered({
emailsToReceive: [organizerDestinationCalendarEmailOnEventType],
emails,
});
},
timeout
);
test(
"should not send workflow sms when booking is created if the organizer is locked for sms sending",
async ({ sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
});
const organizerOtherEmail = "organizer2@example.com";
const organizerDestinationCalendarEmailOnEventType = "organizerEventTypeEmail@example.com";
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",
primaryEmail: organizerOtherEmail,
},
smsLockState: SMSLockState.LOCKED,
});
await createBookingScenario(
getScenarioData({
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "SMS_ATTENDEE",
template: "REMINDER",
activeEventTypeId: 1,
},
],
eventTypes: [
{
id: 1,
slotInterval: 30,
length: 30,
useEventTypeDestinationCalendarEmail: true,
users: [
{
id: 101,
},
],
destinationCalendar: {
integration: "google_calendar",
externalId: "event-type-1@google-calendar.com",
primaryEmail: organizerDestinationCalendarEmailOnEventType,
},
},
],
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
})
);
mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "MOCKED_GOOGLE_CALENDAR_EVENT_ID",
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
smsReminderNumber: "000",
},
},
});
const { req } = createMockNextJsRequest({
method: "POST",
body: mockBookingData,
});
await handleNewBooking(req);
expectSMSWorkflowToBeNotTriggered({
sms,
toNumber: "000",
});
},
timeout
);
});
describe("Team Workflows", () => {
test(
"should send workflow email and sms when booking is created",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
});
const organizerDestinationCalendarEmailOnEventType = "organizerEventTypeEmail@example.com";
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",
primaryEmail: organizerDestinationCalendarEmailOnEventType,
},
teams: [
{
membership: {
accepted: true,
},
team: {
id: 1,
name: "Team 1",
slug: "team-1",
},
},
],
});
const otherTeamMembers = [
{
name: "Other Team Member 1",
username: "other-team-member-1",
defaultScheduleId: null,
email: "other-team-member-1@example.com",
timeZone: Timezones["+0:00"],
id: 102,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "other-team-member-1@google-calendar.com",
},
},
];
await createBookingScenario(
getScenarioData({
workflows: [
{
teamId: 1,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeEventTypeId: 1,
},
{
teamId: 1,
trigger: "NEW_EVENT",
action: "SMS_ATTENDEE",
template: "REMINDER",
activeEventTypeId: 1,
},
],
eventTypes: [
{
id: 1,
slotInterval: 15,
schedulingType: SchedulingType.COLLECTIVE,
length: 15,
users: [
{
id: 101,
},
{
id: 102,
},
],
teamId: 1,
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "event-type-1@google-calendar.com",
},
},
],
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 mockBookingData = getMockRequestDataForBooking({
data: {
start: `${getDate({ dateIncrement: 1 }).dateString}T09:00:00.000Z`,
end: `${getDate({ dateIncrement: 1 }).dateString}T09:15:00.000Z`,
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
smsReminderNumber: "000",
},
},
});
const { req } = createMockNextJsRequest({
method: "POST",
body: mockBookingData,
});
await handleNewBooking(req);
expectSMSWorkflowToBeTriggered({
sms,
toNumber: "000",
});
expectWorkflowToBeTriggered({
// emailsToReceive: [organizer.email].concat(otherTeamMembers.map(member => member.email)),
emailsToReceive: [organizer.email],
emails,
});
},
timeout
);
test(
"should not send workflow sms when booking is created if the team is locked for sms sending",
async ({ emails, sms }) => {
const handleNewBooking = (await import("@calcom/features/bookings/lib/handleNewBooking")).default;
const booker = getBooker({
email: "booker@example.com",
name: "Booker",
});
const organizerDestinationCalendarEmailOnEventType = "organizerEventTypeEmail@example.com";
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",
primaryEmail: organizerDestinationCalendarEmailOnEventType,
},
teams: [
{
membership: {
accepted: true,
},
team: {
id: 1,
name: "Team 1",
slug: "team-1",
smsLockState: SMSLockState.LOCKED,
},
},
],
});
const otherTeamMembers = [
{
name: "Other Team Member 1",
username: "other-team-member-1",
defaultScheduleId: null,
email: "other-team-member-1@example.com",
timeZone: Timezones["+0:00"],
id: 102,
schedules: [TestData.schedules.IstWorkHours],
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "other-team-member-1@google-calendar.com",
},
},
];
await createBookingScenario(
getScenarioData({
workflows: [
{
teamId: 1,
trigger: "NEW_EVENT",
action: "SMS_ATTENDEE",
template: "REMINDER",
activeEventTypeId: 1,
},
],
eventTypes: [
{
id: 1,
slotInterval: 15,
schedulingType: SchedulingType.COLLECTIVE,
length: 15,
users: [
{
id: 101,
},
{
id: 102,
},
],
teamId: 1,
destinationCalendar: {
integration: TestData.apps["google-calendar"].type,
externalId: "event-type-1@google-calendar.com",
},
},
],
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 mockBookingData = getMockRequestDataForBooking({
data: {
start: `${getDate({ dateIncrement: 1 }).dateString}T09:00:00.000Z`,
end: `${getDate({ dateIncrement: 1 }).dateString}T09:15:00.000Z`,
user: organizer.username,
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
smsReminderNumber: "000",
},
},
});
const { req } = createMockNextJsRequest({
method: "POST",
body: mockBookingData,
});
await handleNewBooking(req);
expectSMSWorkflowToBeNotTriggered({
sms,
toNumber: "000",
});
},
timeout
);
});
});
@@ -2,6 +2,7 @@ import TwilioClient from "twilio";
import { checkSMSRateLimit } from "@calcom/lib/checkRateLimitAndThrowError";
import logger from "@calcom/lib/logger";
import { setTestSMS } from "@calcom/lib/testSMS";
import prisma from "@calcom/prisma";
import { SMSLockState } from "@calcom/prisma/enums";
@@ -31,7 +32,7 @@ function getDefaultSender(whatsapp = false) {
if (whatsapp) {
defaultSender = `whatsapp:+${process.env.TWILIO_WHATSAPP_PHONE_NUMBER}`;
}
return defaultSender;
return defaultSender || "";
}
function getSMSNumber(phone: string, whatsapp = false) {
@@ -46,8 +47,6 @@ export const sendSMS = async (
teamId?: number | null,
whatsapp = false
) => {
assertTwilio(twilio);
const isSMSSendingLocked = await isLockedForSMSSending(userId, teamId);
if (isSMSSendingLocked) {
@@ -55,6 +54,23 @@ export const sendSMS = async (
return;
}
const testMode = process.env.NEXT_PUBLIC_IS_E2E || process.env.INTEGRATION_TEST_MODE;
if (testMode) {
setTestSMS({
to: getSMSNumber(phoneNumber, whatsapp),
from: whatsapp ? getDefaultSender(whatsapp) : sender ? sender : getDefaultSender(),
message: body,
});
console.log(
"Skipped sending SMS because process.env.NEXT_PUBLIC_IS_E2E or process.env.INTEGRATION_TEST_MODE is set. SMS are available in globalThis.testSMS"
);
return;
}
assertTwilio(twilio);
if (!teamId && userId) {
await checkSMSRateLimit({
identifier: `sms:user:${userId}`,
+21
View File
@@ -0,0 +1,21 @@
declare global {
// eslint-disable-next-line no-var
var testSMS: {
to: string;
from: string;
message: string;
}[];
}
export const setTestSMS = (sms: (typeof globalThis.testSMS)[number]) => {
globalThis.testSMS = globalThis.testSMS || [];
globalThis.testSMS.push(sms);
};
export const getTestSMS = () => {
return globalThis.testSMS;
};
export const resetTestSMS = () => {
globalThis.testSMS = [];
};