mirror of
https://github.com/Since-3/Kuma.git
synced 2026-10-12 19:51:17 +00:00
174 lines
6.3 KiB
TypeScript
174 lines
6.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
|
|
vi.mock("@/src/lib/prisma", () => ({ prisma: { product: { findUnique: vi.fn() } } }));
|
|
vi.mock("@/src/lib/auth/getUser", () => ({
|
|
getUserData: vi.fn(),
|
|
isUser: (user: { role: string }) => user.role === "user",
|
|
}));
|
|
vi.mock("@/src/lib/stripe", () => ({ getPlatformFeeCents: () => 50 }));
|
|
vi.mock("@/src/lib/stripe-checkout", () => ({
|
|
createBrandedCheckoutSession: vi.fn(),
|
|
studioCheckoutBranding: () => ({}),
|
|
}));
|
|
vi.mock("@/src/modules/settings/lib/discount-validation", async (importOriginal) => {
|
|
const actual =
|
|
await importOriginal<typeof import("@/src/modules/settings/lib/discount-validation")>();
|
|
return {
|
|
reserveDiscountCode: vi.fn(),
|
|
releaseDiscountReservation: vi.fn().mockResolvedValue(undefined),
|
|
calculateDiscountedPriceCents: actual.calculateDiscountedPriceCents,
|
|
};
|
|
});
|
|
vi.mock("@/src/modules/marketing/lib/consent", () => ({ grantConsentQuietly: vi.fn() }));
|
|
|
|
import { POST } from "@/src/app/api/stripe/checkout/pass/route";
|
|
import { prisma } from "@/src/lib/prisma";
|
|
import { getUserData } from "@/src/lib/auth/getUser";
|
|
import { createBrandedCheckoutSession } from "@/src/lib/stripe-checkout";
|
|
import {
|
|
releaseDiscountReservation,
|
|
reserveDiscountCode,
|
|
} from "@/src/modules/settings/lib/discount-validation";
|
|
import { grantConsentQuietly } from "@/src/modules/marketing/lib/consent";
|
|
|
|
const product = {
|
|
id: "prod-1",
|
|
name: "10er-Karte",
|
|
credits: 10,
|
|
priceCents: 12000,
|
|
validityMonths: 6,
|
|
isActive: true,
|
|
business: {
|
|
id: "biz-1",
|
|
name: "Studio",
|
|
slug: "studio",
|
|
isPublic: true,
|
|
branding: null,
|
|
stripeAccountId: "acct_studio",
|
|
stripeAccountStatus: "active",
|
|
},
|
|
};
|
|
|
|
function request(body: unknown) {
|
|
return new Request("http://localhost/api/stripe/checkout/pass", {
|
|
method: "POST",
|
|
body: JSON.stringify(body),
|
|
}) as never;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(getUserData).mockResolvedValue({
|
|
id: "user-1",
|
|
email: "kunde@example.com",
|
|
role: "user",
|
|
} as never);
|
|
vi.mocked(prisma.product.findUnique).mockResolvedValue(product as never);
|
|
vi.mocked(createBrandedCheckoutSession).mockResolvedValue({ url: "https://stripe/x" } as never);
|
|
});
|
|
|
|
describe("POST /api/stripe/checkout/pass", () => {
|
|
it("schickt Gäste zum Login", async () => {
|
|
vi.mocked(getUserData).mockResolvedValue(null);
|
|
const res = await POST(request({ productId: "prod-1" }));
|
|
expect(res.status).toBe(401);
|
|
expect((await res.json()).loginRequired).toBe(true);
|
|
});
|
|
|
|
it("lässt Team-Accounts keine Karte kaufen", async () => {
|
|
vi.mocked(getUserData).mockResolvedValue({ id: "m1", role: "manager" } as never);
|
|
expect((await POST(request({ productId: "prod-1" }))).status).toBe(403);
|
|
});
|
|
|
|
it("verkauft keine Karte, die nicht im Verkauf ist", async () => {
|
|
vi.mocked(prisma.product.findUnique).mockResolvedValue({
|
|
...product,
|
|
isActive: false,
|
|
} as never);
|
|
expect((await POST(request({ productId: "prod-1" }))).status).toBe(404);
|
|
expect(createBrandedCheckoutSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("nimmt die Servicegebühr pro Einheit als application_fee auf dem Studio-Account", async () => {
|
|
const res = await POST(request({ productId: "prod-1" }));
|
|
|
|
expect(res.status).toBe(200);
|
|
const [params, , accountId] = vi.mocked(createBrandedCheckoutSession).mock.calls[0];
|
|
expect(accountId).toBe("acct_studio");
|
|
expect(params.payment_intent_data?.application_fee_amount).toBe(500);
|
|
expect(params.line_items).toEqual([
|
|
expect.objectContaining({
|
|
quantity: 1,
|
|
price_data: expect.objectContaining({ unit_amount: 12000 }),
|
|
}),
|
|
expect.objectContaining({
|
|
quantity: 10,
|
|
price_data: expect.objectContaining({ unit_amount: 50 }),
|
|
}),
|
|
]);
|
|
expect(params.metadata).toMatchObject({ kind: "pass", productId: "prod-1", userId: "user-1" });
|
|
});
|
|
|
|
describe("mit Rabattcode", () => {
|
|
beforeEach(() => {
|
|
vi.mocked(reserveDiscountCode).mockResolvedValue({
|
|
reserved: true,
|
|
reservationToken: "tok-1",
|
|
discountCode: { id: "code-1", type: "percent", value: 25 },
|
|
});
|
|
});
|
|
|
|
it("mindert nur den Kartenpreis, nie die Servicegebühr", async () => {
|
|
const res = await POST(request({ productId: "prod-1", discountCode: "KARTE25" }));
|
|
expect(res.status).toBe(200);
|
|
|
|
expect(reserveDiscountCode).toHaveBeenCalledWith(expect.anything(), {
|
|
code: "KARTE25",
|
|
businessId: "biz-1",
|
|
productId: "prod-1",
|
|
userId: "user-1",
|
|
});
|
|
const params = vi.mocked(createBrandedCheckoutSession).mock.calls[0][0];
|
|
expect(params.line_items?.[0].price_data?.unit_amount).toBe(9000);
|
|
expect(params.payment_intent_data?.application_fee_amount).toBe(500);
|
|
expect(params.metadata).toMatchObject({
|
|
discountCodeId: "code-1",
|
|
discountAmountCents: "3000",
|
|
discountReservationToken: "tok-1",
|
|
});
|
|
});
|
|
|
|
it("lehnt einen ungültigen Code ab, statt zum vollen Preis weiterzumachen", async () => {
|
|
vi.mocked(reserveDiscountCode).mockResolvedValue({
|
|
reserved: false,
|
|
reason: "Dieser Rabattcode gilt nicht für Karten.",
|
|
});
|
|
const res = await POST(request({ productId: "prod-1", discountCode: "KURS10" }));
|
|
expect(res.status).toBe(400);
|
|
expect((await res.json()).error).toBe("Dieser Rabattcode gilt nicht für Karten.");
|
|
expect(createBrandedCheckoutSession).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("gibt die Reservierung frei, wenn Stripe die Session nicht anlegt", async () => {
|
|
vi.mocked(createBrandedCheckoutSession).mockRejectedValue(new Error("Stripe weg"));
|
|
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
const res = await POST(request({ productId: "prod-1", discountCode: "KARTE25" }));
|
|
expect(res.status).toBe(500);
|
|
expect(releaseDiscountReservation).toHaveBeenCalledWith(expect.anything(), "tok-1");
|
|
});
|
|
});
|
|
|
|
it("speichert das Häkchen für Werbe-Mails", async () => {
|
|
await POST(request({ productId: "prod-1", marketingConsent: true }));
|
|
expect(grantConsentQuietly).toHaveBeenCalledWith({
|
|
userId: "user-1",
|
|
businessId: "biz-1",
|
|
source: "pass_purchase",
|
|
});
|
|
});
|
|
|
|
it("ohne Häkchen keine Einwilligung", async () => {
|
|
await POST(request({ productId: "prod-1" }));
|
|
expect(grantConsentQuietly).not.toHaveBeenCalled();
|
|
});
|
|
});
|