mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* fix: remove @calcom/web imports from packages/features to eliminate circular dependency - Migrate UserTableUser and MemberPermissions types to packages/features/users/types/user-table.ts - Migrate useGeo hook to packages/features/geo/GeoContext.tsx - Migrate buildLegacyRequest to packages/lib/buildLegacyCtx.ts - Migrate Calendar component to packages/features/calendars/weeklyview/components/ - Move test utilities (bookingScenario, fixtures) to packages/features/test/ - Update all imports in packages/features to use new locations - Add re-exports in apps/web for backward compatibility Co-Authored-By: [email protected] <[email protected]> * fix: delete original implementation files and fix type issues - Delete original calendar component files in apps/web (keep only re-export stubs) - Migrate OutOfOfficeInSlots to packages/features/bookings/components - Convert apps/web OutOfOfficeInSlots to re-export stub - Fix className vs class issue in Calendar.tsx - Fix @calcom/trpc import violation in user-table.ts by using structural type Co-Authored-By: [email protected] <[email protected]> * fix: add missing isGroup and contains fields to UserTableUser attributes type Co-Authored-By: [email protected] <[email protected]> * fix: update customRole type to match actual Prisma Role model Co-Authored-By: [email protected] <[email protected]> * fix build * fix * fix * cleanup weeklyview * fix * refactor to mv MemberPermissions to types package * add types dependency to features * fix * fix * fix * fix * fix * fix * rename * rename * migrate * migrate * migrate * fix * fix * fix * refactor: move test utilities from packages/features/test to tests/libs - Move bookingScenario utilities to tests/libs/bookingScenario - Move fixtures to tests/libs/fixtures - Update all imports in packages/features test files to use new location - Update all imports in apps/web test files to use new location - Eliminates duplication of test utilities between packages/features and apps/web Co-Authored-By: [email protected] <[email protected]> * fix: correct relative import paths for tests/libs in test files Co-Authored-By: [email protected] <[email protected]> * refactor: replace test utility implementations with re-exports to tests/libs Co-Authored-By: [email protected] <[email protected]> * fix: fix test import paths and move signup handler tests to apps/web Co-Authored-By: [email protected] <[email protected]> * fix: move buildLegacyCtx to packages/lib and restore handlers to packages/features - Move buildLegacyCtx from apps/web/lib to packages/lib to break circular dependency - Update apps/web/lib/buildLegacyCtx.ts to re-export from @calcom/lib - Restore signup handlers and tests to packages/features/auth/signup/handlers - Update handler imports to use @calcom/lib/buildLegacyCtx instead of @calcom/web/lib/buildLegacyCtx Co-Authored-By: [email protected] <[email protected]> * fix: update recurring-event.test.ts imports to use tests/libs path Co-Authored-By: [email protected] <[email protected]> * refactor: delete test re-export files and update imports to use tests/libs directly Co-Authored-By: [email protected] <[email protected]> * fix: update remaining test imports to use tests/libs directly Co-Authored-By: [email protected] <[email protected]> * fix: update handleRecurringEventBooking calls to match function signature (1 arg) Co-Authored-By: [email protected] <[email protected]> * remove * migrate tests * migrate tests * refactor: update test mock imports by removing and using async for mock creators. * fix type errors * fix: add type assertion for MockUser in p2002.test-suite.ts Co-Authored-By: [email protected] <[email protected]> * fix: restore locale import in compareReminderBodyToTemplate.test.ts using relative path Co-Authored-By: [email protected] <[email protected]> * feat: create @calcom/testing package and migrate tests from /tests directory - Created new @calcom/testing package in /packages/testing - Moved all files from /tests to /packages/testing - Updated all imports across the codebase to use @calcom/testing alias - Removed /tests directory at root level This allows other packages like @calcom/features and @calcom/web to import testing utilities using the @calcom/testing alias instead of relative paths. Co-Authored-By: [email protected] <[email protected]> * fix * fix * fix: add missing useBookings export to @calcom/atoms package Co-Authored-By: [email protected] <[email protected]> * fix: add missing useCalendarsBusyTimes and useConnectedCalendars exports to @calcom/atoms Co-Authored-By: [email protected] <[email protected]> * chore: add @calcom/testing as explicit devDependency to packages that use it Co-Authored-By: [email protected] <[email protected]> * refactor: move setupVitest.ts into @calcom/testing package Co-Authored-By: [email protected] <[email protected]> * fix * chore: add biome rules to restrict @calcom/testing imports Co-Authored-By: [email protected] <[email protected]> * fix * rename libs to lib * rename libs to lib * add rule * add rule * refactor: remove @calcom/features imports from @calcom/testing - Move mockPaymentSuccessWebhookFromStripe to fresh-booking.test.ts - Replace ProfileRepository.generateProfileUid() with uuidv4() - Clone Tracking type into @calcom/testing/src/lib/types.ts - Update imports in expects.ts and getMockRequestDataForBooking.ts - Move source files into src/ folder - Move CalendarManager mock to @calcom/features Co-Authored-By: [email protected] <[email protected]> * fix: add explicit exports for nested paths in @calcom/testing Co-Authored-By: [email protected] <[email protected]> * improve * improve * fix * fix * fix type checks * fix type checks * fix type checks * fix tests --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
412 lines
14 KiB
TypeScript
412 lines
14 KiB
TypeScript
/**
|
|
* These tests are integration tests that test the flow from receiving a api/book/event request and then verifying
|
|
* - database entries created in In-MEMORY DB using prismock
|
|
* - emails sent by checking the testEmails global variable
|
|
* - webhooks fired by mocking fetch
|
|
* - APIs of various apps called by mocking those apps' modules
|
|
*
|
|
* They don't intend to test what the apps logic should do, but rather test if the apps are called with the correct data. For testing that, once should write tests within each app.
|
|
*/
|
|
import {
|
|
createBookingScenario,
|
|
getDate,
|
|
getGoogleCalendarCredential,
|
|
TestData,
|
|
getOrganizer,
|
|
getBooker,
|
|
getScenarioData,
|
|
mockCalendar,
|
|
} from "@calcom/testing/lib/bookingScenario/bookingScenario";
|
|
import { expectBookingToBeInDatabase } from "@calcom/testing/lib/bookingScenario/expects";
|
|
import { getMockRequestDataForDynamicGroupBooking } from "@calcom/testing/lib/bookingScenario/getMockRequestDataForBooking";
|
|
import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTeardown";
|
|
|
|
import type { Request, Response } from "express";
|
|
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { ErrorCode } from "@calcom/lib/errorCodes";
|
|
import { BookingStatus } from "@calcom/prisma/enums";
|
|
import { test } from "@calcom/testing/lib/fixtures/fixtures";
|
|
|
|
import { getNewBookingHandler } from "./getNewBookingHandler";
|
|
|
|
export type CustomNextApiRequest = NextApiRequest & Request;
|
|
|
|
export type CustomNextApiResponse = NextApiResponse & Response;
|
|
// Local test runs sometime gets too slow
|
|
const timeout = process.env.CI ? 5000 : 20000;
|
|
|
|
describe("handleNewBooking", () => {
|
|
setupAndTeardown();
|
|
describe("Dynamic Group Booking", () => {
|
|
test(
|
|
`should allow a booking if there is no conflicting booking in any of the users' selectedCalendars`,
|
|
async () => {
|
|
const handleNewBooking = getNewBookingHandler();
|
|
const groupUserId1 = 101;
|
|
const groupUserId2 = 102;
|
|
const booker = getBooker({
|
|
email: "[email protected]",
|
|
name: "Booker",
|
|
});
|
|
|
|
const groupUser1 = getOrganizer({
|
|
name: "group-user-1",
|
|
username: "group-user-1",
|
|
email: "[email protected]",
|
|
id: groupUserId1,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
const groupUser2 = getOrganizer({
|
|
name: "group-user-2",
|
|
username: "group-user-2",
|
|
email: "[email protected]",
|
|
id: groupUserId2,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [],
|
|
webhooks: [
|
|
{
|
|
userId: groupUserId1,
|
|
eventTriggers: ["BOOKING_CREATED"],
|
|
subscriberUrl: "http://my-webhook.example.com",
|
|
active: true,
|
|
eventTypeId: 1,
|
|
appId: null,
|
|
},
|
|
],
|
|
users: [groupUser1, groupUser2],
|
|
apps: [TestData.apps["google-calendar"]],
|
|
})
|
|
);
|
|
|
|
const _calendarMock = mockCalendar("googlecalendar", {
|
|
create: {
|
|
uid: "MOCK_ID",
|
|
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
|
},
|
|
busySlots: [
|
|
{
|
|
start: `${plus1DateString}T05:00:00.000Z`,
|
|
end: `${plus1DateString}T05:15:00.000Z`,
|
|
},
|
|
],
|
|
});
|
|
|
|
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
|
|
data: {
|
|
start: `${getDate({ dateIncrement: 1 }).dateString}T05:00:00.000Z`,
|
|
end: `${getDate({ dateIncrement: 1 }).dateString}T05:30:00.000Z`,
|
|
eventTypeId: 0,
|
|
eventTypeSlug: "group-user-1+group-user-2",
|
|
user: "group-user-1+group-user-2",
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: "New York" },
|
|
},
|
|
},
|
|
});
|
|
|
|
const createdBooking = await handleNewBooking({
|
|
bookingData: mockBookingData,
|
|
});
|
|
|
|
await expectBookingToBeInDatabase({
|
|
description: "",
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
uid: createdBooking.uid!,
|
|
eventTypeId: null,
|
|
status: BookingStatus.ACCEPTED,
|
|
iCalUID: createdBooking.iCalUID,
|
|
});
|
|
},
|
|
timeout
|
|
);
|
|
|
|
test(
|
|
`should correctly assign the first user in the URL as the organizer`,
|
|
async () => {
|
|
const handleNewBooking = getNewBookingHandler();
|
|
const booker = getBooker({
|
|
email: "[email protected]",
|
|
name: "Booker",
|
|
});
|
|
|
|
// Create two users where alphabetically the second would come first
|
|
const userZebra = getOrganizer({
|
|
name: "Zebra User",
|
|
username: "zebra",
|
|
email: "[email protected]",
|
|
id: 201,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
const userAlpha = getOrganizer({
|
|
name: "Alpha User",
|
|
username: "alpha",
|
|
email: "[email protected]",
|
|
id: 202,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [],
|
|
users: [userZebra, userAlpha],
|
|
})
|
|
);
|
|
|
|
// Book with zebra first in the URL - zebra should be the organizer
|
|
const mockBookingDataZebraFirst = getMockRequestDataForDynamicGroupBooking({
|
|
data: {
|
|
start: `${getDate({ dateIncrement: 1 }).dateString}T05:00:00.000Z`,
|
|
end: `${getDate({ dateIncrement: 1 }).dateString}T05:30:00.000Z`,
|
|
eventTypeId: 0,
|
|
eventTypeSlug: "zebra+alpha",
|
|
user: "zebra+alpha",
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: "New York" },
|
|
},
|
|
},
|
|
});
|
|
|
|
const bookingZebraFirst = await handleNewBooking({
|
|
bookingData: mockBookingDataZebraFirst,
|
|
});
|
|
|
|
await expectBookingToBeInDatabase({
|
|
description: "",
|
|
uid: bookingZebraFirst.uid!,
|
|
eventTypeId: null,
|
|
status: BookingStatus.ACCEPTED,
|
|
iCalUID: bookingZebraFirst.iCalUID,
|
|
});
|
|
|
|
// Verify zebra is the organizer (the booking.userId should be zebra's id)
|
|
expect(bookingZebraFirst.userId).toBe(userZebra.id);
|
|
|
|
// Book with alpha first in the URL - alpha should be the organizer
|
|
const mockBookingDataAlphaFirst = getMockRequestDataForDynamicGroupBooking({
|
|
data: {
|
|
start: `${getDate({ dateIncrement: 1 }).dateString}T06:00:00.000Z`,
|
|
end: `${getDate({ dateIncrement: 1 }).dateString}T06:30:00.000Z`,
|
|
eventTypeId: 0,
|
|
eventTypeSlug: "alpha+zebra",
|
|
user: "alpha+zebra",
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: "New York" },
|
|
},
|
|
},
|
|
});
|
|
|
|
const bookingAlphaFirst = await handleNewBooking({
|
|
bookingData: mockBookingDataAlphaFirst,
|
|
});
|
|
|
|
await expectBookingToBeInDatabase({
|
|
description: "",
|
|
uid: bookingAlphaFirst.uid!,
|
|
eventTypeId: null,
|
|
status: BookingStatus.ACCEPTED,
|
|
iCalUID: bookingAlphaFirst.iCalUID,
|
|
});
|
|
|
|
// Verify alpha is the organizer (the booking.userId should be alpha's id)
|
|
expect(bookingAlphaFirst.userId).toBe(userAlpha.id);
|
|
},
|
|
timeout
|
|
);
|
|
|
|
describe("Availability Check During Booking", () => {
|
|
test(
|
|
`should fail a booking if there is already a conflicting booking in the first user's selectedCalendars`,
|
|
async () => {
|
|
const handleNewBooking = getNewBookingHandler();
|
|
const groupUserId1 = 101;
|
|
const groupUserId2 = 102;
|
|
const booker = getBooker({
|
|
email: "[email protected]",
|
|
name: "Booker",
|
|
});
|
|
|
|
const groupUser1 = getOrganizer({
|
|
name: "group-user-1",
|
|
username: "group-user-1",
|
|
email: "[email protected]",
|
|
id: groupUserId1,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [getGoogleCalendarCredential()],
|
|
selectedCalendars: [TestData.selectedCalendars.google],
|
|
});
|
|
|
|
const groupUser2 = getOrganizer({
|
|
name: "group-user-2",
|
|
username: "group-user-2",
|
|
email: "[email protected]",
|
|
id: groupUserId2,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
// Second user has no selected calendars and credentials
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [],
|
|
users: [groupUser1, groupUser2],
|
|
apps: [TestData.apps["google-calendar"]],
|
|
})
|
|
);
|
|
|
|
const _calendarMock = mockCalendar("googlecalendar", {
|
|
create: {
|
|
uid: "MOCK_ID",
|
|
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
|
},
|
|
busySlots: [
|
|
{
|
|
start: `${plus1DateString}T05:00:00.000Z`,
|
|
end: `${plus1DateString}T05:15:00.000Z`,
|
|
},
|
|
],
|
|
});
|
|
|
|
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
|
|
data: {
|
|
start: `${getDate({ dateIncrement: 1 }).dateString}T05:00:00.000Z`,
|
|
end: `${getDate({ dateIncrement: 1 }).dateString}T05:30:00.000Z`,
|
|
eventTypeId: 0,
|
|
eventTypeSlug: "group-user-1+group-user-2",
|
|
user: "group-user-1+group-user-2",
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: "New York" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(
|
|
async () =>
|
|
await handleNewBooking({
|
|
bookingData: mockBookingData,
|
|
})
|
|
).rejects.toThrowError(ErrorCode.FixedHostsUnavailableForBooking);
|
|
},
|
|
timeout
|
|
);
|
|
|
|
test(
|
|
`should fail a booking if there is already a conflicting booking in the second user's selectedCalendars`,
|
|
async () => {
|
|
const handleNewBooking = getNewBookingHandler();
|
|
const groupUserId1 = 101;
|
|
const groupUserId2 = 102;
|
|
const booker = getBooker({
|
|
email: "[email protected]",
|
|
name: "Booker",
|
|
});
|
|
|
|
const groupUser1 = getOrganizer({
|
|
name: "group-user-1",
|
|
username: "group-user-1",
|
|
email: "[email protected]",
|
|
id: groupUserId1,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [],
|
|
selectedCalendars: [],
|
|
});
|
|
|
|
const groupUser2 = getOrganizer({
|
|
name: "group-user-2",
|
|
username: "group-user-2",
|
|
email: "[email protected]",
|
|
id: groupUserId2,
|
|
schedules: [TestData.schedules.IstWorkHours],
|
|
credentials: [getGoogleCalendarCredential()],
|
|
selectedCalendars: [TestData.selectedCalendars.google],
|
|
});
|
|
|
|
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
|
|
|
await createBookingScenario(
|
|
getScenarioData({
|
|
eventTypes: [],
|
|
webhooks: [
|
|
{
|
|
userId: groupUserId1,
|
|
eventTriggers: ["BOOKING_CREATED"],
|
|
subscriberUrl: "http://my-webhook.example.com",
|
|
active: true,
|
|
eventTypeId: 1,
|
|
appId: null,
|
|
},
|
|
],
|
|
users: [groupUser1, groupUser2],
|
|
apps: [TestData.apps["google-calendar"]],
|
|
})
|
|
);
|
|
|
|
const _calendarMock = mockCalendar("googlecalendar", {
|
|
create: {
|
|
uid: "MOCK_ID",
|
|
iCalUID: "MOCKED_GOOGLE_CALENDAR_ICS_ID",
|
|
},
|
|
busySlots: [
|
|
{
|
|
start: `${plus1DateString}T05:00:00.000Z`,
|
|
end: `${plus1DateString}T05:15:00.000Z`,
|
|
},
|
|
],
|
|
});
|
|
|
|
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
|
|
data: {
|
|
start: `${getDate({ dateIncrement: 1 }).dateString}T05:00:00.000Z`,
|
|
end: `${getDate({ dateIncrement: 1 }).dateString}T05:30:00.000Z`,
|
|
eventTypeId: 0,
|
|
eventTypeSlug: "group-user-1+group-user-2",
|
|
user: "group-user-1+group-user-2",
|
|
responses: {
|
|
email: booker.email,
|
|
name: booker.name,
|
|
location: { optionValue: "", value: "New York" },
|
|
},
|
|
},
|
|
});
|
|
|
|
await expect(
|
|
async () =>
|
|
await handleNewBooking({
|
|
bookingData: mockBookingData,
|
|
})
|
|
).rejects.toThrowError(ErrorCode.FixedHostsUnavailableForBooking);
|
|
},
|
|
timeout
|
|
);
|
|
});
|
|
});
|
|
});
|