import { afterAll, beforeAll, beforeEach, describe, expect, test } from "vitest"; import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "@/generated/prisma/client"; import { createHold } from "@/lib/booking"; import { resetEmailTransport, sendBookingConfirmation } from "@/lib/email"; import { seed, type SeedResult } from "@/lib/seed"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); const db = new PrismaClient({ adapter }); const MAILPIT = "http://localhost:8025"; let fx: SeedResult; let serviceId: string; let therapistA: string; let roomA: string; let customer: { id: string; email: string }; beforeAll(async () => { fx = await seed(db); serviceId = fx.services.find((s) => s.name === "60-minute Swedish")!.id; therapistA = fx.therapists[0].id; roomA = fx.rooms[0].id; customer = fx.customers[0]; // Point email transport at Mailpit even though .env.test doesn't define it. process.env.SMTP_HOST = "localhost"; process.env.SMTP_PORT = "1025"; process.env.SMTP_FROM = "TouchBase "; resetEmailTransport(); }); afterAll(async () => { await db.$disconnect(); }); beforeEach(async () => { await db.notification.deleteMany(); await db.booking.deleteMany(); // Clear Mailpit between tests to keep assertions clean. await fetch(`${MAILPIT}/api/v1/messages`, { method: "DELETE" }); }); const D = (iso: string) => new Date(iso); describe("sendBookingConfirmation", () => { test("delivers an email to Mailpit and writes a sent Notification row", async () => { const hold = await createHold(db, { customerId: customer.id, serviceId, therapistId: therapistA, roomId: roomA, startsAt: D("2026-05-05T14:00:00Z"), }); const result = await sendBookingConfirmation({ db, bookingId: hold.id }); expect(result.status).toBe("sent"); // Notification row recorded const noti = await db.notification.findUnique({ where: { id: result.notificationId }, }); expect(noti?.status).toBe("sent"); expect(noti?.template).toBe("booking_confirmation"); expect(noti?.bookingId).toBe(hold.id); expect(noti?.userId).toBe(customer.id); expect(noti?.to).toBe(customer.email); expect(noti?.sentAt).toBeTruthy(); // Mailpit received the message const res = await fetch(`${MAILPIT}/api/v1/messages`); const json = (await res.json()) as { messages: { To: { Address: string }[]; Subject: string }[] }; expect(json.messages).toHaveLength(1); expect(json.messages[0].To[0].Address).toBe(customer.email); expect(json.messages[0].Subject).toContain("60-minute Swedish"); }); test("formats local time in the practice timezone (America/Detroit)", async () => { process.env.APP_TZ = "America/Detroit"; const hold = await createHold(db, { customerId: customer.id, serviceId, therapistId: therapistA, roomId: roomA, startsAt: D("2026-05-05T14:00:00Z"), // 10:00 EDT }); await sendBookingConfirmation({ db, bookingId: hold.id }); const res = await fetch(`${MAILPIT}/api/v1/messages`); const list = (await res.json()) as { messages: { ID: string }[] }; const msgRes = await fetch(`${MAILPIT}/api/v1/message/${list.messages[0].ID}`); const msg = (await msgRes.json()) as { Text: string }; // 14:00 UTC → 10:00 EDT expect(msg.Text).toContain("10:00"); expect(msg.Text).toContain("EDT"); }); test("missing booking id throws", async () => { await expect( sendBookingConfirmation({ db, bookingId: "no-such-booking" }), ).rejects.toThrow(/not found/); }); });