import { beforeEach, describe, expect, it, vi } from "vitest"; const { selectLimit, insertOnDup, mockSendMail, mockRedirect } = vi.hoisted( () => ({ selectLimit: vi.fn(), insertOnDup: vi.fn().mockResolvedValue({}), mockSendMail: vi.fn(), mockRedirect: vi.fn(), }), ); vi.mock("next/navigation", () => ({ redirect: (...args: unknown[]) => { mockRedirect(...args); throw new Error("redirect"); }, })); vi.mock("@/lib/db", () => { return { db: { select: vi.fn(() => ({ from: vi.fn(() => ({ where: vi.fn(() => ({ limit: selectLimit, })), })), })), insert: vi.fn(() => ({ values: vi.fn(() => ({ onDuplicateKeyUpdate: insertOnDup, })), })), }, User: { id: "id", mail: "mail" }, PasswordReset: { email: "email", token: "token", createdAt: "createdAt" }, }; }); vi.mock("@/lib/services/email", () => ({ sendMail: mockSendMail, })); vi.mock("@/lib/services/captcha", () => ({ captchaConfig: vi.fn().mockResolvedValue({ provider: "none", siteKey: "", field: "", }), verifyCaptcha: vi.fn().mockResolvedValue(true), })); vi.mock("@/lib/rate-limit", () => ({ rateLimit: vi.fn().mockResolvedValue({ ok: true, retryAfter: 0 }), clientIp: vi.fn().mockResolvedValue("127.0.0.1"), })); vi.mock("@/env", () => ({ env: { APP_URL: "http://localhost:3000", HOTEL_NAME: "TestHotel" }, })); import { requestReset } from "./password-reset"; beforeEach(() => { vi.clearAllMocks(); insertOnDup.mockResolvedValue({}); }); describe("requestReset", () => { it("sends a reset email when the user exists", async () => { selectLimit.mockResolvedValue([{ id: 1 }]); const fd = new FormData(); fd.set("email", "user@example.com"); await expect(requestReset(fd)).rejects.toThrow("redirect"); expect(selectLimit).toHaveBeenCalled(); expect(insertOnDup).toHaveBeenCalled(); expect(mockSendMail).toHaveBeenCalledWith( "user@example.com", expect.stringContaining("password reset"), expect.stringContaining("http://localhost:3000/reset"), ); }); it("does not send email when user is not found", async () => { selectLimit.mockResolvedValue([]); const fd = new FormData(); fd.set("email", "unknown@example.com"); await expect(requestReset(fd)).rejects.toThrow("redirect"); expect(mockSendMail).not.toHaveBeenCalled(); }); it("rate limits and does not throw on email without @", async () => { const fd = new FormData(); fd.set("email", "not-an-email"); await expect(requestReset(fd)).rejects.toThrow("redirect"); expect(selectLimit).not.toHaveBeenCalled(); }); });