see comments

This commit is contained in:
Rajpreet0 2026-10-01 23:54:20 +02:00
parent 55d8838c0e
commit 85c2bd95eb
7 changed files with 153 additions and 47 deletions

View file

@ -177,6 +177,25 @@ describe("Stripe-Webhook – Buchung", () => {
expect(recordStripeFee).toHaveBeenCalledWith("pi_1", "acct_studio");
});
it("bucht nicht mit Gebühr 0, wenn der PaymentIntent nicht abrufbar ist", async () => {
// Mit 0 als Ersatz wäre die Gebühr dauerhaft falsch verbucht - eine erneute
// Zustellung ändert eine bezahlte Buchung nicht mehr. Stattdessen 500,
// damit Stripe das Event erneut zustellt.
vi.mocked(stripe.paymentIntents.retrieve).mockRejectedValue(
new Error("Stripe nicht erreichbar")
);
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
const res = await deliver(
sessionEvent("checkout.session.completed", { account: "acct_studio" }),
"whsec_connect"
);
expect(res.status).toBe(500);
expect(tx.courseBooking.upsert).not.toHaveBeenCalled();
consoleError.mockRestore();
});
it("speichert die Rechnungsadresse aus dem Checkout an der Buchung", async () => {
const event = sessionEvent("checkout.session.completed", { account: "acct_studio" });
(event.data.object as Record<string, unknown>).customer_details = {

View file

@ -1,7 +1,8 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
vi.mock("next/navigation", () => ({ useSearchParams: () => new URLSearchParams() }));
const nav = vi.hoisted(() => ({ params: new URLSearchParams() }));
vi.mock("next/navigation", () => ({ useSearchParams: () => nav.params }));
vi.mock("next/image", () => ({ default: () => null }));
// Block (Desktop) und Zeitleiste (Handy) sind eigene Bausteine - hier zählt,
// welche Kurse in welcher Woche bzw. an welchem Tag erscheinen.
@ -20,7 +21,8 @@ vi.mock("@/src/modules/courses/ui/components/DayTimeline", () => ({
),
}));
vi.mock("@/src/modules/courses/ui/components/BookingDialog", () => ({
BookingDialog: () => null,
BookingDialog: ({ open, course }: { open: boolean; course: { name: string } }) =>
open ? <div role="dialog">{course.name}</div> : null,
}));
vi.mock("@/src/modules/courses/actions/booking-actions", () => ({
getPublishedCoursesForBusiness: vi.fn(),
@ -96,6 +98,7 @@ beforeEach(() => {
vi.useFakeTimers({ toFake: ["Date"] });
vi.setSystemTime(new Date("2026-10-01T08:00:00Z"));
vi.mocked(getPublishedCoursesForBusiness).mockReset();
nav.params = new URLSearchParams();
});
afterEach(() => {
@ -173,6 +176,27 @@ describe("Öffentliche Studio-Seite – Wochenkalender", () => {
});
});
it("öffnet einen geteilten Link auf einen Kurs in einer späteren Woche", () => {
nav.params = new URLSearchParams({ course: "pilates" });
renderView();
expect(screen.getByRole("dialog").textContent).toBe("Reformer Pilates");
// Der Kalender steht dahinter in der Woche des Kurses.
expect(cards()).toEqual(["18:00 Reformer Pilates"]);
});
it("öffnet keinen Buchungsdialog für einen bereits begonnenen Kurs", () => {
// "Frueh" lief heute um 07:30 - jetzt ist es 10:00.
nav.params = new URLSearchParams({ course: "started" });
renderView();
expect(screen.queryByRole("dialog")).toBeNull();
expect(screen.getByRole("status").textContent).toMatch(
/„Frueh“ hat bereits begonnen und kann nicht mehr gebucht werden/
);
expect(cards()).toEqual(["18:00 Yoga Flow", "07:30 Spinning"]);
});
it("nennt Stornoregel und Servicegebühr in einer Zeile", () => {
renderView();
expect(

View file

@ -1,3 +1,4 @@
import "@testing-library/jest-dom/vitest";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, cleanup } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
@ -44,7 +45,7 @@ const defaultProps = {
primaryColor: "#1d3557",
accentColor: "#c9a84c",
},
policy: { tiers: [] },
policy: { enabled: true, mode: "tiered" as const, fixedRefundPercent: 100, tiers: [] },
isPublic: false,
slug: null,
stripeAccountStatus: null,