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>
1169 lines
36 KiB
TypeScript
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
|
|
);
|
|
});
|
|
});
|