Files
calendar/packages/features/auth/lib/next-auth-options.test.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

236 lines
6.5 KiB
TypeScript

import type { User } from "next-auth";
import { describe, expect, it, vi, beforeEach } from "vitest";
import { IdentityProvider, UserPermissionRole } from "@calcom/prisma/enums";
import { ErrorCode } from "./ErrorCode";
// Mock dependencies
vi.mock("@calcom/prisma", () => ({
prisma: {
user: {
update: vi.fn(),
},
},
default: {
user: {
update: vi.fn(),
},
},
}));
const mockFindByEmailAndIncludeProfilesAndPassword = vi.fn();
vi.mock("@calcom/features/users/repositories/UserRepository", () => {
return {
UserRepository: vi.fn().mockImplementation(function () {
return {
findByEmailAndIncludeProfilesAndPassword: mockFindByEmailAndIncludeProfilesAndPassword,
};
}),
};
});
vi.mock("./verifyPassword", () => ({
verifyPassword: vi.fn(),
}));
vi.mock("@calcom/lib/checkRateLimitAndThrowError", () => ({
checkRateLimitAndThrowError: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("@calcom/lib/server/PiiHasher", () => ({
hashEmail: vi.fn((email: string) => `hashed_${email}`),
}));
vi.mock("@calcom/lib/totp", () => ({
totpAuthenticatorCheck: vi.fn(),
}));
vi.mock("@calcom/lib/crypto", () => ({
symmetricDecrypt: vi.fn(),
symmetricEncrypt: vi.fn(),
}));
vi.mock("@calcom/lib/auth/isPasswordValid", () => ({
isPasswordValid: vi.fn(),
}));
vi.mock("@calcom/lib/env", () => ({
isENVDev: false,
}));
vi.mock("@calcom/lib/constants", async (importOriginal) => {
const actual = await importOriginal<typeof import("@calcom/lib/constants")>();
return {
...actual,
IS_TEAM_BILLING_ENABLED: false,
ENABLE_PROFILE_SWITCHER: false,
WEBAPP_URL: "http://localhost:3000",
};
});
vi.mock("@calcom/lib/logger", () => ({
default: {
getSubLogger: vi.fn(() => ({
debug: vi.fn(),
error: vi.fn(),
info: vi.fn(),
})),
},
}));
vi.mock("@calcom/lib/safeStringify", () => ({
safeStringify: vi.fn((obj) => JSON.stringify(obj)),
}));
vi.mock("./next-auth-custom-adapter", () => ({
default: vi.fn(() => ({
linkAccount: vi.fn(),
})),
}));
vi.mock("@calcom/features/profile/repositories/ProfileRepository", () => ({
ProfileRepository: {
findAllProfilesForUserIncludingMovedUser: vi.fn(),
findByUpIdWithAuth: vi.fn(),
},
}));
describe("CredentialsProvider authorize", () => {
let authorizeCredentials: typeof import("./next-auth-options").authorizeCredentials;
let verifyPassword: any;
beforeEach(async () => {
vi.clearAllMocks();
mockFindByEmailAndIncludeProfilesAndPassword.mockReset();
const verifyPasswordModule = await import("./verifyPassword");
verifyPassword = verifyPasswordModule.verifyPassword;
// Import the exported authorize function directly
const authModule = await import("./next-auth-options");
authorizeCredentials = authModule.authorizeCredentials;
});
const createMockUser = (overrides: Partial<any> = {}) => ({
id: 1,
email: "test@example.com",
name: "Test User",
username: "testuser",
role: UserPermissionRole.USER,
locked: false,
identityProvider: IdentityProvider.CAL,
twoFactorEnabled: false,
twoFactorSecret: null,
backupCodes: null,
password: {
hash: "$2a$10$hashedpassword",
},
allProfiles: [
{
id: 1,
upId: "usr_123",
username: "testuser",
},
],
teams: [],
...overrides,
});
describe("Password validation", () => {
it("should throw error when user has no password hash with CAL identity provider", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.CAL,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
it("should throw error when user has no password hash with Google identity provider", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.GOOGLE,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
it("should throw error when user has no password hash with SAML identity provider", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.SAML,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
it("should throw error when user has no password hash even with TOTP code provided", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.CAL,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
totpCode: "123456",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
it("should throw error when user has no password hash with Google identity provider and TOTP code", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.GOOGLE,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
totpCode: "123456",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
it("should throw error when user has no password hash with SAML identity provider and TOTP code", async () => {
const mockUser = createMockUser({
password: null,
identityProvider: IdentityProvider.SAML,
});
mockFindByEmailAndIncludeProfilesAndPassword.mockResolvedValue(mockUser);
await expect(
authorizeCredentials({
email: "test@example.com",
password: "password123",
totpCode: "123456",
} as any)
).rejects.toThrow(ErrorCode.IncorrectEmailPassword);
});
});
});