Kuma/tests/notifications/create-notification.test.ts
2026-08-28 18:20:28 +02:00

125 lines
4.1 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("@/src/lib/prisma", () => ({
prisma: {
business: { findUnique: vi.fn() },
notification: { create: vi.fn() },
},
}));
import { prisma } from "@/src/lib/prisma";
import {
createNotification,
isNotificationEnabled,
notifyBusinessManager,
} from "@/src/modules/notifications/lib/create-notification";
const businessFindUnique = vi.mocked(prisma.business.findUnique);
const notificationCreate = vi.mocked(prisma.notification.create);
beforeEach(() => {
vi.clearAllMocks();
});
describe("createNotification", () => {
it("schreibt einen Datensatz und gibt true zurück", async () => {
notificationCreate.mockResolvedValueOnce({} as never);
const ok = await createNotification({
recipientId: "mgr-1",
type: "booking.created",
title: "Neue Buchung",
body: "…",
linkUrl: "/courses/edit/c1",
});
expect(ok).toBe(true);
expect(notificationCreate).toHaveBeenCalledWith({
data: {
recipientId: "mgr-1",
type: "booking.created",
title: "Neue Buchung",
body: "…",
linkUrl: "/courses/edit/c1",
},
});
});
it("wirft nie, sondern gibt bei DB-Fehler false zurück", async () => {
notificationCreate.mockRejectedValueOnce(new Error("db down"));
const ok = await createNotification({
recipientId: "mgr-1",
type: "booking.created",
title: "x",
body: "y",
});
expect(ok).toBe(false);
});
it("setzt linkUrl auf null wenn nicht angegeben", async () => {
notificationCreate.mockResolvedValueOnce({} as never);
await createNotification({ recipientId: "m", type: "course.full", title: "a", body: "b" });
expect(notificationCreate.mock.calls[0][0].data.linkUrl).toBeNull();
});
});
describe("isNotificationEnabled", () => {
it("gibt den Registry-Default zurück wenn kein Business gefunden wird", async () => {
businessFindUnique.mockResolvedValueOnce(null);
expect(await isNotificationEnabled("nope", "booking.created")).toBe(true);
});
it("respektiert ein gespeichertes Opt-out", async () => {
businessFindUnique.mockResolvedValueOnce({
notificationSettings: { "booking.created": false },
} as never);
expect(await isNotificationEnabled("b1", "booking.created")).toBe(false);
});
it("fällt bei DB-Fehler auf den Default zurück (Zustellung im Zweifel)", async () => {
businessFindUnique.mockRejectedValueOnce(new Error("boom"));
expect(await isNotificationEnabled("b1", "waitlist.spot")).toBe(true);
});
});
describe("notifyBusinessManager", () => {
it("no-op bei fehlender businessId", async () => {
await notifyBusinessManager(null, { type: "booking.created", title: "a", body: "b" });
await notifyBusinessManager(undefined, { type: "booking.created", title: "a", body: "b" });
expect(businessFindUnique).not.toHaveBeenCalled();
expect(notificationCreate).not.toHaveBeenCalled();
});
it("erstellt eine Notification für den Manager des Business", async () => {
businessFindUnique.mockResolvedValueOnce({
managerId: "mgr-42",
notificationSettings: null,
} as never);
notificationCreate.mockResolvedValueOnce({} as never);
await notifyBusinessManager("b1", {
type: "booking.created",
title: "Neue Buchung",
body: "…",
linkUrl: "/x",
});
expect(notificationCreate).toHaveBeenCalledTimes(1);
expect(notificationCreate.mock.calls[0][0].data.recipientId).toBe("mgr-42");
});
it("stellt nichts zu, wenn der Typ für das Business abgeschaltet ist", async () => {
businessFindUnique.mockResolvedValueOnce({
managerId: "mgr-42",
notificationSettings: { "booking.created": false },
} as never);
await notifyBusinessManager("b1", { type: "booking.created", title: "a", body: "b" });
expect(notificationCreate).not.toHaveBeenCalled();
});
it("wirft nie, auch wenn der Business-Lookup fehlschlägt", async () => {
businessFindUnique.mockRejectedValueOnce(new Error("boom"));
await expect(
notifyBusinessManager("b1", { type: "course.full", title: "a", body: "b" })
).resolves.toBeUndefined();
});
});