Files
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f5d345b133 refactor: remove @calcom/web imports from @calcom/features and add @calcom/testing package (#26480)
* 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>
2026-01-08 16:59:11 +09:00

1169 lines
36 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,
TestData,
getDate,
getOrganizer,
getBooker,
getScenarioData,
mockSuccessfulVideoMeetingCreation,
mockCalendarToHaveNoBusySlots,
BookingLocations,
createDelegationCredential,
createOrganization,
buildDelegationCredential,
} from "@calcom/testing/lib/bookingScenario/bookingScenario";
import {
expectWorkflowToBeTriggered,
expectSuccessfulBookingCreationEmails,
expectBookingToBeInDatabase,
expectBookingCreatedWebhookToHaveBeenFired,
expectSuccessfulCalendarEventCreationInCalendar,
expectICalUIDAsString,
expectBookingToNotHaveReference,
expectNoAttemptToCreateCalendarEvent,
} from "@calcom/testing/lib/bookingScenario/expects";
import {
getMockRequestDataForBooking,
getMockRequestDataForDynamicGroupBooking,
} from "@calcom/testing/lib/bookingScenario/getMockRequestDataForBooking";
import { setupAndTeardown } from "@calcom/testing/lib/bookingScenario/setupAndTeardown";
import { vi } from "vitest";
import { describe, expect } from "vitest";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { BookingStatus } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
import { test } from "@calcom/testing/lib/fixtures/fixtures";
import { getNewBookingHandler } from "./getNewBookingHandler";
vi.mock("@calcom/app-store/calendar.services.generated", () => {
class MockGoogleCalendarService {
credential: any;
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent(calEvent: any, credentialId: any, externalCalendarId?: string) {
return {
type: "google_calendar",
additionalInfo: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
uid: "GOOGLE_CALENDAR_EVENT_ID",
id: "GOOGLE_CALENDAR_EVENT_ID",
iCalUID: calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
createdEvent: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
class MockOffice365CalendarService {
credential: any;
constructor(credential: any) {
this.credential = credential;
}
getCredentialId() {
return this.credential.id;
}
async createEvent(calEvent: any, credentialId: any, externalCalendarId?: string) {
return {
type: "office365_calendar",
additionalInfo: {},
uid: "OFFICE_365_CALENDAR_EVENT_ID",
id: "OFFICE_365_CALENDAR_EVENT_ID",
iCalUID: calEvent.iCalUID || "OFFICE_365_CALENDAR_EVENT_ID",
password: "MOCK_PASSWORD",
url: "https://UNUSED_URL",
};
}
async updateEvent() {
return {};
}
async deleteEvent() {
return {};
}
async getAvailability() {
return [];
}
async getAvailabilityWithTimeZones() {
return [];
}
async listCalendars() {
return [];
}
}
return {
CalendarServiceMap: {
googlecalendar: Promise.resolve({ default: MockGoogleCalendarService }),
office365calendar: Promise.resolve({ default: MockOffice365CalendarService }),
},
};
});
// Local test runs sometime gets too slow
const timeout = process.env.CI ? 5000 : 20000;
describe("handleNewBooking", () => {
setupAndTeardown();
describe("Delegation Credential", () => {
test(
`should create a successful booking using the delegation credential when User's destination calendar is set toGoogle Calendar
1. Should create a booking in the database with reference having Delegation credential
2. Should create an event in calendar with Delegation credential
3. Should use Google Meet as the location even when not explicitly set.
`,
async ({ emails }) => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
// User must be part of organization to be able to use that organization's Delegation credential
teams: payloadToMakePartOfOrganization,
// No regular credentials provided
credentials: [],
destinationCalendar: TestData.selectedCalendars.google,
});
const delegationCredential = await createDelegationCredential(org.id);
const payloadToCreateUserEventTypeForOrganizer = {
id: 1,
slotInterval: 30,
length: 30,
users: [
{
id: organizer.id,
},
],
};
await createBookingScenario(
getScenarioData({
webhooks: [
{
userId: organizer.id,
eventTriggers: ["BOOKING_CREATED"],
subscriberUrl: "http://my-webhook.example.com",
active: true,
eventTypeId: 1,
appId: null,
},
],
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeOn: [1],
},
],
eventTypes: [payloadToCreateUserEventTypeForOrganizer],
organizer,
apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]],
})
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
googleCalendar: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
},
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.GoogleMeet },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expect(createdBooking.responses).toEqual(
expect.objectContaining({
email: booker.email,
name: booker.name,
})
);
expect(createdBooking).toEqual(
expect.objectContaining({
location: BookingLocations.GoogleMeet,
})
);
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: mockBookingData.eventTypeId,
status: BookingStatus.ACCEPTED,
references: [
{
type: appStoreMetadata.googlecalendar.type,
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
// Verify Delegation credential was used
delegationCredentialId: delegationCredential.id,
},
],
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
credential: buildDelegationCredential({
serviceAccountKey: delegationCredential.serviceAccountKey,
}),
calendarId: TestData.selectedCalendars.google.externalId,
// There would be no videoCallUrl in this case as it is not a dedicated conferencing case and hangoutLink is used instead
videoCallUrl: null,
});
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
expectSuccessfulBookingCreationEmails({
booking: {
uid: createdBooking.uid!,
},
booker,
organizer,
emails,
iCalUID,
});
expectBookingCreatedWebhookToHaveBeenFired({
booker,
organizer,
location: BookingLocations.GoogleMeet,
subscriberUrl: "http://my-webhook.example.com",
videoCallUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
});
},
timeout
);
test(
`should create a successful booking using the delegation credential event when User/EventType's destination calendar is not set
1. Should create a booking in the database with reference having Delegation credential
2. Should create an event in calendar with Delegation credential
3. Should use Google Meet as the location even when not explicitly set and no destination calendar is set for User/EventType
`,
async ({ emails }) => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
// User must be part of organization to be able to use that organization's Delegation credential
teams: payloadToMakePartOfOrganization,
// No regular credentials provided
credentials: [],
});
const delegationCredential = await createDelegationCredential(org.id);
const payloadToCreateUserEventTypeForOrganizer = {
id: 1,
slotInterval: 30,
length: 30,
users: [
{
id: organizer.id,
},
],
};
await createBookingScenario(
getScenarioData({
webhooks: [
{
userId: organizer.id,
eventTriggers: ["BOOKING_CREATED"],
subscriberUrl: "http://my-webhook.example.com",
active: true,
eventTypeId: 1,
appId: null,
},
],
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeOn: [1],
},
],
eventTypes: [payloadToCreateUserEventTypeForOrganizer],
organizer,
apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]],
})
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
googleCalendar: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
},
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.GoogleMeet },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expect(createdBooking.responses).toEqual(
expect.objectContaining({
email: booker.email,
name: booker.name,
})
);
expect(createdBooking).toEqual(
expect.objectContaining({
location: BookingLocations.GoogleMeet,
})
);
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: mockBookingData.eventTypeId,
status: BookingStatus.ACCEPTED,
references: [
{
type: appStoreMetadata.googlecalendar.type,
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
// Verify Delegation credential was used
delegationCredentialId: delegationCredential.id,
},
],
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
credential: buildDelegationCredential({
serviceAccountKey: delegationCredential.serviceAccountKey,
}),
calendarIdUsingFallbackOfFirstCalendarCredential: TestData.selectedCalendars.google.externalId,
// There would be no videoCallUrl in this case as it is not a dedicated conferencing case and hangoutLink is used instead
videoCallUrl: null,
});
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
expectSuccessfulBookingCreationEmails({
booking: {
uid: createdBooking.uid!,
},
booker,
organizer,
emails,
iCalUID,
});
expectBookingCreatedWebhookToHaveBeenFired({
booker,
organizer,
location: BookingLocations.GoogleMeet,
subscriberUrl: "http://my-webhook.example.com",
videoCallUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
});
},
timeout
);
test(
`should create a successful booking using the office365 delegation credential with Cal Video as the location
1. Should create a booking in the database with reference having Delegation credential
2. Should create an event in Outlook calendar with Delegation credential
`,
async ({ emails }) => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.office365],
// User must be part of organization to be able to use that organization's Delegation credential
teams: payloadToMakePartOfOrganization,
// No regular credentials provided
credentials: [],
destinationCalendar: TestData.selectedCalendars.office365,
});
const delegationCredential = await createDelegationCredential(org.id, "office365");
const payloadToCreateUserEventTypeForOrganizer = {
id: 1,
slotInterval: 30,
length: 30,
users: [
{
id: organizer.id,
},
],
};
await createBookingScenario(
getScenarioData({
webhooks: [
{
userId: organizer.id,
eventTriggers: ["BOOKING_CREATED"],
subscriberUrl: "http://my-webhook.example.com",
active: true,
eventTypeId: 1,
appId: null,
},
],
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeOn: [1],
},
],
eventTypes: [payloadToCreateUserEventTypeForOrganizer],
organizer,
apps: [TestData.apps["daily-video"], TestData.apps["office365-calendar"]],
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
videoMeetingData: {
id: "MOCK_ID",
password: "MOCK_PASS",
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = await mockCalendarToHaveNoBusySlots("office365calendar", {
create: {
id: "OFFICE_365_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
office365Calendar: {
url: "http://mock-dailyvideo.example.com/meeting-1",
},
},
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expect(createdBooking.responses).toEqual(
expect.objectContaining({
email: booker.email,
name: booker.name,
})
);
expect(createdBooking).toEqual(
expect.objectContaining({
location: BookingLocations.CalVideo,
})
);
console.log("createdBooking", createdBooking);
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: mockBookingData.eventTypeId,
status: BookingStatus.ACCEPTED,
references: [
{
type: appStoreMetadata.dailyvideo.type,
uid: "MOCK_ID",
meetingId: "MOCK_ID",
meetingPassword: "MOCK_PASS",
meetingUrl: "http://mock-dailyvideo.example.com/meeting-1",
},
{
type: appStoreMetadata.office365calendar.type,
uid: "OFFICE_365_CALENDAR_EVENT_ID",
meetingId: "OFFICE_365_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
// Verify Delegation credential was used
delegationCredentialId: delegationCredential.id,
},
],
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ emailsToReceive: [organizer.email], emails });
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
credential: buildDelegationCredential({
serviceAccountKey: delegationCredential.serviceAccountKey,
}),
calendarId: TestData.selectedCalendars.office365.externalId,
// There would be no videoCallUrl in this case as it is not a dedicated conferencing case and hangoutLink is used instead
videoCallUrl: null,
});
const iCalUID = expectICalUIDAsString(createdBooking.iCalUID);
expectSuccessfulBookingCreationEmails({
booking: {
uid: createdBooking.uid!,
},
booker,
organizer,
emails,
iCalUID,
calendarType: "office365_calendar",
});
expectBookingCreatedWebhookToHaveBeenFired({
booker,
organizer,
location: BookingLocations.CalVideo,
subscriberUrl: "http://my-webhook.example.com",
videoCallUrl: `http://app.cal.local:3000/video/${createdBooking.uid}`,
});
},
timeout
);
test(
`should fail calendar event creation when organizer isn't part of the organization of DelegationCredential Credential`,
async () => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
withTeam: true,
});
const anotherOrg = await createOrganization({
name: "Another Org",
slug: "anotherorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const organizer = getOrganizer({
name: "Organizer",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
// User must be part of organization to be able to use that organization's Delegation credential
teams: payloadToMakePartOfOrganization,
// No regular credentials provided
credentials: [],
});
await createDelegationCredential(anotherOrg.id);
const payloadToCreateUserEventTypeForOrganizer = {
id: 1,
slotInterval: 30,
length: 30,
users: [
{
id: organizer.id,
},
],
};
await createBookingScenario(
getScenarioData({
webhooks: [
{
userId: organizer.id,
eventTriggers: ["BOOKING_CREATED"],
subscriberUrl: "http://my-webhook.example.com",
active: true,
eventTypeId: 1,
appId: null,
},
],
workflows: [
{
userId: organizer.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeOn: [1],
},
],
eventTypes: [payloadToCreateUserEventTypeForOrganizer],
organizer,
apps: [TestData.apps["daily-video"]],
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
videoMeetingData: {
id: "MOCK_ID",
password: "MOCK_PASS",
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
googleCalendar: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
},
},
});
const mockBookingData = getMockRequestDataForBooking({
data: {
eventTypeId: 1,
responses: {
email: booker.email,
name: booker.name,
location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expect(createdBooking.responses).toEqual(
expect.objectContaining({
email: booker.email,
name: booker.name,
})
);
expect(createdBooking).toEqual(
expect.objectContaining({
location: BookingLocations.CalVideo,
})
);
expectNoAttemptToCreateCalendarEvent(calendarMock);
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: mockBookingData.eventTypeId,
status: BookingStatus.ACCEPTED,
references: [
{
type: appStoreMetadata.dailyvideo.type,
uid: "MOCK_ID",
meetingId: "MOCK_ID",
meetingPassword: "MOCK_PASS",
meetingUrl: "http://mock-dailyvideo.example.com/meeting-1",
},
],
iCalUID: createdBooking.iCalUID,
});
// Could not create calendar-event
expectBookingToNotHaveReference(createdBooking, {
type: appStoreMetadata.googlecalendar.type,
uid: "GOOGLE_CALENDAR_EVENT_ID",
});
},
timeout
);
test(
`should create a successful dynamic group booking using the delegation credential
1. Should create a booking in the database with reference having Delegation credential
2. Should create an event in calendar with Delegation credential for both users
3. Should use Google Meet as the location even when not explicitly set.
`,
async () => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const groupUser1 = getOrganizer({
name: "group-user-1",
username: "group-user-1",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
teams: payloadToMakePartOfOrganization,
credentials: [],
destinationCalendar: TestData.selectedCalendars.google,
});
const groupUser2 = getOrganizer({
name: "group-user-2",
username: "group-user-2",
email: "[email protected]",
id: 102,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
teams: payloadToMakePartOfOrganization,
credentials: [],
destinationCalendar: TestData.selectedCalendars.google,
});
const delegationCredential = await createDelegationCredential(org.id);
await createBookingScenario(
getScenarioData({
webhooks: [
{
userId: groupUser1.id,
eventTriggers: ["BOOKING_CREATED"],
subscriberUrl: "http://my-webhook.example.com",
active: true,
eventTypeId: 0,
appId: null,
},
],
workflows: [
{
userId: groupUser1.id,
trigger: "NEW_EVENT",
action: "EMAIL_HOST",
template: "REMINDER",
activeOn: [0],
},
],
eventTypes: [],
users: [groupUser1, groupUser2],
apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]],
})
);
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
const calendarMock = await mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
googleCalendar: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
},
},
});
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
data: {
start: `${plus1DateString}T05:00:00.000Z`,
end: `${plus1DateString}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,
// There is no location option during booking for Dynamic Group Bookings
// location: { optionValue: "", value: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
expect(createdBooking.responses).toEqual(
expect.objectContaining({
email: booker.email,
name: booker.name,
})
);
expect(createdBooking).toEqual(
expect.objectContaining({
location: BookingLocations.GoogleMeet,
})
);
await expectBookingToBeInDatabase({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: null,
status: BookingStatus.ACCEPTED,
location: BookingLocations.GoogleMeet,
references: [
{
type: appStoreMetadata.googlecalendar.type,
uid: "GOOGLE_CALENDAR_EVENT_ID",
meetingId: "GOOGLE_CALENDAR_EVENT_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
// Verify Delegation credential was used
delegationCredentialId: delegationCredential.id,
},
],
iCalUID: createdBooking.iCalUID,
});
},
timeout
);
test(
`should use Cal Video as the location if that is the default conferencing app set by the user. It must not use Google Meet coming from Delegation credential.`,
async () => {
const handleNewBooking = getNewBookingHandler();
const org = await createOrganization({
name: "Test Org",
slug: "testorg",
});
const payloadToMakePartOfOrganization = [
{
membership: {
accepted: true,
role: MembershipRole.ADMIN,
},
team: {
id: org.id,
name: "Test Org",
slug: "testorg",
},
},
];
const booker = getBooker({
email: "[email protected]",
name: "Booker",
});
const groupUser1 = getOrganizer({
name: "group-user-1",
username: "group-user-1",
email: "[email protected]",
id: 101,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
teams: payloadToMakePartOfOrganization,
credentials: [],
destinationCalendar: TestData.selectedCalendars.google,
metadata: {
defaultConferencingApp: {
appSlug: "daily-video",
},
},
});
const groupUser2 = getOrganizer({
name: "group-user-2",
username: "group-user-2",
email: "[email protected]",
id: 102,
schedules: [TestData.schedules.IstWorkHours],
selectedCalendars: [TestData.selectedCalendars.google],
teams: payloadToMakePartOfOrganization,
credentials: [],
destinationCalendar: TestData.selectedCalendars.google,
metadata: {
defaultConferencingApp: {
appSlug: "daily-video",
},
},
});
await createDelegationCredential(org.id);
await createBookingScenario(
getScenarioData({
eventTypes: [],
users: [groupUser1, groupUser2],
apps: [TestData.apps["daily-video"], TestData.apps["google-calendar"]],
})
);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
videoMeetingData: {
id: "MOCK_ID",
password: "MOCK_PASS",
url: `http://mock-dailyvideo.example.com/meeting-1`,
},
});
// Mock a Scenario where iCalUID isn't returned by Google Calendar in which case booking UID is used as the ics UID
mockCalendarToHaveNoBusySlots("googlecalendar", {
create: {
id: "GOOGLE_CALENDAR_EVENT_ID",
uid: "MOCK_ID",
appSpecificData: {
googleCalendar: {
hangoutLink: "https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
},
},
},
});
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const mockBookingData = getMockRequestDataForDynamicGroupBooking({
data: {
start: `${plus1DateString}T05:00:00.000Z`,
end: `${plus1DateString}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: BookingLocations.CalVideo },
},
},
});
const createdBooking = await handleNewBooking({
bookingData: mockBookingData,
});
await expectBookingToBeInDatabase({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
uid: createdBooking.uid!,
eventTypeId: null,
status: BookingStatus.ACCEPTED,
location: BookingLocations.CalVideo,
references: [
{
type: appStoreMetadata.dailyvideo.type,
// Verify Delegation credential was not used
delegationCredentialId: null,
},
],
iCalUID: createdBooking.iCalUID,
});
},
timeout
);
});
});