mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* fix: add missing vi.mock() calls to prevent vitest worker shutdown flakiness
Add vi.mock() calls for modules that trigger background network requests
or database connections during import. These transitive imports can cause
the vitest worker RPC to shut down while pending fetch/network operations
are still in flight, resulting in flaky test failures with:
Error: [vitest-worker]: Closing rpc while "fetch" was pending
The primary modules mocked are:
- @calcom/app-store/delegationCredential (triggers credential lookups)
- @calcom/prisma (triggers database initialization)
- @calcom/features/calendars/lib/CalendarManager (triggers calendar API calls)
- @calcom/features/auth/lib/verifyEmail (triggers email service)
- @calcom/lib/domainManager/organization (triggers domain lookups)
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix: remove conflicting empty prisma mocks from files with prismock/prismaMock setups
- Remove vi.mock('@calcom/prisma', () => ({ default: {}, prisma: {} })) from 28 files
that already have prismock/prismaMock test doubles. Vitest hoists all vi.mock() calls
and the last one wins, so these empty mocks were overriding the functional test doubles.
- Fix CalendarSubscriptionService.test.ts to reuse the shared mock from
__mocks__/delegationCredential instead of creating a new unconfigured vi.fn()
- Remove DelegationCredentialRepository.test.ts empty prisma mock (different pattern)
- Remove vi.mock from inside beforeEach in intentToCreateOrg.handler.test.ts
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix: add comprehensive delegationCredential mock exports to prevent CI test failures
The vi.mock blocks for @calcom/app-store/delegationCredential were missing
exports that the code under test transitively imports (e.g.
enrichUsersWithDelegationCredentials, enrichUserWithDelegationCredentialsIncludeServiceAccountKey,
buildAllCredentials, getFirstDelegationConferencingCredentialAppLocation).
Added all exports with passthrough implementations so the booking flow
works correctly without triggering real network requests.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix: correct credential mock return shapes to match real module API
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix: revert unintended yarn.lock changes
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
230 lines
7.4 KiB
TypeScript
230 lines
7.4 KiB
TypeScript
import prismaMock from "@calcom/testing/lib/__mocks__/prismaMock";
|
|
import type { Schedule, User } from "@calcom/prisma/client";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { ScheduleRepository } from "./ScheduleRepository";
|
|
|
|
vi.mock("@calcom/lib/hasEditPermissionForUser", () => ({
|
|
hasReadPermissionsForUserId: vi.fn(),
|
|
}));
|
|
import { hasReadPermissionsForUserId } from "@calcom/lib/hasEditPermissionForUser";
|
|
|
|
const mockHasReadPermissions = vi.mocked(hasReadPermissionsForUserId);
|
|
|
|
describe("ScheduleRepository", () => {
|
|
let scheduleRepository: ScheduleRepository;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
scheduleRepository = new ScheduleRepository(prismaMock);
|
|
});
|
|
|
|
describe("constructor", () => {
|
|
it("should throw error if prismaClient is not provided", () => {
|
|
// @ts-expect-error - testing invalid input
|
|
expect(() => new ScheduleRepository(null)).toThrow("PrismaClient is required for ScheduleRepository");
|
|
// @ts-expect-error - testing invalid input
|
|
expect(() => new ScheduleRepository(undefined)).toThrow(
|
|
"PrismaClient is required for ScheduleRepository"
|
|
);
|
|
});
|
|
|
|
it("should create instance successfully with valid prismaClient", () => {
|
|
const repo = new ScheduleRepository(prismaMock);
|
|
expect(repo).toBeInstanceOf(ScheduleRepository);
|
|
});
|
|
});
|
|
|
|
describe("getDefaultScheduleId", () => {
|
|
it("should return defaultScheduleId if user has one", async () => {
|
|
const userId = 1;
|
|
const defaultScheduleId = 123;
|
|
|
|
prismaMock.user.findUnique.mockResolvedValue({ defaultScheduleId } as User);
|
|
|
|
const result = await scheduleRepository.getDefaultScheduleId(userId);
|
|
|
|
expect(prismaMock.user.findUnique).toHaveBeenCalledWith({
|
|
where: { id: userId },
|
|
select: { defaultScheduleId: true },
|
|
});
|
|
expect(result).toBe(defaultScheduleId);
|
|
});
|
|
|
|
it("should find and return first schedule if user has no defaultScheduleId", async () => {
|
|
const userId = 1;
|
|
const scheduleId = 456;
|
|
|
|
prismaMock.user.findUnique.mockResolvedValue({ defaultScheduleId: null } as User);
|
|
prismaMock.schedule.findFirst.mockResolvedValue({ id: scheduleId } as Schedule);
|
|
|
|
const result = await scheduleRepository.getDefaultScheduleId(userId);
|
|
|
|
expect(prismaMock.schedule.findFirst).toHaveBeenCalledWith({
|
|
where: { userId },
|
|
select: { id: true },
|
|
});
|
|
expect(result).toBe(scheduleId);
|
|
});
|
|
|
|
it("should throw error if no schedules found", async () => {
|
|
const userId = 1;
|
|
|
|
prismaMock.user.findUnique.mockResolvedValue({ defaultScheduleId: null } as User);
|
|
prismaMock.schedule.findFirst.mockResolvedValue(null);
|
|
|
|
await expect(scheduleRepository.getDefaultScheduleId(userId)).rejects.toThrow(
|
|
"No schedules found for user"
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("hasDefaultSchedule", () => {
|
|
it("should return true if user has defaultScheduleId", async () => {
|
|
const user = { id: 1, defaultScheduleId: 123 };
|
|
|
|
const result = await scheduleRepository.hasDefaultSchedule(user);
|
|
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should return true if user has a schedule", async () => {
|
|
const user = { id: 1, defaultScheduleId: null };
|
|
|
|
prismaMock.schedule.findFirst.mockResolvedValue({ id: 456 } as Schedule);
|
|
|
|
const result = await scheduleRepository.hasDefaultSchedule(user);
|
|
|
|
expect(prismaMock.schedule.findFirst).toHaveBeenCalledWith({
|
|
where: { userId: user.id },
|
|
});
|
|
expect(result).toBe(true);
|
|
});
|
|
|
|
it("should return false if user has no defaultScheduleId and no schedules", async () => {
|
|
const user = { id: 1, defaultScheduleId: null };
|
|
|
|
prismaMock.schedule.findFirst.mockResolvedValue(null);
|
|
|
|
const result = await scheduleRepository.hasDefaultSchedule(user);
|
|
|
|
expect(result).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("setupDefaultSchedule", () => {
|
|
it("should update user with new defaultScheduleId", async () => {
|
|
const userId = 1;
|
|
const scheduleId = 123;
|
|
const updatedUser = { id: userId, defaultScheduleId: scheduleId } as User;
|
|
|
|
prismaMock.user.update.mockResolvedValue(updatedUser);
|
|
|
|
const result = await scheduleRepository.setupDefaultSchedule(userId, scheduleId);
|
|
|
|
expect(prismaMock.user.update).toHaveBeenCalledWith({
|
|
where: { id: userId },
|
|
data: { defaultScheduleId: scheduleId },
|
|
});
|
|
expect(result).toEqual(updatedUser);
|
|
});
|
|
});
|
|
|
|
describe("findDetailedScheduleById", () => {
|
|
const createMockSchedule = (overrides: Partial<Schedule> = {}): Partial<Schedule> => ({
|
|
id: 100,
|
|
userId: 2,
|
|
name: "Working Hours",
|
|
availability: [],
|
|
timeZone: "America/New_York",
|
|
...overrides,
|
|
});
|
|
|
|
beforeEach(() => {
|
|
prismaMock.schedule.count.mockResolvedValue(1);
|
|
});
|
|
|
|
it("should allow access when user is the schedule owner", async () => {
|
|
const ownerId = 2;
|
|
const mockSchedule = createMockSchedule({ userId: ownerId });
|
|
|
|
prismaMock.schedule.findUnique.mockResolvedValue(mockSchedule as Schedule);
|
|
mockHasReadPermissions.mockResolvedValue(false);
|
|
|
|
const result = await scheduleRepository.findDetailedScheduleById({
|
|
scheduleId: 100,
|
|
userId: ownerId,
|
|
timeZone: "UTC",
|
|
defaultScheduleId: 100,
|
|
});
|
|
|
|
expect(result).toMatchObject({
|
|
id: 100,
|
|
name: "Working Hours",
|
|
userId: ownerId,
|
|
});
|
|
});
|
|
|
|
it("should allow access when user is part of the same team", async () => {
|
|
const scheduleOwnerId = 2;
|
|
const teamMemberId = 3;
|
|
const mockSchedule = createMockSchedule({ userId: scheduleOwnerId });
|
|
|
|
prismaMock.schedule.findUnique.mockResolvedValue(mockSchedule as Schedule);
|
|
mockHasReadPermissions.mockResolvedValue(true);
|
|
|
|
const result = await scheduleRepository.findDetailedScheduleById({
|
|
scheduleId: 100,
|
|
userId: teamMemberId,
|
|
timeZone: "UTC",
|
|
defaultScheduleId: null,
|
|
});
|
|
|
|
expect(mockHasReadPermissions).toHaveBeenCalledWith({
|
|
memberId: scheduleOwnerId,
|
|
userId: teamMemberId,
|
|
});
|
|
expect(result).toMatchObject({
|
|
id: 100,
|
|
name: "Working Hours",
|
|
userId: scheduleOwnerId,
|
|
});
|
|
});
|
|
|
|
it("should deny access when user is not owner and not part of team", async () => {
|
|
const scheduleOwnerId = 2;
|
|
const unauthorizedUserId = 999;
|
|
const mockSchedule = createMockSchedule({ userId: scheduleOwnerId });
|
|
|
|
prismaMock.schedule.findUnique.mockResolvedValue(mockSchedule as Schedule);
|
|
mockHasReadPermissions.mockResolvedValue(false);
|
|
|
|
await expect(
|
|
scheduleRepository.findDetailedScheduleById({
|
|
scheduleId: 100,
|
|
userId: unauthorizedUserId,
|
|
timeZone: "UTC",
|
|
defaultScheduleId: null,
|
|
})
|
|
).rejects.toThrow("UNAUTHORIZED");
|
|
|
|
expect(mockHasReadPermissions).toHaveBeenCalledWith({
|
|
memberId: scheduleOwnerId,
|
|
userId: unauthorizedUserId,
|
|
});
|
|
});
|
|
|
|
it("should throw error when schedule is not found", async () => {
|
|
prismaMock.schedule.findUnique.mockResolvedValue(null);
|
|
|
|
await expect(
|
|
scheduleRepository.findDetailedScheduleById({
|
|
scheduleId: 999,
|
|
userId: 1,
|
|
timeZone: "UTC",
|
|
defaultScheduleId: null,
|
|
})
|
|
).rejects.toThrow("Schedule not found");
|
|
});
|
|
});
|
|
});
|