Files
turnosxpress/server/src/Models/Appointments/__tests__/Appointments.snapshotFallback.test.ts
T

828 lines
30 KiB
TypeScript

/**
* Tests for Appointments.getAppointmentEvent snapshot fallback
* when a removed employee's User account has been deleted.
*
* The fallback uses Employee.profileSnapshot fields instead of crashing.
*/
// Mock all external dependencies
jest.mock("../../Users/Users", () => ({
__esModule: true,
default: {
users: {
findOne: jest.fn(),
},
getUserFullName: jest.fn(),
},
}));
jest.mock("../../Clients/Clients", () => ({
__esModule: true,
default: {
clients: {
findOne: jest.fn(),
},
getClientFullName: jest.fn(),
},
}));
jest.mock("../../Companies/Companies", () => ({
__esModule: true,
default: {
companies: {
findOne: jest.fn(),
},
getCompanyAddress: jest.fn(),
},
}));
jest.mock("../../Employees/Employee", () => ({
__esModule: true,
default: {
employees: {
findOne: jest.fn(),
},
checkPermission: jest.fn(),
},
}));
jest.mock("../../Services/Service", () => ({
__esModule: true,
default: {
services: {
find: jest.fn(),
findOne: jest.fn(),
},
},
}));
jest.mock("../../HeatMap/HeatMap", () => ({
__esModule: true,
default: {
getHeatMap: jest.fn(),
addHeatMap: jest.fn(),
},
}));
jest.mock("../../Schedules/Schedules", () => ({
__esModule: true,
default: {
find: jest.fn(),
},
}));
jest.mock("../../SchedulesEnabled/SchedulesEnabled", () => ({
__esModule: true,
default: {
findOne: jest.fn(),
},
}));
jest.mock("../../SchedulesDisabled/SchedulesDisabled", () => ({
__esModule: true,
default: {
find: jest.fn(),
},
}));
jest.mock("../../SchedulesOverrides/SchedulesOverrides", () => ({
__esModule: true,
default: {
find: jest.fn(),
},
}));
jest.mock("../../Metrics/Metrics", () => ({
__esModule: true,
default: {
addAppointment: jest.fn(),
canAddAppointment: jest.fn(),
},
}));
jest.mock("../../Notifications/Notifications", () => ({
__esModule: true,
NotificationsManager: {
sendSystemNotification: jest.fn(),
sendPushNotification: jest.fn(),
sendEmail: jest.fn(),
},
}));
jest.mock("../../SystemNotifications/SystemNotification", () => ({
__esModule: true,
default: {
create: jest.fn(),
},
}));
jest.mock("../../PlanSubscriptions/PlanSubscriptons", () => ({
__esModule: true,
default: {
findOne: jest.fn(),
checkFeature: jest.fn(),
},
}));
jest.mock("../../Repeats/Repeats", () => ({
__esModule: true,
default: {
repeats: {
find: jest.fn().mockResolvedValue([]),
findOne: jest.fn(),
},
existDisabledDate: jest.fn(),
},
}));
jest.mock("../../Discounts/Discounts", () => ({
__esModule: true,
default: {
getDiscountData: jest.fn().mockResolvedValue({
name: "",
value: 0,
type: "PERCENTAGE",
code: "",
}),
},
}));
jest.mock("../../../helpers/check", () => ({
validatePermissionsByCompany: jest.fn(),
validateSessionUser: jest.fn(),
}));
jest.mock("../../Templates/Templates", () => ({
__esModule: true,
default: {
templates: {
findOne: jest.fn(),
},
sendEmail: jest.fn(),
},
}));
jest.mock("../../ClientAccounts/ClientAccount", () => ({
__esModule: true,
default: {
getAvailableAmount: jest.fn().mockResolvedValue(0),
},
}));
jest.mock("../../DiscountsEmail/DiscountsEmail", () => ({
__esModule: true,
default: {
findOne: jest.fn(),
},
}));
jest.mock("../../../index", () => ({
io: {
to: jest.fn(() => ({ emit: jest.fn() })),
},
}));
import AppointmentsList from "../Appointments";
import UsersManager from "../../Users/Users";
import EmployeesList from "../../Employees/Employee";
import ClientsManager from "../../Clients/Clients";
import ServiceList from "../../Services/Service";
import CompaniesManager from "../../Companies/Companies";
import Templates from "../../Templates/Templates";
import PlanSubscriptionsList from "../../PlanSubscriptions/PlanSubscriptons";
import { NotificationsManager } from "../../Notifications/Notifications";
import { APPOINTMENT_NOTIFICATION_TYPE } from "../Appointments.Interface";
describe("getAppointmentEvent — historical read snapshot fallback", () => {
let originalAdapter: any;
beforeEach(() => {
jest.clearAllMocks();
// Save and replace adapter
originalAdapter = AppointmentsList.Appointments;
const mockAppointmentFindOne = jest.fn();
(AppointmentsList as any).Appointments = {
find: jest.fn(),
findOne: mockAppointmentFindOne,
AppointmentList: {
findOne: mockAppointmentFindOne,
find: jest.fn(),
},
};
});
afterEach(() => {
(AppointmentsList as any).Appointments = originalAdapter;
});
it("falls back to Employee snapshot when User account is deleted", async () => {
const mockAppointment = {
_id: "appt-001",
id: "appt-001",
companyId: "company-001",
clientId: "client-001",
employeeId: "emp-removed",
serviceId: "svc-001",
start: new Date("2026-07-20T10:00:00Z"),
startHour: 10,
endHour: 11,
dateDay: new Date("2026-07-20"),
price: 100,
length: 60,
payment: false,
comments: "",
discountId: undefined,
};
const mockClient = {
_id: "client-001",
id: "client-001",
userId: "client-user-001",
firstName: "Client",
lastName: "Smith",
};
const mockClientUser = {
_id: "client-user-001",
id: "client-user-001",
firstName: "Client",
lastName: "Smith",
avatar: "client-avatar",
};
// Employee exists (removed, with snapshot)
const mockRemovedEmployee = {
_id: "emp-removed",
id: "emp-removed",
companyId: "company-001",
userId: "deleted-user-id",
calendarColor: "#ff0000",
removed: true,
profileSnapshot: {
firstName: "John",
lastName: "Doe",
email: "john@example.com",
avatar: "old-avatar-url",
},
};
// Mock adapter.findOne for the appointment
const mockAppointmentFindOne = (AppointmentsList as any).Appointments.AppointmentList.findOne;
mockAppointmentFindOne.mockResolvedValue(mockAppointment);
// Mock lookups
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue(mockClient);
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
_id: "svc-001",
name: "Haircut",
description: "Professional haircut",
});
// Call order for UsersManager.users.findOne (same mock as UserList):
// 1. Line 1217: UserList.users.findOne (NOT awaited, but consumes a value)
// 2. Line 1241: UsersManager.users.findOne for userClient
// 3. Line 1253: UsersManager.users.findOne for userEmployee
(UsersManager.users.findOne as jest.Mock)
.mockResolvedValueOnce({ _id: "session-user" }) // line 1217: not awaited, just needs truthy
.mockResolvedValueOnce(mockClientUser) // line 1241: client user
.mockResolvedValueOnce(null); // line 1253: employee user DELETED → triggers fallback
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue(mockRemovedEmployee);
(EmployeesList as any).findById = jest.fn().mockResolvedValue({
id: "emp-removed",
fullName: "John Doe",
avatar: "old-avatar-url",
});
const result = await AppointmentsList.getAppointmentEvent({
appointmentId: "appt-001",
sessionUser: "session-user",
});
// Verify snapshot fields are used for collaboratorName
expect(result.collaboratorName).toBe("John Doe");
// Verify snapshot avatar is used
expect(result.collaboratorAvatar).toBe("old-avatar-url");
// Verify collaboratorId comes from employee, not user
expect(result.collaboratorId).toBe("emp-removed");
});
it("uses live User data when User account still exists", async () => {
const mockAppointment = {
_id: "appt-002",
id: "appt-002",
companyId: "company-001",
clientId: "client-001",
employeeId: "emp-active",
serviceId: "svc-001",
start: new Date("2026-07-20T10:00:00Z"),
startHour: 10,
endHour: 11,
dateDay: new Date("2026-07-20"),
price: 100,
length: 60,
payment: false,
comments: "",
discountId: undefined,
};
const mockClient = {
_id: "client-001",
id: "client-001",
userId: "client-user-001",
firstName: "Client",
lastName: "Smith",
};
const mockClientUser = {
_id: "client-user-001",
id: "client-user-001",
firstName: "Client",
lastName: "Smith",
avatar: "client-avatar",
};
const mockActiveEmployee = {
_id: "emp-active",
id: "emp-active",
companyId: "company-001",
userId: "active-user-id",
calendarColor: "#00ff00",
removed: false,
profileSnapshot: undefined,
};
const mockActiveUser = {
_id: "active-user-id",
id: "active-user-id",
firstName: "Jane",
lastName: "Roe",
avatar: "live-avatar",
email: "jane@example.com",
};
const mockAppointmentFindOne = (AppointmentsList as any).Appointments.AppointmentList.findOne;
mockAppointmentFindOne.mockResolvedValue(mockAppointment);
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue(mockClient);
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
_id: "svc-001",
name: "Haircut",
description: "Professional haircut",
});
// Call order for UsersManager.users.findOne (same mock as UserList):
// 1. Line 1217: UserList.users.findOne (NOT awaited, but consumes a value)
// 2. Line 1241: UsersManager.users.findOne for userClient
// 3. Line 1253: UsersManager.users.findOne for userEmployee
(UsersManager.users.findOne as jest.Mock)
.mockResolvedValueOnce({ _id: "session-user" }) // line 1217: not awaited
.mockResolvedValueOnce(mockClientUser) // line 1241: client user
.mockResolvedValueOnce(mockActiveUser); // line 1253: live user exists
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue(mockActiveEmployee);
(EmployeesList as any).findById = jest.fn().mockResolvedValue({
id: "emp-active",
fullName: "Jane Roe",
avatar: "live-avatar",
});
const result = await AppointmentsList.getAppointmentEvent({
appointmentId: "appt-002",
sessionUser: "session-user",
});
// Live user data is used, not snapshot
expect(result.collaboratorName).toBe("Jane Roe");
});
});
describe("cancellation notification jobs", () => {
let originalAdapter: any;
let originalJobService: any;
beforeEach(() => {
jest.clearAllMocks();
originalAdapter = AppointmentsList.Appointments;
originalJobService = (AppointmentsList as any).jobService;
(AppointmentsList as any).Appointments = {
findOne: jest.fn().mockResolvedValue({
_id: "appt-001",
companyId: "company-001",
clientId: "client-001",
employeeId: "employee-001",
serviceId: "service-001",
start: new Date("2026-07-22T11:00:00.000Z"),
price: 1500,
}),
};
(AppointmentsList as any).jobService = {
createJob: jest.fn().mockResolvedValue([]),
createMandatoryCreationJob: jest.fn().mockResolvedValue([]),
createReminderJobs: jest.fn().mockResolvedValue([]),
};
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
_id: "company-001",
name: "clases llavallol",
ownerId: "owner-001",
templateEmailCancellationId: "email-template-001",
templateWapCancellationId: "wap-template-001",
address: "Av. Siempre Viva 123",
});
(CompaniesManager.getCompanyAddress as jest.Mock).mockReturnValue("Av. Siempre Viva 123");
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue({
_id: "client-001",
userId: "client-user-001",
firstName: "Ada",
lastName: "Lovelace",
email: "ada@example.com",
});
(ClientsManager.getClientFullName as jest.Mock).mockReturnValue("Ada Lovelace");
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue({
_id: "employee-001",
userId: "employee-user-001",
});
(EmployeesList.checkPermission as jest.Mock | undefined)?.mockResolvedValue?.(true);
(UsersManager.users.findOne as jest.Mock).mockResolvedValue({
_id: "employee-user-001",
firstName: "Grace",
lastName: "Hopper",
});
(UsersManager.getUserFullName as jest.Mock).mockReturnValue("Grace Hopper");
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
_id: "service-001",
name: "Contabilidad",
});
(Templates.templates.findOne as jest.Mock).mockImplementation(({ id }) => {
if (id === "email-template-001") {
return Promise.resolve({
template: "EMAIL CANCEL <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
});
}
if (id === "wap-template-001") {
return Promise.resolve({
template: "WAP CANCEL <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
});
}
return Promise.resolve(null);
});
});
afterEach(() => {
(AppointmentsList as any).Appointments = originalAdapter;
(AppointmentsList as any).jobService = originalJobService;
});
it("stores organization-rendered cancellation template content per channel", async () => {
const emailContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "cancellation",
channel: "email",
});
const wapContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "cancellation",
channel: "whatsapp",
});
await (AppointmentsList as any).createCancellationNotificationJobs({
appointmentId: "appt-001",
companyId: "company-001",
clientId: "client-001",
clientUserId: "client-user-001",
clientEmail: "ada@example.com",
clientPhoneNumber: "5491112345678",
companyOwnerId: "owner-001",
companyName: "clases llavallol",
emailMessage: emailContent.message,
wapMessage: wapContent.message,
});
expect((AppointmentsList as any).jobService.createJob).toHaveBeenCalledWith(
expect.objectContaining({
type: "cancellation",
payload: expect.objectContaining({
email: "ada@example.com",
phoneNumber: "5491112345678",
userId: "client-user-001",
companyOwnerId: "owner-001",
emailSubject: "TurnosXpress :: Turno cancelado",
emailMessage: "EMAIL CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
wapMessage: "WAP CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
systemSubject: "Turno cancelado en clases llavallol",
systemMessage: "EMAIL CANCEL Ada Lovelace Contabilidad clases llavallol 22/07/2026 08:00hs.",
}),
})
);
});
it("sends update email notifications with a non-empty subject", async () => {
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
await (AppointmentsList as any).sendEmailNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: APPOINTMENT_NOTIFICATION_TYPE.UPDATE,
});
expect(NotificationsManager.sendEmail).toHaveBeenCalledWith(
expect.objectContaining({
email: "ada@example.com",
subject: "TurnosXpress :: Turno actualizado",
})
);
});
});
describe("creation notification jobs", () => {
let originalAdapter: any;
let originalJobService: any;
beforeEach(() => {
jest.clearAllMocks();
originalAdapter = AppointmentsList.Appointments;
originalJobService = (AppointmentsList as any).jobService;
(AppointmentsList as any).Appointments = {
findOne: jest.fn().mockResolvedValue({
_id: "appt-001",
companyId: "company-001",
clientId: "client-001",
employeeId: "employee-001",
serviceId: "service-001",
start: new Date("2026-07-22T11:00:00.000Z"),
price: 1500,
}),
};
(AppointmentsList as any).jobService = {
createJob: jest.fn().mockResolvedValue([]),
createMandatoryCreationJob: jest.fn().mockResolvedValue([]),
createReminderJobs: jest.fn().mockResolvedValue([]),
};
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
_id: "company-001",
name: "clases llavallol",
ownerId: "owner-001",
templateEmailAltaId: "email-alta-template-001",
templateWapAltaId: "wap-alta-template-001",
address: "Av. Siempre Viva 123",
});
(CompaniesManager.getCompanyAddress as jest.Mock).mockReturnValue("Av. Siempre Viva 123");
(ClientsManager.clients.findOne as jest.Mock).mockResolvedValue({
_id: "client-001",
userId: "client-user-001",
firstName: "Ada",
lastName: "Lovelace",
email: "ada@example.com",
});
(ClientsManager.getClientFullName as jest.Mock).mockReturnValue("Ada Lovelace");
(EmployeesList.employees.findOne as jest.Mock).mockResolvedValue({
_id: "employee-001",
userId: "employee-user-001",
});
(EmployeesList.checkPermission as jest.Mock | undefined)?.mockResolvedValue?.(true);
(UsersManager.users.findOne as jest.Mock).mockResolvedValue({
_id: "employee-user-001",
firstName: "Grace",
lastName: "Hopper",
});
(UsersManager.getUserFullName as jest.Mock).mockReturnValue("Grace Hopper");
(ServiceList.services.findOne as jest.Mock).mockResolvedValue({
_id: "service-001",
name: "Historia",
});
(Templates.templates.findOne as jest.Mock).mockImplementation(({ id }) => {
if (id === "email-alta-template-001") {
return Promise.resolve({
template: "EMAIL ALTA <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
});
}
if (id === "wap-alta-template-001") {
return Promise.resolve({
template: "WAP ALTA <---CLIENTE---> <---SERVICIO---> <---ORGANIZACION---> <---FECHA---> <---HORARIO--->",
});
}
return Promise.resolve(null);
});
});
afterEach(() => {
(AppointmentsList as any).Appointments = originalAdapter;
(AppointmentsList as any).jobService = originalJobService;
});
it("stores organization-rendered Alta template content per channel", async () => {
const emailContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "creation",
channel: "email",
});
const wapContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "creation",
channel: "whatsapp",
});
await (AppointmentsList as any).createCreationNotificationJobs({
appointmentId: "appt-001",
companyId: "company-001",
clientId: "client-001",
clientUserId: "client-user-001",
clientEmail: "ada@example.com",
clientPhoneNumber: "5491112345678",
companyOwnerId: "owner-001",
companyName: "clases llavallol",
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
emailMessage: emailContent.message,
wapMessage: wapContent.message,
reminderEmailMessage: "EMAIL REMINDER Ada Lovelace",
reminderWapMessage: "WAP REMINDER Ada Lovelace",
});
expect((AppointmentsList as any).jobService.createMandatoryCreationJob).toHaveBeenCalledWith(
expect.objectContaining({
type: "creation",
payload: expect.objectContaining({
emailSubject: "TurnosXpress :: Alta de turno",
emailMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
wapMessage: "WAP ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
systemSubject: "Turno reservado en clases llavallol",
systemMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
}),
})
);
expect((AppointmentsList as any).jobService.createReminderJobs).toHaveBeenCalledWith(
expect.objectContaining({
type: "reminder",
reminderPayload: expect.objectContaining({
email: "ada@example.com",
phoneNumber: "5491112345678",
userId: "client-user-001",
companyOwnerId: "owner-001",
emailSubject: "TurnosXpress :: Recordatorio",
emailMessage: "EMAIL REMINDER Ada Lovelace",
wapMessage: "WAP REMINDER Ada Lovelace",
}),
})
);
});
it("routes customer notification jobs for creation, repeat reminder-only, and disabled notifications", () => {
expect((AppointmentsList as any).shouldCreateCustomerNotificationJobs(true, undefined)).toBe(true);
expect((AppointmentsList as any).shouldCreateCustomerNotificationJobs(false, "repeat-001")).toBe(true);
expect((AppointmentsList as any).shouldCreateCustomerNotificationJobs(false, undefined)).toBe(false);
});
it("creates reminder-only jobs without rendering Alta content for repeat-created appointments", async () => {
const tryToSendNotificationSpy = jest
.spyOn(AppointmentsList as any, "tryToSendNotification")
.mockImplementation(({ type, channel }: any) => {
if (type === APPOINTMENT_NOTIFICATION_TYPE.CREATION) {
throw new Error("Creation content should not be rendered");
}
return Promise.resolve({
message: channel === "email" ? "EMAIL REMINDER Ada Lovelace" : "WAP REMINDER Ada Lovelace",
});
});
const getOptionalClientWapNumberSpy = jest
.spyOn(AppointmentsList as any, "getOptionalClientWapNumber")
.mockResolvedValue("5491112345678");
try {
await (AppointmentsList as any).createAppointmentNotificationJobs({
appointmentId: "appt-001",
companyId: "company-001",
clientId: "client-001",
clientUserId: "client-user-001",
clientEmail: "ada@example.com",
client: { _id: "client-001" },
companyOwnerId: "owner-001",
companyName: "clases llavallol",
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
notification: false,
});
expect(tryToSendNotificationSpy).toHaveBeenCalledTimes(2);
expect(tryToSendNotificationSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: APPOINTMENT_NOTIFICATION_TYPE.REMINDER, channel: "email" })
);
expect(tryToSendNotificationSpy).toHaveBeenCalledWith(
expect.objectContaining({ type: APPOINTMENT_NOTIFICATION_TYPE.REMINDER, channel: "whatsapp" })
);
expect((AppointmentsList as any).jobService.createMandatoryCreationJob).not.toHaveBeenCalled();
expect((AppointmentsList as any).jobService.createReminderJobs).toHaveBeenCalledWith(
expect.objectContaining({
type: "reminder",
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
reminderPayload: expect.objectContaining({
email: "ada@example.com",
phoneNumber: "5491112345678",
userId: "client-user-001",
companyOwnerId: "owner-001",
emailSubject: "TurnosXpress :: Recordatorio",
emailMessage: "EMAIL REMINDER Ada Lovelace",
wapMessage: "WAP REMINDER Ada Lovelace",
systemSubject: "Recordatorio de turno en clases llavallol",
systemMessage: "EMAIL REMINDER Ada Lovelace",
}),
})
);
} finally {
tryToSendNotificationSpy.mockRestore();
getOptionalClientWapNumberSpy.mockRestore();
}
});
it("falls back to organization WAP Alta template for email when email Alta is absent", async () => {
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
_id: "company-001",
name: "clases llavallol",
ownerId: "owner-001",
templateWapAltaId: "wap-alta-template-001",
address: "Av. Siempre Viva 123",
});
const emailContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "creation",
channel: "email",
});
expect(emailContent.message).toBe("WAP ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.");
});
it("stores organization-rendered update template content with contact snapshot fields", async () => {
const emailContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "update",
channel: "email",
});
const wapContent = await (AppointmentsList as any).tryToSendNotification({
appointmentId: "appt-001",
sessionUser: "owner-001",
type: "update",
channel: "whatsapp",
});
await (AppointmentsList as any).createUpdateNotificationJobs({
appointmentId: "appt-001",
companyId: "company-001",
clientId: "client-001",
clientUserId: "client-user-001",
clientEmail: "ada@example.com",
clientPhoneNumber: "5491112345678",
companyOwnerId: "owner-001",
companyName: "clases llavallol",
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
emailMessage: emailContent.message,
wapMessage: wapContent.message,
reminderEmailMessage: "EMAIL REMINDER Ada Lovelace",
reminderWapMessage: "WAP REMINDER Ada Lovelace",
});
expect((AppointmentsList as any).jobService.createJob).toHaveBeenCalledWith(
expect.objectContaining({
type: "update",
appointmentStart: new Date("2026-07-22T11:00:00.000Z"),
payload: expect.objectContaining({
email: "ada@example.com",
phoneNumber: "5491112345678",
userId: "client-user-001",
companyOwnerId: "owner-001",
emailSubject: "TurnosXpress :: Turno actualizado",
emailMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
wapMessage: "WAP ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
systemSubject: "Turno actualizado en clases llavallol",
systemMessage: "EMAIL ALTA Ada Lovelace Historia clases llavallol 22/07/2026 08:00hs.",
}),
reminderPayload: expect.objectContaining({
email: "ada@example.com",
phoneNumber: "5491112345678",
userId: "client-user-001",
companyOwnerId: "owner-001",
emailSubject: "TurnosXpress :: Recordatorio",
emailMessage: "EMAIL REMINDER Ada Lovelace",
wapMessage: "WAP REMINDER Ada Lovelace",
}),
})
);
});
});