- Remove .prettierrc (dead config, Biome replaces Prettier) - Rename lighthouserc.json to lighthouserc.cjs with module.exports for ESM compat - Add logger.warn to empty catch blocks in auth, register, site-settings, prisma-cache, redis, security, rate-limit - Unify ActionResult type: action-helper.ts uses 'ok' consistent with safe-action-shared.ts - Add noUnusedLocals + noUnusedParameters to tsconfig + fix 25 pre-existing unused vars - Replace barrel export src/types/index.ts with direct @/types/common imports - Make trustHost conditional (development only) in auth.ts - Add pre-flight URL validation to update-Nitrov3.sh to catch image.library.url misconfigurations - Improve NITRO_IMAGE_LIBRARY_URL content validation in pre-flight & post-compute checks
94 lines
2.4 KiB
TypeScript
94 lines
2.4 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const mockGet = vi.hoisted(() => vi.fn());
|
|
const mockSendMail = vi.hoisted(() => vi.fn());
|
|
const mockGetTranslations = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("@/env", () => ({
|
|
env: {
|
|
APP_KEY: "test-app-key-for-hmac",
|
|
AUTH_SECRET: "",
|
|
APP_URL: "http://localhost:3000",
|
|
HOTEL_NAME: "TestHotel",
|
|
},
|
|
}));
|
|
|
|
vi.mock("@/lib/services/site-settings", () => ({
|
|
siteSettings: { get: mockGet },
|
|
}));
|
|
|
|
vi.mock("@/lib/services/email", () => ({
|
|
sendMail: mockSendMail,
|
|
}));
|
|
|
|
vi.mock("next-intl/server", () => ({
|
|
getTranslations: mockGetTranslations,
|
|
}));
|
|
|
|
import {
|
|
isValidVerificationToken,
|
|
sendVerification,
|
|
verificationToken,
|
|
} from "./email-verify";
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
mockGet.mockResolvedValue("TestHotel");
|
|
mockSendMail.mockResolvedValue(true);
|
|
mockGetTranslations.mockRejectedValue(new Error("missing"));
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe("email verification tokens", () => {
|
|
it("issues timestamped HMAC tokens that validate", async () => {
|
|
const token = await verificationToken("[email protected]");
|
|
expect(token).toMatch(/^\d+\.[a-f0-9]{64}$/);
|
|
expect(await isValidVerificationToken("[email protected]", token)).toBe(
|
|
true,
|
|
);
|
|
});
|
|
|
|
it("rejects legacy forever-valid digests", async () => {
|
|
const legacy = "a".repeat(64);
|
|
expect(await isValidVerificationToken("[email protected]", legacy)).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("rejects expired tokens", async () => {
|
|
vi.useFakeTimers();
|
|
vi.setSystemTime(new Date("2026-01-01T00:00:00Z"));
|
|
const token = await verificationToken("[email protected]");
|
|
vi.setSystemTime(new Date("2026-01-03T00:00:00Z")); // > 24h
|
|
expect(await isValidVerificationToken("[email protected]", token)).toBe(
|
|
false,
|
|
);
|
|
});
|
|
|
|
it("sends mail with a verify link", async () => {
|
|
mockGetTranslations.mockResolvedValue(((
|
|
key: string,
|
|
values?: { hotel?: string },
|
|
) => {
|
|
const map: Record<string, string> = {
|
|
subject: `Verify your email · ${values?.hotel}`,
|
|
heading: "Verify your email",
|
|
body: `Welcome to ${values?.hotel}!`,
|
|
button: "Verify email",
|
|
fallback: "Paste this link:",
|
|
};
|
|
return map[key] ?? key;
|
|
}) as never);
|
|
|
|
await sendVerification("[email protected]");
|
|
expect(mockSendMail).toHaveBeenCalledWith(
|
|
"[email protected]",
|
|
expect.stringContaining("Verify your email"),
|
|
expect.stringContaining("/verify?token="),
|
|
);
|
|
});
|
|
});
|