added floor plan to rooms

This commit is contained in:
Rajpreet0 2026-10-02 00:41:54 +02:00
parent 9fa335ea40
commit a8db32bf24
33 changed files with 2695 additions and 61 deletions

View file

@ -0,0 +1,154 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import {
assignSpot,
getCourseSpots,
holdSpot,
spotHolderKey,
} from "@/src/modules/courses/lib/course-spots";
import type { FloorPlanItem } from "@/src/modules/rooms/lib/floor-plan";
const db = {
room: { findUnique: vi.fn() },
courseBooking: { findMany: vi.fn(), findFirst: vi.fn() },
courseSpotHold: { findMany: vi.fn(), deleteMany: vi.fn(), create: vi.fn() },
};
const spot = (id: string, label: string): FloorPlanItem => ({
id,
type: "reformer",
x: 1,
y: 1.5,
rotation: 0,
label,
});
const spots = [spot("s1", "1"), spot("s2", "2"), spot("s3", "3")];
beforeEach(() => {
vi.clearAllMocks();
db.courseBooking.findMany.mockResolvedValue([]);
db.courseBooking.findFirst.mockResolvedValue(null);
db.courseSpotHold.findMany.mockResolvedValue([]);
db.courseSpotHold.deleteMany.mockResolvedValue({ count: 0 });
db.courseSpotHold.create.mockResolvedValue({ id: "hold-1" });
});
describe("getCourseSpots", () => {
it("liest den Raum gar nicht erst, wenn der Kurs keine Platzwahl hat", async () => {
expect(await getCourseSpots(db as never, { spotBooking: false, room: "room-1" })).toBeNull();
expect(db.room.findUnique).not.toHaveBeenCalled();
});
it("behandelt einen Raum ohne Geräte wie einen Kurs ohne Platzwahl", async () => {
db.room.findUnique.mockResolvedValue({ floorPlan: null });
expect(await getCourseSpots(db as never, { spotBooking: true, room: "room-1" })).toBeNull();
});
it("liefert die buchbaren Geräte des Raums", async () => {
db.room.findUnique.mockResolvedValue({
floorPlan: { width: 8, depth: 6, items: [spot("s2", "2"), spot("s1", "1")] },
});
const result = await getCourseSpots(db as never, { spotBooking: true, room: "room-1" });
expect(result?.map((s) => s.id)).toEqual(["s1", "s2"]);
});
});
describe("assignSpot", () => {
it("gibt den Wunschplatz, wenn er frei ist", async () => {
const result = await assignSpot(db as never, {
courseId: "c1",
spots,
requestedSpotId: "s2",
holdId: "hold-1",
});
expect(result).toEqual({ spotId: "s2", spotLabel: "Reformer 2", changed: false });
});
it("weicht auf einen freien Platz aus, den niemand gerade im Checkout hält", async () => {
db.courseBooking.findMany.mockResolvedValue([{ spotId: "s1" }]);
db.courseSpotHold.findMany.mockResolvedValue([{ spotId: "s2" }]);
const result = await assignSpot(db as never, {
courseId: "c1",
spots,
requestedSpotId: "s1",
holdId: "hold-1",
});
expect(result).toEqual({ spotId: "s3", spotLabel: "Reformer 3", changed: true });
// Der eigene Hold zählt nicht als "von jemand anderem gehalten".
expect(db.courseSpotHold.findMany.mock.calls[0][0].where.id).toEqual({ not: "hold-1" });
});
it("nimmt notfalls einen gehaltenen Platz - bezahlt schlägt Checkout", async () => {
db.courseBooking.findMany.mockResolvedValue([{ spotId: "s1" }, { spotId: "s3" }]);
db.courseSpotHold.findMany.mockResolvedValue([{ spotId: "s2" }]);
const result = await assignSpot(db as never, {
courseId: "c1",
spots,
requestedSpotId: "s1",
holdId: null,
});
expect(result?.spotId).toBe("s2");
});
it("liefert null, wenn alle Plätze bezahlt sind", async () => {
db.courseBooking.findMany.mockResolvedValue(spots.map((s) => ({ spotId: s.id })));
const result = await assignSpot(db as never, {
courseId: "c1",
spots,
requestedSpotId: "s1",
holdId: null,
});
expect(result).toBeNull();
});
it("weist bei alten Sessions ohne Wunschplatz den ersten freien zu", async () => {
const result = await assignSpot(db as never, {
courseId: "c1",
spots,
requestedSpotId: null,
holdId: null,
});
expect(result).toEqual({ spotId: "s1", spotLabel: "Reformer 1", changed: false });
});
});
describe("holdSpot", () => {
const params = {
courseId: "c1",
spotId: "s1",
holderKey: spotHolderKey({ userId: "u1" }),
expiresAt: new Date(Date.now() + 30 * 60_000),
};
it("räumt abgelaufene und eigene alte Holds weg, bevor es hält", async () => {
const result = await holdSpot(db as never, params);
expect(result).toEqual({ held: true, holdId: "hold-1" });
const where = db.courseSpotHold.deleteMany.mock.calls[0][0].where;
expect(where.courseId).toBe("c1");
expect(where.OR).toContainEqual({ holderKey: "user:u1" });
});
it("hält keinen Platz, der schon bezahlt ist", async () => {
db.courseBooking.findFirst.mockResolvedValue({ id: "b1" });
const result = await holdSpot(db as never, params);
expect(result.held).toBe(false);
expect(db.courseSpotHold.create).not.toHaveBeenCalled();
});
it("verliert gegen einen gleichzeitigen Checkout für denselben Platz", async () => {
db.courseSpotHold.create.mockRejectedValue(
Object.assign(new Error("Unique constraint failed"), { code: "P2002" })
);
const result = await holdSpot(db as never, params);
expect(result).toMatchObject({ held: false });
});
});
describe("spotHolderKey", () => {
it("normalisiert Gast-E-Mails, damit derselbe Gast seinen alten Hold ersetzt", () => {
expect(spotHolderKey({ guestEmail: " Gast@Example.com " })).toBe("guest:gast@example.com");
});
});