97 lines
2.3 KiB
TypeScript
97 lines
2.3 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const {
|
|
mockFindFirst,
|
|
mockUpsert,
|
|
mockFindUnique,
|
|
mockUpdate,
|
|
mockDelete,
|
|
mockSendMail,
|
|
mockRedirect,
|
|
} = vi.hoisted(() => ({
|
|
mockFindFirst: vi.fn(),
|
|
mockUpsert: vi.fn(),
|
|
mockFindUnique: vi.fn(),
|
|
mockUpdate: vi.fn(),
|
|
mockDelete: vi.fn(),
|
|
mockSendMail: vi.fn(),
|
|
mockRedirect: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("next/navigation", () => ({
|
|
redirect: (...args: unknown[]) => {
|
|
mockRedirect(...args);
|
|
throw new Error("redirect");
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/prisma", () => ({
|
|
prisma: {
|
|
user: { findFirst: mockFindFirst, update: mockUpdate },
|
|
passwordReset: {
|
|
upsert: mockUpsert,
|
|
findUnique: mockFindUnique,
|
|
delete: mockDelete,
|
|
},
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/services/email", () => ({
|
|
sendMail: mockSendMail,
|
|
}));
|
|
|
|
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();
|
|
});
|
|
|
|
describe("requestReset", () => {
|
|
it("sends a reset email when the user exists", async () => {
|
|
mockFindFirst.mockResolvedValue({ id: 1 });
|
|
mockUpsert.mockResolvedValue({});
|
|
|
|
const fd = new FormData();
|
|
fd.set("email", "[email protected]");
|
|
|
|
await expect(requestReset(fd)).rejects.toThrow("redirect");
|
|
|
|
expect(mockFindFirst).toHaveBeenCalledWith(
|
|
expect.objectContaining({ where: { mail: "[email protected]" } }),
|
|
);
|
|
expect(mockUpsert).toHaveBeenCalled();
|
|
expect(mockSendMail).toHaveBeenCalledWith(
|
|
"[email protected]",
|
|
expect.stringContaining("password reset"),
|
|
expect.stringContaining("http://localhost:3000/reset"),
|
|
);
|
|
});
|
|
|
|
it("does not send email when user is not found", async () => {
|
|
mockFindFirst.mockResolvedValue(null);
|
|
|
|
const fd = new FormData();
|
|
fd.set("email", "[email protected]");
|
|
|
|
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(mockFindFirst).not.toHaveBeenCalled();
|
|
});
|
|
});
|