352 lines
13 KiB
TypeScript
352 lines
13 KiB
TypeScript
import { NotificationChannel } from "../../NotificationJobs/NotificationJobs.Interface";
|
|
|
|
// Mock all external dependencies before importing the module under test
|
|
jest.mock("../../PlanSubscriptions/PlanSubscriptons", () => ({
|
|
__esModule: true,
|
|
default: {
|
|
checkFeature: jest.fn(),
|
|
},
|
|
}));
|
|
|
|
jest.mock("../CompanyNotificationPolicy.Adapter.Mongoose", () => ({
|
|
CompanyNotificationPolicyAdapterMongoose: jest.fn().mockImplementation(() => ({
|
|
findOne: jest.fn(),
|
|
})),
|
|
}));
|
|
|
|
jest.mock("../ClientNotificationPreferences.Adapter.Mongoose", () => ({
|
|
ClientNotificationPreferencesAdapterMongoose: jest.fn().mockImplementation(() => ({
|
|
findOne: jest.fn(),
|
|
})),
|
|
}));
|
|
|
|
jest.mock("../ClientCompanyNotificationOverride.Adapter.Mongoose", () => ({
|
|
ClientCompanyNotificationOverrideAdapterMongoose: jest.fn().mockImplementation(() => ({
|
|
findOne: jest.fn(),
|
|
})),
|
|
}));
|
|
|
|
jest.mock("../../Companies/Companies", () => ({
|
|
__esModule: true,
|
|
default: {
|
|
companies: {
|
|
findOne: jest.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
jest.mock("../../Clients/Clients", () => ({
|
|
__esModule: true,
|
|
default: {
|
|
clients: {
|
|
findOne: jest.fn(),
|
|
},
|
|
},
|
|
}));
|
|
|
|
import { PolicyResolver } from "../PolicyResolver";
|
|
import PlanSubscriptionsList from "../../PlanSubscriptions/PlanSubscriptons";
|
|
import CompaniesManager from "../../Companies/Companies";
|
|
import ClientsList from "../../Clients/Clients";
|
|
import { PlanFeatures } from "../../Plans/Plans.interface";
|
|
|
|
describe("PolicyResolver", () => {
|
|
let resolver: PolicyResolver;
|
|
let mockCompanyPolicyFindOne: jest.Mock;
|
|
let mockClientPreferencesFindOne: jest.Mock;
|
|
let mockOverrideFindOne: jest.Mock;
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
|
|
resolver = new PolicyResolver();
|
|
|
|
// Access the mocked adapter instances
|
|
mockCompanyPolicyFindOne = (
|
|
resolver as any
|
|
).companyPolicyAdapter.findOne as jest.Mock;
|
|
mockClientPreferencesFindOne = (
|
|
resolver as any
|
|
).clientPreferencesAdapter.findOne as jest.Mock;
|
|
mockOverrideFindOne = (
|
|
resolver as any
|
|
).clientCompanyOverrideAdapter.findOne as jest.Mock;
|
|
|
|
// Mock CompaniesManager to return a company with an ownerId
|
|
(CompaniesManager.companies.findOne as jest.Mock).mockResolvedValue({
|
|
_id: "company-1",
|
|
ownerId: "owner-user-1",
|
|
});
|
|
(ClientsList.clients.findOne as jest.Mock).mockResolvedValue({
|
|
_id: "client-1",
|
|
userId: "client-user-1",
|
|
});
|
|
});
|
|
|
|
it("returns system defaults when no policies exist", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(
|
|
expect.arrayContaining(["whatsapp", "email", "system"])
|
|
);
|
|
expect(result.channels).toHaveLength(3);
|
|
});
|
|
|
|
it("uses company policy defaults when no client override exists", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["email" as NotificationChannel],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(["email"]);
|
|
});
|
|
|
|
it("client override wins over company default", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["email" as NotificationChannel],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue({
|
|
clientId: "client-1",
|
|
companyId: "company-1",
|
|
preferredChannels: ["whatsapp" as NotificationChannel],
|
|
mutedChannels: [],
|
|
});
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(["whatsapp"]);
|
|
});
|
|
|
|
it("client global preferences used when no override exists", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["whatsapp" as NotificationChannel, "email" as NotificationChannel],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue({
|
|
userId: "client-user-1",
|
|
preferredChannels: ["email" as NotificationChannel],
|
|
mutedChannels: [],
|
|
});
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(["email"]);
|
|
});
|
|
|
|
it("excludes muted channels from override", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue({
|
|
clientId: "client-1",
|
|
companyId: "company-1",
|
|
preferredChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
],
|
|
mutedChannels: ["whatsapp" as NotificationChannel],
|
|
});
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(["email"]);
|
|
expect(result.channels).not.toContain("whatsapp");
|
|
});
|
|
|
|
it("excludes muted channels from client preferences", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue({
|
|
userId: "client-user-1",
|
|
preferredChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
"system" as NotificationChannel,
|
|
],
|
|
mutedChannels: ["email" as NotificationChannel],
|
|
});
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual(
|
|
expect.arrayContaining(["whatsapp", "system"])
|
|
);
|
|
expect(result.channels).not.toContain("email");
|
|
});
|
|
|
|
it("loads global preferences by the appointment client's userId", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue({
|
|
userId: "client-user-1",
|
|
preferredChannels: ["system" as NotificationChannel],
|
|
mutedChannels: [],
|
|
});
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(ClientsList.clients.findOne).toHaveBeenCalledWith({ _id: "client-1" });
|
|
expect(mockClientPreferencesFindOne).toHaveBeenCalledWith({ userId: "client-user-1" });
|
|
expect(result.channels).toEqual(["system"]);
|
|
});
|
|
|
|
it("plan blocks WhatsApp when feature not available", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockImplementation(
|
|
async (params: { feature: string }) => {
|
|
if (params.feature === PlanFeatures.WAP_NOTIFICATIONS) return false;
|
|
return true;
|
|
}
|
|
);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).not.toContain("whatsapp");
|
|
expect(result.channels).toContain("email");
|
|
});
|
|
|
|
it("plan blocks email when feature not available", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockImplementation(
|
|
async (params: { feature: string }) => {
|
|
if (params.feature === PlanFeatures.MAIL_NOTIFICATIONS) return false;
|
|
return true;
|
|
}
|
|
);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).not.toContain("email");
|
|
expect(result.channels).toContain("whatsapp");
|
|
});
|
|
|
|
it("returns timezone from company policy", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["system" as NotificationChannel],
|
|
timezone: "America/New_York",
|
|
quietHours: { from: "22:00", to: "07:00" },
|
|
});
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.timezone).toBe("America/New_York");
|
|
expect(result.quietHours).toEqual({ from: "22:00", to: "07:00" });
|
|
});
|
|
|
|
it("returns default timezone when no company policy", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue(null);
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.timezone).toBe("America/Argentina/Buenos_Aires");
|
|
});
|
|
|
|
it("returns empty channels when all are muted via override", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue(null);
|
|
mockClientPreferencesFindOne.mockResolvedValue(null);
|
|
mockOverrideFindOne.mockResolvedValue({
|
|
clientId: "client-1",
|
|
companyId: "company-1",
|
|
preferredChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
"system" as NotificationChannel,
|
|
],
|
|
mutedChannels: [
|
|
"whatsapp" as NotificationChannel,
|
|
"email" as NotificationChannel,
|
|
"system" as NotificationChannel,
|
|
],
|
|
});
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolve("company-1", "client-1");
|
|
|
|
expect(result.channels).toEqual([]);
|
|
});
|
|
|
|
it("resolves available channels from company defaults and plan limits without client preferences", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["whatsapp" as NotificationChannel, "email" as NotificationChannel],
|
|
mutedChannels: ["email" as NotificationChannel],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockResolvedValue(true);
|
|
|
|
const result = await resolver.resolveAvailableChannels("company-1", [
|
|
"whatsapp",
|
|
"email",
|
|
"system",
|
|
]);
|
|
|
|
expect(result).toEqual(["whatsapp", "email"]);
|
|
expect(mockClientPreferencesFindOne).not.toHaveBeenCalled();
|
|
expect(mockOverrideFindOne).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("removes available channels disallowed by the owner plan", async () => {
|
|
mockCompanyPolicyFindOne.mockResolvedValue({
|
|
companyId: "company-1",
|
|
defaultChannels: ["whatsapp" as NotificationChannel, "email" as NotificationChannel],
|
|
timezone: "America/Argentina/Buenos_Aires",
|
|
});
|
|
(PlanSubscriptionsList.checkFeature as jest.Mock).mockImplementation(
|
|
async (params: { feature: string }) =>
|
|
params.feature !== PlanFeatures.WAP_NOTIFICATIONS
|
|
);
|
|
|
|
const result = await resolver.resolveAvailableChannels("company-1", [
|
|
"whatsapp",
|
|
"email",
|
|
]);
|
|
|
|
expect(result).toEqual(["email"]);
|
|
});
|
|
});
|