Kuma/tests/marketing/consent.test.ts
2026-10-02 17:44:59 +02:00

118 lines
4 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from "vitest";
vi.mock("next/cache", () => ({ revalidateTag: vi.fn() }));
vi.mock("@/src/lib/prisma", () => {
const prisma = {
marketingConsent: { findUnique: vi.fn(), upsert: vi.fn() },
marketingAuditLog: { createMany: vi.fn() },
$transaction: vi.fn(),
};
prisma.$transaction.mockImplementation((fn: (tx: unknown) => unknown) => fn(prisma));
return { prisma };
});
import { Prisma } from "@/src/generated/prisma/client";
import { prisma } from "@/src/lib/prisma";
import { setMarketingConsent } from "@/src/modules/marketing/lib/consent";
import { CONSENT_TEXT_VERSION } from "@/src/modules/marketing/lib/consent-text";
beforeEach(() => {
vi.clearAllMocks();
});
describe("setMarketingConsent", () => {
it("speichert die Einwilligung mit Wortlaut-Version und protokolliert sie", async () => {
vi.mocked(prisma.marketingConsent.findUnique).mockResolvedValue(null);
const changed = await setMarketingConsent({
userId: "user-1",
businessId: "biz-1",
granted: true,
source: "course_booking",
});
expect(changed).toBe(true);
const upsert = vi.mocked(prisma.marketingConsent.upsert).mock.calls[0][0];
expect(upsert.create).toMatchObject({
granted: true,
textVersion: CONSENT_TEXT_VERSION,
source: "course_booking",
});
expect(upsert.create?.grantedAt).toBeInstanceOf(Date);
const [entry] = vi.mocked(prisma.marketingAuditLog.createMany).mock.calls[0][0]!.data as {
action: string;
actorType: string;
userId: string;
}[];
expect(entry).toMatchObject({
action: "consent.granted",
actorType: "customer",
userId: "user-1",
});
});
it("protokolliert den Widerruf", async () => {
vi.mocked(prisma.marketingConsent.findUnique).mockResolvedValue({ granted: true } as never);
await setMarketingConsent({
userId: "user-1",
businessId: "biz-1",
granted: false,
source: "unsubscribe_link",
});
const upsert = vi.mocked(prisma.marketingConsent.upsert).mock.calls[0][0];
expect(upsert.update).toMatchObject({ granted: false });
expect(upsert.update?.revokedAt).toBeInstanceOf(Date);
const [entry] = vi.mocked(prisma.marketingAuditLog.createMany).mock.calls[0][0]!.data as {
action: string;
}[];
expect(entry.action).toBe("consent.revoked");
});
it("ändert nichts, wenn der Stand schon stimmt", async () => {
vi.mocked(prisma.marketingConsent.findUnique).mockResolvedValue({ granted: true } as never);
const changed = await setMarketingConsent({
userId: "user-1",
businessId: "biz-1",
granted: true,
source: "profile",
});
expect(changed).toBe(false);
expect(prisma.marketingConsent.upsert).not.toHaveBeenCalled();
expect(prisma.marketingAuditLog.createMany).not.toHaveBeenCalled();
});
it("versucht es einmal neu, wenn eine parallele Anfrage die Zeile zuerst anlegt", async () => {
// Beide Anfragen lesen "keine Zeile"; unsere scheitert beim Anlegen am Unique-Key.
// Im zweiten Durchlauf ist die Zeile der anderen Anfrage da.
vi.mocked(prisma.marketingConsent.findUnique)
.mockResolvedValueOnce(null)
.mockResolvedValueOnce({ granted: true } as never);
vi.mocked(prisma.marketingConsent.upsert).mockRejectedValueOnce(
new Prisma.PrismaClientKnownRequestError("Unique constraint failed", {
code: "P2002",
clientVersion: "test",
})
);
const changed = await setMarketingConsent({
userId: "user-1",
businessId: "biz-1",
granted: true,
source: "profile",
});
expect(changed).toBe(false);
expect(prisma.marketingConsent.findUnique).toHaveBeenCalledTimes(2);
});
it("gibt andere Fehler weiter", async () => {
vi.mocked(prisma.marketingConsent.findUnique).mockRejectedValue(new Error("DB weg"));
await expect(
setMarketingConsent({ userId: "u", businessId: "b", granted: true, source: "profile" })
).rejects.toThrow("DB weg");
});
});