* chore: Upgrade prisma to 6.7.0 * Build fixes * type fixes Signed-off-by: Omar López <zomars@me.com> * Update schema.prisma * Patching * Revert "Update schema.prisma" This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1. * Revert "Patching" This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb. * Revert schema changes Signed-off-by: Omar López <zomars@me.com> * WIP Signed-off-by: Omar López <zomars@me.com> * Update getPublicEvent.ts * Update imports Signed-off-by: Omar López <zomars@me.com> * Update gitignore Signed-off-by: Omar López <zomars@me.com> * update remaining imports Signed-off-by: Omar López <zomars@me.com> * Delete .cursor/config.json * Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts * Update _get.ts * Update user.ts * Update .gitignore * update * Update WorkflowStepContainer.tsx * Update next-auth-custom-adapter.ts * Update getPublicEvent.ts * Update workflow.ts * Update next-auth-custom-adapter.ts * Update next-auth-options.ts * Update bookingScenario.ts * fix missing imports * upgrades prismock Signed-off-by: Omar López <zomars@me.com> * patches prismock Signed-off-by: Omar López <zomars@me.com> * Update reschedule.test.ts * Update prisma.ts * patch prismock Signed-off-by: Omar López <zomars@me.com> * fix enums imports Signed-off-by: Omar López <zomars@me.com> * Revert "Update prisma.ts" This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619. * Revert "patch prismock" This reverts commit e95819113dc9d88e7130947aa120cd42710977c8. * fix patch * Fix test that overrun the boundary, it shouldn't test too much * Move prisma import to changeSMSLockState * Bring back broken test without illegal imports * Merge with main and fix filter hosts by same round robin host * Fixed buildDryRunBooking fn tests * Fix and move ooo create or update handler test * Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts * Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts * Mock @calcom/prisma * Fix: verify-email.test.ts * fix: Moved WebhookService test and fixed default import mock * Fix: Added missing prisma mock, handleNewBooking uses that of course * We're not testing createContext here * fix: Prisma mock fix for listMembers.test.ts * More fixes to broken testcases * Forgot to remove borked test * Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file * Temporarily skip getCalendarEvents, needs a rewrite * Fix: turns out you can access protected in testcases * fix further mocks * Added packages/features/insights/server/buildBaseWhereCondition.ts, types * Always great to have a mock and then not use it * And one less again. * fix: confirm.handler.test, didn't mock prisma * fix: Address minor nit by @eunjae & fix ImpersonationProvider test * Updated isPrismaAvailableCheck that doesn't crash on import * fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion * Add zod-prisma-types without the generator enabled (commented out) * Uncomment and see what happens * Change method of import as imports did not work in Input Schemas * Remove custom 'zod' booking model, it does not belong with Prisma * Fix all other global Model imports * Rewrite most schema includes AND remove barrel file * Add bookingCreateBodySchema to features/bookings * Flurry of type fixes for compatibility with new zod gen * Refactor out the custom prisma type createEventTypeInput * Work around nullable eventTypeLocations * HandlePayment type fix * More fixes, final fix remaining is CompleteEventType * Should fix a bunch more booking related type errors * Missed one * Some props missing from BookingCreateBodySchema * Fix location type in handleChildrenEventTypes * Little bit hacky imo but it works * Final type error \o/ * Forgot to include Prisma * Do not include zod-utils in booker/types * Oops, was already including Booker/types * Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post * Fix api v1 type errors * Fix EventTypeDescription typings * Remove getParserWithGeneric, use userBodySchema with UserSchema * use centralized timeZoneSchema * Implement feedback by @zomars * Couple of WIP pushes * Fix tests * Type fixes in `handleChildrenEventTypes` test * Try and parse metadata before use * Change zod-prisma-types configuration for optimal performance * Fix prisma validator error in `prisma/selects/credential` * Disable seperate relations model, hits a bug * Import absolute - this makes rollup work in @platform/libraries * Attempt at removing resolutions override * Refactor using `Prisma.validator` to `satisfies` * Build atoms using @calcom/prisma/client * Build atoms using @calcom/prisma/client * fixes * Update eventTypeSelect.ts * Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)` * `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description` * Add `seatsPerTimeSlot` to event type public select * Fix typing in `users-public-view` getServerSide props * Add missing `schedulingType` to prop * chore: bump platform libraries * Function return type is illegal, not sure how this passed eslint (#21567) * Merged with main * Update updateTokenObject.ts * Update handleResponse.ts * Update index.ts * Update handleChildrenEventTypes.ts * Update booking-idempotency-key.ts * Update WebhookService.test.ts * Update events.test.ts * Update queued-response.test.ts * Update events.test.ts * Update getRoutedUrl.test.ts * fix: type checks Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * chore: bump platform libraries * Update yarn.lock * more fixes Signed-off-by: Omar López <zomars@me.com> * fixes Signed-off-by: Omar López <zomars@me.com> * biuld fixes * chore: bump platform libraries * Update conferencing.repository.ts * Update conferencing.repository.ts * Update getCalendarsEvents.test.ts * Update vite.config.js * chore: bump platform libraries * Update users.ts * Discard changes to docs/api-reference/v2/openapi.json * Update vite.config.ts * updated platform libraries * Update get.handler.test.ts * Update get.handler.test.ts * Update schema.prisma * Discard changes to docs/api-reference/v2/openapi.json * Update next-auth-custom-adapter.ts * Update team.ts * Flurry of type fixes * Fix majority of insight related type errors * Type fixes for unlink of account * Make user nullable again * Fixed a bunch of unit tests and one type error * Attempted mock fix * Attempted fix for Attribute type * Ensure default import becomes prisma, but not direct usage * Import default as prisma in prisma.module * Add attributeOption to attribute type * Fix calcom/prisma mock * Refactor Prisma client imports to @calcom/prisma/client Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase. * Undo removal of max-warnings=0 to get main to merge * Remove unit tests for e2e fixtures, provide new prisma mock * Mock @calcom/prisma in event manager * Mock @calcom/prisma in event manager * Add correct format even with --no-verify * Mock prisma in CalendarManager * Add mock for permission-check.service * Better injection in PrismaApiKeyRepository imports * More mock fixes :) * Fix listMembers.handler.test * Fix User import * Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps * Why was this a thing? * Strictly speaking; Not using prismock anymore * Ditched patch file for prismock * Fix output.service.ts platform type imports, need concrete for plainToClass * Better typing and tests for unlinkConnectedAccount.handler * Small type fix * Disable calendar cache tests as they are dependent on prismock * chore: bump platform lib * getRoutedUrl test remove of unused import * Extract select to external const on getEventTypesFromDB * Direct select of userSelect from selects/user * fix type error from merging 23653 * Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer * fix: vite config atoms prisma client type location * revert: example app prisma client * revert: example app prisma client * bump platform libs * fix: use class instead of type for DI of PlatformBookingsService * update platform libs * remove unused variable * chore: generate prisma client for api v2 * fix: api v2 e2e * fix: atoms e2e * fix: atoms e2e * fix: atoms e2e * fix: api v2 e2e * fix: tsconfig apiv2 enums * publish libraries * Simplify check for existence teamId --------- Signed-off-by: Omar López <zomars@me.com> Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com> Co-authored-by: supalarry <laurisskraucis@gmail.com> Co-authored-by: cal.com <morgan@cal.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Benny Joo <sldisek783@gmail.com>
2553 lines
71 KiB
TypeScript
2553 lines
71 KiB
TypeScript
import i18nMock from "../../../../../tests/libs/__mocks__/libServerI18n";
|
|
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
|
|
|
import type Stripe from "stripe";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { vi } from "vitest";
|
|
import "vitest-fetch-mock";
|
|
import type { z } from "zod";
|
|
|
|
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
|
|
import { handleStripePaymentSuccess } from "@calcom/features/ee/payments/api/webhook";
|
|
import { weekdayToWeekIndex, type WeekDays } from "@calcom/lib/dayjs";
|
|
import type { HttpError } from "@calcom/lib/http-error";
|
|
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
|
import type { BookingReference, Attendee, Booking, Membership } from "@calcom/prisma/client";
|
|
import type { Prisma } from "@calcom/prisma/client";
|
|
import type { WebhookTriggerEvents } from "@calcom/prisma/client";
|
|
import type {
|
|
WorkflowActions,
|
|
WorkflowTemplates,
|
|
WorkflowTriggerEvents,
|
|
WorkflowMethods,
|
|
} from "@calcom/prisma/client";
|
|
import type { PaymentOption, SchedulingType, SMSLockState, TimeUnit } from "@calcom/prisma/enums";
|
|
import type { BookingStatus } from "@calcom/prisma/enums";
|
|
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
import type { userMetadataType } from "@calcom/prisma/zod-utils";
|
|
import type { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
|
|
import type { AppMeta } from "@calcom/types/App";
|
|
import type {
|
|
CalendarEvent,
|
|
IntegrationCalendar,
|
|
NewCalendarEventType,
|
|
EventBusyDate,
|
|
} from "@calcom/types/Calendar";
|
|
import type { CredentialForCalendarService } from "@calcom/types/Credential";
|
|
|
|
import { getMockPaymentService } from "./MockPaymentService";
|
|
import type { getMockRequestDataForBooking } from "./getMockRequestDataForBooking";
|
|
|
|
vi.mock("@calcom/app-store/calendar.services.generated", () => ({
|
|
CalendarServiceMap: {
|
|
googlecalendar: Promise.resolve({ default: vi.fn() }),
|
|
office365calendar: Promise.resolve({ default: vi.fn() }),
|
|
applecalendar: Promise.resolve({ default: vi.fn() }),
|
|
caldavcalendar: Promise.resolve({ default: vi.fn() }),
|
|
},
|
|
}));
|
|
|
|
const mockVideoAdapterRegistry: Record<string, any> = {};
|
|
|
|
vi.mock("@calcom/app-store/video.adapters.generated", () => ({
|
|
VideoApiAdapterMap: new Proxy(
|
|
{},
|
|
{
|
|
get(target, prop) {
|
|
if (typeof prop === "string" && mockVideoAdapterRegistry[prop]) {
|
|
return mockVideoAdapterRegistry[prop];
|
|
}
|
|
return Promise.resolve({ default: vi.fn() });
|
|
},
|
|
}
|
|
),
|
|
}));
|
|
|
|
// We don't need to test it. Also, it causes Formbricks error when imported
|
|
vi.mock("@calcom/lib/raqb/findTeamMembersMatchingAttributeLogic", () => ({
|
|
default: {},
|
|
}));
|
|
|
|
vi.mock("@calcom/lib/crypto", async (importOriginal) => {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
const actual = await importOriginal<any>();
|
|
return {
|
|
...actual,
|
|
symmetricEncrypt: vi.fn((serviceAccountKey) => serviceAccountKey),
|
|
symmetricDecrypt: vi.fn((serviceAccountKey) => serviceAccountKey),
|
|
};
|
|
});
|
|
|
|
type Fields = z.infer<typeof eventTypeBookingFields>;
|
|
|
|
logger.settings.minLevel = 1;
|
|
const log = logger.getSubLogger({ prefix: ["[bookingScenario]"] });
|
|
|
|
type InputWebhook = {
|
|
id?: string;
|
|
appId: string | null;
|
|
userId?: number | null;
|
|
teamId?: number | null;
|
|
eventTypeId?: number;
|
|
active: boolean;
|
|
eventTriggers: WebhookTriggerEvents[];
|
|
subscriberUrl: string;
|
|
time?: number | null;
|
|
timeUnit?: TimeUnit | null;
|
|
};
|
|
|
|
type InputWorkflow = {
|
|
id?: number;
|
|
userId?: number | null;
|
|
teamId?: number | null;
|
|
name?: string;
|
|
activeOn?: number[];
|
|
activeOnTeams?: number[];
|
|
trigger: WorkflowTriggerEvents;
|
|
action: WorkflowActions;
|
|
template: WorkflowTemplates;
|
|
time?: number | null;
|
|
timeUnit?: TimeUnit | null;
|
|
sendTo?: string;
|
|
verifiedAt?: Date;
|
|
};
|
|
|
|
type InputPayment = {
|
|
id?: number;
|
|
uid: string;
|
|
appId?: string | null;
|
|
bookingId: number;
|
|
amount: number;
|
|
fee: number;
|
|
currency: string;
|
|
success: boolean;
|
|
refunded: boolean;
|
|
data: Record<string, any>;
|
|
externalId: string;
|
|
paymentOption?: PaymentOption;
|
|
};
|
|
|
|
type InputWorkflowReminder = {
|
|
id?: number;
|
|
bookingUid: string;
|
|
method: WorkflowMethods;
|
|
scheduledDate: Date;
|
|
scheduled: boolean;
|
|
workflowStepId?: number;
|
|
workflowId: number;
|
|
};
|
|
|
|
type InputHost = {
|
|
userId: number;
|
|
isFixed?: boolean;
|
|
scheduleId?: number | null;
|
|
groupId?: string | null;
|
|
};
|
|
|
|
type InputSelectedSlot = {
|
|
eventTypeId: number;
|
|
userId: number;
|
|
slotUtcStartDate: Date;
|
|
slotUtcEndDate: Date;
|
|
uid: string;
|
|
releaseAt: Date;
|
|
isSeat?: boolean;
|
|
};
|
|
|
|
/**
|
|
* Data to be mocked
|
|
*/
|
|
export type ScenarioData = {
|
|
/**
|
|
* Prisma would return these eventTypes
|
|
*/
|
|
eventTypes: InputEventType[];
|
|
/**
|
|
* Prisma would return these users
|
|
*/
|
|
users: InputUser[];
|
|
/**
|
|
* Prisma would return these apps
|
|
*/
|
|
apps?: Partial<AppMeta>[];
|
|
bookings?: InputBooking[];
|
|
webhooks?: InputWebhook[];
|
|
workflows?: InputWorkflow[];
|
|
payment?: InputPayment[];
|
|
selectedSlots?: InputSelectedSlot[];
|
|
};
|
|
|
|
type InputCredential = typeof TestData.credentials.google & {
|
|
id?: number;
|
|
};
|
|
|
|
type InputSelectedCalendar = (typeof TestData.selectedCalendars)[keyof typeof TestData.selectedCalendars] & {
|
|
eventTypeId?: number | null;
|
|
};
|
|
|
|
type InputUser = Omit<typeof TestData.users.example, "defaultScheduleId"> & {
|
|
id: number;
|
|
defaultScheduleId?: number | null;
|
|
credentials?: InputCredential[];
|
|
organizationId?: number | null;
|
|
selectedCalendars?: InputSelectedCalendar[];
|
|
teams?: {
|
|
membership: Partial<Membership>;
|
|
team: {
|
|
id: number;
|
|
name: string;
|
|
slug: string;
|
|
parentId?: number;
|
|
isPrivate?: boolean;
|
|
};
|
|
}[];
|
|
schedules: {
|
|
// Allows giving id in the input directly so that it can be referenced somewhere else as well
|
|
id?: number;
|
|
name: string;
|
|
availability: {
|
|
days: number[];
|
|
startTime: Date;
|
|
endTime: Date;
|
|
date: string | null;
|
|
}[];
|
|
timeZone: string;
|
|
}[];
|
|
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
|
weekStart?: string;
|
|
profiles?: Prisma.ProfileUncheckedCreateWithoutUserInput[];
|
|
completedOnboarding?: boolean;
|
|
outOfOffice?: {
|
|
dateRanges: {
|
|
start: string;
|
|
end: string;
|
|
}[];
|
|
};
|
|
};
|
|
|
|
export type InputEventType = {
|
|
id: number;
|
|
title?: string;
|
|
length?: number;
|
|
offsetStart?: number;
|
|
slotInterval?: number;
|
|
userId?: number;
|
|
minimumBookingNotice?: number;
|
|
useEventLevelSelectedCalendars?: boolean;
|
|
users?: { id: number }[];
|
|
hosts?: InputHost[];
|
|
hostGroups?: { id: string; name: string }[];
|
|
schedulingType?: SchedulingType;
|
|
parent?: { id: number };
|
|
beforeEventBuffer?: number;
|
|
afterEventBuffer?: number;
|
|
teamId?: number | null;
|
|
team?: {
|
|
id?: number | null;
|
|
parentId?: number | null;
|
|
bookingLimits?: IntervalLimit;
|
|
includeManagedEventsInLimits?: boolean;
|
|
};
|
|
requiresConfirmation?: boolean;
|
|
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
|
schedule?: InputUser["schedules"][number] | null;
|
|
bookingLimits?: IntervalLimit;
|
|
durationLimits?: IntervalLimit;
|
|
owner?: number;
|
|
metadata?: any;
|
|
rescheduleWithSameRoundRobinHost?: boolean;
|
|
restrictionSchedule?: {
|
|
create: {
|
|
name: string;
|
|
timeZone: string;
|
|
availability: {
|
|
createMany: {
|
|
data: {
|
|
days: number[];
|
|
startTime: Date;
|
|
endTime: Date;
|
|
date: string | null;
|
|
}[];
|
|
};
|
|
};
|
|
user: { connect: { id: number } };
|
|
};
|
|
};
|
|
useBookerTimezone?: boolean;
|
|
restrictionScheduleId?: number;
|
|
} & Partial<Omit<Prisma.EventTypeCreateInput, "users" | "schedule" | "bookingLimits" | "durationLimits">>;
|
|
|
|
type AttendeeBookingSeatInput = Pick<Prisma.BookingSeatCreateInput, "referenceUid" | "data">;
|
|
|
|
type WhiteListedBookingProps = {
|
|
id?: number;
|
|
uid?: string;
|
|
userId?: number;
|
|
eventTypeId: number;
|
|
startTime: string;
|
|
endTime: string;
|
|
title?: string;
|
|
status: BookingStatus;
|
|
attendees?: {
|
|
email: string;
|
|
phoneNumber?: string;
|
|
bookingSeat?: AttendeeBookingSeatInput | null;
|
|
}[];
|
|
references?: (Omit<ReturnType<typeof getMockBookingReference>, "credentialId"> & {
|
|
// TODO: Make sure that all references start providing credentialId and then remove this intersection of optional credentialId
|
|
credentialId?: number | null;
|
|
})[];
|
|
user?: { id: number };
|
|
bookingSeat?: Prisma.BookingSeatCreateInput[];
|
|
createdAt?: string;
|
|
};
|
|
|
|
type InputBooking = Partial<Omit<Booking, keyof WhiteListedBookingProps>> & WhiteListedBookingProps;
|
|
|
|
export const Timezones = {
|
|
"+5:30": "Asia/Kolkata",
|
|
"+6:00": "Asia/Dhaka",
|
|
"-11:00": "Pacific/Pago_Pago",
|
|
};
|
|
|
|
async function addHostsToDb(eventTypes: InputEventType[]) {
|
|
for (const eventType of eventTypes) {
|
|
// Create host groups first if they exist
|
|
if (eventType.hostGroups?.length) {
|
|
await prismock.hostGroup.createMany({
|
|
data: eventType.hostGroups.map((group) => ({
|
|
id: group.id, // Preserve the input ID
|
|
name: group.name,
|
|
eventTypeId: eventType.id,
|
|
})),
|
|
});
|
|
}
|
|
|
|
if (!eventType.hosts?.length) continue;
|
|
for (const host of eventType.hosts) {
|
|
const data: Prisma.HostCreateInput = {
|
|
eventType: {
|
|
connect: {
|
|
id: eventType.id,
|
|
},
|
|
},
|
|
isFixed: host.isFixed ?? false,
|
|
user: {
|
|
connect: {
|
|
id: host.userId,
|
|
},
|
|
},
|
|
schedule: host.scheduleId
|
|
? {
|
|
connect: {
|
|
id: host.scheduleId,
|
|
},
|
|
}
|
|
: undefined,
|
|
group: host.groupId
|
|
? {
|
|
connect: {
|
|
id: host.groupId,
|
|
},
|
|
}
|
|
: undefined,
|
|
};
|
|
|
|
await prismock.host.create({
|
|
data,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export async function addEventTypesToDb(
|
|
eventTypes: (Omit<
|
|
Prisma.EventTypeCreateInput,
|
|
"users" | "workflows" | "destinationCalendar" | "schedule"
|
|
> & {
|
|
id?: number;
|
|
users?: any[];
|
|
userId?: number;
|
|
hosts?: any[];
|
|
workflows?: any[];
|
|
destinationCalendar?: any;
|
|
schedule?: any;
|
|
metadata?: any;
|
|
team?: { id?: number | null; bookingLimits?: IntervalLimit; includeManagedEventsInLimits?: boolean };
|
|
restrictionSchedule?: {
|
|
create: {
|
|
name: string;
|
|
timeZone: string;
|
|
availability: {
|
|
createMany: {
|
|
data: {
|
|
days: number[];
|
|
startTime: Date;
|
|
endTime: Date;
|
|
date: string | null;
|
|
}[];
|
|
};
|
|
};
|
|
user: { connect: { id: number } };
|
|
};
|
|
};
|
|
})[]
|
|
) {
|
|
log.silly("TestData: Add EventTypes to DB", JSON.stringify(eventTypes));
|
|
await prismock.eventType.createMany({
|
|
data: eventTypes,
|
|
});
|
|
const allEventTypes = await prismock.eventType.findMany({
|
|
include: {
|
|
users: true,
|
|
workflows: true,
|
|
destinationCalendar: true,
|
|
schedule: true,
|
|
hostGroups: true,
|
|
},
|
|
});
|
|
|
|
for (let i = 0; i < eventTypes.length; i++) {
|
|
const eventType = eventTypes[i];
|
|
const createdEventType = allEventTypes[i];
|
|
|
|
if (eventType.schedule) {
|
|
log.silly("TestData: Creating Schedule for EventType", JSON.stringify(eventType));
|
|
await prismock.schedule.create({
|
|
data: {
|
|
...eventType.schedule.create,
|
|
eventType: {
|
|
connect: {
|
|
id: createdEventType.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
// Handle restrictionSchedule creation and connection
|
|
if (eventType.restrictionSchedule) {
|
|
const createdRestrictionSchedule = await prismock.schedule.create({
|
|
data: {
|
|
...eventType.restrictionSchedule.create,
|
|
eventType: {
|
|
connect: {
|
|
id: createdEventType.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await prismock.eventType.update({
|
|
where: { id: createdEventType.id },
|
|
data: { restrictionScheduleId: createdRestrictionSchedule.id },
|
|
});
|
|
}
|
|
|
|
if (eventType.team?.id) {
|
|
const createdTeam = await prismock.team.create({
|
|
data: {
|
|
id: eventType.team?.id,
|
|
bookingLimits: eventType.team?.bookingLimits,
|
|
includeManagedEventsInLimits: eventType.team?.includeManagedEventsInLimits,
|
|
name: "",
|
|
},
|
|
});
|
|
|
|
await prismock.eventType.update({
|
|
where: { id: eventType.id },
|
|
data: { teamId: createdTeam.id },
|
|
});
|
|
}
|
|
}
|
|
|
|
log.silly(
|
|
"TestData: All EventTypes in DB are",
|
|
JSON.stringify({
|
|
eventTypes: allEventTypes,
|
|
})
|
|
);
|
|
return allEventTypes;
|
|
}
|
|
|
|
export async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
|
|
const baseEventType = {
|
|
title: "Base EventType Title",
|
|
slug: "base-event-type-slug",
|
|
timeZone: null,
|
|
beforeEventBuffer: 0,
|
|
afterEventBuffer: 0,
|
|
bookingLimits: {},
|
|
includeManagedEventsInLimits: false,
|
|
schedulingType: null,
|
|
length: 15,
|
|
//TODO: What is the purpose of periodStartDate and periodEndDate? Test these?
|
|
periodStartDate: new Date("2022-01-21T09:03:48.000Z"),
|
|
periodEndDate: new Date("2022-01-21T09:03:48.000Z"),
|
|
periodCountCalendarDays: false,
|
|
periodDays: 30,
|
|
seatsPerTimeSlot: null,
|
|
metadata: {},
|
|
minimumBookingNotice: 0,
|
|
offsetStart: 0,
|
|
};
|
|
const foundEvents: Record<number, boolean> = {};
|
|
const eventTypesWithUsers = eventTypes.map((eventType) => {
|
|
if (!eventType.slotInterval && !eventType.length) {
|
|
throw new Error("eventTypes[number]: slotInterval or length must be defined");
|
|
}
|
|
if (foundEvents[eventType.id]) {
|
|
throw new Error(`eventTypes[number]: id ${eventType.id} is not unique`);
|
|
}
|
|
foundEvents[eventType.id] = true;
|
|
const users =
|
|
eventType.users?.map((userWithJustId) => {
|
|
return usersStore.find((user) => user.id === userWithJustId.id);
|
|
}) || [];
|
|
const hosts =
|
|
eventType.users?.map((host) => {
|
|
const user = usersStore.find((user) => user.id === host.id);
|
|
return { ...host, user };
|
|
}) || [];
|
|
return {
|
|
...baseEventType,
|
|
...eventType,
|
|
workflows: [],
|
|
users,
|
|
hosts,
|
|
hostGroups: eventType.hostGroups || [],
|
|
destinationCalendar: eventType.destinationCalendar
|
|
? {
|
|
create: eventType.destinationCalendar,
|
|
}
|
|
: eventType.destinationCalendar,
|
|
schedule: eventType.schedule
|
|
? {
|
|
create: {
|
|
...eventType.schedule,
|
|
availability: {
|
|
createMany: {
|
|
data: eventType.schedule.availability,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
: eventType.schedule,
|
|
restrictionScheduleId: eventType.restrictionScheduleId ?? undefined,
|
|
useBookerTimezone: eventType.useBookerTimezone ?? false,
|
|
restrictionSchedule: eventType.restrictionSchedule
|
|
? {
|
|
create: {
|
|
name: eventType.restrictionSchedule.create.name,
|
|
timeZone: eventType.restrictionSchedule.create.timeZone,
|
|
availability: {
|
|
createMany: {
|
|
data: eventType.restrictionSchedule.create.availability.createMany.data,
|
|
},
|
|
},
|
|
user: { connect: { id: eventType.userId || (eventType.users && eventType.users[0]?.id) || 1 } },
|
|
},
|
|
}
|
|
: undefined,
|
|
owner: eventType.owner ? { connect: { id: eventType.owner } } : undefined,
|
|
schedulingType: eventType.schedulingType,
|
|
parent: eventType.parent ? { connect: { id: eventType.parent.id } } : undefined,
|
|
rescheduleWithSameRoundRobinHost: eventType.rescheduleWithSameRoundRobinHost,
|
|
};
|
|
});
|
|
log.silly("TestData: Creating EventType", JSON.stringify(eventTypesWithUsers));
|
|
return await addEventTypesToDb(eventTypesWithUsers);
|
|
}
|
|
|
|
function addBookingReferencesToDB(bookingReferences: Prisma.BookingReferenceCreateManyInput[]) {
|
|
prismock.bookingReference.createMany({
|
|
data: bookingReferences,
|
|
});
|
|
}
|
|
|
|
async function addBookingsToDb(
|
|
bookings: (Prisma.BookingCreateInput & {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
references: any[];
|
|
user?: { id: number };
|
|
})[]
|
|
) {
|
|
log.silly("TestData: Creating Bookings", JSON.stringify(bookings));
|
|
|
|
function getDateObj(time: string | Date) {
|
|
return time instanceof Date ? time : new Date(time);
|
|
}
|
|
|
|
// Make sure that we store the date in Date object always. This is to ensure consistency which Prisma does but not prismock
|
|
log.silly("Handling Prismock bug-3");
|
|
const fixedBookings = bookings.map((booking) => {
|
|
const startTime = getDateObj(booking.startTime);
|
|
const endTime = getDateObj(booking.endTime);
|
|
return { ...booking, startTime, endTime };
|
|
});
|
|
|
|
await prismock.booking.createMany({
|
|
data: fixedBookings,
|
|
});
|
|
log.silly(
|
|
"TestData: Bookings as in DB",
|
|
JSON.stringify({
|
|
bookings: await prismock.booking.findMany({
|
|
include: {
|
|
references: true,
|
|
attendees: true,
|
|
},
|
|
}),
|
|
})
|
|
);
|
|
}
|
|
|
|
export async function addBookings(bookings: InputBooking[]) {
|
|
log.silly("TestData: Creating Bookings", JSON.stringify(bookings));
|
|
const allBookings = [...bookings].map((booking) => {
|
|
if (booking.references) {
|
|
addBookingReferencesToDB(
|
|
booking.references.map((reference) => {
|
|
return {
|
|
...reference,
|
|
bookingId: booking.id,
|
|
};
|
|
})
|
|
);
|
|
}
|
|
return {
|
|
uid: booking.uid || uuidv4(),
|
|
workflowReminders: [],
|
|
references: [],
|
|
title: "Test Booking Title",
|
|
...booking,
|
|
};
|
|
});
|
|
|
|
await addBookingsToDb(
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
allBookings.map((booking) => {
|
|
const bookingCreate = booking;
|
|
if (booking.references) {
|
|
bookingCreate.references = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
createMany: {
|
|
data: booking.references,
|
|
},
|
|
};
|
|
}
|
|
if (booking.attendees) {
|
|
bookingCreate.attendees = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
createMany: {
|
|
data: booking.attendees.map((attendee) => {
|
|
if (attendee.bookingSeat) {
|
|
const { bookingSeat, ...attendeeWithoutBookingSeat } = attendee;
|
|
return {
|
|
...attendeeWithoutBookingSeat,
|
|
bookingSeat: {
|
|
create: { ...bookingSeat, bookingId: booking.id },
|
|
},
|
|
};
|
|
} else {
|
|
return attendee;
|
|
}
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
|
|
if (booking?.user?.id) {
|
|
bookingCreate.user = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
connect: {
|
|
id: booking.user.id,
|
|
},
|
|
};
|
|
}
|
|
|
|
return bookingCreate;
|
|
})
|
|
);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async function addWebhooksToDb(webhooks: any[]) {
|
|
await prismock.webhook.createMany({
|
|
data: webhooks,
|
|
});
|
|
}
|
|
|
|
async function addPaymentToDb(payment: InputPayment[]) {
|
|
await prismock.payment.createMany({
|
|
data: payment,
|
|
});
|
|
}
|
|
|
|
async function addWebhooks(webhooks: InputWebhook[]) {
|
|
log.silly("TestData: Creating Webhooks", safeStringify(webhooks));
|
|
|
|
await addWebhooksToDb(webhooks);
|
|
}
|
|
|
|
async function addWorkflowsToDb(workflows: InputWorkflow[]) {
|
|
await Promise.all(
|
|
workflows.map(async (workflow) => {
|
|
const team = await prismock.team.findUnique({
|
|
where: {
|
|
id: workflow.teamId ?? 0,
|
|
},
|
|
});
|
|
|
|
if (workflow.teamId && !team) {
|
|
throw new Error(`Team with ID ${workflow.teamId} not found`);
|
|
}
|
|
|
|
const isOrg = team?.isOrganization;
|
|
|
|
// Create the workflow first
|
|
const createdWorkflow = await prismock.workflow.create({
|
|
data: {
|
|
...(workflow.id && { id: workflow.id }),
|
|
userId: workflow.userId,
|
|
teamId: workflow.teamId,
|
|
trigger: workflow.trigger,
|
|
name: workflow.name ? workflow.name : "Test Workflow",
|
|
time: workflow.time,
|
|
timeUnit: workflow.timeUnit,
|
|
},
|
|
include: {
|
|
steps: true,
|
|
},
|
|
});
|
|
|
|
await prismock.workflowStep.create({
|
|
data: {
|
|
stepNumber: 1,
|
|
action: workflow.action,
|
|
template: workflow.template,
|
|
numberVerificationPending: false,
|
|
includeCalendarEvent: false,
|
|
sendTo: workflow.sendTo,
|
|
workflow: {
|
|
connect: {
|
|
id: createdWorkflow.id,
|
|
},
|
|
},
|
|
verifiedAt: workflow?.verifiedAt ?? new Date(),
|
|
},
|
|
});
|
|
|
|
//activate event types and teams on workflows
|
|
if (isOrg && workflow.activeOnTeams) {
|
|
await Promise.all(
|
|
workflow.activeOnTeams.map((id) =>
|
|
prismock.workflowsOnTeams.create({
|
|
data: {
|
|
workflowId: createdWorkflow.id,
|
|
teamId: id,
|
|
},
|
|
})
|
|
)
|
|
);
|
|
} else if (workflow.activeOn) {
|
|
await Promise.all(
|
|
workflow.activeOn.map((id) =>
|
|
prismock.workflowsOnEventTypes.create({
|
|
data: {
|
|
workflowId: createdWorkflow.id,
|
|
eventTypeId: id,
|
|
},
|
|
})
|
|
)
|
|
);
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
async function addWorkflows(workflows: InputWorkflow[]) {
|
|
log.silly("TestData: Creating Workflows", safeStringify(workflows));
|
|
|
|
return await addWorkflowsToDb(workflows);
|
|
}
|
|
|
|
export async function addWorkflowReminders(workflowReminders: InputWorkflowReminder[]) {
|
|
log.silly("TestData: Creating Workflow Reminders", safeStringify(workflowReminders));
|
|
|
|
return await prismock.workflowReminder.createMany({
|
|
data: workflowReminders,
|
|
});
|
|
}
|
|
|
|
export async function addUsersToDb(users: InputUser[]) {
|
|
log.silly("TestData: Creating Users", JSON.stringify(users));
|
|
await prismock.user.createMany({
|
|
data: users,
|
|
});
|
|
|
|
// Create OutOfOfficeEntry for users with outOfOffice data
|
|
for (const user of users) {
|
|
if (user.outOfOffice) {
|
|
log.debug("Creating OutOfOfficeEntry for user", user.id);
|
|
for (const dateRange of user.outOfOffice.dateRanges) {
|
|
await prismock.outOfOfficeEntry.create({
|
|
data: {
|
|
uuid: uuidv4(),
|
|
start: new Date(dateRange.start),
|
|
end: new Date(dateRange.end),
|
|
user: {
|
|
connect: {
|
|
id: user.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const allUsers = await prismock.user.findMany({
|
|
include: {
|
|
credentials: true,
|
|
teams: true,
|
|
profiles: true,
|
|
schedules: {
|
|
include: {
|
|
availability: true,
|
|
},
|
|
},
|
|
destinationCalendar: true,
|
|
},
|
|
});
|
|
|
|
log.silly(
|
|
"Added users to Db",
|
|
safeStringify({
|
|
allUsers,
|
|
})
|
|
);
|
|
|
|
return allUsers;
|
|
}
|
|
|
|
export async function addTeamsToDb(teams: NonNullable<InputUser["teams"]>[number]["team"][]) {
|
|
log.silly("TestData: Creating Teams", JSON.stringify(teams));
|
|
|
|
for (const team of teams) {
|
|
const teamsWithParentId = {
|
|
...team,
|
|
parentId: team.parentId,
|
|
};
|
|
await prismock.team.upsert({
|
|
where: {
|
|
id: teamsWithParentId.id,
|
|
},
|
|
update: {
|
|
...teamsWithParentId,
|
|
},
|
|
create: {
|
|
...teamsWithParentId,
|
|
},
|
|
});
|
|
}
|
|
|
|
const addedTeams = await prismock.team.findMany({
|
|
where: {
|
|
id: {
|
|
in: teams.map((team) => team.id),
|
|
},
|
|
},
|
|
});
|
|
log.silly(
|
|
"Added teams to Db",
|
|
safeStringify({
|
|
addedTeams,
|
|
})
|
|
);
|
|
return addedTeams;
|
|
}
|
|
|
|
export async function addUsers(users: InputUser[]) {
|
|
const prismaUsersCreate = [];
|
|
for (let i = 0; i < users.length; i++) {
|
|
const newUser = users[i];
|
|
const user = users[i];
|
|
if (user.schedules) {
|
|
newUser.schedules = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore
|
|
createMany: {
|
|
data: user.schedules.map((schedule) => {
|
|
return {
|
|
...schedule,
|
|
availability: {
|
|
createMany: {
|
|
data: schedule.availability,
|
|
},
|
|
},
|
|
};
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
if (user.credentials) {
|
|
newUser.credentials = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
createMany: {
|
|
data: user.credentials,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (user.teams) {
|
|
const addedTeams = await addTeamsToDb(user.teams.map((team) => team.team));
|
|
newUser.teams = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
createMany: {
|
|
data: user.teams.map((team, index) => {
|
|
return {
|
|
teamId: addedTeams[index].id,
|
|
...team.membership,
|
|
};
|
|
}),
|
|
},
|
|
};
|
|
}
|
|
if (user.selectedCalendars) {
|
|
newUser.selectedCalendars = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
createMany: {
|
|
data: user.selectedCalendars,
|
|
},
|
|
};
|
|
}
|
|
if (user.destinationCalendar) {
|
|
newUser.destinationCalendar = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
create: user.destinationCalendar,
|
|
};
|
|
}
|
|
if (user.profiles) {
|
|
newUser.profiles = {
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-expect-error Not sure why this is not working
|
|
createMany: {
|
|
data: user.profiles,
|
|
},
|
|
};
|
|
}
|
|
|
|
prismaUsersCreate.push(newUser);
|
|
}
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
return await addUsersToDb(prismaUsersCreate);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
async function addAppsToDb(apps: any[]) {
|
|
log.silly("TestData: Creating Apps", JSON.stringify({ apps }));
|
|
await prismock.app.createMany({
|
|
data: apps,
|
|
});
|
|
const allApps = await prismock.app.findMany();
|
|
log.silly("TestData: Apps as in DB", JSON.stringify({ apps: allApps }));
|
|
}
|
|
|
|
async function addSelectedSlotsToDb(selectedSlots: InputSelectedSlot[]) {
|
|
log.silly("TestData: Creating Selected Slots", JSON.stringify(selectedSlots));
|
|
await prismock.selectedSlots.createMany({
|
|
data: selectedSlots,
|
|
});
|
|
const allSelectedSlots = await prismock.selectedSlots.findMany();
|
|
log.silly("TestData: Selected Slots as in DB", JSON.stringify({ selectedSlots: allSelectedSlots }));
|
|
}
|
|
|
|
export async function createBookingScenario(data: ScenarioData) {
|
|
log.silly("TestData: Creating Scenario", JSON.stringify({ data }));
|
|
await addUsers(data.users);
|
|
if (data.apps) {
|
|
await addAppsToDb(
|
|
data.apps.map((app) => {
|
|
// Enable the app by default
|
|
return { enabled: true, ...app };
|
|
})
|
|
);
|
|
}
|
|
const eventTypes = await addEventTypes(data.eventTypes, data.users);
|
|
await addHostsToDb(data.eventTypes);
|
|
|
|
data.bookings = data.bookings || [];
|
|
// allowSuccessfulBookingCreation();
|
|
await addBookings(data.bookings);
|
|
// mockBusyCalendarTimes([]);
|
|
await addWebhooks(data.webhooks || []);
|
|
// addPaymentMock();
|
|
const workflows = await addWorkflows(data.workflows || []);
|
|
await addPaymentToDb(data.payment || []);
|
|
|
|
if (data.selectedSlots) {
|
|
await addSelectedSlotsToDb(data.selectedSlots);
|
|
}
|
|
|
|
return {
|
|
eventTypes,
|
|
workflows,
|
|
};
|
|
}
|
|
|
|
type TeamCreateReturnType = Awaited<ReturnType<typeof prismock.team.create>>;
|
|
|
|
function assertNonNullableSlug<T extends { slug: string | null }>(
|
|
org: T
|
|
): asserts org is T & { slug: string } {
|
|
if (org.slug === null) {
|
|
throw new Error("Slug cannot be null");
|
|
}
|
|
}
|
|
|
|
export async function createOrganization(orgData: {
|
|
name: string;
|
|
slug: string;
|
|
metadata?: z.infer<typeof teamMetadataSchema>;
|
|
withTeam?: boolean;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
}): Promise<TeamCreateReturnType & { slug: NonNullable<TeamCreateReturnType["slug"]>; children: any }> {
|
|
const org = await prismock.team.create({
|
|
data: {
|
|
name: orgData.name,
|
|
slug: orgData.slug,
|
|
isOrganization: true,
|
|
metadata: {
|
|
...(orgData.metadata || {}),
|
|
isOrganization: true,
|
|
},
|
|
},
|
|
});
|
|
if (orgData.withTeam) {
|
|
await prismock.team.create({
|
|
data: {
|
|
name: "Org Team",
|
|
slug: "org-team",
|
|
isOrganization: false,
|
|
parent: {
|
|
connect: {
|
|
id: org.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
assertNonNullableSlug(org);
|
|
const team = await prismock.team.findUnique({ where: { id: org.id }, include: { children: true } });
|
|
if (!team) {
|
|
throw new Error(`Team with id ${org.id} not found`);
|
|
}
|
|
assertNonNullableSlug(team);
|
|
return team;
|
|
}
|
|
|
|
export async function createCredentials(
|
|
credentialData: {
|
|
type: string;
|
|
key: any;
|
|
id?: number;
|
|
userId?: number | null;
|
|
teamId?: number | null;
|
|
}[]
|
|
) {
|
|
const credentials = await prismock.credential.createMany({
|
|
data: credentialData,
|
|
});
|
|
return credentials;
|
|
}
|
|
|
|
// async function addPaymentsToDb(payments: Prisma.PaymentCreateInput[]) {
|
|
// await prismaMock.payment.createMany({
|
|
// data: payments,
|
|
// });
|
|
// }
|
|
|
|
/**
|
|
* This fn indents to /ally compute day, month, year for the purpose of testing.
|
|
* We are not using DayJS because that's actually being tested by this code.
|
|
* - `dateIncrement` adds the increment to current day
|
|
* - `monthIncrement` adds the increment to current month
|
|
* - `yearIncrement` adds the increment to current year
|
|
* - `fromDate` starts incrementing from this date (default: today)
|
|
* @deprecated Stop using this function as it is not timezone aware and can return wrong date depending on the time of the day and timezone. Instead
|
|
* use vi.setSystemTime to fix the date and time and then use hardcoded days instead of dynamic date calculation.
|
|
*/
|
|
export const getDate = (
|
|
param: {
|
|
dateIncrement?: number;
|
|
monthIncrement?: number;
|
|
yearIncrement?: number;
|
|
fromDate?: Date;
|
|
} = {}
|
|
) => {
|
|
let { dateIncrement, monthIncrement, yearIncrement, fromDate } = param;
|
|
|
|
dateIncrement = dateIncrement || 0;
|
|
monthIncrement = monthIncrement || 0;
|
|
yearIncrement = yearIncrement || 0;
|
|
fromDate = fromDate || new Date();
|
|
|
|
fromDate.setDate(fromDate.getDate() + dateIncrement);
|
|
fromDate.setMonth(fromDate.getMonth() + monthIncrement);
|
|
fromDate.setFullYear(fromDate.getFullYear() + yearIncrement);
|
|
|
|
let _date = fromDate.getDate();
|
|
let year = fromDate.getFullYear();
|
|
|
|
// Make it start with 1 to match with DayJS requiremet
|
|
let _month = fromDate.getMonth() + 1;
|
|
|
|
// If last day of the month(As _month is plus 1 already it is going to be the 0th day of next month which is the last day of current month)
|
|
const lastDayOfMonth = new Date(year, _month, 0).getDate();
|
|
const numberOfDaysForNextMonth = +_date - +lastDayOfMonth;
|
|
if (numberOfDaysForNextMonth > 0) {
|
|
_date = numberOfDaysForNextMonth;
|
|
_month = _month + 1;
|
|
}
|
|
|
|
if (_month === 13) {
|
|
_month = 1;
|
|
year = year + 1;
|
|
}
|
|
|
|
const date = _date < 10 ? `0${_date}` : _date;
|
|
const month = _month < 10 ? `0${_month}` : _month;
|
|
|
|
return {
|
|
date: String(date),
|
|
month: String(month),
|
|
year: String(year),
|
|
dateString: `${year}-${month}-${date}`,
|
|
};
|
|
};
|
|
|
|
const isWeekStart = (date: Date, weekStart: WeekDays) => {
|
|
return date.getDay() === weekdayToWeekIndex(weekStart);
|
|
};
|
|
|
|
export const getNextMonthNotStartingOnWeekStart = (weekStart: WeekDays, from?: Date) => {
|
|
const date = from ?? new Date();
|
|
|
|
const incrementMonth = (date: Date) => {
|
|
date.setMonth(date.getMonth() + 1);
|
|
};
|
|
|
|
// start searching from the 1st day of next month
|
|
incrementMonth(date);
|
|
date.setDate(1);
|
|
|
|
while (isWeekStart(date, weekStart)) {
|
|
incrementMonth(date);
|
|
}
|
|
|
|
return getDate({ fromDate: date });
|
|
};
|
|
|
|
export function getMockedCredential({
|
|
metadataLookupKey,
|
|
key,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
key: {
|
|
expiry_date?: number;
|
|
token_type?: string;
|
|
access_token?: string;
|
|
refresh_token?: string;
|
|
scope: string;
|
|
};
|
|
}) {
|
|
const app = appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata];
|
|
return {
|
|
type: app.type,
|
|
appId: app.slug,
|
|
app: app,
|
|
key: {
|
|
expiry_date: Date.now() + 1000000,
|
|
token_type: "Bearer",
|
|
access_token: "ACCESS_TOKEN",
|
|
refresh_token: "REFRESH_TOKEN",
|
|
...key,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function getGoogleCalendarCredential() {
|
|
return getMockedCredential({
|
|
metadataLookupKey: "googlecalendar",
|
|
key: {
|
|
scope:
|
|
"https://www.googleapis.com/auth/calendar.events https://www.googleapis.com/auth/calendar.readonly",
|
|
},
|
|
});
|
|
}
|
|
|
|
export function getGoogleMeetCredential() {
|
|
return getMockedCredential({
|
|
metadataLookupKey: "googlevideo",
|
|
key: {
|
|
scope: "",
|
|
},
|
|
});
|
|
}
|
|
|
|
export function getAppleCalendarCredential() {
|
|
return getMockedCredential({
|
|
metadataLookupKey: "applecalendar",
|
|
key: {
|
|
scope:
|
|
"https://www.applecalendar.example/auth/calendar.events https://www.applecalendar.example/auth/calendar.readonly",
|
|
},
|
|
});
|
|
}
|
|
|
|
export function getZoomAppCredential() {
|
|
return getMockedCredential({
|
|
metadataLookupKey: "zoomvideo",
|
|
key: {
|
|
scope: "meeting:write",
|
|
},
|
|
});
|
|
}
|
|
|
|
export function getStripeAppCredential() {
|
|
return getMockedCredential({
|
|
metadataLookupKey: "stripepayment",
|
|
key: {
|
|
scope: "read_write",
|
|
},
|
|
});
|
|
}
|
|
|
|
export const TestData = {
|
|
selectedCalendars: {
|
|
google: {
|
|
integration: "google_calendar",
|
|
externalId: "john@example.com",
|
|
},
|
|
office365: {
|
|
integration: "office365_calendar",
|
|
externalId: "john@example.com",
|
|
},
|
|
},
|
|
credentials: {
|
|
google: getGoogleCalendarCredential(),
|
|
},
|
|
schedules: {
|
|
IstWorkHours: {
|
|
id: 1,
|
|
name: "9:30AM to 6PM in India - 4:00AM to 12:30PM in GMT",
|
|
availability: [
|
|
{
|
|
// userId: null,
|
|
// eventTypeId: null,
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T09:30:00.000Z"),
|
|
endTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
/**
|
|
* Has an overlap with IstEveningShift from 5PM to 6PM IST(11:30AM to 12:30PM GMT)
|
|
*/
|
|
IstMorningShift: {
|
|
name: "9:30AM to 6PM in India - 4:00AM to 12:30PM in GMT",
|
|
availability: [
|
|
{
|
|
// userId: null,
|
|
// eventTypeId: null,
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T09:30:00.000Z"),
|
|
endTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
/**
|
|
* Has an overlap with IstMorningShift and IstEveningShift
|
|
*/
|
|
IstMidShift: {
|
|
name: "12:30AM to 8PM in India - 7:00AM to 14:30PM in GMT",
|
|
availability: [
|
|
{
|
|
// userId: null,
|
|
// eventTypeId: null,
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T12:30:00.000Z"),
|
|
endTime: new Date("1970-01-01T20:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
/**
|
|
* Has an overlap with IstMorningShift from 5PM to 6PM IST(11:30AM to 12:30PM GMT)
|
|
*/
|
|
IstEveningShift: {
|
|
name: "5:00PM to 10PM in India - 11:30AM to 16:30PM in GMT",
|
|
availability: [
|
|
{
|
|
// userId: null,
|
|
// eventTypeId: null,
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T17:00:00.000Z"),
|
|
endTime: new Date("1970-01-01T22:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
EmptyAvailability: {
|
|
name: "Empty Availability",
|
|
availability: [],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
IstNotAvailableForFullMonth: (monthYear: string) => {
|
|
const [year, month] = monthYear.split("-").map(Number); // Expecting format 'YYYY-MM'
|
|
const startOfMonth = new Date(year, month - 1, 1);
|
|
const endOfMonth = new Date(year, month, 0);
|
|
const availability: {
|
|
days: number[];
|
|
startTime: Date;
|
|
endTime: Date;
|
|
date: string | null;
|
|
}[] = [
|
|
{
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
endTime: new Date("1970-01-01T22:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
];
|
|
// Generate unavailable dates for the entire month
|
|
const currentDate = new Date(startOfMonth);
|
|
while (currentDate <= endOfMonth) {
|
|
const dateString = currentDate.toISOString().split("T")[0];
|
|
availability.push({
|
|
days: [],
|
|
startTime: new Date(`${dateString}T00:00:00.000Z`),
|
|
endTime: new Date(`${dateString}T00:00:00.000Z`),
|
|
date: dateString,
|
|
});
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return {
|
|
name: "Unavailable for the entire month, then available from 18:00AM to 22:00 IST",
|
|
availability,
|
|
timeZone: Timezones["+5:30"],
|
|
};
|
|
},
|
|
IstWorkHoursWithFirstTwoWeeksUnavailable: (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
const availability: {
|
|
days: number[];
|
|
startTime: Date;
|
|
endTime: Date;
|
|
date: string | null;
|
|
}[] = [
|
|
{
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
endTime: new Date("1970-01-01T22:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
];
|
|
|
|
// Generate dateoverride for each day in the first two weeks
|
|
for (let i = 0; i < 15; i++) {
|
|
const dateString = date.toISOString().split("T")[0];
|
|
availability.push({
|
|
days: [],
|
|
startTime: new Date(`${dateString}T00:00:00.000Z`),
|
|
endTime: new Date(`${dateString}T00:00:00.000Z`),
|
|
date: dateString,
|
|
});
|
|
date.setDate(date.getDate() + 1);
|
|
}
|
|
|
|
return {
|
|
name: "Unavailable for the first two weeks, then available from 18:00 to 22:00 IST",
|
|
availability,
|
|
timeZone: Timezones["+5:30"],
|
|
};
|
|
},
|
|
IstWorkHoursWithDateOverride: (dateString: string) => ({
|
|
name: "9:30AM to 6PM in India - 4:00AM to 12:30PM in GMT but with a Date Override for 2PM to 6PM IST(in GST time it is 8:30AM to 12:30PM)",
|
|
availability: [
|
|
{
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date("1970-01-01T09:30:00.000Z"),
|
|
endTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
{
|
|
days: [0, 1, 2, 3, 4, 5, 6],
|
|
startTime: new Date(`1970-01-01T14:00:00.000Z`),
|
|
endTime: new Date(`1970-01-01T18:00:00.000Z`),
|
|
date: dateString,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
}),
|
|
IstWorkHoursNoWeekends: {
|
|
id: 1,
|
|
name: "9:30AM to 6PM in India - 4:00AM to 12:30PM in GMT",
|
|
availability: [
|
|
{
|
|
// userId: null,
|
|
// eventTypeId: null,
|
|
days: [/*0*/ 1, 2, 3, 4, 5 /*6*/],
|
|
startTime: new Date("1970-01-01T09:30:00.000Z"),
|
|
endTime: new Date("1970-01-01T18:00:00.000Z"),
|
|
date: null,
|
|
},
|
|
],
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
},
|
|
users: {
|
|
example: {
|
|
name: "Example",
|
|
email: "example@example.com",
|
|
username: "example.username",
|
|
defaultScheduleId: 1,
|
|
timeZone: Timezones["+5:30"],
|
|
},
|
|
},
|
|
apps: {
|
|
"google-calendar": {
|
|
...appStoreMetadata.googlecalendar,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
redirect_uris: ["http://localhost:3000/auth/callback"],
|
|
},
|
|
},
|
|
"office365-calendar": {
|
|
...appStoreMetadata.office365calendar,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
},
|
|
},
|
|
"google-meet": {
|
|
...appStoreMetadata.googlevideo,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
redirect_uris: ["http://localhost:3000/auth/callback"],
|
|
},
|
|
},
|
|
"daily-video": {
|
|
...appStoreMetadata.dailyvideo,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
api_key: "",
|
|
scale_plan: "false",
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
redirect_uris: ["http://localhost:3000/auth/callback"],
|
|
},
|
|
},
|
|
zoomvideo: {
|
|
...appStoreMetadata.zoomvideo,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
api_key: "",
|
|
scale_plan: "false",
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
redirect_uris: ["http://localhost:3000/auth/callback"],
|
|
},
|
|
},
|
|
"stripe-payment": {
|
|
...appStoreMetadata.stripepayment,
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
//@ts-ignore
|
|
keys: {
|
|
expiry_date: Infinity,
|
|
api_key: "",
|
|
scale_plan: "false",
|
|
client_id: "client_id",
|
|
client_secret: "client_secret",
|
|
redirect_uris: ["http://localhost:3000/auth/callback"],
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
export class MockError extends Error {
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = "MockError";
|
|
}
|
|
}
|
|
|
|
export function getOrganizer({
|
|
name,
|
|
email,
|
|
id,
|
|
schedules,
|
|
credentials,
|
|
selectedCalendars,
|
|
destinationCalendar,
|
|
defaultScheduleId,
|
|
weekStart = "Sunday",
|
|
teams,
|
|
organizationId,
|
|
metadata,
|
|
smsLockState,
|
|
completedOnboarding,
|
|
username,
|
|
locked,
|
|
}: {
|
|
name: string;
|
|
email: string;
|
|
id: number;
|
|
organizationId?: number | null;
|
|
schedules: InputUser["schedules"];
|
|
credentials?: InputCredential[];
|
|
selectedCalendars?: InputSelectedCalendar[];
|
|
defaultScheduleId?: number | null;
|
|
destinationCalendar?: Prisma.DestinationCalendarCreateInput;
|
|
weekStart?: WeekDays;
|
|
teams?: InputUser["teams"];
|
|
metadata?: userMetadataType;
|
|
smsLockState?: SMSLockState;
|
|
completedOnboarding?: boolean;
|
|
username?: string;
|
|
locked?: boolean;
|
|
}) {
|
|
username = username ?? TestData.users.example.username;
|
|
return {
|
|
...TestData.users.example,
|
|
name,
|
|
username,
|
|
email,
|
|
id,
|
|
schedules,
|
|
credentials,
|
|
selectedCalendars,
|
|
destinationCalendar,
|
|
defaultScheduleId,
|
|
weekStart,
|
|
teams,
|
|
organizationId,
|
|
profiles: [],
|
|
metadata,
|
|
smsLockState,
|
|
completedOnboarding,
|
|
locked,
|
|
};
|
|
}
|
|
|
|
export function getScenarioData(
|
|
{
|
|
/**
|
|
* organizer has no special meaning. It is a regular user. It is supposed to be deprecated along with `usersApartFromOrganizer` and we should introduce a new `users` field instead
|
|
*/
|
|
organizer,
|
|
eventTypes,
|
|
usersApartFromOrganizer = [],
|
|
apps = [],
|
|
users: _users,
|
|
webhooks,
|
|
workflows,
|
|
bookings,
|
|
payment,
|
|
}: {
|
|
organizer?: ReturnType<typeof getOrganizer>;
|
|
eventTypes: ScenarioData["eventTypes"];
|
|
apps?: ScenarioData["apps"];
|
|
users?: ScenarioData["users"];
|
|
usersApartFromOrganizer?: ScenarioData["users"];
|
|
webhooks?: ScenarioData["webhooks"];
|
|
workflows?: ScenarioData["workflows"];
|
|
bookings?: ScenarioData["bookings"];
|
|
payment?: ScenarioData["payment"];
|
|
},
|
|
org?: { id: number | null } | undefined | null
|
|
) {
|
|
if (_users && (usersApartFromOrganizer.length || organizer)) {
|
|
throw new Error("When users are provided, usersApartFromOrganizer and organizer should not be provided");
|
|
}
|
|
const users = _users ? _users : organizer ? [organizer, ...usersApartFromOrganizer] : [];
|
|
if (!users.length) {
|
|
throw new Error("No users are specified in any way");
|
|
}
|
|
if (org) {
|
|
const orgId = org.id;
|
|
if (!orgId) {
|
|
throw new Error("If org is specified org.id is required");
|
|
}
|
|
users.forEach((user) => {
|
|
user.profiles = [
|
|
{
|
|
organizationId: orgId,
|
|
username: user.username || "",
|
|
uid: ProfileRepository.generateProfileUid(),
|
|
},
|
|
];
|
|
});
|
|
}
|
|
eventTypes.forEach((eventType) => {
|
|
if (
|
|
eventType.users?.filter((eventTypeUser) => {
|
|
return !users.find((userToCreate) => userToCreate.id === eventTypeUser.id);
|
|
}).length
|
|
) {
|
|
throw new Error(`EventType ${eventType.id} has users that are not present in ScenarioData["users"]`);
|
|
}
|
|
});
|
|
return {
|
|
eventTypes: eventTypes.map((eventType, index) => {
|
|
return {
|
|
...eventType,
|
|
teamId: eventType.teamId || null,
|
|
team: {
|
|
id: eventType.teamId ?? eventType.team?.id,
|
|
parentId: org ? org.id : null,
|
|
bookingLimits: eventType?.team?.bookingLimits,
|
|
includeManagedEventsInLimits: eventType?.team?.includeManagedEventsInLimits,
|
|
},
|
|
title: `Test Event Type - ${index + 1}`,
|
|
description: `It's a test event type - ${index + 1}`,
|
|
};
|
|
}),
|
|
users: users.map((user) => {
|
|
const newUser = {
|
|
...user,
|
|
organizationId: user.organizationId ?? null,
|
|
};
|
|
return newUser;
|
|
}),
|
|
apps: [...apps],
|
|
webhooks,
|
|
bookings: bookings || [],
|
|
workflows,
|
|
payment,
|
|
} satisfies ScenarioData;
|
|
}
|
|
|
|
export function enableEmailFeature() {
|
|
prismock.feature.create({
|
|
data: {
|
|
slug: "emails",
|
|
enabled: false,
|
|
type: "KILL_SWITCH",
|
|
},
|
|
});
|
|
}
|
|
|
|
export function mockNoTranslations() {
|
|
log.silly("Mocking i18n.getTranslation to return identity function");
|
|
i18nMock.getTranslation.mockImplementation(() => {
|
|
return new Promise((resolve) => {
|
|
const identityFn = (key: string) => key;
|
|
resolve(identityFn);
|
|
});
|
|
});
|
|
}
|
|
|
|
export const enum BookingLocations {
|
|
CalVideo = "integrations:daily",
|
|
ZoomVideo = "integrations:zoom",
|
|
GoogleMeet = "integrations:google:meet",
|
|
}
|
|
|
|
export type CalendarServiceMethodMockCallBase = {
|
|
calendarServiceConstructorArgs: {
|
|
credential: CredentialForCalendarService;
|
|
};
|
|
};
|
|
|
|
type CreateEventMethodMockCall = CalendarServiceMethodMockCallBase & {
|
|
args: {
|
|
calEvent: CalendarEvent;
|
|
credentialId: number;
|
|
externalCalendarId?: string;
|
|
};
|
|
};
|
|
|
|
type UpdateEventMethodMockCall = CalendarServiceMethodMockCallBase & {
|
|
args: {
|
|
uid: string;
|
|
event: CalendarEvent;
|
|
externalCalendarId?: string;
|
|
};
|
|
};
|
|
|
|
type DeleteEventMethodMockCall = CalendarServiceMethodMockCallBase & {
|
|
args: {
|
|
uid: string;
|
|
event: CalendarEvent;
|
|
externalCalendarId?: string;
|
|
};
|
|
};
|
|
|
|
type GetAvailabilityMethodMockCall = CalendarServiceMethodMockCallBase & {
|
|
args: {
|
|
dateFrom: string;
|
|
dateTo: string;
|
|
selectedCalendars: IntegrationCalendar[];
|
|
shouldServeCache?: boolean;
|
|
};
|
|
};
|
|
|
|
export type CalendarServiceMethodMock = {
|
|
createEventCalls: CreateEventMethodMockCall[];
|
|
updateEventCalls: UpdateEventMethodMockCall[];
|
|
deleteEventCalls: DeleteEventMethodMockCall[];
|
|
getAvailabilityCalls: GetAvailabilityMethodMockCall[];
|
|
};
|
|
|
|
/**
|
|
* @param metadataLookupKey
|
|
* @param calendarData Specify uids and other data to be faked to be returned by createEvent and updateEvent
|
|
*/
|
|
export async function mockCalendar(
|
|
metadataLookupKey: keyof typeof appStoreMetadata,
|
|
calendarData?: {
|
|
create?: {
|
|
id?: string;
|
|
uid?: string;
|
|
iCalUID?: string;
|
|
appSpecificData?: {
|
|
googleCalendar?: {
|
|
hangoutLink?: string;
|
|
};
|
|
office365Calendar?: {
|
|
url?: string;
|
|
};
|
|
};
|
|
};
|
|
update?: {
|
|
id?: string;
|
|
uid: string;
|
|
iCalUID?: string;
|
|
appSpecificData?: {
|
|
googleCalendar?: {
|
|
hangoutLink?: string;
|
|
};
|
|
office365Calendar?: {
|
|
url?: string;
|
|
};
|
|
};
|
|
};
|
|
busySlots?: { start: `${string}Z`; end: `${string}Z` }[];
|
|
creationCrash?: boolean;
|
|
updationCrash?: boolean;
|
|
getAvailabilityCrash?: boolean;
|
|
}
|
|
): Promise<CalendarServiceMethodMock> {
|
|
const appStoreLookupKey = metadataLookupKey;
|
|
const normalizedCalendarData = calendarData || {
|
|
create: {
|
|
uid: "MOCK_ID",
|
|
},
|
|
update: {
|
|
uid: "UPDATED_MOCK_ID",
|
|
},
|
|
};
|
|
log.silly(`Mocking ${appStoreLookupKey} on appStoreMock`);
|
|
|
|
const createEventCalls: CreateEventMethodMockCall[] = [];
|
|
const updateEventCalls: UpdateEventMethodMockCall[] = [];
|
|
const deleteEventCalls: DeleteEventMethodMockCall[] = [];
|
|
const getAvailabilityCalls: GetAvailabilityMethodMockCall[] = [];
|
|
const app = appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata];
|
|
|
|
const { CalendarServiceMap } = await import("@calcom/app-store/calendar.services.generated");
|
|
const calendarServiceKey = appStoreLookupKey as keyof typeof CalendarServiceMap;
|
|
|
|
const calendarServicePromise = CalendarServiceMap[calendarServiceKey];
|
|
if (calendarServicePromise) {
|
|
const resolvedService = await calendarServicePromise;
|
|
vi.mocked(resolvedService.default as any).mockImplementation(function MockCalendarService(
|
|
credential: any
|
|
) {
|
|
return {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
createEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
|
|
if (calendarData?.creationCrash) {
|
|
throw new Error("MockCalendarService.createEvent fake error");
|
|
}
|
|
const [calEvent, credentialId, externalCalendarId] = rest;
|
|
log.debug(
|
|
"mockCalendar.createEvent",
|
|
JSON.stringify({ calEvent, credentialId, externalCalendarId })
|
|
);
|
|
createEventCalls.push({
|
|
args: {
|
|
calEvent,
|
|
credentialId,
|
|
externalCalendarId,
|
|
},
|
|
calendarServiceConstructorArgs: {
|
|
credential,
|
|
},
|
|
});
|
|
const isGoogleMeetLocation = calEvent?.location === BookingLocations.GoogleMeet;
|
|
if (app.type === "google_calendar") {
|
|
return Promise.resolve({
|
|
type: app.type,
|
|
additionalInfo: {
|
|
hangoutLink:
|
|
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
|
|
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
|
|
},
|
|
hangoutLink:
|
|
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
|
|
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
|
|
uid: normalizedCalendarData.create?.uid || "GOOGLE_CALENDAR_EVENT_ID",
|
|
id: normalizedCalendarData.create?.id || "GOOGLE_CALENDAR_EVENT_ID",
|
|
iCalUID:
|
|
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "GOOGLE_CALENDAR_EVENT_ID",
|
|
password: "MOCK_PASSWORD",
|
|
url:
|
|
normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink ||
|
|
"https://GOOGLE_MEET_URL_IN_CALENDAR_EVENT",
|
|
});
|
|
} else if (app.type === "office365_calendar") {
|
|
return Promise.resolve({
|
|
type: app.type,
|
|
additionalInfo: {},
|
|
uid: normalizedCalendarData.create?.uid || "OFFICE_365_CALENDAR_EVENT_ID",
|
|
id: normalizedCalendarData.create?.id || "OFFICE_365_CALENDAR_EVENT_ID",
|
|
iCalUID:
|
|
normalizedCalendarData.create?.iCalUID || calEvent.iCalUID || "OFFICE_365_CALENDAR_EVENT_ID",
|
|
password: "MOCK_PASSWORD",
|
|
url:
|
|
normalizedCalendarData.create?.appSpecificData?.office365Calendar?.url ||
|
|
"https://UNUSED_URL",
|
|
});
|
|
} else {
|
|
return Promise.resolve({
|
|
type: app.type,
|
|
additionalInfo: {},
|
|
uid: "PROBABLY_UNUSED_UID",
|
|
hangoutLink:
|
|
(isGoogleMeetLocation
|
|
? normalizedCalendarData.create?.appSpecificData?.googleCalendar?.hangoutLink
|
|
: null) || "https://UNUSED_URL",
|
|
// A Calendar is always expected to return an id.
|
|
id: normalizedCalendarData.create?.id || "FALLBACK_MOCK_CALENDAR_EVENT_ID",
|
|
iCalUID: normalizedCalendarData.create?.iCalUID,
|
|
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
|
|
password: "MOCK_PASSWORD",
|
|
url: "https://UNUSED_URL",
|
|
});
|
|
}
|
|
},
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
updateEvent: async function (...rest: any[]): Promise<NewCalendarEventType> {
|
|
if (calendarData?.updationCrash) {
|
|
throw new Error("MockCalendarService.updateEvent fake error");
|
|
}
|
|
const [uid, event, externalCalendarId] = rest;
|
|
log.silly("mockCalendar.updateEvent", JSON.stringify({ uid, event, externalCalendarId }));
|
|
// eslint-disable-next-line prefer-rest-params
|
|
updateEventCalls.push({
|
|
args: {
|
|
uid,
|
|
event,
|
|
externalCalendarId,
|
|
},
|
|
calendarServiceConstructorArgs: {
|
|
credential,
|
|
},
|
|
});
|
|
const isGoogleMeetLocation = event.location === BookingLocations.GoogleMeet;
|
|
return Promise.resolve({
|
|
type: app.type,
|
|
additionalInfo: {},
|
|
uid: "PROBABLY_UNUSED_UID",
|
|
iCalUID: normalizedCalendarData.update?.iCalUID,
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
id: normalizedCalendarData.update?.uid || "FALLBACK_MOCK_ID",
|
|
// Password and URL seems useless for CalendarService, plan to remove them if that's the case
|
|
password: "MOCK_PASSWORD",
|
|
url: "https://UNUSED_URL",
|
|
location: isGoogleMeetLocation ? "https://UNUSED_URL" : undefined,
|
|
hangoutLink:
|
|
(isGoogleMeetLocation
|
|
? normalizedCalendarData.update?.appSpecificData?.googleCalendar?.hangoutLink
|
|
: null) || "https://UNUSED_URL",
|
|
conferenceData: isGoogleMeetLocation ? event.conferenceData : undefined,
|
|
});
|
|
},
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
deleteEvent: async (...rest: any[]) => {
|
|
log.silly("mockCalendar.deleteEvent", JSON.stringify({ rest }));
|
|
// eslint-disable-next-line prefer-rest-params
|
|
deleteEventCalls.push({
|
|
args: {
|
|
uid: rest[0],
|
|
event: rest[1],
|
|
externalCalendarId: rest[2],
|
|
},
|
|
calendarServiceConstructorArgs: {
|
|
credential,
|
|
},
|
|
});
|
|
},
|
|
getAvailability: async (...rest: any[]): Promise<EventBusyDate[]> => {
|
|
if (calendarData?.getAvailabilityCrash) {
|
|
throw new Error("MockCalendarService.getAvailability fake error");
|
|
}
|
|
const [dateFrom, dateTo, selectedCalendars, shouldServeCache] = rest;
|
|
getAvailabilityCalls.push({
|
|
args: {
|
|
dateFrom,
|
|
dateTo,
|
|
selectedCalendars,
|
|
shouldServeCache,
|
|
},
|
|
calendarServiceConstructorArgs: {
|
|
credential,
|
|
},
|
|
});
|
|
return new Promise((resolve) => {
|
|
resolve(calendarData?.busySlots || []);
|
|
});
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
return {
|
|
createEventCalls,
|
|
updateEventCalls,
|
|
deleteEventCalls,
|
|
getAvailabilityCalls,
|
|
};
|
|
}
|
|
|
|
export async function mockCalendarToHaveNoBusySlots(
|
|
metadataLookupKey: keyof typeof appStoreMetadata,
|
|
calendarData?: Parameters<typeof mockCalendar>[1]
|
|
) {
|
|
calendarData = calendarData || {
|
|
create: {
|
|
uid: "MOCK_ID",
|
|
},
|
|
update: {
|
|
uid: "UPDATED_MOCK_ID",
|
|
},
|
|
};
|
|
return await mockCalendar(metadataLookupKey, { ...calendarData, busySlots: [] });
|
|
}
|
|
|
|
export async function mockCalendarToCrashOnCreateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
|
|
return await mockCalendar(metadataLookupKey, { creationCrash: true });
|
|
}
|
|
|
|
export async function mockCalendarToCrashOnUpdateEvent(metadataLookupKey: keyof typeof appStoreMetadata) {
|
|
return await mockCalendar(metadataLookupKey, { updationCrash: true });
|
|
}
|
|
|
|
export function mockVideoApp({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
videoMeetingData,
|
|
creationCrash,
|
|
updationCrash,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
appStoreLookupKey?: string;
|
|
videoMeetingData?: {
|
|
password: string;
|
|
id: string;
|
|
url: string;
|
|
};
|
|
creationCrash?: boolean;
|
|
updationCrash?: boolean;
|
|
}) {
|
|
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
|
|
videoMeetingData = videoMeetingData || {
|
|
id: "MOCK_ID",
|
|
password: "MOCK_PASS",
|
|
url: `http://mock-${metadataLookupKey}.example.com`,
|
|
};
|
|
log.silly("mockVideoApp", JSON.stringify({ metadataLookupKey, appStoreLookupKey }));
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const createMeetingCalls: any[] = [];
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const updateMeetingCalls: any[] = [];
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const deleteMeetingCalls: any[] = [];
|
|
|
|
const mockVideoAdapter = (credential: any) => {
|
|
return {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
createMeeting: (...rest: any[]) => {
|
|
if (creationCrash) {
|
|
throw new Error("MockVideoApiAdapter.createMeeting fake error");
|
|
}
|
|
createMeetingCalls.push({
|
|
credential,
|
|
args: rest,
|
|
});
|
|
|
|
return Promise.resolve({
|
|
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
|
|
...videoMeetingData,
|
|
});
|
|
},
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
updateMeeting: async (...rest: any[]) => {
|
|
if (updationCrash) {
|
|
throw new Error("MockVideoApiAdapter.updateMeeting fake error");
|
|
}
|
|
const [bookingRef, calEvent] = rest;
|
|
updateMeetingCalls.push({
|
|
credential,
|
|
args: rest,
|
|
});
|
|
if (!bookingRef.type) {
|
|
throw new Error("bookingRef.type is not defined");
|
|
}
|
|
if (!calEvent.organizer) {
|
|
throw new Error("calEvent.organizer is not defined");
|
|
}
|
|
log.silly("MockVideoApiAdapter.updateMeeting", JSON.stringify({ bookingRef, calEvent }));
|
|
return Promise.resolve({
|
|
type: appStoreMetadata[metadataLookupKey as keyof typeof appStoreMetadata].type,
|
|
...videoMeetingData,
|
|
});
|
|
},
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
deleteMeeting: async (...rest: any[]) => {
|
|
log.silly("MockVideoApiAdapter.deleteMeeting", JSON.stringify(rest));
|
|
deleteMeetingCalls.push({
|
|
credential,
|
|
args: rest,
|
|
});
|
|
},
|
|
};
|
|
};
|
|
|
|
mockVideoAdapterRegistry[appStoreLookupKey] = Promise.resolve({
|
|
default: mockVideoAdapter,
|
|
});
|
|
|
|
return {
|
|
createMeetingCalls,
|
|
updateMeetingCalls,
|
|
deleteMeetingCalls,
|
|
};
|
|
}
|
|
|
|
export function mockSuccessfulVideoMeetingCreation({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
videoMeetingData,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
appStoreLookupKey?: string;
|
|
videoMeetingData?: {
|
|
password: string;
|
|
id: string;
|
|
url: string;
|
|
};
|
|
}) {
|
|
return mockVideoApp({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
videoMeetingData,
|
|
});
|
|
}
|
|
|
|
export function mockVideoAppToCrashOnCreateMeeting({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
appStoreLookupKey?: string;
|
|
}) {
|
|
return mockVideoApp({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
creationCrash: true,
|
|
});
|
|
}
|
|
|
|
export function mockPaymentApp({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
appStoreLookupKey?: string;
|
|
}) {
|
|
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
|
|
const { paymentUid, externalId } = getMockPaymentService();
|
|
|
|
return {
|
|
paymentUid,
|
|
externalId,
|
|
};
|
|
}
|
|
|
|
export function mockErrorOnVideoMeetingCreation({
|
|
metadataLookupKey,
|
|
appStoreLookupKey,
|
|
}: {
|
|
metadataLookupKey: string;
|
|
appStoreLookupKey?: string;
|
|
}) {
|
|
appStoreLookupKey = appStoreLookupKey || metadataLookupKey;
|
|
|
|
const mockErrorAdapter = () => ({
|
|
createMeeting: () => {
|
|
throw new MockError("Error creating Video meeting");
|
|
},
|
|
});
|
|
|
|
mockVideoAdapterRegistry[appStoreLookupKey] = Promise.resolve({
|
|
default: mockErrorAdapter,
|
|
});
|
|
}
|
|
|
|
export function mockCrmApp(
|
|
metadataLookupKey: string,
|
|
crmData?: {
|
|
createContacts?: {
|
|
id: string;
|
|
email: string;
|
|
}[];
|
|
getContacts?: {
|
|
id: string;
|
|
email: string;
|
|
ownerEmail: string;
|
|
}[];
|
|
}
|
|
) {
|
|
let contactsCreated: {
|
|
id: string;
|
|
email: string;
|
|
}[] = [];
|
|
let contactsQueried: {
|
|
id: string;
|
|
email: string;
|
|
ownerEmail: string;
|
|
}[] = [];
|
|
const eventsCreated: boolean[] = [];
|
|
|
|
// Mock the CrmServiceMap directly instead of using the old app-store index approach
|
|
vi.doMock("@calcom/app-store/crm.apps.generated", async (importOriginal) => {
|
|
const original = await importOriginal<typeof import("@calcom/app-store/crm.apps.generated")>();
|
|
|
|
class MockCrmService {
|
|
constructor() {
|
|
log.debug("Create CrmService");
|
|
}
|
|
|
|
createContact() {
|
|
if (crmData?.createContacts) {
|
|
contactsCreated = crmData.createContacts;
|
|
return Promise.resolve(crmData?.createContacts);
|
|
}
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
getContacts(email: string) {
|
|
if (crmData?.getContacts) {
|
|
contactsQueried = crmData?.getContacts;
|
|
const contactsOfEmail = contactsQueried.filter((contact) => contact.email === email);
|
|
return Promise.resolve(contactsOfEmail);
|
|
}
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
createEvent() {
|
|
eventsCreated.push(true);
|
|
return Promise.resolve({});
|
|
}
|
|
}
|
|
|
|
return {
|
|
...original,
|
|
CrmServiceMap: {
|
|
...original.CrmServiceMap,
|
|
[metadataLookupKey]: Promise.resolve({
|
|
default: MockCrmService,
|
|
}),
|
|
},
|
|
};
|
|
});
|
|
|
|
return {
|
|
contactsCreated,
|
|
contactsQueried,
|
|
eventsCreated,
|
|
};
|
|
}
|
|
|
|
export function getBooker({
|
|
name,
|
|
email,
|
|
attendeePhoneNumber,
|
|
}: {
|
|
name: string;
|
|
email: string;
|
|
attendeePhoneNumber?: string;
|
|
}) {
|
|
return {
|
|
name,
|
|
email,
|
|
attendeePhoneNumber,
|
|
};
|
|
}
|
|
|
|
export function getMockedStripePaymentEvent({ paymentIntentId }: { paymentIntentId: string }) {
|
|
return {
|
|
id: null,
|
|
data: {
|
|
object: {
|
|
id: paymentIntentId,
|
|
},
|
|
},
|
|
} as unknown as Stripe.Event;
|
|
}
|
|
|
|
export async function mockPaymentSuccessWebhookFromStripe({ externalId }: { externalId: string }) {
|
|
let webhookResponse = null;
|
|
try {
|
|
await handleStripePaymentSuccess(getMockedStripePaymentEvent({ paymentIntentId: externalId }));
|
|
} catch (e) {
|
|
log.silly("mockPaymentSuccessWebhookFromStripe:catch", JSON.stringify(e));
|
|
webhookResponse = e as HttpError;
|
|
}
|
|
return { webhookResponse };
|
|
}
|
|
|
|
export function getExpectedCalEventForBookingRequest({
|
|
bookingRequest,
|
|
eventType,
|
|
}: {
|
|
bookingRequest: ReturnType<typeof getMockRequestDataForBooking>;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
eventType: any;
|
|
}) {
|
|
return {
|
|
// keep adding more fields as needed, so that they can be verified in all scenarios
|
|
type: eventType.slug,
|
|
// Not sure why, but milliseconds are missing in cal Event.
|
|
startTime: bookingRequest.start.replace(".000Z", "Z"),
|
|
endTime: bookingRequest.end.replace(".000Z", "Z"),
|
|
};
|
|
}
|
|
|
|
export function getMockBookingReference(
|
|
bookingReference: Partial<BookingReference> & Pick<BookingReference, "type" | "uid" | "credentialId">
|
|
) {
|
|
let credentialId = bookingReference.credentialId;
|
|
if (bookingReference.type === appStoreMetadata.dailyvideo.type) {
|
|
// Right now we seems to be storing credentialId for `dailyvideo` in BookingReference as null. Another possible value is 0 in there.
|
|
credentialId = null;
|
|
log.debug("Ensuring null credentialId for dailyvideo");
|
|
}
|
|
return {
|
|
...bookingReference,
|
|
credentialId,
|
|
};
|
|
}
|
|
|
|
export function getMockBookingAttendee(
|
|
attendee: Omit<Attendee, "bookingId" | "phoneNumber" | "email" | "noShow"> & {
|
|
bookingSeat?: AttendeeBookingSeatInput;
|
|
phoneNumber?: string | null;
|
|
email: string;
|
|
noShow?: boolean;
|
|
}
|
|
) {
|
|
return {
|
|
id: attendee.id,
|
|
timeZone: attendee.timeZone,
|
|
name: attendee.name,
|
|
email: attendee.email,
|
|
locale: attendee.locale,
|
|
bookingSeat: attendee.bookingSeat || null,
|
|
phoneNumber: attendee.phoneNumber ?? undefined,
|
|
noShow: attendee.noShow ?? false,
|
|
};
|
|
}
|
|
|
|
const getMockAppStatus = ({
|
|
slug,
|
|
failures,
|
|
success,
|
|
overrideName,
|
|
}: {
|
|
slug: string;
|
|
failures: number;
|
|
success: number;
|
|
overrideName?: string;
|
|
}) => {
|
|
const foundEntry = Object.entries(appStoreMetadata).find(([, app]) => {
|
|
return app.slug === slug;
|
|
});
|
|
if (!foundEntry) {
|
|
throw new Error("App not found for the slug");
|
|
}
|
|
const foundApp = foundEntry[1];
|
|
return {
|
|
appName: overrideName ?? foundApp.slug,
|
|
type: foundApp.type,
|
|
failures,
|
|
success,
|
|
errors: [],
|
|
};
|
|
};
|
|
export const getMockFailingAppStatus = ({ slug }: { slug: string }) => {
|
|
return getMockAppStatus({ slug, failures: 1, success: 0 });
|
|
};
|
|
|
|
export const getMockPassingAppStatus = ({ slug, overrideName }: { slug: string; overrideName?: string }) => {
|
|
return getMockAppStatus({ slug, overrideName, failures: 0, success: 1 });
|
|
};
|
|
|
|
export const replaceDates = (dates: string[], replacement: Record<string, string>) => {
|
|
return dates.map((date) => {
|
|
return date.replace(/(.*)T/, (_, group1) => `${replacement[group1]}T`);
|
|
});
|
|
};
|
|
|
|
export const getDefaultBookingFields = ({
|
|
emailField,
|
|
bookingFields = [],
|
|
}: {
|
|
emailField?: Fields[number];
|
|
bookingFields: Fields;
|
|
}) => {
|
|
return [
|
|
{
|
|
name: "name",
|
|
type: "name",
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system",
|
|
required: true,
|
|
defaultLabel: "your_name",
|
|
},
|
|
!!emailField
|
|
? emailField
|
|
: {
|
|
name: "email",
|
|
type: "email",
|
|
label: "",
|
|
hidden: false,
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system",
|
|
required: true,
|
|
placeholder: "",
|
|
defaultLabel: "email_address",
|
|
},
|
|
{
|
|
name: "location",
|
|
type: "radioInput",
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system",
|
|
required: false,
|
|
defaultLabel: "location",
|
|
getOptionsAt: "locations",
|
|
optionsInputs: {
|
|
phone: { type: "phone", required: true, placeholder: "" },
|
|
attendeeInPerson: { type: "address", required: true, placeholder: "" },
|
|
},
|
|
hideWhenJustOneOption: true,
|
|
},
|
|
{
|
|
name: "title",
|
|
type: "text",
|
|
hidden: true,
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system-but-optional",
|
|
required: true,
|
|
defaultLabel: "what_is_this_meeting_about",
|
|
defaultPlaceholder: "",
|
|
},
|
|
{
|
|
name: "notes",
|
|
type: "textarea",
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system-but-optional",
|
|
required: false,
|
|
defaultLabel: "additional_notes",
|
|
defaultPlaceholder: "share_additional_notes",
|
|
},
|
|
{
|
|
name: "guests",
|
|
type: "multiemail",
|
|
hidden: false,
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system-but-optional",
|
|
required: false,
|
|
defaultLabel: "additional_guests",
|
|
defaultPlaceholder: "email",
|
|
},
|
|
{
|
|
name: "rescheduleReason",
|
|
type: "textarea",
|
|
views: [{ id: "reschedule", label: "Reschedule View" }],
|
|
sources: [{ id: "default", type: "default", label: "Default" }],
|
|
editable: "system-but-optional",
|
|
required: false,
|
|
defaultLabel: "reason_for_reschedule",
|
|
defaultPlaceholder: "reschedule_placeholder",
|
|
},
|
|
...bookingFields,
|
|
] as Fields;
|
|
};
|
|
|
|
export const createDelegationCredential = async (orgId: number, type: "google" | "office365" = "google") => {
|
|
if (type === "google") {
|
|
const encryptedServiceAccountKey = {
|
|
type: "service_account",
|
|
auth_uri: "https://accounts.google.com/o/oauth2/auth",
|
|
client_id: "CLIENT_ID",
|
|
token_uri: "https://oauth2.googleapis.com/token",
|
|
project_id: "PROJECT_ID",
|
|
encrypted_credentials: `{"private_key": "PRIVATE_KEY"}`,
|
|
client_email: "CLIENT_EMAIL",
|
|
private_key_id: "PRIVATE_KEY_ID",
|
|
universe_domain: "googleapis.com",
|
|
client_x509_cert_url: "CLIENT_X509_CERT_URL",
|
|
auth_provider_x509_cert_url: "AUTH_PROVIDER_X509_CERT_URL",
|
|
};
|
|
|
|
const workspace = await prismock.workspacePlatform.create({
|
|
data: {
|
|
name: "Test Workspace",
|
|
slug: "google",
|
|
description: "Test Workspace",
|
|
defaultServiceAccountKey: encryptedServiceAccountKey,
|
|
enabled: true,
|
|
},
|
|
});
|
|
|
|
const decryptedServiceAccountKey = {
|
|
type: "service_account",
|
|
auth_uri: "https://accounts.google.com/o/oauth2/auth",
|
|
client_id: "CLIENT_ID",
|
|
token_uri: "https://oauth2.googleapis.com/token",
|
|
project_id: "PROJECT_ID",
|
|
private_key: "PRIVATE_KEY",
|
|
client_email: "CLIENT_EMAIL",
|
|
private_key_id: "PRIVATE_KEY_ID",
|
|
universe_domain: "googleapis.com",
|
|
client_x509_cert_url: "CLIENT_X509_CERT_URL",
|
|
auth_provider_x509_cert_url: "AUTH_PROVIDER_X509_CERT_URL",
|
|
};
|
|
|
|
const delegationCredential = await prismock.delegationCredential.create({
|
|
data: {
|
|
workspacePlatform: {
|
|
connect: {
|
|
id: workspace.id,
|
|
},
|
|
},
|
|
domain: "example.com",
|
|
enabled: true,
|
|
organization: {
|
|
connect: {
|
|
id: orgId,
|
|
},
|
|
},
|
|
serviceAccountKey: workspace.defaultServiceAccountKey,
|
|
},
|
|
});
|
|
|
|
return { ...delegationCredential, serviceAccountKey: decryptedServiceAccountKey };
|
|
} else if (type === "office365") {
|
|
const encryptedServiceAccountKey = {
|
|
client_id: "CLIENT_ID",
|
|
encrypted_credentials: `{"private_key": "PRIVATE_KEY"}`,
|
|
tenant_id: "TENANT_ID",
|
|
};
|
|
|
|
const workspace = await prismock.workspacePlatform.create({
|
|
data: {
|
|
name: "Test Workspace",
|
|
slug: "office365",
|
|
description: "Test Workspace",
|
|
defaultServiceAccountKey: encryptedServiceAccountKey,
|
|
enabled: true,
|
|
},
|
|
});
|
|
|
|
const decryptedServiceAccountKey = {
|
|
client_id: "CLIENT_ID",
|
|
private_key: "PRIVATE_KEY",
|
|
tenant_id: "TENANT_ID",
|
|
};
|
|
|
|
const delegationCredential = await prismock.delegationCredential.create({
|
|
data: {
|
|
workspacePlatform: {
|
|
connect: {
|
|
id: workspace.id,
|
|
},
|
|
},
|
|
domain: "example.com",
|
|
enabled: true,
|
|
organization: {
|
|
connect: {
|
|
id: orgId,
|
|
},
|
|
},
|
|
serviceAccountKey: workspace.defaultServiceAccountKey,
|
|
},
|
|
});
|
|
|
|
return { ...delegationCredential, serviceAccountKey: decryptedServiceAccountKey };
|
|
}
|
|
throw new Error(`Unsupported type: ${type}`);
|
|
};
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export const buildDelegationCredential = ({ serviceAccountKey }: { serviceAccountKey: any }) => {
|
|
return {
|
|
id: -1,
|
|
key: {
|
|
access_token: "NOOP_UNUSED_DELEGATION_TOKEN",
|
|
},
|
|
delegatedTo: {
|
|
serviceAccountKey,
|
|
},
|
|
};
|
|
};
|