Files
touchbase/test/email.test.ts

103 lines
3.6 KiB
TypeScript
Raw Normal View History

2026-05-01 18:59:19 -04:00
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 <noreply@touchbase.local>";
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/);
});
});