/** * Abo-Reservierung im Stapel: Produktlisten brauchen den freien Bestand * vieler Produkte auf einmal — mit festen drei Abfragen statt drei je Produkt. */ import { describe, expect, test, vi } from "vitest"; vi.mock("@/lib/prisma", () => ({ prisma: {} })); const { reservedJarsByProduct, reservedJarsForProduct, availableForSale, availableForSaleByProduct } = await import("./reservation"); const NOW = new Date("2026-10-16T12:00:00.000Z"); function fakeDb(data: { quarterly?: { offer: { honeyProductId: string | null; honeyJars: number } }[]; windows?: { offerId: string; startsOn: Date; endsOn: Date; plannedJars: number; offer: { honeyProductId: string | null } }[]; deliveries?: { jars: number; dueAt: Date; subscription: { offerId: string } }[]; inventory?: { productId: string; quantity: number }[]; }) { return { sponsorshipSubscription: { findMany: vi.fn(async () => data.quarterly ?? []) }, harvestWindow: { findMany: vi.fn(async () => data.windows ?? []) }, subscriptionDelivery: { findMany: vi.fn(async () => data.deliveries ?? []) }, inventory: { findMany: vi.fn(async () => data.inventory ?? []), findUnique: vi.fn(async ({ where }: { where: { productId: string } }) => (data.inventory ?? []).find((row) => row.productId === where.productId) ?? null ), }, }; } type Db = Parameters[2]; describe("reservedJarsByProduct", () => { test("summiert vierteljaehrliche Abos und offene Erntefenster je Produkt in drei Abfragen", async () => { const db = fakeDb({ quarterly: [ { offer: { honeyProductId: "wald", honeyJars: 2 } }, { offer: { honeyProductId: "wald", honeyJars: 2 } }, { offer: { honeyProductId: "raps", honeyJars: 3 } }, ], windows: [ { offerId: "ernte", startsOn: new Date("2026-10-01T00:00:00.000Z"), endsOn: new Date("2026-10-31T00:00:00.000Z"), plannedJars: 10, offer: { honeyProductId: "wald" }, }, ], deliveries: [ { jars: 4, dueAt: new Date("2026-10-10T08:00:00.000Z"), subscription: { offerId: "ernte" } }, // Ausserhalb des Fensters: zaehlt nicht. { jars: 6, dueAt: new Date("2026-09-10T08:00:00.000Z"), subscription: { offerId: "ernte" } }, ], }); const result = await reservedJarsByProduct(["wald", "raps", "linde"], NOW, db as unknown as Db); expect(result.get("wald")).toBe(2 + 2 + (10 - 4)); expect(result.get("raps")).toBe(3); expect(result.get("linde")).toBe(0); expect(db.sponsorshipSubscription.findMany).toHaveBeenCalledOnce(); expect(db.harvestWindow.findMany).toHaveBeenCalledOnce(); expect(db.subscriptionDelivery.findMany).toHaveBeenCalledOnce(); }); test("ein ueberliefertes Erntefenster reserviert nichts mehr (nie negativ)", async () => { const db = fakeDb({ windows: [ { offerId: "ernte", startsOn: new Date("2026-10-01T00:00:00.000Z"), endsOn: new Date("2026-10-31T00:00:00.000Z"), plannedJars: 3, offer: { honeyProductId: "wald" }, }, ], deliveries: [{ jars: 5, dueAt: new Date("2026-10-31T20:00:00.000Z"), subscription: { offerId: "ernte" } }], }); const result = await reservedJarsByProduct(["wald"], NOW, db as unknown as Db); expect(result.get("wald")).toBe(0); }); test("ohne Produkte keine Abfrage", async () => { const db = fakeDb({}); const result = await reservedJarsByProduct([], NOW, db as unknown as Db); expect(result.size).toBe(0); expect(db.sponsorshipSubscription.findMany).not.toHaveBeenCalled(); }); test("Einzelabfrage und freier Bestand bauen auf dem Stapel auf", async () => { const db = fakeDb({ quarterly: [{ offer: { honeyProductId: "wald", honeyJars: 2 } }], inventory: [{ productId: "wald", quantity: 5 }], }); expect(await reservedJarsForProduct("wald", NOW, db as unknown as Db)).toBe(2); expect(await availableForSale("wald", NOW, db as unknown as Db)).toBe(3); }); test("freier Bestand im Stapel: Lager minus Reservierung, ohne Lagerzeile 0", async () => { const db = fakeDb({ quarterly: [{ offer: { honeyProductId: "wald", honeyJars: 4 } }], inventory: [ { productId: "wald", quantity: 3 }, { productId: "raps", quantity: 7 }, ], }); const result = await availableForSaleByProduct(["wald", "raps", "linde"], NOW, db as unknown as Db); expect(Object.fromEntries(result)).toEqual({ wald: 0, raps: 7, linde: 0 }); }); });