fix: use smsReminderNumber fallback for attendee phone in SMS reminders (#27942)
* SMS reminders not trigger * addressed devin review * Add tests: packages/features/ee/workflows/lib/service/scheduleLazySMSWorkflow.test.ts Generated by Paragon from proposal for PR #27942 * Add tests: packages/features/ee/workflows/lib/reminders/reminderScheduler.smsLazy.test.ts Generated by Paragon from proposal for PR #27942 * Add tests: packages/features/tasker/tasks/sendWorkflowSMS.test.ts Generated by Paragon from proposal for PR #27942 * Add tests: packages/features/ee/workflows/lib/reminders/smsReminderManager.test.ts Generated by Paragon from proposal for PR #27942 * Delete packages/features/ee/workflows/lib/reminders/reminderScheduler.smsLazy.test.ts * fix: update sendWorkflowSMS tests to match refactored implementation Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use smsReminderNumber fallback for attendee phone in SMS workflows Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * revert: keep only scheduleSMSReminders.ts changes, revert all other files Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: restore reminderScheduler.ts and smsReminderManager.ts to original branch state Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> * fix: use smsReminderNumber fallback for attendee phone in WhatsApp reminders Co-Authored-By: anik@cal.com <adhabal2002@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
co-authored by
anik@cal.com <adhabal2002@gmail.com>
anik@cal.com <adhabal2002@gmail.com>
anik@cal.com <adhabal2002@gmail.com>
anik@cal.com <adhabal2002@gmail.com>
anik@cal.com <adhabal2002@gmail.com>
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
parent
7e73d675d7
commit
0a84ce5c73
@@ -0,0 +1,318 @@
|
||||
import prismaMock from "@calcom/testing/lib/__mocks__/prismaMock";
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
|
||||
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
|
||||
import { handler } from "./scheduleSMSReminders";
|
||||
|
||||
const mockScheduleSmsOrFallbackEmail = vi.fn();
|
||||
vi.mock("@calcom/features/ee/workflows/lib/reminders/messageDispatcher", () => ({
|
||||
scheduleSmsOrFallbackEmail: (...args: unknown[]) => mockScheduleSmsOrFallbackEmail(...args),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/billing/credit-service", () => {
|
||||
return {
|
||||
CreditService: class MockCreditService {
|
||||
hasAvailableCredits = vi.fn().mockResolvedValue(true);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/bookings/repositories/BookingSeatRepository", () => {
|
||||
return {
|
||||
BookingSeatRepository: class MockBookingSeatRepository {
|
||||
getByReferenceUidWithAttendeeDetails = vi.fn().mockResolvedValue(null);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/alphanumericSenderIdSupport", () => ({
|
||||
getSenderId: vi.fn().mockReturnValue("CalCom"),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/service/workflowOptOutService", () => ({
|
||||
WorkflowOptOutService: {
|
||||
addOptOutMessage: vi.fn().mockResolvedValue("message without opt out"),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/reminders/templates/smsReminderTemplate", () => ({
|
||||
default: vi.fn().mockReturnValue("Test SMS reminder"),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/reminders/templates/customTemplate", () => ({
|
||||
default: vi.fn().mockReturnValue({ text: "Custom message" }),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/bookings/lib/getCalEventResponses", () => ({
|
||||
getCalEventResponses: vi.fn().mockReturnValue({ responses: {} }),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/organizations/lib/getBookerUrlServer", () => ({
|
||||
getBookerBaseUrl: vi.fn().mockResolvedValue("https://app.cal.com"),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/ee/workflows/lib/reminders/utils", () => ({
|
||||
bulkShortenLinks: vi.fn().mockResolvedValue([
|
||||
{ shortLink: "https://short.link/meet" },
|
||||
{ shortLink: "https://short.link/cancel" },
|
||||
{ shortLink: "https://short.link/reschedule" },
|
||||
]),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/i18n/server", () => ({
|
||||
getTranslation: vi.fn().mockResolvedValue((key: string) => key),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/actionHelperFunctions", () => ({
|
||||
isAttendeeAction: vi.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/getWorkflowReminders", () => ({
|
||||
select: {
|
||||
id: true,
|
||||
scheduledDate: true,
|
||||
workflowStep: true,
|
||||
booking: true,
|
||||
seatReferenceId: true,
|
||||
isMandatoryReminder: true,
|
||||
uuid: true,
|
||||
},
|
||||
getWorkflowRecipientEmail: vi.fn().mockReturnValue("attendee@example.com"),
|
||||
}));
|
||||
|
||||
function createMockNextRequest(): { headers: { get: (key: string) => string | null }; nextUrl: { searchParams: { get: (key: string) => string | null } } } {
|
||||
return {
|
||||
headers: {
|
||||
get: (key: string) => (key === "authorization" ? "test-api-key" : null),
|
||||
},
|
||||
nextUrl: {
|
||||
searchParams: {
|
||||
get: () => null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createMockReminder(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const now = new Date();
|
||||
const scheduledDate = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
|
||||
return {
|
||||
id: 1,
|
||||
scheduledDate,
|
||||
isMandatoryReminder: false,
|
||||
uuid: "test-uuid",
|
||||
seatReferenceId: null,
|
||||
retryCount: 0,
|
||||
workflowStep: {
|
||||
id: 100,
|
||||
action: WorkflowActions.SMS_ATTENDEE,
|
||||
sendTo: null,
|
||||
reminderBody: null,
|
||||
emailSubject: null,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
sender: null,
|
||||
includeCalendarEvent: false,
|
||||
workflow: {
|
||||
userId: 1,
|
||||
teamId: null,
|
||||
},
|
||||
},
|
||||
booking: {
|
||||
startTime: new Date(now.getTime() + 2 * 60 * 60 * 1000),
|
||||
endTime: new Date(now.getTime() + 3 * 60 * 60 * 1000),
|
||||
location: "https://meet.google.com/test",
|
||||
description: "Test booking",
|
||||
metadata: {},
|
||||
customInputs: {},
|
||||
responses: {},
|
||||
uid: "booking-uid-123",
|
||||
userPrimaryEmail: "organizer@example.com",
|
||||
smsReminderNumber: null,
|
||||
title: "Test Meeting",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee One",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: "+1111111111",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
eventType: {
|
||||
bookingFields: null,
|
||||
title: "Test Event",
|
||||
slug: "test-event",
|
||||
hosts: [],
|
||||
recurringEvent: null,
|
||||
team: { parentId: null, hideBranding: false },
|
||||
customReplyToEmail: null,
|
||||
},
|
||||
user: {
|
||||
id: 1,
|
||||
email: "organizer@example.com",
|
||||
name: "Organizer",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
username: "organizer",
|
||||
timeFormat: 12,
|
||||
hideBranding: false,
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("scheduleSMSReminders handler - smsReminderNumber fallback", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
process.env.CRON_API_KEY = "test-api-key";
|
||||
mockScheduleSmsOrFallbackEmail.mockResolvedValue({ sid: "SM123", emailReminderId: null });
|
||||
prismaMock.workflowReminder.update.mockResolvedValue({} as never);
|
||||
prismaMock.profile.findFirst.mockResolvedValue(null);
|
||||
});
|
||||
|
||||
it("should use attendee phone when booking has no smsReminderNumber", async () => {
|
||||
const reminder = createMockReminder();
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledTimes(1);
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+1111111111",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use smsReminderNumber when booking has it set", async () => {
|
||||
const base = createMockReminder();
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...(base.booking as Record<string, unknown>),
|
||||
smsReminderNumber: "+2222222222",
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledTimes(1);
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+2222222222",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should prefer smsReminderNumber over attendee phone for non-seated events", async () => {
|
||||
const baseReminder = createMockReminder();
|
||||
const booking = baseReminder.booking as Record<string, unknown>;
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...booking,
|
||||
smsReminderNumber: "+3333333333",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: "+1111111111",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+3333333333",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use workflowStep.sendTo when action is SMS_NUMBER", async () => {
|
||||
const base = createMockReminder();
|
||||
const reminder = createMockReminder({
|
||||
workflowStep: {
|
||||
...(base.workflowStep as Record<string, unknown>),
|
||||
action: WorkflowActions.SMS_NUMBER,
|
||||
sendTo: "+4444444444",
|
||||
},
|
||||
booking: {
|
||||
...(base.booking as Record<string, unknown>),
|
||||
smsReminderNumber: "+2222222222",
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+4444444444",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use smsReminderNumber when attendee has no phone number", async () => {
|
||||
const baseReminder = createMockReminder();
|
||||
const booking = baseReminder.booking as Record<string, unknown>;
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...booking,
|
||||
smsReminderNumber: "+5555555555",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: null,
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+5555555555",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should return early with ok:true when no unscheduled reminders exist", async () => {
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
const response = await handler(req as never);
|
||||
const body = await response.json();
|
||||
|
||||
expect(body).toEqual({ ok: true });
|
||||
expect(mockScheduleSmsOrFallbackEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -74,10 +74,15 @@ export async function handler(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
const attendeePhoneNumber =
|
||||
reminder.seatReferenceId && targetAttendee?.phoneNumber
|
||||
? targetAttendee.phoneNumber
|
||||
: reminder.booking?.smsReminderNumber || targetAttendee?.phoneNumber;
|
||||
|
||||
const sendTo =
|
||||
reminder.workflowStep.action === WorkflowActions.SMS_NUMBER
|
||||
? reminder.workflowStep.sendTo
|
||||
: targetAttendee?.phoneNumber;
|
||||
: attendeePhoneNumber;
|
||||
|
||||
const userName =
|
||||
reminder.workflowStep.action === WorkflowActions.SMS_ATTENDEE ? targetAttendee?.name || "" : "";
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
import prismaMock from "@calcom/testing/lib/__mocks__/prismaMock";
|
||||
|
||||
import { describe, it, expect, beforeEach, vi } from "vitest";
|
||||
|
||||
import { WorkflowActions, WorkflowMethods, WorkflowTemplates } from "@calcom/prisma/enums";
|
||||
|
||||
import { handler } from "./scheduleWhatsappReminders";
|
||||
|
||||
const mockScheduleSmsOrFallbackEmail = vi.fn();
|
||||
vi.mock("@calcom/features/ee/workflows/lib/reminders/messageDispatcher", () => ({
|
||||
scheduleSmsOrFallbackEmail: (...args: unknown[]) => mockScheduleSmsOrFallbackEmail(...args),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/billing/credit-service", () => {
|
||||
return {
|
||||
CreditService: class MockCreditService {
|
||||
hasAvailableCredits = vi.fn().mockResolvedValue(true);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/bookings/repositories/BookingSeatRepository", () => {
|
||||
return {
|
||||
BookingSeatRepository: class MockBookingSeatRepository {
|
||||
getByReferenceUidWithAttendeeDetails = vi.fn().mockResolvedValue(null);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/actionHelperFunctions", () => ({
|
||||
getWhatsappTemplateFunction: vi.fn().mockReturnValue(() => "Test WhatsApp message"),
|
||||
isAttendeeAction: vi.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/features/ee/workflows/lib/reminders/templates/whatsapp/ContentSidMapping", () => ({
|
||||
getContentSidForTemplate: vi.fn().mockReturnValue("content-sid"),
|
||||
getContentVariablesForTemplate: vi.fn().mockReturnValue({}),
|
||||
}));
|
||||
|
||||
vi.mock("@calcom/i18n/server", () => ({
|
||||
getTranslation: vi.fn().mockResolvedValue((key: string) => key),
|
||||
}));
|
||||
|
||||
function createMockNextRequest(): { headers: { get: (key: string) => string | null }; nextUrl: { searchParams: { get: (key: string) => string | null } } } {
|
||||
return {
|
||||
headers: {
|
||||
get: (key: string) => (key === "authorization" ? "test-api-key" : null),
|
||||
},
|
||||
nextUrl: {
|
||||
searchParams: {
|
||||
get: () => null,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createMockReminder(overrides: Record<string, unknown> = {}): Record<string, unknown> {
|
||||
const now = new Date();
|
||||
const scheduledDate = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
|
||||
return {
|
||||
id: 1,
|
||||
scheduledDate,
|
||||
isMandatoryReminder: false,
|
||||
uuid: "test-uuid",
|
||||
seatReferenceId: null,
|
||||
workflowStep: {
|
||||
action: WorkflowActions.WHATSAPP_ATTENDEE,
|
||||
sendTo: null,
|
||||
reminderBody: null,
|
||||
emailSubject: null,
|
||||
template: WorkflowTemplates.REMINDER,
|
||||
sender: null,
|
||||
includeCalendarEvent: false,
|
||||
workflow: {
|
||||
userId: 1,
|
||||
teamId: null,
|
||||
},
|
||||
},
|
||||
booking: {
|
||||
startTime: new Date(now.getTime() + 2 * 60 * 60 * 1000),
|
||||
endTime: new Date(now.getTime() + 3 * 60 * 60 * 1000),
|
||||
location: "https://meet.google.com/test",
|
||||
description: "Test booking",
|
||||
metadata: {},
|
||||
customInputs: {},
|
||||
responses: {},
|
||||
uid: "booking-uid-123",
|
||||
userPrimaryEmail: "organizer@example.com",
|
||||
smsReminderNumber: null,
|
||||
title: "Test Meeting",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee One",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: "+1111111111",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
eventType: {
|
||||
bookingFields: null,
|
||||
title: "Test Event",
|
||||
slug: "test-event",
|
||||
hosts: [],
|
||||
recurringEvent: null,
|
||||
team: { parentId: null, hideBranding: false },
|
||||
customReplyToEmail: null,
|
||||
},
|
||||
user: {
|
||||
id: 1,
|
||||
email: "organizer@example.com",
|
||||
name: "Organizer",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
username: "organizer",
|
||||
timeFormat: 12,
|
||||
hideBranding: false,
|
||||
},
|
||||
},
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("scheduleWhatsappReminders handler - smsReminderNumber fallback", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
process.env.CRON_API_KEY = "test-api-key";
|
||||
mockScheduleSmsOrFallbackEmail.mockResolvedValue({ sid: "SM123", emailReminderId: null });
|
||||
prismaMock.workflowReminder.update.mockResolvedValue({} as never);
|
||||
});
|
||||
|
||||
it("should use attendee phone when booking has no smsReminderNumber", async () => {
|
||||
const reminder = createMockReminder();
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledTimes(1);
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+1111111111",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use smsReminderNumber when booking has it set", async () => {
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...createMockReminder().booking,
|
||||
smsReminderNumber: "+2222222222",
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledTimes(1);
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+2222222222",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should prefer smsReminderNumber over attendee phone for non-seated events", async () => {
|
||||
const baseReminder = createMockReminder();
|
||||
const booking = baseReminder.booking as Record<string, unknown>;
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...booking,
|
||||
smsReminderNumber: "+3333333333",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: "+1111111111",
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+3333333333",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use workflowStep.sendTo when action is WHATSAPP_NUMBER", async () => {
|
||||
const baseReminder = createMockReminder();
|
||||
const ws = baseReminder.workflowStep as Record<string, unknown>;
|
||||
const reminder = createMockReminder({
|
||||
workflowStep: {
|
||||
...ws,
|
||||
action: WorkflowActions.WHATSAPP_NUMBER,
|
||||
sendTo: "+4444444444",
|
||||
},
|
||||
booking: {
|
||||
...(baseReminder.booking as Record<string, unknown>),
|
||||
smsReminderNumber: "+2222222222",
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+4444444444",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should use smsReminderNumber when attendee has no phone number", async () => {
|
||||
const baseReminder = createMockReminder();
|
||||
const booking = baseReminder.booking as Record<string, unknown>;
|
||||
const reminder = createMockReminder({
|
||||
booking: {
|
||||
...booking,
|
||||
smsReminderNumber: "+5555555555",
|
||||
attendees: [
|
||||
{
|
||||
name: "Attendee",
|
||||
email: "attendee@example.com",
|
||||
phoneNumber: null,
|
||||
timeZone: "America/New_York",
|
||||
locale: "en",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([reminder] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
await handler(req as never);
|
||||
|
||||
expect(mockScheduleSmsOrFallbackEmail).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
twilioData: expect.objectContaining({
|
||||
phoneNumber: "+5555555555",
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should return early with ok:true when no unscheduled reminders exist", async () => {
|
||||
prismaMock.workflowReminder.findMany.mockResolvedValue([] as never);
|
||||
|
||||
const req = createMockNextRequest();
|
||||
const response = await handler(req as never);
|
||||
const body = await response.json();
|
||||
|
||||
expect(body).toEqual({ ok: true });
|
||||
expect(mockScheduleSmsOrFallbackEmail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -66,10 +66,15 @@ export async function handler(req: NextRequest) {
|
||||
}
|
||||
}
|
||||
|
||||
const attendeePhoneNumber =
|
||||
reminder.seatReferenceId && targetAttendee?.phoneNumber
|
||||
? targetAttendee.phoneNumber
|
||||
: reminder.booking?.smsReminderNumber || targetAttendee?.phoneNumber;
|
||||
|
||||
const sendTo =
|
||||
reminder.workflowStep.action === WorkflowActions.WHATSAPP_NUMBER
|
||||
? reminder.workflowStep.sendTo
|
||||
: targetAttendee?.phoneNumber;
|
||||
: attendeePhoneNumber;
|
||||
|
||||
const userName =
|
||||
reminder.workflowStep.action === WorkflowActions.WHATSAPP_ATTENDEE ? targetAttendee?.name || "" : "";
|
||||
|
||||
Reference in New Issue
Block a user