Files
EpicNext-Cms/src/actions/email-verify.test.ts
T
openhands 17847545dd
Deploy / release (push) Skipped
Deploy / deploy (push) Successful in 1m52s
Improvements: remove dead config, fix ESM, add URL validation, unify types, add missing logging
- 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
2026-07-26 20:28:11 +02:00

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="),
);
});
});