refactor: modularize notification job creation and enforce plan-based channel availability in policy resolver
This commit is contained in:
@@ -10,6 +10,7 @@ jest.mock("../../NotificationJobs/NotificationJobs.Adapter.Mongoose", () => ({
|
||||
jest.mock("../PolicyResolver", () => ({
|
||||
PolicyResolver: jest.fn().mockImplementation(() => ({
|
||||
resolve: jest.fn(),
|
||||
resolveAvailableChannels: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
@@ -27,6 +28,7 @@ describe("NotificationJobService.createJob", () => {
|
||||
let service: NotificationJobService;
|
||||
let mockJobCreate: jest.Mock;
|
||||
let mockResolverResolve: jest.Mock;
|
||||
let mockResolveAvailableChannels: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
@@ -36,6 +38,7 @@ describe("NotificationJobService.createJob", () => {
|
||||
|
||||
mockJobCreate = (service as any).jobAdapter.create as jest.Mock;
|
||||
mockResolverResolve = (service as any).policyResolver.resolve as jest.Mock;
|
||||
mockResolveAvailableChannels = (service as any).policyResolver.resolveAvailableChannels as jest.Mock;
|
||||
});
|
||||
|
||||
it("creates a job with resolved channels", async () => {
|
||||
@@ -219,6 +222,114 @@ describe("NotificationJobService.createJob", () => {
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("creates mandatory creation jobs from available payload destinations without resolving reminder policy", async () => {
|
||||
jest.useFakeTimers().setSystemTime(new Date("2026-07-21T14:00:00.000Z"));
|
||||
mockJobCreate.mockResolvedValue({ id: "job-1" });
|
||||
mockResolveAvailableChannels.mockResolvedValue(["email", "whatsapp"]);
|
||||
const payload = {
|
||||
email: "client@example.com",
|
||||
phoneNumber: "5491112345678",
|
||||
companyOwnerId: "owner-1",
|
||||
emailMessage: "Alta email",
|
||||
wapMessage: "Alta WAP",
|
||||
};
|
||||
|
||||
const result = await service.createMandatoryCreationJob({
|
||||
companyId: "company-1",
|
||||
clientId: "client-1",
|
||||
appointmentId: "appt-1",
|
||||
type: "creation",
|
||||
payload,
|
||||
});
|
||||
|
||||
const createCalls = mockJobCreate.mock.calls.map((call) => call[0]);
|
||||
expect(mockResolverResolve).not.toHaveBeenCalled();
|
||||
expect(mockResolveAvailableChannels).toHaveBeenCalledWith("company-1", [
|
||||
"email",
|
||||
"whatsapp",
|
||||
]);
|
||||
expect(result).toHaveLength(2);
|
||||
expect(createCalls).toEqual([
|
||||
expect.objectContaining({
|
||||
channel: "email",
|
||||
type: "creation",
|
||||
scheduledAt: new Date("2026-07-21T14:00:00.000Z"),
|
||||
payload,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
channel: "whatsapp",
|
||||
type: "creation",
|
||||
scheduledAt: new Date("2026-07-21T14:00:00.000Z"),
|
||||
payload,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not create mandatory creation jobs for channels missing destination data", async () => {
|
||||
mockResolveAvailableChannels.mockResolvedValue([]);
|
||||
|
||||
await service.createMandatoryCreationJob({
|
||||
companyId: "company-1",
|
||||
clientId: "client-1",
|
||||
appointmentId: "appt-1",
|
||||
type: "creation",
|
||||
payload: {
|
||||
companyOwnerId: "owner-1",
|
||||
emailMessage: "Alta email",
|
||||
wapMessage: "Alta WAP",
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockResolverResolve).not.toHaveBeenCalled();
|
||||
expect(mockResolveAvailableChannels).toHaveBeenCalledWith("company-1", []);
|
||||
expect(mockJobCreate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not create mandatory creation jobs for unavailable channels", async () => {
|
||||
mockResolveAvailableChannels.mockResolvedValue(["email"]);
|
||||
const payload = {
|
||||
email: "client@example.com",
|
||||
phoneNumber: "5491112345678",
|
||||
companyOwnerId: "owner-1",
|
||||
};
|
||||
|
||||
const result = await service.createMandatoryCreationJob({
|
||||
companyId: "company-1",
|
||||
clientId: "client-1",
|
||||
appointmentId: "appt-1",
|
||||
type: "creation",
|
||||
payload,
|
||||
});
|
||||
|
||||
const createCalls = mockJobCreate.mock.calls.map((call) => call[0]);
|
||||
expect(result).toHaveLength(1);
|
||||
expect(createCalls).toEqual([
|
||||
expect.objectContaining({ channel: "email", type: "creation", payload }),
|
||||
]);
|
||||
});
|
||||
|
||||
it("keeps reminder jobs policy-aware when resolved channels are empty", async () => {
|
||||
const resolvedPolicy: ResolvedPolicy = {
|
||||
channels: [],
|
||||
timezone: "America/Argentina/Buenos_Aires",
|
||||
reminderRules: [{ offset: 30, enabled: true }],
|
||||
};
|
||||
mockResolverResolve.mockResolvedValue(resolvedPolicy);
|
||||
|
||||
const result = await service.createReminderJobs({
|
||||
companyId: "company-1",
|
||||
clientId: "client-1",
|
||||
appointmentId: "appt-1",
|
||||
type: "reminder",
|
||||
appointmentStart: new Date("2026-07-21T15:00:00.000Z"),
|
||||
reminderPayload: { email: "client@example.com" },
|
||||
});
|
||||
|
||||
expect(mockResolverResolve).toHaveBeenCalledWith("company-1", "client-1");
|
||||
expect(mockJobCreate).not.toHaveBeenCalled();
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("adjusts scheduledAt for quiet hours crossing midnight", async () => {
|
||||
const resolvedPolicy: ResolvedPolicy = {
|
||||
channels: ["system"],
|
||||
|
||||
Reference in New Issue
Block a user