mirror of
https://github.com/formbricks/formbricks.git
synced 2026-05-13 01:08:45 -05:00
test: unit test for auth module (#4612)
Co-authored-by: Matti Nannt <mail@matthiasnannt.com>
This commit is contained in:
committed by
GitHub
parent
d8386328e7
commit
eac97db665
@@ -0,0 +1,280 @@
|
||||
import { randomBytes } from "crypto";
|
||||
import { Provider } from "next-auth/providers/index";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { EMAIL_VERIFICATION_DISABLED } from "@formbricks/lib/constants";
|
||||
import { createToken } from "@formbricks/lib/jwt";
|
||||
import { authOptions } from "./authOptions";
|
||||
import { mockUser } from "./mock-data";
|
||||
import { hashPassword } from "./utils";
|
||||
|
||||
const mockUserId = "cm5yzxcp900000cl78fzocjal";
|
||||
const mockPassword = randomBytes(12).toString("hex");
|
||||
const mockHashedPassword = await hashPassword(mockPassword);
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Helper to get the provider by id from authOptions.providers.
|
||||
function getProviderById(id: string): Provider {
|
||||
const provider = authOptions.providers.find((p) => p.options.id === id);
|
||||
if (!provider) {
|
||||
throw new Error(`Provider with id ${id} not found`);
|
||||
}
|
||||
return provider;
|
||||
}
|
||||
|
||||
describe("authOptions", () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("CredentialsProvider (credentials) - email/password login", () => {
|
||||
const credentialsProvider = getProviderById("credentials");
|
||||
|
||||
it("should throw error if credentials are not provided", async () => {
|
||||
await expect(credentialsProvider.options.authorize(undefined, {})).rejects.toThrow(
|
||||
"Invalid credentials"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if user not found", async () => {
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(null);
|
||||
|
||||
const credentials = { email: mockUser.email, password: mockPassword };
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"Invalid credentials"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if user has no password stored", async () => {
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue({
|
||||
id: mockUser.id,
|
||||
email: mockUser.email,
|
||||
password: null,
|
||||
});
|
||||
|
||||
const credentials = { email: mockUser.email, password: mockPassword };
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"User has no password stored"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if password verification fails", async () => {
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue({
|
||||
id: mockUserId,
|
||||
email: mockUser.email,
|
||||
password: mockHashedPassword,
|
||||
});
|
||||
|
||||
const credentials = { email: mockUser.email, password: "wrongPassword" };
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"Invalid credentials"
|
||||
);
|
||||
});
|
||||
|
||||
it("should successfully login when credentials are valid", async () => {
|
||||
const fakeUser = {
|
||||
id: mockUserId,
|
||||
email: mockUser.email,
|
||||
password: mockHashedPassword,
|
||||
emailVerified: new Date(),
|
||||
imageUrl: "http://example.com/avatar.png",
|
||||
twoFactorEnabled: false,
|
||||
};
|
||||
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(fakeUser);
|
||||
|
||||
const credentials = { email: mockUser.email, password: mockPassword };
|
||||
|
||||
const result = await credentialsProvider.options.authorize(credentials, {});
|
||||
expect(result).toEqual({
|
||||
id: fakeUser.id,
|
||||
email: fakeUser.email,
|
||||
emailVerified: fakeUser.emailVerified,
|
||||
imageUrl: fakeUser.imageUrl,
|
||||
});
|
||||
});
|
||||
|
||||
describe("Two-Factor Backup Code login", () => {
|
||||
it("should throw error if backup codes are missing", async () => {
|
||||
const mockUser = {
|
||||
id: mockUserId,
|
||||
email: "2fa@example.com",
|
||||
password: mockHashedPassword,
|
||||
twoFactorEnabled: true,
|
||||
backupCodes: null,
|
||||
};
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(mockUser);
|
||||
|
||||
const credentials = { email: mockUser.email, password: mockPassword, backupCode: "123456" };
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"No backup codes found"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("CredentialsProvider (token) - Token-based email verification", () => {
|
||||
const tokenProvider = getProviderById("token");
|
||||
|
||||
it("should throw error if token is not provided", async () => {
|
||||
await expect(tokenProvider.options.authorize({}, {})).rejects.toThrow(
|
||||
"Either a user does not match the provided token or the token is invalid"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if token is invalid or user not found", async () => {
|
||||
const credentials = { token: "badtoken" };
|
||||
|
||||
await expect(tokenProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"Either a user does not match the provided token or the token is invalid"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if email is already verified", async () => {
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(mockUser);
|
||||
|
||||
const credentials = { token: createToken(mockUser.id, mockUser.email) };
|
||||
|
||||
await expect(tokenProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"Email already verified"
|
||||
);
|
||||
});
|
||||
|
||||
it("should update user and verify email when token is valid", async () => {
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue({ id: mockUser.id, emailVerified: null });
|
||||
vi.spyOn(prisma.user, "update").mockResolvedValue({
|
||||
...mockUser,
|
||||
password: mockHashedPassword,
|
||||
backupCodes: null,
|
||||
twoFactorSecret: null,
|
||||
identityProviderAccountId: null,
|
||||
groupId: null,
|
||||
});
|
||||
|
||||
const credentials = { token: createToken(mockUserId, mockUser.email) };
|
||||
|
||||
const result = await tokenProvider.options.authorize(credentials, {});
|
||||
expect(result.email).toBe(mockUser.email);
|
||||
expect(result.emailVerified).toBeInstanceOf(Date);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Callbacks", () => {
|
||||
describe("jwt callback", () => {
|
||||
it("should add profile information to token if user is found", async () => {
|
||||
vi.spyOn(prisma.user, "findFirst").mockResolvedValue({
|
||||
id: mockUser.id,
|
||||
locale: mockUser.locale,
|
||||
email: mockUser.email,
|
||||
emailVerified: mockUser.emailVerified,
|
||||
});
|
||||
|
||||
const token = { email: mockUser.email };
|
||||
if (!authOptions.callbacks?.jwt) {
|
||||
throw new Error("jwt callback is not defined");
|
||||
}
|
||||
const result = await authOptions.callbacks.jwt({ token } as any);
|
||||
expect(result).toEqual({
|
||||
...token,
|
||||
profile: { id: mockUser.id },
|
||||
});
|
||||
});
|
||||
|
||||
it("should return token unchanged if no existing user is found", async () => {
|
||||
vi.spyOn(prisma.user, "findFirst").mockResolvedValue(null);
|
||||
|
||||
const token = { email: "nonexistent@example.com" };
|
||||
if (!authOptions.callbacks?.jwt) {
|
||||
throw new Error("jwt callback is not defined");
|
||||
}
|
||||
const result = await authOptions.callbacks.jwt({ token } as any);
|
||||
expect(result).toEqual(token);
|
||||
});
|
||||
});
|
||||
|
||||
describe("session callback", () => {
|
||||
it("should add user profile to session", async () => {
|
||||
const token = {
|
||||
id: "user6",
|
||||
profile: { id: "user6", email: "user6@example.com" },
|
||||
};
|
||||
|
||||
const session = { user: {} };
|
||||
if (!authOptions.callbacks?.session) {
|
||||
throw new Error("session callback is not defined");
|
||||
}
|
||||
const result = await authOptions.callbacks.session({ session, token } as any);
|
||||
expect(result.user).toEqual(token.profile);
|
||||
});
|
||||
});
|
||||
|
||||
describe("signIn callback", () => {
|
||||
it("should throw error if email is not verified and email verification is enabled", async () => {
|
||||
const user = { ...mockUser, emailVerified: null };
|
||||
const account = { provider: "credentials" } as any;
|
||||
// EMAIL_VERIFICATION_DISABLED is imported from constants.
|
||||
if (!EMAIL_VERIFICATION_DISABLED && authOptions.callbacks?.signIn) {
|
||||
await expect(authOptions.callbacks.signIn({ user, account })).rejects.toThrow(
|
||||
"Email Verification is Pending"
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Two-Factor Authentication (TOTP)", () => {
|
||||
const credentialsProvider = getProviderById("credentials");
|
||||
|
||||
it("should throw error if TOTP code is missing when 2FA is enabled", async () => {
|
||||
const mockUser = {
|
||||
id: mockUserId,
|
||||
email: "2fa@example.com",
|
||||
password: mockHashedPassword,
|
||||
twoFactorEnabled: true,
|
||||
twoFactorSecret: "encrypted_secret",
|
||||
};
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(mockUser);
|
||||
|
||||
const credentials = { email: mockUser.email, password: mockPassword };
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"second factor required"
|
||||
);
|
||||
});
|
||||
|
||||
it("should throw error if two factor secret is missing", async () => {
|
||||
const mockUser = {
|
||||
id: mockUserId,
|
||||
email: "2fa@example.com",
|
||||
password: mockHashedPassword,
|
||||
twoFactorEnabled: true,
|
||||
twoFactorSecret: null,
|
||||
};
|
||||
vi.spyOn(prisma.user, "findUnique").mockResolvedValue(mockUser);
|
||||
|
||||
const credentials = {
|
||||
email: mockUser.email,
|
||||
password: mockPassword,
|
||||
totpCode: "123456",
|
||||
};
|
||||
|
||||
await expect(credentialsProvider.options.authorize(credentials, {})).rejects.toThrow(
|
||||
"Internal Server Error"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -39,6 +39,9 @@ export const authOptions: NextAuthOptions = {
|
||||
backupCode: { label: "Backup Code", type: "input", placeholder: "Two-factor backup code" },
|
||||
},
|
||||
async authorize(credentials, _req) {
|
||||
if (!credentials) {
|
||||
throw new Error("Invalid credentials");
|
||||
}
|
||||
let user;
|
||||
try {
|
||||
user = await prisma.user.findUnique({
|
||||
@@ -50,11 +53,11 @@ export const authOptions: NextAuthOptions = {
|
||||
console.error(e);
|
||||
throw Error("Internal server error. Please try again later");
|
||||
}
|
||||
if (!user || !credentials) {
|
||||
if (!user) {
|
||||
throw new Error("Invalid credentials");
|
||||
}
|
||||
if (!user.password) {
|
||||
throw new Error("Invalid credentials");
|
||||
throw new Error("User has no password stored");
|
||||
}
|
||||
|
||||
const isValid = await verifyPassword(credentials.password, user.password);
|
||||
@@ -102,12 +105,12 @@ export const authOptions: NextAuthOptions = {
|
||||
|
||||
const secret = symmetricDecrypt(user.twoFactorSecret, ENCRYPTION_KEY);
|
||||
if (secret.length !== 32) {
|
||||
throw new Error("Internal Server Error");
|
||||
throw new Error("Invalid two factor secret");
|
||||
}
|
||||
|
||||
const isValidToken = (await import("./totp")).totpAuthenticatorCheck(credentials.totpCode, secret);
|
||||
if (!isValidToken) {
|
||||
throw new Error("Invalid second factor code");
|
||||
throw new Error("Invalid two factor code");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,7 +149,6 @@ export const authOptions: NextAuthOptions = {
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw new Error("Either a user does not match the provided token or the token is invalid");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { TUser } from "@formbricks/types/user";
|
||||
|
||||
export const mockUser: TUser = {
|
||||
id: "cm5xj580r00000cmgdj9ohups",
|
||||
name: "mock User",
|
||||
email: "john.doe@example.com",
|
||||
emailVerified: new Date("2024-01-01T00:00:00.000Z"),
|
||||
imageUrl: "https://www.google.com",
|
||||
createdAt: new Date("2024-01-01T00:00:00.000Z"),
|
||||
updatedAt: new Date("2024-01-01T00:00:00.000Z"),
|
||||
twoFactorEnabled: false,
|
||||
identityProvider: "google",
|
||||
objective: "improve_user_retention",
|
||||
notificationSettings: {
|
||||
alert: {},
|
||||
weeklySummary: {},
|
||||
unsubscribedOrganizationIds: [],
|
||||
},
|
||||
role: "other",
|
||||
locale: "en-US",
|
||||
};
|
||||
@@ -0,0 +1,159 @@
|
||||
import { Prisma } from "@prisma/client";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { prisma } from "@formbricks/database";
|
||||
import { createCustomerIoCustomer } from "@formbricks/lib/customerio";
|
||||
import { userCache } from "@formbricks/lib/user/cache";
|
||||
import { InvalidInputError, ResourceNotFoundError } from "@formbricks/types/errors";
|
||||
import { mockUser } from "./mock-data";
|
||||
import { createUser, getUser, getUserByEmail, updateUser } from "./user";
|
||||
|
||||
const mockPrismaUser = {
|
||||
...mockUser,
|
||||
password: "password",
|
||||
identityProviderAccountId: "identityProviderAccountId",
|
||||
twoFactorSecret: "twoFactorSecret",
|
||||
backupCodes: "backupCodes",
|
||||
groupId: "groupId",
|
||||
};
|
||||
|
||||
vi.mock("@formbricks/database", () => ({
|
||||
prisma: {
|
||||
user: {
|
||||
create: vi.fn(),
|
||||
update: vi.fn(),
|
||||
findFirst: vi.fn(),
|
||||
findUnique: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/lib/customerio", () => ({
|
||||
createCustomerIoCustomer: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@formbricks/lib/user/cache", () => ({
|
||||
userCache: {
|
||||
revalidate: vi.fn(),
|
||||
tag: {
|
||||
byEmail: vi.fn(),
|
||||
byId: vi.fn(),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("User Management", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
describe("createUser", () => {
|
||||
it("creates a user successfully", async () => {
|
||||
vi.mocked(prisma.user.create).mockResolvedValueOnce(mockPrismaUser);
|
||||
|
||||
const result = await createUser({
|
||||
email: mockUser.email,
|
||||
name: mockUser.name,
|
||||
locale: mockUser.locale,
|
||||
});
|
||||
|
||||
expect(result).toEqual(mockPrismaUser);
|
||||
expect(createCustomerIoCustomer).toHaveBeenCalledWith({
|
||||
id: mockPrismaUser.id,
|
||||
email: mockPrismaUser.email,
|
||||
});
|
||||
expect(userCache.revalidate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws InvalidInputError when email already exists", async () => {
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError("Mock error message", {
|
||||
code: "P2002",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
vi.mocked(prisma.user.create).mockRejectedValueOnce(errToThrow);
|
||||
|
||||
await expect(
|
||||
createUser({
|
||||
email: mockUser.email,
|
||||
name: mockUser.name,
|
||||
locale: mockUser.locale,
|
||||
})
|
||||
).rejects.toThrow(InvalidInputError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateUser", () => {
|
||||
const mockUpdateData = { name: "Updated Name" };
|
||||
|
||||
it("updates a user successfully", async () => {
|
||||
vi.mocked(prisma.user.update).mockResolvedValueOnce({ ...mockPrismaUser, name: mockUpdateData.name });
|
||||
|
||||
const result = await updateUser(mockUser.id, mockUpdateData);
|
||||
|
||||
expect(result).toEqual({ ...mockPrismaUser, name: mockUpdateData.name });
|
||||
expect(userCache.revalidate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("throws ResourceNotFoundError when user doesn't exist", async () => {
|
||||
const errToThrow = new Prisma.PrismaClientKnownRequestError("Mock error message", {
|
||||
code: "P2016",
|
||||
clientVersion: "0.0.1",
|
||||
});
|
||||
vi.mocked(prisma.user.update).mockRejectedValueOnce(errToThrow);
|
||||
|
||||
await expect(updateUser(mockUser.id, mockUpdateData)).rejects.toThrow(ResourceNotFoundError);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUserByEmail", () => {
|
||||
const mockEmail = "test@example.com";
|
||||
|
||||
it("retrieves a user by email successfully", async () => {
|
||||
const mockUser = {
|
||||
id: "user123",
|
||||
email: mockEmail,
|
||||
locale: "en",
|
||||
emailVerified: null,
|
||||
};
|
||||
vi.mocked(prisma.user.findFirst).mockResolvedValueOnce(mockUser);
|
||||
|
||||
const result = await getUserByEmail(mockEmail);
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
});
|
||||
|
||||
it("throws DatabaseError on prisma error", async () => {
|
||||
vi.mocked(prisma.user.findFirst).mockRejectedValueOnce(new Error("Database error"));
|
||||
|
||||
await expect(getUserByEmail(mockEmail)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("getUser", () => {
|
||||
const mockUserId = "cm5xj580r00000cmgdj9ohups";
|
||||
|
||||
it("retrieves a user by id successfully", async () => {
|
||||
const mockUser = {
|
||||
id: mockUserId,
|
||||
};
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValueOnce(mockUser);
|
||||
|
||||
const result = await getUser(mockUserId);
|
||||
|
||||
expect(result).toEqual(mockUser);
|
||||
});
|
||||
|
||||
it("returns null when user doesn't exist", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValueOnce(null);
|
||||
|
||||
const result = await getUser(mockUserId);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("throws DatabaseError on prisma error", async () => {
|
||||
vi.mocked(prisma.user.findUnique).mockRejectedValueOnce(new Error("Database error"));
|
||||
|
||||
await expect(getUser(mockUserId)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { hashPassword, verifyPassword } from "./utils";
|
||||
|
||||
describe("Password Utils", () => {
|
||||
const password = "password";
|
||||
const hashedPassword = "$2a$12$LZsLq.9nkZlU0YDPx2aLNelnwD/nyavqbewLN.5.Q5h/UxRD8Ymcy";
|
||||
|
||||
describe("hashPassword", () => {
|
||||
it("should hash a password", async () => {
|
||||
const hashedPassword = await hashPassword(password);
|
||||
|
||||
expect(typeof hashedPassword).toBe("string");
|
||||
expect(hashedPassword).not.toBe(password);
|
||||
expect(hashedPassword.length).toBe(60);
|
||||
});
|
||||
|
||||
it("should generate different hashes for the same password", async () => {
|
||||
const hash1 = await hashPassword(password);
|
||||
const hash2 = await hashPassword(password);
|
||||
|
||||
expect(hash1).not.toBe(hash2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verifyPassword", () => {
|
||||
it("should verify a correct password", async () => {
|
||||
const isValid = await verifyPassword(password, hashedPassword);
|
||||
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject an incorrect password", async () => {
|
||||
const isValid = await verifyPassword("WrongPassword123!", hashedPassword);
|
||||
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import posthog from "posthog-js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { captureFailedSignup, verifyTurnstileToken } from "./utils";
|
||||
|
||||
beforeEach(() => {
|
||||
global.fetch = vi.fn();
|
||||
vi.useFakeTimers();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
describe("verifyTurnstileToken", () => {
|
||||
const secretKey = "test-secret";
|
||||
const token = "test-token";
|
||||
|
||||
it("should return true when verification is successful", async () => {
|
||||
const mockResponse = { success: true };
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: true,
|
||||
json: vi.fn().mockResolvedValue(mockResponse),
|
||||
});
|
||||
|
||||
const result = await verifyTurnstileToken(secretKey, token);
|
||||
expect(result).toBe(true);
|
||||
expect(fetch).toHaveBeenCalledWith(
|
||||
"https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||
expect.objectContaining({
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ secret: secretKey, response: token }),
|
||||
signal: expect.any(AbortSignal),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it("should return false when response is not ok", async () => {
|
||||
(global.fetch as any).mockResolvedValue({
|
||||
ok: false,
|
||||
status: 400,
|
||||
});
|
||||
|
||||
const result = await verifyTurnstileToken(secretKey, token);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when verification fails", async () => {
|
||||
(global.fetch as any).mockRejectedValue(new Error("Network error"));
|
||||
|
||||
const result = await verifyTurnstileToken(secretKey, token);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("should return false when request times out", async () => {
|
||||
const mockAbortError = new Error("The operation was aborted");
|
||||
mockAbortError.name = "AbortError";
|
||||
(global.fetch as any).mockRejectedValue(mockAbortError);
|
||||
|
||||
const result = await verifyTurnstileToken(secretKey, token);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("captureFailedSignup", () => {
|
||||
it("should capture TELEMETRY_FAILED_SIGNUP event with email and name", () => {
|
||||
const captureSpy = vi.spyOn(posthog, "capture");
|
||||
const email = "test@example.com";
|
||||
const name = "Test User";
|
||||
|
||||
captureFailedSignup(email, name);
|
||||
|
||||
expect(captureSpy).toHaveBeenCalledWith("TELEMETRY_FAILED_SIGNUP", {
|
||||
email,
|
||||
name,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -56,6 +56,10 @@ export const verifyToken = async (token: string): Promise<JwtPayload> => {
|
||||
const decoded = jwt.decode(token);
|
||||
const payload: JwtPayload = decoded as JwtPayload;
|
||||
|
||||
if (!payload) {
|
||||
throw new Error("Token is invalid");
|
||||
}
|
||||
|
||||
const { id } = payload;
|
||||
if (!id) {
|
||||
throw new Error("Token missing required field: id");
|
||||
|
||||
Reference in New Issue
Block a user