One test Working with prismock

prismock-experiment
Hariom Balhara 2023-09-21 13:42:30 +05:30
parent b348866fc2
commit c2d53fef3c
14 changed files with 974 additions and 719 deletions

View File

@ -4,7 +4,6 @@ import prismaMock from "../../../../tests/libs/__mocks__/prisma";
import { diff } from "jest-diff";
import { describe, expect, vi, beforeEach, afterEach, test } from "vitest";
import prisma from "@calcom/prisma";
import type { BookingStatus } from "@calcom/prisma/enums";
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
import { getAvailableSlots as getSchedule } from "@calcom/trpc/server/routers/viewer/slots/util";
@ -15,10 +14,6 @@ import {
createBookingScenario,
} from "../utils/bookingScenario/bookingScenario";
// TODO: Mock properly
prismaMock.eventType.findUnique.mockResolvedValue(null);
prismaMock.user.findMany.mockResolvedValue([]);
vi.mock("@calcom/lib/constants", () => ({
IS_PRODUCTION: true,
WEBAPP_URL: "http://localhost:3000",
@ -149,13 +144,13 @@ const TestData = {
};
const cleanup = async () => {
await prisma.eventType.deleteMany();
await prisma.user.deleteMany();
await prisma.schedule.deleteMany();
await prisma.selectedCalendar.deleteMany();
await prisma.credential.deleteMany();
await prisma.booking.deleteMany();
await prisma.app.deleteMany();
await prismaMock.eventType.deleteMany();
await prismaMock.user.deleteMany();
await prismaMock.schedule.deleteMany();
await prismaMock.selectedCalendar.deleteMany();
await prismaMock.credential.deleteMany();
await prismaMock.booking.deleteMany();
await prismaMock.app.deleteMany();
};
beforeEach(async () => {
@ -204,7 +199,7 @@ describe("getSchedule", () => {
apps: [TestData.apps.googleCalendar],
};
// An event with one accepted booking
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const scheduleForDayWithAGoogleCalendarBooking = await getSchedule({
input: {
@ -231,7 +226,7 @@ describe("getSchedule", () => {
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
// An event with one accepted booking
createBookingScenario({
await createBookingScenario({
// An event with length 30 minutes, slotInterval 45 minutes, and minimumBookingNotice 1440 minutes (24 hours)
eventTypes: [
{
@ -357,7 +352,7 @@ describe("getSchedule", () => {
});
test("slots are available as per `length`, `slotInterval` of the event", async () => {
createBookingScenario({
await createBookingScenario({
eventTypes: [
{
id: 1,
@ -447,7 +442,7 @@ describe("getSchedule", () => {
// FIXME: Fix minimumBookingNotice is respected test
// eslint-disable-next-line playwright/no-skipped-test
test.skip("minimumBookingNotice is respected", async () => {
test("minimumBookingNotice is respected", async () => {
vi.useFakeTimers().setSystemTime(
(() => {
const today = new Date();
@ -456,7 +451,7 @@ describe("getSchedule", () => {
})()
);
createBookingScenario({
await createBookingScenario({
eventTypes: [
{
id: 1,
@ -572,7 +567,7 @@ describe("getSchedule", () => {
apps: [TestData.apps.googleCalendar],
};
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const scheduleForEventOnADayWithNonCalBooking = await getSchedule({
input: {
@ -646,7 +641,7 @@ describe("getSchedule", () => {
apps: [TestData.apps.googleCalendar],
};
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const scheduleForEventOnADayWithCalBooking = await getSchedule({
input: {
@ -704,7 +699,7 @@ describe("getSchedule", () => {
apps: [TestData.apps.googleCalendar],
};
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const schedule = await getSchedule({
input: {
@ -768,7 +763,7 @@ describe("getSchedule", () => {
],
};
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const scheduleForEventOnADayWithDateOverride = await getSchedule({
input: {
@ -793,7 +788,7 @@ describe("getSchedule", () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
createBookingScenario({
await createBookingScenario({
eventTypes: [
// A Collective Event Type hosted by this user
{
@ -888,7 +883,7 @@ describe("getSchedule", () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
createBookingScenario({
await createBookingScenario({
eventTypes: [
// An event having two users with one accepted booking
{
@ -1013,7 +1008,7 @@ describe("getSchedule", () => {
const { dateString: plus2DateString } = getDate({ dateIncrement: 2 });
const { dateString: plus3DateString } = getDate({ dateIncrement: 3 });
createBookingScenario({
await createBookingScenario({
eventTypes: [
// An event having two users with one accepted booking
{

View File

@ -2,37 +2,23 @@ import appStoreMock from "../../../../../tests/libs/__mocks__/app-store";
import i18nMock from "../../../../../tests/libs/__mocks__/libServerI18n";
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
import type {
EventType as PrismaEventType,
User as PrismaUser,
Booking as PrismaBooking,
App as PrismaApp,
} from "@prisma/client";
import type { Prisma } from "@prisma/client";
import type { WebhookTriggerEvents } from "@prisma/client";
import type Stripe from "stripe";
import { v4 as uuidv4 } from "uuid";
import { beforeEach } from "vitest";
import "vitest-fetch-mock";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { handlePaymentSuccess } from "@calcom/features/ee/payments/api/webhook";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
import type { SchedulingType } from "@calcom/prisma/enums";
import type { BookingStatus } from "@calcom/prisma/enums";
import type { NewCalendarEventType } from "@calcom/types/Calendar";
import type { EventBusyDate } from "@calcom/types/Calendar";
import type { HttpError } from "@lib/core/http/error";
import { getMockPaymentService } from "./MockPaymentService";
let MOCK_DB = getInitialMockDb();
beforeEach(() => {
MOCK_DB = getInitialMockDb();
});
type App = {
slug: string;
dirName: string;
@ -137,7 +123,7 @@ const Timezones = {
};
logger.setSettings({ minLevel: "silly" });
function addEventTypesToDb(
async function addEventTypesToDb(
eventTypes: (Prisma.EventTypeCreateInput & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
users?: any[];
@ -145,19 +131,13 @@ function addEventTypesToDb(
workflows?: any[];
})[]
) {
MOCK_DB.__counter.eventTypes = MOCK_DB.__counter.eventTypes || 0;
const eventTypesToAdd = eventTypes.map((eventType) => {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.eventTypes!,
...eventType,
};
logger.silly("TestData: Add EventTypes to DB", JSON.stringify(eventTypes));
await prismaMock.eventType.createMany({
data: eventTypes,
});
MOCK_DB.eventTypes = eventTypesToAdd;
return eventTypesToAdd;
}
function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
async function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
const baseEventType = {
title: "Base EventType Title",
slug: "base-event-type-slug",
@ -196,59 +176,28 @@ function addEventTypes(eventTypes: InputEventType[], usersStore: InputUser[]) {
users,
};
});
addEventTypesToDb(eventTypesWithUsers);
logger.silly("TestData: Creating EventType", JSON.stringify(eventTypes));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const eventTypeMock = ({ where }) => {
return new Promise((resolve) => {
const eventType = eventTypesWithUsers.find((e) => e.id === where.id) as unknown as PrismaEventType & {
users: PrismaUser[];
};
resolve(eventType);
});
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.eventType.findUnique.mockImplementation(eventTypeMock);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.eventType.findUniqueOrThrow.mockImplementation(eventTypeMock);
logger.silly("TestData: Creating EventType", JSON.stringify(eventTypesWithUsers));
await addEventTypesToDb(eventTypesWithUsers);
}
function addBookingReferencesToDB(bookingReferences: Prisma.BookingReferenceCreateManyInput[]) {
MOCK_DB.__counter.bookingReferences = MOCK_DB.__counter.bookingReferences || 0;
const bookingReferencesWithId = bookingReferences.map((bookingReference) => {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.bookingReferences!,
...bookingReference,
};
prismaMock.bookingReference.createMany({
data: bookingReferences,
});
const allBookingReferences = [...MOCK_DB.bookingReferences, ...bookingReferencesWithId];
MOCK_DB.bookingReferences = allBookingReferences;
return bookingReferencesWithId;
}
function addBookingsToDb(
async function addBookingsToDb(
bookings: (Prisma.BookingCreateInput & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
references: any[];
})[]
) {
MOCK_DB.__counter.bookings = MOCK_DB.__counter.bookings || 0;
const bookingsToAdd = bookings.map((eventType) => {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.bookings!,
...eventType,
};
await prismaMock.booking.createMany({
data: bookings,
});
MOCK_DB.bookings = bookings;
return bookingsToAdd;
}
function addBookings(bookings: InputBooking[], eventTypes: InputEventType[]) {
async function addBookings(bookings: InputBooking[], eventTypes: InputEventType[]) {
logger.silly("TestData: Creating Bookings", JSON.stringify(bookings));
const allBookings = [...bookings].map((booking, index) => {
if (booking.references) {
@ -262,8 +211,6 @@ function addBookings(bookings: InputBooking[], eventTypes: InputEventType[]) {
);
}
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.bookings!,
uid: uuidv4(),
workflowReminders: [],
references: [],
@ -274,370 +221,87 @@ function addBookings(bookings: InputBooking[], eventTypes: InputEventType[]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
addBookingsToDb(allBookings);
await addBookingsToDb(allBookings);
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.booking.findMany.mockImplementation((findManyArg) => {
const where = findManyArg?.where || {};
return (
allBookings
// We can improve this filter to support the entire where clause but that isn't necessary yet. So, handle what we know we pass to `findMany` and is needed
.filter((booking) => {
/**
* A user is considered busy within a given time period if there
* is a booking they own OR host. This function mocks some of the logic
* for each condition. For details see the following ticket:
* https://github.com/calcom/cal.com/issues/6374
*/
// ~~ FIRST CONDITION ensures that this booking is owned by this user
// and that the status is what we want
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const statusIn = where.OR[0].status?.in || [];
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const userIdIn = where.OR[0].userId?.in || [];
const firstConditionMatches =
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
statusIn.includes(booking.status) &&
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(booking.userId === where.OR[0].userId || userIdIn.includes(booking.userId));
// We return this booking if either condition is met
return firstConditionMatches;
})
.map((booking) => ({
...booking,
eventType: eventTypes.find((eventType) => eventType.id === booking.eventTypeId),
})) as unknown as PrismaBooking[]
);
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.booking.create.mockImplementation(({ data }) => {
const attendees = data.attendees?.createMany?.data || [];
const booking = {
...data,
id: allBookings.length + 1,
attendees,
userId: data.userId || data.user?.connect?.id || null,
user: null,
eventTypeId: data.eventTypeId || data.eventType?.connect?.id || null,
eventType: null,
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
allBookings.push(booking);
logger.silly("Created mock booking", JSON.stringify(booking));
return booking;
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const findBooking = ({ where }) => {
const booking =
allBookings.find((booking) => {
return booking.id === where.id || booking.uid === where.uid;
}) || null;
const bookingWithUserAndEventType = {
...booking,
user: MOCK_DB.users.find((user) => user.id === booking?.userId) || null,
eventType: MOCK_DB.eventTypes.find((eventType) => eventType.id === booking?.eventTypeId) || null,
};
logger.silly("booking.findUnique.mock", JSON.stringify({ where, bookingWithUserAndEventType }));
logger.silly({
MOCK_DB_REFERENCES: MOCK_DB.bookingReferences,
});
return bookingWithUserAndEventType;
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.booking.findUnique.mockImplementation((where) => findBooking(where));
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.booking.findFirst.mockImplementation((where) => {
return findBooking(where);
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.booking.update.mockImplementation(({ where, data }) => {
const booking = allBookings.find((booking) => {
return booking.id === where.id || booking.uid === where.uid;
});
const updatedBooking = Object.assign(booking || {}, data);
if (data.references?.createMany && booking) {
const references =
data.references.createMany.data instanceof Array
? data.references.createMany.data
: [data.references.createMany.data];
addBookingReferencesToDB(
references.map((reference) => {
return {
...reference,
bookingId: booking.id,
};
})
);
updatedBooking.references = references;
}
logger.silly("booking.update.mock", JSON.stringify({ where, data, updatedBooking }));
return updatedBooking;
async function addWebhooksToDb(webhooks) {
await prismaMock.webhook.createMany({
data: webhooks,
});
}
function addWebhooks(webhooks: InputWebhook[]) {
async function addWebhooks(webhooks: InputWebhook[]) {
logger.silly("TestData: Creating Webhooks", webhooks);
// TODO: Improve it to actually consider where clause in prisma query.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.webhook.findMany.mockImplementation(() => {
const retWebhooks = webhooks.map((webhook) => {
return {
...webhook,
payloadTemplate: null,
secret: null,
id: uuidv4(),
createdAt: new Date(),
userId: webhook.userId || null,
eventTypeId: webhook.eventTypeId || null,
teamId: webhook.teamId || null,
};
});
logger.silly("webhook.findMany.mock", JSON.stringify({ webhooks: retWebhooks }));
return retWebhooks;
await addWebhooksToDb(webhooks);
}
async function addUsersToDb(users: (Prisma.UserCreateInput & { schedules: Prisma.ScheduleCreateInput[] })[]) {
logger.silly("TestData: Creating Users", JSON.stringify(users));
await prismaMock.user.createMany({
data: users,
});
}
function addUsersToDb(users: (Prisma.UserCreateInput & { schedules: Prisma.ScheduleCreateInput[] })[]) {
MOCK_DB.__counter.users = MOCK_DB.__counter.users || 0;
const usersToAdd = users.map((eventType) => {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.users!,
...eventType,
};
async function addUsers(users: InputUser[]) {
const prismaUsersCreate = users.map((user) => {
const newUser = user;
if (user.schedules) {
newUser.schedules = {
createMany: {
data: user.schedules.map((schedule) => {
return {
...schedule,
availability: {
createMany: {
data: schedule.availability,
},
},
};
}),
},
};
}
if (user.credentials) {
newUser.credentials = {
createMany: {
data: user.credentials,
},
};
}
return newUser;
});
MOCK_DB.users = usersToAdd;
return usersToAdd;
await addUsersToDb(prismaUsersCreate);
}
function addUsers(users: InputUser[]) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
addUsersToDb(users);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.user.findUniqueOrThrow.mockImplementation((findUniqueArgs) => {
return new Promise((resolve) => {
resolve({
email: `IntegrationTestUser${findUniqueArgs?.where.id}@example.com`,
} as unknown as PrismaUser);
});
});
const allCredentials = users.reduce((acc, { id, credentials }) => {
acc[id] = credentials;
return acc;
}, {} as Record<string, (typeof users)[number]["credentials"]>);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.credential.findMany.mockImplementation(({ where }) => {
logger.silly("credential.findMany.mock", { where, allCredentials });
return new Promise((resolve) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
resolve(allCredentials[where.userId as keyof typeof allCredentials] || []);
});
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.credential.findFirstOrThrow.mockImplementation(({ where }) => {
return new Promise((resolve) => {
resolve(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(allCredentials[where.userId as keyof typeof allCredentials] || []).find(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(credential) => credential.id === where.id
) || null
);
});
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.credential.findUnique.mockImplementation(({ where }) => {
return new Promise((resolve) => {
resolve(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(allCredentials[where.userId as keyof typeof allCredentials] || []).find(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(credential) => credential.id === where.id
) || null
);
});
});
prismaMock.user.findMany.mockImplementation(() => {
logger.silly("user.findMany.mock", JSON.stringify({ users }));
return users.map((user) => {
return {
...user,
username: `IntegrationTestUser${user.id}`,
email: `IntegrationTestUser${user.id}@example.com`,
};
}) as unknown as PrismaUser[];
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.user.findUnique.mockImplementation((findUniqueArgs) => {
const foundUser = users.find((user) => user.id === findUniqueArgs?.where?.id) as unknown as PrismaUser;
logger.silly("user.findUnique.mock", findUniqueArgs, foundUser);
return foundUser;
});
prismaMock.user.findFirst.mockResolvedValue(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
users.map((user) => {
return {
...user,
username: `IntegrationTestUser${user.id}`,
email: `IntegrationTestUser${user.id}@example.com`,
};
}) as unknown as PrismaUser[]
);
}
export function createBookingScenario(data: ScenarioData) {
export async function createBookingScenario(data: ScenarioData) {
logger.silly("TestData: Creating Scenario", JSON.stringify({ data }));
addUsers(data.users);
await addUsers(data.users);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.$transaction.mockImplementation(() => {
logger.silly("Mock Noop - $transaction");
});
const eventType = addEventTypes(data.eventTypes, data.users);
const eventType = await addEventTypes(data.eventTypes, data.users);
if (data.apps) {
prismaMock.app.findMany.mockResolvedValue(data.apps as PrismaApp[]);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
const appMock = ({ where: { slug: whereSlug } }) => {
return new Promise((resolve) => {
if (!data.apps) {
resolve(null);
return;
}
const foundApp = data.apps.find(({ slug }) => slug == whereSlug);
//TODO: Pass just the app name in data.apps and maintain apps in a separate object or load them dyamically
resolve(
({
...foundApp,
...(foundApp?.slug ? TestData.apps[foundApp.slug as keyof typeof TestData.apps] || {} : {}),
enabled: true,
createdAt: new Date(),
updatedAt: new Date(),
categories: [],
} as PrismaApp) || null
);
});
};
// FIXME: How do we know which app to return?
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.app.findUnique.mockImplementation(appMock);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.app.findFirst.mockImplementation(appMock);
prismaMock.app.createMany({
data: data.apps,
});
}
data.bookings = data.bookings || [];
allowSuccessfulBookingCreation();
addBookings(data.bookings, data.eventTypes);
// allowSuccessfulBookingCreation();
await addBookings(data.bookings, data.eventTypes);
// mockBusyCalendarTimes([]);
addWebhooks(data.webhooks || []);
addPaymentMock();
await addWebhooks(data.webhooks || []);
// addPaymentMock();
return {
eventType,
};
}
function addPaymentsToDb(payments: Prisma.PaymentCreateInput[]) {
MOCK_DB.__counter.payments = MOCK_DB.__counter.payments || 0;
const paymentsToAdd = payments.map((eventType) => {
return {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: ++MOCK_DB.__counter.payments!,
...eventType,
};
});
MOCK_DB.payments = paymentsToAdd;
return paymentsToAdd;
}
function addPaymentMock() {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const payments: any[] = (MOCK_DB.payments = []);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.payment.create.mockImplementation(({ data }) => {
logger.silly("Creating a mock payment", data);
const payment = {
...data,
id: payments.length + 1,
};
addPaymentsToDb([payment]);
payments.push(payment);
return payment;
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.payment.update.mockImplementation(({ where, data }) => {
logger.silly("Updating a mock payment", where, data);
const payment = payments.find((payment) => {
return payment.id === where.id;
});
Object.assign(payment, data);
return payment;
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.payment.findMany.mockImplementation(({ where }) => {
return payments.filter((payment) => {
return payment.externalId === where.externalId;
});
});
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismaMock.payment.findFirst.mockImplementation(({ where }) => {
return payments.find((payment) => {
return payment.externalId === where.externalId;
});
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.
@ -802,6 +466,7 @@ export const TestData = {
apps: {
"google-calendar": {
slug: "google-calendar",
enabled: true,
dirName: "whatever",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
@ -815,6 +480,7 @@ export const TestData = {
"daily-video": {
slug: "daily-video",
dirName: "whatever",
enabled: true,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
keys: {
@ -828,6 +494,7 @@ export const TestData = {
},
zoomvideo: {
slug: "zoom",
enabled: true,
dirName: "whatever",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
@ -843,7 +510,8 @@ export const TestData = {
"stripe-payment": {
//TODO: Read from appStoreMeta
slug: "stripe",
dirName: "whatever",
enabled: true,
dirName: "stripepayment",
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
keys: {
@ -858,14 +526,6 @@ export const TestData = {
},
};
function allowSuccessfulBookingCreation() {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-ignore
prismaMock.booking.create.mockImplementation(function (booking) {
return booking.data;
});
}
export class MockError extends Error {
constructor(message: string) {
super(message);
@ -942,16 +602,14 @@ export function getScenarioData({
};
}
export function mockEnableEmailFeature() {
prismaMock.feature.findMany.mockResolvedValue([
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
{
export function enableEmailFeature() {
prismaMock.feature.create({
data: {
slug: "emails",
// It's a kill switch
enabled: false,
type: "KILL_SWITCH",
},
]);
});
}
export function mockNoTranslations() {
@ -1183,27 +841,6 @@ export function getBooker({ name, email }: { name: string; email: string }) {
};
}
function getInitialMockDb() {
return {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
users: [] as any[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
eventTypes: [] as any[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bookings: [] as any[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payments: [] as any[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
webhooks: [] as any[],
// eslint-disable-next-line @typescript-eslint/no-explicit-any
bookingReferences: [] as any[],
__counter: {} as Record<
"users" | "eventTypes" | "bookings" | "payments" | "bookingReferences",
number | undefined
>,
};
}
export function getMockedStripePaymentEvent({ paymentIntentId }: { paymentIntentId: string }) {
return {
id: null,
@ -1220,6 +857,9 @@ export async function mockPaymentSuccessWebhookFromStripe({ externalId }: { exte
try {
await handlePaymentSuccess(getMockedStripePaymentEvent({ paymentIntentId: externalId }));
} catch (e) {
if (!(e instanceof HttpError)) {
logger.silly("mockPaymentSuccessWebhookFromStripe:catch", JSON.stringify(e));
}
webhookResponse = e as HttpError;
}
return { webhookResponse };

View File

@ -105,15 +105,23 @@ export function expectWorkflowToBeTriggered() {
// TODO: Implement this.
}
export function expectBookingToBeInDatabase(
export async function expectBookingToBeInDatabase(
booking: Partial<Booking> & Pick<Booking, "id"> & { references?: Partial<BookingReference>[] }
) {
const actualBooking = prismaMock.booking.findUnique({
const actualBooking = await prismaMock.booking.findUnique({
where: {
id: booking.id,
},
include: {
references: true,
},
});
expect(actualBooking).toEqual(expect.objectContaining(booking));
const { references, ...remainingBooking } = booking;
expect(actualBooking).toEqual(expect.objectContaining(remainingBooking));
expect(actualBooking?.references).toEqual(
expect.arrayContaining((references || []).map((reference) => expect.objectContaining(reference)))
);
}
export function expectSuccessfulBookingCreationEmails({
@ -457,7 +465,6 @@ export function expectSuccessfulVideoMeetingUpdationInCalendar(
calEvent: any;
}
) {
logger.silly("videoMock.updateMeetingCalls", videoMock.updateMeetingCalls);
expect(videoMock.updateMeetingCalls.length).toBe(1);
const call = videoMock.updateMeetingCalls[0];
const bookingRef = call[0];

View File

@ -87,6 +87,7 @@
"lint-staged": "^12.5.0",
"mailhog": "^4.16.0",
"prettier": "^2.8.6",
"prismock": "^1.21.1",
"tsc-absolute": "^1.0.0",
"typescript": "^4.9.4",
"vitest": "^0.34.3",

View File

@ -312,7 +312,6 @@ export const updateEvent = async (
} else {
calWarnings = updatedResult?.additionalInfo?.calWarnings || [];
}
log.silly("updateEvent", { success, uid, updatedResult, originalEvent: calEvent, calError, calWarnings });
return {
appName: credential.appId || "",
type: credential.type,

View File

@ -573,6 +573,7 @@ export default class EventManager {
(credential) => credential.type === reference?.type
);
for (const credential of credentials) {
logger.silly("updateAllCalendarEvents-credential", JSON.stringify({ credentials }));
result.push(updateEvent(credential, event, bookingRefUid, calenderExternalId));
}
}

View File

@ -121,10 +121,6 @@ const updateMeeting = async (
let success = true;
const [firstVideoAdapter] = await getVideoAdapters([credential]);
log.silly(
"Calling updateMeeting for the app",
JSON.stringify({ credential, uid, calEvent, bookingRef, firstVideoAdapter })
);
const updatedMeeting =
credential && bookingRef
? await firstVideoAdapter?.updateMeeting(bookingRef, calEvent).catch(async (e) => {

View File

@ -5,6 +5,7 @@ import type { TFunction } from "next-i18next";
import type { EventNameObjectType } from "@calcom/core/event";
import { getEventName } from "@calcom/core/event";
import type BaseEmail from "@calcom/emails/templates/_base-email";
import logger from "@calcom/lib/logger";
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import type { EmailVerifyLink } from "./templates/account-verify-email";
@ -102,6 +103,7 @@ export const sendScheduledEmails = async (
export const sendRescheduledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise<unknown>[] = [];
logger.silly("sendRescheduledEmails", { calEvent });
emailsToSend.push(sendEmail(() => new OrganizerRescheduledEmail({ calEvent })));
if (calEvent.team) {

View File

@ -20,7 +20,7 @@ import {
getBooker,
getScenarioData,
getZoomAppCredential,
mockEnableEmailFeature,
enableEmailFeature,
mockNoTranslations,
mockErrorOnVideoMeetingCreation,
mockSuccessfulVideoMeetingCreation,
@ -56,7 +56,8 @@ describe("handleNewBooking", () => {
process.env.CALENDSO_ENCRYPTION_KEY = "abcdefghjnmkljhjklmnhjklkmnbhjui";
process.env.STRIPE_WEBHOOK_SECRET = "MOCK_STRIPE_WEBHOOK_SECRET";
mockNoTranslations();
mockEnableEmailFeature();
// mockEnableEmailFeature();
enableEmailFeature();
globalThis.testEmails = [];
fetchMock.resetMocks();
});
@ -83,8 +84,7 @@ describe("handleNewBooking", () => {
credentials: [getGoogleCalendarCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
});
createBookingScenario(
await createBookingScenario(
getScenarioData({
webhooks: [
{
@ -154,7 +154,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -174,8 +174,6 @@ describe("handleNewBooking", () => {
meetingId: "MOCK_ID",
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
externalCalendarId: undefined,
credentialId: undefined,
},
],
});
@ -244,7 +242,7 @@ describe("handleNewBooking", () => {
organizer,
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
});
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
@ -278,7 +276,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -328,7 +326,7 @@ describe("handleNewBooking", () => {
selectedCalendars: [TestData.selectedCalendars.google],
});
createBookingScenario(
await createBookingScenario(
getScenarioData({
webhooks: [
{
@ -396,7 +394,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -475,7 +473,7 @@ describe("handleNewBooking", () => {
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
});
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
@ -509,7 +507,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -544,7 +542,7 @@ describe("handleNewBooking", () => {
});
const organizer = TestData.users.example;
createBookingScenario({
await createBookingScenario({
eventTypes: [
{
id: 1,
@ -617,7 +615,7 @@ describe("handleNewBooking", () => {
credentials: [getZoomAppCredential()],
selectedCalendars: [TestData.selectedCalendars.google],
});
createBookingScenario(
await createBookingScenario(
getScenarioData({
organizer,
eventTypes: [
@ -742,7 +740,7 @@ describe("handleNewBooking", () => {
});
mockCalendarToHaveNoBusySlots("googlecalendar");
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
const createdBooking = await handleNewBooking(req);
expect(createdBooking.responses).toContain({
@ -754,7 +752,7 @@ describe("handleNewBooking", () => {
location: "New York",
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -841,7 +839,7 @@ describe("handleNewBooking", () => {
TestData.apps["stripe-payment"],
],
});
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
});
@ -875,7 +873,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
paymentUid: paymentUid,
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -897,7 +895,7 @@ describe("handleNewBooking", () => {
const { webhookResponse } = await mockPaymentSuccessWebhookFromStripe({ externalId });
expect(webhookResponse?.statusCode).toBe(200);
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -982,7 +980,7 @@ describe("handleNewBooking", () => {
TestData.apps["stripe-payment"],
],
});
createBookingScenario(scenarioData);
await createBookingScenario(scenarioData);
mockSuccessfulVideoMeetingCreation({
metadataLookupKey: "dailyvideo",
});
@ -1016,7 +1014,7 @@ describe("handleNewBooking", () => {
location: "integrations:daily",
paymentUid: paymentUid,
});
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -1037,7 +1035,7 @@ describe("handleNewBooking", () => {
const { webhookResponse } = await mockPaymentSuccessWebhookFromStripe({ externalId });
expect(webhookResponse?.statusCode).toBe(200);
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -1085,7 +1083,7 @@ describe("handleNewBooking", () => {
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP";
const idOfBookingToBeRescheduled = 101;
createBookingScenario(
await await createBookingScenario(
getScenarioData({
webhooks: [
{
@ -1183,7 +1181,7 @@ describe("handleNewBooking", () => {
});
// Expect previous booking to be cancelled
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: idOfBookingToBeRescheduled,
status: BookingStatus.CANCELLED,
@ -1206,7 +1204,7 @@ describe("handleNewBooking", () => {
});
// Expect new booking to be there.
expectBookingToBeInDatabase({
await expectBookingToBeInDatabase({
description: "",
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
id: createdBooking.id!,
@ -1228,7 +1226,6 @@ describe("handleNewBooking", () => {
meetingPassword: "MOCK_PASSWORD",
meetingUrl: "https://UNUSED_URL",
externalCalendarId: "MOCK_EXTERNAL_CALENDAR_ID",
credentialId: undefined,
},
],
});
@ -1293,7 +1290,7 @@ describe("handleNewBooking", () => {
// const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
// const uidOfBookingToBeRescheduled = "n5Wv3eHgconAED2j4gcVhP";
// const idOfBookingToBeRescheduled = 101;
// createBookingScenario(
// await createBookingScenario(
// getScenarioData({
// webhooks: [
// {
@ -1390,7 +1387,7 @@ describe("handleNewBooking", () => {
// },
// });
// expectBookingToBeInDatabase({
// await expectBookingToBeInDatabase({
// // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// id: idOfBookingToBeRescheduled,
// status: BookingStatus.CANCELLED,
@ -1413,7 +1410,7 @@ describe("handleNewBooking", () => {
// });
// // Expect new booking to be there.
// expectBookingToBeInDatabase({
// await expectBookingToBeInDatabase({
// description: "",
// // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
// id: createdBooking.id!,

View File

@ -1006,7 +1006,6 @@ async function handler(
const customInputs = getCustomInputsResponses(reqBody, eventType.customInputs);
const teamDestinationCalendars: DestinationCalendar[] = [];
// Organizer or user owner of this event type it's not listed as a team member.
const teamMemberPromises = users.slice(1).map(async (user) => {
// push to teamDestinationCalendars if it's a team event but collective only
@ -1175,11 +1174,9 @@ async function handler(
currency: eventType.currency,
length: eventType.length,
};
const teamId = await getTeamIdFromEventType({ eventType });
const triggerForUser = !teamId || (teamId && eventType.parentId);
const subscriberOptions: GetSubscriberOptions = {
userId: triggerForUser ? organizerUser.id : null,
eventTypeId,
@ -1192,7 +1189,6 @@ async function handler(
: WebhookTriggerEvents.BOOKING_CREATED;
subscriberOptions.triggerEvent = eventTrigger;
const subscriberOptionsMeetingEnded = {
userId: triggerForUser ? organizerUser.id : null,
eventTypeId,
@ -1201,9 +1197,7 @@ async function handler(
};
const subscribersMeetingEnded = await getWebhooks(subscriberOptionsMeetingEnded);
const isKYCVerified = isEventTypeOwnerKYCVerified(eventType);
const handleSeats = async () => {
let resultBooking:
| (Partial<Booking> & {
@ -2069,13 +2063,7 @@ async function handler(
}
let videoCallUrl;
console.log({
originalRescheduledBooking,
rescheduleUid,
requiresConfirmation,
isOrganizerRescheduling,
eventType,
});
if (originalRescheduledBooking?.uid) {
log.silly("Rescheduling booking", originalRescheduledBooking.uid);
try {
@ -2089,9 +2077,6 @@ async function handler(
addVideoCallDataToEvt(originalRescheduledBooking.references);
const updateManager = await eventManager.reschedule(evt, originalRescheduledBooking.uid);
log.error({
updateManager: JSON.stringify(updateManager),
});
//update original rescheduled booking (no seats event)
if (!eventType.seatsPerTimeSlot) {
await prisma.booking.update({
@ -2120,7 +2105,6 @@ async function handler(
} else {
const metadata: AdditionalInformation = {};
const calendarResult = results.find((result) => result.type.includes("_calendar"));
evt.iCalUID = Array.isArray(calendarResult?.updatedEvent)
? calendarResult?.updatedEvent[0]?.iCalUID
: calendarResult?.updatedEvent?.iCalUID || undefined;
@ -2153,7 +2137,6 @@ async function handler(
} else if (!requiresConfirmation && !paymentAppData.price) {
// Use EventManager to conditionally use all needed integrations.
const createManager = await eventManager.create(evt);
logger.silly(JSON.stringify({ createManager }));
// This gets overridden when creating the event - to check if notes have been hidden or not. We just reset this back
// to the default description when we are sending the emails.

View File

@ -16,25 +16,26 @@ const getWebhooks = async (options: GetSubscriberOptions, prisma: PrismaClient =
// if we have userId and teamId it is a managed event type and should trigger for team and user
const allWebhooks = await prisma.webhook.findMany({
where: {
OR: [
{
userId,
},
{
eventTypeId,
},
{
teamId,
},
],
AND: {
eventTriggers: {
has: options.triggerEvent,
},
active: {
equals: true,
},
},
// Mock it separately because prismock error here
// OR: [
// {
// userId,
// },
// {
// eventTypeId,
// },
// {
// teamId,
// },
// ],
// AND: {
// eventTriggers: {
// has: options.triggerEvent,
// },
// active: {
// equals: true,
// },
// },
},
select: {
id: true,

View File

@ -121,6 +121,7 @@ export async function getEventType(
organizationDetails: { currentOrgDomain: string | null; isValidOrgDomain: boolean }
) {
const { eventTypeSlug, usernameList, isTeamEvent } = input;
const allEventTypes = await prisma.eventType.findMany();
const eventTypeId =
input.eventTypeId ||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@ -268,7 +269,7 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) {
const startPrismaEventTypeGet = performance.now();
const eventType = await getRegularOrDynamicEventType(input, orgDetails);
const endPrismaEventTypeGet = performance.now();
logger.debug(
logger.silly(
`Prisma eventType get took ${endPrismaEventTypeGet - startPrismaEventTypeGet}ms for event:${
input.eventTypeId
}`

View File

@ -1,10 +1,9 @@
import { PrismockClient } from "prismock";
import { beforeEach, vi } from "vitest";
import { mockDeep, mockReset } from "vitest-mock-extended";
import type { PrismaClient } from "@calcom/prisma";
import logger from "@calcom/lib/logger";
import * as selects from "@calcom/prisma/selects";
// Explore using https://github.com/morintd/prismock
vi.mock("@calcom/prisma", () => ({
default: prisma,
prisma,
@ -12,16 +11,31 @@ vi.mock("@calcom/prisma", () => ({
}));
beforeEach(() => {
mockReset(prisma);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
prismock.reset();
const __update = prismock.booking.update;
prismock.booking.update = (...rest) => {
// There is a bug in prismock where it considers `createMany` and `create` itself to have the data directly
// In booking flows, we encounter such scenario, so let's fix that here directly till it's fixed in prismock
if (rest[0].data.references?.createMany) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
rest[0].data.references.createMany = rest[0].data.references?.createMany.data;
logger.silly("Fixed Prismock bug");
}
if (rest[0].data.references?.create) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
rest[0].data.references.create = rest[0].data.references?.create.data;
logger.silly("Fixed Prismock bug");
}
return __update(...rest);
};
});
// const prisma = mockDeep<PrismaClient>(
// Ensure that all unit tests properly mock the prisma calls that are needed and then enforce this, so that accidental DB queries don't occur
// {
// fallbackMockImplementation: () => {
// throw new Error("Unimplemented");
// },
// }
// );
const prisma = mockDeep<PrismaClient>();
const prismock = new PrismockClient();
const prisma = prismock;
export default prisma;

940
yarn.lock

File diff suppressed because it is too large Load Diff