test: cover guardian notification preferences

This commit is contained in:
Marc-André Bombeck committed 2026-08-06 05:30:49 +02:00
1 parent a78189b7f6
commit be4ee9ac6d
2 files changed
+360 -2

No files matched your search

@@ -10,6 +10,7 @@ vi.mock("@/lib/db", () => ({
upsert: vi.fn(),
},
user: { findUnique: vi.fn() },
accountGrant: { findMany: vi.fn() },
auditLog: { create: vi.fn() },
$transaction: (...args: unknown[]) => transactionMock(...args),
},
@@ -32,15 +33,17 @@ vi.mock("@/lib/logging/context", () => ({
}));
const sendViaWebPushMock = vi.fn();
const sendViaNtfyMock = vi.fn();
const sendViaApnsMock = vi.fn();
vi.mock("@/lib/notifications/senders/apns", () => ({
sendViaApns: vi.fn(),
sendViaApns: (...args: unknown[]) => sendViaApnsMock(...args),
}));
vi.mock("@/lib/notifications/senders/email", () => ({
sendViaEmail: vi.fn(),
}));
vi.mock("@/lib/notifications/senders/ntfy", () => ({
sendViaNtfy: vi.fn(),
sendViaNtfy: (...args: unknown[]) => sendViaNtfyMock(...args),
}));
vi.mock("@/lib/notifications/senders/telegram", () => ({
sendViaTelegram: vi.fn(),
@@ -94,6 +97,8 @@ beforeEach(() => {
}),
);
sendViaWebPushMock.mockResolvedValue({ ok: true });
sendViaNtfyMock.mockResolvedValue({ ok: true });
sendViaApnsMock.mockResolvedValue({ ok: true });
});
describe("notification delivery identity", () => {
@@ -180,4 +185,155 @@ describe("notification delivery identity", () => {
);
expect(sendViaWebPushMock).not.toHaveBeenCalled();
});
it("renders for each Guardian while isolating channel preferences", async () => {
const firstGuardian = "guardian-en";
const secondGuardian = "guardian-de";
(
prisma.user.findUnique as unknown as ReturnType<typeof vi.fn>
).mockImplementation(async ({ where }: { where: { id: string } }) => {
if (where.id === recordUserId) {
return { managedProfileAt: new Date() } as never;
}
if (where.id === firstGuardian) {
return {
managedProfileAt: null,
locale: "en",
notificationPrefs: { medication: { clientManaged: true } },
} as never;
}
return {
managedProfileAt: null,
locale: "de",
notificationPrefs: { medication: { clientManaged: true } },
} as never;
});
vi.mocked(prisma.accountGrant.findMany).mockResolvedValue([
{ granteeId: firstGuardian, grantee: { managedProfileAt: null } },
{ granteeId: secondGuardian, grantee: { managedProfileAt: null } },
] as never);
(
prisma.notificationChannel.findMany as unknown as ReturnType<typeof vi.fn>
).mockImplementation(async ({ where }: { where: { userId: string } }) => {
if (where.userId === firstGuardian) {
return [
{
id: "first-web-disabled",
userId: firstGuardian,
type: "WEB_PUSH",
config: "{}",
nextRetryAt: null,
preferences: [{ eventType: "MEDICATION_REMINDER", enabled: false }],
},
{
id: "first-ntfy-enabled",
userId: firstGuardian,
type: "NTFY",
config: "{}",
nextRetryAt: null,
preferences: [],
},
] as never;
}
return [
{
id: "second-web-enabled",
userId: secondGuardian,
type: "WEB_PUSH",
config: "{}",
nextRetryAt: null,
preferences: [],
},
{
id: "second-ntfy-disabled",
userId: secondGuardian,
type: "NTFY",
config: "{}",
nextRetryAt: null,
preferences: [{ eventType: "MEDICATION_REMINDER", enabled: false }],
},
] as never;
});
const outcome = await dispatchNotification({
eventType: "MEDICATION_REMINDER",
userId: recordUserId,
title: "record-language-title",
message: "record-language-message",
renderForRecipient: (locale) => ({
title: `title-${locale}`,
message: `message-${locale}`,
}),
});
expect(outcome).toEqual({
dispatched: true,
channelsAttempted: 2,
channelsSucceeded: 2,
});
expect(sendViaNtfyMock).toHaveBeenCalledWith(
{},
expect.objectContaining({
recipientUserId: firstGuardian,
title: "title-en",
message: "message-en",
}),
);
expect(sendViaWebPushMock).toHaveBeenCalledWith(
secondGuardian,
expect.objectContaining({
recipientUserId: secondGuardian,
title: "title-de",
message: "message-de",
}),
);
expect(sendViaWebPushMock).toHaveBeenCalledTimes(1);
expect(sendViaNtfyMock).toHaveBeenCalledTimes(1);
});
it("bypasses client-managed APNs suppression only for managed Guardian delivery", async () => {
const guardian = "guardian-apns";
(
prisma.user.findUnique as unknown as ReturnType<typeof vi.fn>
).mockImplementation(
async ({ where }: { where: { id: string } }) =>
(where.id === recordUserId
? { managedProfileAt: new Date() }
: {
managedProfileAt: null,
notificationPrefs: { medication: { clientManaged: true } },
}) as never,
);
vi.mocked(prisma.accountGrant.findMany).mockResolvedValue([
{ granteeId: guardian, grantee: { managedProfileAt: null } },
] as never);
vi.mocked(prisma.notificationChannel.findMany).mockResolvedValue([
{
id: "guardian-apns",
userId: guardian,
type: "APNS",
config: "{}",
nextRetryAt: null,
preferences: [],
},
] as never);
await expect(
dispatchNotification({
eventType: "MEDICATION_REMINDER",
userId: recordUserId,
title: "Record content",
message: "Record schedule",
}),
).resolves.toMatchObject({ dispatched: true, channelsSucceeded: 1 });
expect(sendViaApnsMock).toHaveBeenCalledWith(
guardian,
expect.objectContaining({
recordUserId,
recipientUserId: guardian,
}),
);
});
});