first commit
This commit is contained in:
12
calcom/apps/web/test/README.md
Normal file
12
calcom/apps/web/test/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Unit and Integration Tests
|
||||
|
||||
Make sure you have copied .env.test.example to .env.test
|
||||
|
||||
You can run all jest tests as
|
||||
|
||||
`yarn test`
|
||||
|
||||
You can run tests matching specific description by following command
|
||||
`yarn test -t getSchedule`
|
||||
|
||||
Tip: Use `--watchAll` flag to run tests on every change
|
||||
15
calcom/apps/web/test/docker-compose.yml
Normal file
15
calcom/apps/web/test/docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Set the version of docker compose to use
|
||||
version: "3.9"
|
||||
|
||||
# The containers that compose the project
|
||||
services:
|
||||
db:
|
||||
image: postgres:13
|
||||
restart: always
|
||||
container_name: integration-tests-prisma
|
||||
ports:
|
||||
- "5433:5432"
|
||||
environment:
|
||||
POSTGRES_USER: prisma
|
||||
POSTGRES_PASSWORD: prisma
|
||||
POSTGRES_DB: tests
|
||||
31
calcom/apps/web/test/fixtures/fixtures.ts
vendored
Normal file
31
calcom/apps/web/test/fixtures/fixtures.ts
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
// my-test.ts
|
||||
import { test as base } from "vitest";
|
||||
|
||||
import { getTestEmails } from "@calcom/lib/testEmails";
|
||||
import { getTestSMS } from "@calcom/lib/testSMS";
|
||||
|
||||
export interface Fixtures {
|
||||
emails: ReturnType<typeof getEmailsFixture>;
|
||||
sms: ReturnType<typeof getSMSFixture>;
|
||||
}
|
||||
|
||||
export const test = base.extend<Fixtures>({
|
||||
emails: async ({}, use) => {
|
||||
await use(getEmailsFixture());
|
||||
},
|
||||
sms: async ({}, use) => {
|
||||
await use(getSMSFixture());
|
||||
},
|
||||
});
|
||||
|
||||
function getEmailsFixture() {
|
||||
return {
|
||||
get: getTestEmails,
|
||||
};
|
||||
}
|
||||
|
||||
function getSMSFixture() {
|
||||
return {
|
||||
get: getTestSMS,
|
||||
};
|
||||
}
|
||||
291
calcom/apps/web/test/handlers/requestReschedule.test.ts
Normal file
291
calcom/apps/web/test/handlers/requestReschedule.test.ts
Normal file
@@ -0,0 +1,291 @@
|
||||
import { getSampleUserInSession } from "../utils/bookingScenario/getSampleUserInSession";
|
||||
import { setupAndTeardown } from "../utils/bookingScenario/setupAndTeardown";
|
||||
import {
|
||||
createBookingScenario,
|
||||
getGoogleCalendarCredential,
|
||||
TestData,
|
||||
getOrganizer,
|
||||
getBooker,
|
||||
getScenarioData,
|
||||
getMockBookingAttendee,
|
||||
getDate,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
import { expectBookingRequestRescheduledEmails } from "@calcom/web/test/utils/bookingScenario/expects";
|
||||
|
||||
import type { Request, Response } from "express";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { describe } from "vitest";
|
||||
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
import { BookingStatus } from "@calcom/prisma/enums";
|
||||
import type { TRequestRescheduleInputSchema } from "@calcom/trpc/server/routers/viewer/bookings/requestReschedule.schema";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/trpc";
|
||||
import { test } from "@calcom/web/test/fixtures/fixtures";
|
||||
|
||||
export type CustomNextApiRequest = NextApiRequest & Request;
|
||||
|
||||
export type CustomNextApiResponse = NextApiResponse & Response;
|
||||
|
||||
describe("Handler: requestReschedule", () => {
|
||||
setupAndTeardown();
|
||||
|
||||
describe("User Event Booking", () => {
|
||||
test(`should be able to request-reschedule for a user booking
|
||||
1. RequestReschedule emails go to both attendee and the person requesting the reschedule`, async ({
|
||||
emails,
|
||||
}) => {
|
||||
const { requestRescheduleHandler } = await import(
|
||||
"@calcom/trpc/server/routers/viewer/bookings/requestReschedule.handler"
|
||||
);
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const bookingUid = "MOCKED_BOOKING_UID";
|
||||
const eventTypeSlug = "event-type-1";
|
||||
await createBookingScenario(
|
||||
getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
},
|
||||
],
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slug: eventTypeSlug,
|
||||
slotInterval: 45,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
uid: bookingUid,
|
||||
eventTypeId: 1,
|
||||
userId: 101,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
startTime: `${plus1DateString}T05:00:00.000Z`,
|
||||
endTime: `${plus1DateString}T05:15:00.000Z`,
|
||||
attendees: [
|
||||
getMockBookingAttendee({
|
||||
id: 2,
|
||||
name: booker.name,
|
||||
email: booker.email,
|
||||
// Booker's locale when the fresh booking happened earlier
|
||||
locale: "hi",
|
||||
// Booker's timezone when the fresh booking happened earlier
|
||||
timeZone: "Asia/Kolkata",
|
||||
noShow: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
const loggedInUser = {
|
||||
organizationId: null,
|
||||
id: 101,
|
||||
username: "reschedule-requester",
|
||||
name: "Reschedule Requester",
|
||||
email: "reschedule-requester@example.com",
|
||||
};
|
||||
await requestRescheduleHandler(
|
||||
getTrpcHandlerData({
|
||||
user: loggedInUser,
|
||||
input: {
|
||||
bookingId: bookingUid,
|
||||
rescheduleReason: "",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expectBookingRequestRescheduledEmails({
|
||||
booking: {
|
||||
uid: bookingUid,
|
||||
},
|
||||
booker,
|
||||
organizer: organizer,
|
||||
loggedInUser,
|
||||
emails,
|
||||
bookNewTimePath: `/${organizer.username}/${eventTypeSlug}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Team Event Booking", () => {
|
||||
test(`should be able to request-reschedule for a team event booking
|
||||
1. RequestReschedule emails go to both attendee and the person requesting the reschedule`, async ({
|
||||
emails,
|
||||
}) => {
|
||||
const { requestRescheduleHandler } = await import(
|
||||
"@calcom/trpc/server/routers/viewer/bookings/requestReschedule.handler"
|
||||
);
|
||||
const booker = getBooker({
|
||||
email: "booker@example.com",
|
||||
name: "Booker",
|
||||
});
|
||||
|
||||
const organizer = getOrganizer({
|
||||
name: "Organizer",
|
||||
email: "organizer@example.com",
|
||||
id: 101,
|
||||
teams: [
|
||||
{
|
||||
membership: {
|
||||
accepted: true,
|
||||
},
|
||||
team: {
|
||||
id: 1,
|
||||
name: "Team 1",
|
||||
slug: "team-1",
|
||||
},
|
||||
},
|
||||
],
|
||||
schedules: [TestData.schedules.IstWorkHours],
|
||||
credentials: [getGoogleCalendarCredential()],
|
||||
selectedCalendars: [TestData.selectedCalendars.google],
|
||||
});
|
||||
const { dateString: plus1DateString } = getDate({ dateIncrement: 1 });
|
||||
const bookingUid = "MOCKED_BOOKING_UID";
|
||||
const eventTypeSlug = "event-type-1";
|
||||
await createBookingScenario(
|
||||
getScenarioData({
|
||||
webhooks: [
|
||||
{
|
||||
userId: organizer.id,
|
||||
eventTriggers: ["BOOKING_CREATED"],
|
||||
subscriberUrl: "http://my-webhook.example.com",
|
||||
active: true,
|
||||
eventTypeId: 1,
|
||||
appId: null,
|
||||
},
|
||||
],
|
||||
eventTypes: [
|
||||
{
|
||||
id: 1,
|
||||
slug: eventTypeSlug,
|
||||
slotInterval: 45,
|
||||
teamId: 1,
|
||||
schedulingType: SchedulingType.COLLECTIVE,
|
||||
length: 45,
|
||||
users: [
|
||||
{
|
||||
id: 101,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
bookings: [
|
||||
{
|
||||
uid: bookingUid,
|
||||
eventTypeId: 1,
|
||||
userId: 101,
|
||||
status: BookingStatus.ACCEPTED,
|
||||
startTime: `${plus1DateString}T05:00:00.000Z`,
|
||||
endTime: `${plus1DateString}T05:15:00.000Z`,
|
||||
attendees: [
|
||||
getMockBookingAttendee({
|
||||
id: 2,
|
||||
name: booker.name,
|
||||
email: booker.email,
|
||||
// Booker's locale when the fresh booking happened earlier
|
||||
locale: "hi",
|
||||
// Booker's timezone when the fresh booking happened earlier
|
||||
timeZone: "Asia/Kolkata",
|
||||
noShow: false,
|
||||
}),
|
||||
],
|
||||
},
|
||||
],
|
||||
organizer,
|
||||
apps: [TestData.apps["google-calendar"], TestData.apps["daily-video"]],
|
||||
})
|
||||
);
|
||||
|
||||
const loggedInUser = {
|
||||
organizationId: null,
|
||||
id: 101,
|
||||
username: "reschedule-requester",
|
||||
name: "Reschedule Requester",
|
||||
email: "reschedule-requester@example.com",
|
||||
};
|
||||
await requestRescheduleHandler(
|
||||
getTrpcHandlerData({
|
||||
user: loggedInUser,
|
||||
input: {
|
||||
bookingId: bookingUid,
|
||||
rescheduleReason: "",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expectBookingRequestRescheduledEmails({
|
||||
booking: {
|
||||
uid: bookingUid,
|
||||
},
|
||||
booker,
|
||||
organizer: organizer,
|
||||
loggedInUser,
|
||||
emails,
|
||||
bookNewTimePath: "/team/team-1/event-type-1",
|
||||
});
|
||||
});
|
||||
test.todo("Verify that the email should go to organizer as well as the team members");
|
||||
});
|
||||
});
|
||||
|
||||
function getTrpcHandlerData({
|
||||
input,
|
||||
user,
|
||||
}: {
|
||||
input: TRequestRescheduleInputSchema;
|
||||
user: Partial<Omit<NonNullable<TrpcSessionUser>, "id" | "email" | "username">> &
|
||||
Pick<NonNullable<TrpcSessionUser>, "id" | "email" | "username">;
|
||||
}) {
|
||||
return {
|
||||
ctx: {
|
||||
user: {
|
||||
...getSampleUserInSession(),
|
||||
...user,
|
||||
avatarUrl: user.avatarUrl || null,
|
||||
profile: {
|
||||
upId: "",
|
||||
id: 1,
|
||||
name: "",
|
||||
avatarUrl: "",
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
username: user.username || "",
|
||||
organizationId: null,
|
||||
organization: null,
|
||||
bufferTime: 5,
|
||||
avatar: null,
|
||||
},
|
||||
} as unknown as NonNullable<TrpcSessionUser>,
|
||||
},
|
||||
input: input,
|
||||
};
|
||||
}
|
||||
15
calcom/apps/web/test/jest-resolver.js
Normal file
15
calcom/apps/web/test/jest-resolver.js
Normal file
@@ -0,0 +1,15 @@
|
||||
module.exports = (path, options) => {
|
||||
// Call the defaultResolver, so we leverage its cache, error handling, etc.
|
||||
return options.defaultResolver(path, {
|
||||
...options,
|
||||
// Use packageFilter to process parsed `package.json` before the resolution (see https://www.npmjs.com/package/resolve#resolveid-opts-cb)
|
||||
packageFilter: (pkg) => {
|
||||
// See https://github.com/microsoft/accessibility-insights-web/blob/40416a4ae6b91baf43102f58e069eff787de4de2/src/tests/common/resolver.js
|
||||
if (pkg.name === "uuid" || pkg.name === "nanoid") {
|
||||
delete pkg["exports"];
|
||||
delete pkg["module"];
|
||||
}
|
||||
return pkg;
|
||||
},
|
||||
});
|
||||
};
|
||||
6
calcom/apps/web/test/jest-setup.js
Normal file
6
calcom/apps/web/test/jest-setup.js
Normal file
@@ -0,0 +1,6 @@
|
||||
// This is a workaround for https://github.com/jsdom/jsdom/issues/2524#issuecomment-902027138
|
||||
|
||||
// See https://github.com/microsoft/accessibility-insights-web/blob/40416a4ae6b91baf43102f58e069eff787de4de2/src/tests/unit/jest-setup.ts
|
||||
const { TextEncoder, TextDecoder } = require("util");
|
||||
global.TextEncoder = TextEncoder;
|
||||
global.TextDecoder = TextDecoder;
|
||||
115
calcom/apps/web/test/lib/availabilityAsString.test.ts
Normal file
115
calcom/apps/web/test/lib/availabilityAsString.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { availabilityAsString } from "@calcom/lib/availability";
|
||||
|
||||
it("correctly handles 1 day", async () => {
|
||||
const availability = {
|
||||
id: 1,
|
||||
userId: 2,
|
||||
eventTypeId: 3,
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(1970, 1, 1, 9, 0, 0, 0)),
|
||||
endTime: new Date(Date.UTC(1970, 1, 1, 17, 0, 0, 0)),
|
||||
date: null,
|
||||
scheduleId: 1,
|
||||
profileId: null,
|
||||
};
|
||||
|
||||
const result = availabilityAsString(availability, {
|
||||
locale: "en",
|
||||
hour12: true,
|
||||
});
|
||||
|
||||
expect(replaceUnicodeSpace(result)).toBe("Mon, 9:00 AM - 5:00 PM");
|
||||
});
|
||||
|
||||
it("correctly handles all days", async () => {
|
||||
const availability = {
|
||||
id: 1,
|
||||
userId: 2,
|
||||
eventTypeId: 3,
|
||||
days: [1, 2, 3, 4, 5, 6, 7],
|
||||
startTime: new Date(Date.UTC(1970, 1, 1, 9, 0, 0, 0)),
|
||||
endTime: new Date(Date.UTC(1970, 1, 1, 17, 0, 0, 0)),
|
||||
date: null,
|
||||
scheduleId: 1,
|
||||
profileId: null,
|
||||
};
|
||||
|
||||
const result = availabilityAsString(availability, {
|
||||
locale: "en",
|
||||
hour12: true,
|
||||
});
|
||||
|
||||
expect(replaceUnicodeSpace(result)).toBe("Mon - Sun, 9:00 AM - 5:00 PM");
|
||||
});
|
||||
|
||||
it("correctly handles staggered days", async () => {
|
||||
const availability = {
|
||||
id: 1,
|
||||
userId: 2,
|
||||
eventTypeId: 3,
|
||||
days: [1, 3, 5, 7],
|
||||
startTime: new Date(Date.UTC(1970, 1, 1, 9, 0, 0, 0)),
|
||||
endTime: new Date(Date.UTC(1970, 1, 1, 17, 0, 0, 0)),
|
||||
date: null,
|
||||
scheduleId: 1,
|
||||
profileId: null,
|
||||
};
|
||||
|
||||
const result = availabilityAsString(availability, {
|
||||
locale: "en",
|
||||
hour12: true,
|
||||
});
|
||||
|
||||
expect(replaceUnicodeSpace(result)).toBe("Mon, Wed, Fri, Sun, 9:00 AM - 5:00 PM");
|
||||
});
|
||||
|
||||
it("correctly produces days and times - 12 hours", async () => {
|
||||
const availability = {
|
||||
id: 1,
|
||||
userId: 2,
|
||||
eventTypeId: 3,
|
||||
days: [1, 2, 3],
|
||||
startTime: new Date(Date.UTC(1970, 1, 1, 9, 0, 0, 0)),
|
||||
endTime: new Date(Date.UTC(1970, 1, 1, 17, 0, 0, 0)),
|
||||
date: null,
|
||||
scheduleId: 1,
|
||||
profileId: null,
|
||||
};
|
||||
|
||||
const result = availabilityAsString(availability, {
|
||||
locale: "en",
|
||||
hour12: true,
|
||||
});
|
||||
|
||||
expect(replaceUnicodeSpace(result)).toBe("Mon - Wed, 9:00 AM - 5:00 PM");
|
||||
});
|
||||
|
||||
it("correctly produces days and times - 24 hours", async () => {
|
||||
const availability = {
|
||||
id: 1,
|
||||
userId: 2,
|
||||
eventTypeId: 3,
|
||||
days: [1, 2, 3],
|
||||
startTime: new Date(Date.UTC(1970, 1, 1, 9, 0, 0, 0)),
|
||||
endTime: new Date(Date.UTC(1970, 1, 1, 17, 0, 0, 0)),
|
||||
date: null,
|
||||
scheduleId: 1,
|
||||
profileId: null,
|
||||
};
|
||||
|
||||
const result = availabilityAsString(availability, {
|
||||
locale: "en",
|
||||
hour12: false,
|
||||
});
|
||||
|
||||
expect(replaceUnicodeSpace(result)).toBe("Mon - Wed, 09:00 - 17:00");
|
||||
});
|
||||
|
||||
// INFO: This is because on GitHub, the international date formatting
|
||||
// produces Unicode characters. Instead of using line for line code from the
|
||||
// availability.ts file, opted for this instead.
|
||||
const replaceUnicodeSpace = (string: string) => {
|
||||
return string.replace(/\u202f/g, " ");
|
||||
};
|
||||
106
calcom/apps/web/test/lib/checkBookingLimits.test.ts
Normal file
106
calcom/apps/web/test/lib/checkBookingLimits.test.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import prismaMock from "../../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib";
|
||||
import { checkBookingLimits, checkBookingLimit } from "@calcom/lib/server";
|
||||
import type { IntervalLimit } from "@calcom/types/Calendar";
|
||||
|
||||
type Mockdata = {
|
||||
id: number;
|
||||
startDate: Date;
|
||||
bookingLimits: IntervalLimit;
|
||||
};
|
||||
|
||||
const MOCK_DATA: Mockdata = {
|
||||
id: 1,
|
||||
startDate: dayjs("2022-09-30T09:00:00+01:00").toDate(),
|
||||
bookingLimits: {
|
||||
PER_DAY: 1,
|
||||
},
|
||||
};
|
||||
|
||||
describe("Check Booking Limits Tests", () => {
|
||||
it("Should return no errors", async () => {
|
||||
prismaMock.booking.count.mockResolvedValue(0);
|
||||
expect(
|
||||
checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should throw an error", async () => {
|
||||
// Mock there being two a day
|
||||
prismaMock.booking.count.mockResolvedValue(2);
|
||||
expect(
|
||||
checkBookingLimits(MOCK_DATA.bookingLimits, MOCK_DATA.startDate, MOCK_DATA.id)
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
|
||||
it("Should pass with multiple booking limits", async () => {
|
||||
prismaMock.booking.count.mockResolvedValue(0);
|
||||
expect(
|
||||
checkBookingLimits(
|
||||
{
|
||||
PER_DAY: 1,
|
||||
PER_WEEK: 2,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should pass with multiple booking limits with one undefined", async () => {
|
||||
prismaMock.booking.count.mockResolvedValue(0);
|
||||
expect(
|
||||
checkBookingLimits(
|
||||
{
|
||||
PER_DAY: 1,
|
||||
PER_WEEK: undefined,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should handle mutiple limits correctly", async () => {
|
||||
prismaMock.booking.count.mockResolvedValue(1);
|
||||
expect(
|
||||
checkBookingLimit({
|
||||
key: "PER_DAY",
|
||||
limitingNumber: 2,
|
||||
eventStartDate: MOCK_DATA.startDate,
|
||||
eventId: MOCK_DATA.id,
|
||||
})
|
||||
).resolves.not.toThrow();
|
||||
prismaMock.booking.count.mockResolvedValue(3);
|
||||
expect(
|
||||
checkBookingLimit({
|
||||
key: "PER_WEEK",
|
||||
limitingNumber: 2,
|
||||
eventStartDate: MOCK_DATA.startDate,
|
||||
eventId: MOCK_DATA.id,
|
||||
})
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Booking limit validation", () => {
|
||||
it("Should validate a correct limit", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 3, PER_MONTH: 5 })).toBe(true);
|
||||
});
|
||||
|
||||
it("Should invalidate an incorrect limit", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 9, PER_MONTH: 5 })).toBe(false);
|
||||
});
|
||||
|
||||
it("Should validate a correct limit with 'gaps' ", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 9, PER_YEAR: 25 })).toBe(true);
|
||||
});
|
||||
|
||||
it("Should validate a correct limit with equal values ", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 1, PER_YEAR: 1 })).toBe(true);
|
||||
});
|
||||
it("Should validate a correct with empty", () => {
|
||||
expect(validateIntervalLimitOrder({})).toBe(true);
|
||||
});
|
||||
});
|
||||
115
calcom/apps/web/test/lib/checkDurationLimits.test.ts
Normal file
115
calcom/apps/web/test/lib/checkDurationLimits.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import prismaMock from "../../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib";
|
||||
import { checkDurationLimit, checkDurationLimits } from "@calcom/lib/server";
|
||||
|
||||
type MockData = {
|
||||
id: number;
|
||||
startDate: Date;
|
||||
};
|
||||
|
||||
const MOCK_DATA: MockData = {
|
||||
id: 1,
|
||||
startDate: dayjs("2022-09-30T09:00:00+01:00").toDate(),
|
||||
};
|
||||
|
||||
// Path: apps/web/test/lib/checkDurationLimits.ts
|
||||
describe("Check Duration Limits Tests", () => {
|
||||
it("Should return no errors if limit is not reached", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 0 }]);
|
||||
await expect(
|
||||
checkDurationLimits({ PER_DAY: 60 }, MOCK_DATA.startDate, MOCK_DATA.id)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should throw an error if limit is reached", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 60 }]);
|
||||
await expect(
|
||||
checkDurationLimits({ PER_DAY: 60 }, MOCK_DATA.startDate, MOCK_DATA.id)
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
it("Should pass with multiple duration limits", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 30 }]);
|
||||
await expect(
|
||||
checkDurationLimits(
|
||||
{
|
||||
PER_DAY: 60,
|
||||
PER_WEEK: 120,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should pass with multiple duration limits with one undefined", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 30 }]);
|
||||
await expect(
|
||||
checkDurationLimits(
|
||||
{
|
||||
PER_DAY: 60,
|
||||
PER_WEEK: undefined,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should return no errors if limit is not reached with multiple bookings", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 60 }]);
|
||||
await expect(
|
||||
checkDurationLimits(
|
||||
{
|
||||
PER_DAY: 90,
|
||||
PER_WEEK: 120,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).resolves.toBeTruthy();
|
||||
});
|
||||
it("Should throw an error if one of the limit is reached with multiple bookings", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 90 }]);
|
||||
await expect(
|
||||
checkDurationLimits(
|
||||
{
|
||||
PER_DAY: 60,
|
||||
PER_WEEK: 120,
|
||||
},
|
||||
MOCK_DATA.startDate,
|
||||
MOCK_DATA.id
|
||||
)
|
||||
).rejects.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
// Path: apps/web/test/lib/checkDurationLimits.ts
|
||||
describe("Check Duration Limit Tests", () => {
|
||||
it("Should return no busyTimes and no error if limit is not reached", async () => {
|
||||
prismaMock.$queryRaw.mockResolvedValue([{ totalMinutes: 60 }]);
|
||||
await expect(
|
||||
checkDurationLimit({
|
||||
key: "PER_DAY",
|
||||
limitingNumber: 90,
|
||||
eventStartDate: MOCK_DATA.startDate,
|
||||
eventId: MOCK_DATA.id,
|
||||
})
|
||||
).resolves.toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Duration limit validation", () => {
|
||||
it("Should validate limit where ranges have ascending values", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 30, PER_MONTH: 60 })).toBe(true);
|
||||
});
|
||||
it("Should invalidate limit where ranges does not have a strict ascending values", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 60, PER_WEEK: 30 })).toBe(false);
|
||||
});
|
||||
it("Should validate a correct limit with 'gaps'", () => {
|
||||
expect(validateIntervalLimitOrder({ PER_DAY: 60, PER_YEAR: 120 })).toBe(true);
|
||||
});
|
||||
it("Should validate empty limit", () => {
|
||||
expect(validateIntervalLimitOrder({})).toBe(true);
|
||||
});
|
||||
});
|
||||
123
calcom/apps/web/test/lib/getAggregateWorkingHours.test.ts
Normal file
123
calcom/apps/web/test/lib/getAggregateWorkingHours.test.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { expect, it, beforeAll, vi } from "vitest";
|
||||
|
||||
import { getAggregateWorkingHours } from "@calcom/core/getAggregateWorkingHours";
|
||||
|
||||
beforeAll(() => {
|
||||
vi.setSystemTime(new Date("2021-06-20T11:59:59Z"));
|
||||
});
|
||||
|
||||
const HAWAII_AND_NEWYORK_TEAM = [
|
||||
{
|
||||
timeZone: "America/Detroit", // GMT -4 per 22th of Aug, 2022
|
||||
workingHours: [{ userId: 1, days: [1, 2, 3, 4, 5], startTime: 780, endTime: 1260 }],
|
||||
busy: [],
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
{
|
||||
timeZone: "Pacific/Honolulu", // GMT -10 per 22th of Aug, 2022
|
||||
workingHours: [
|
||||
{ userId: 1, days: [3, 4, 5], startTime: 0, endTime: 360 },
|
||||
{ userId: 2, days: [6], startTime: 0, endTime: 180 },
|
||||
{ userId: 3, days: [2, 3, 4], startTime: 780, endTime: 1439 },
|
||||
{ userId: 4, days: [5], startTime: 780, endTime: 1439 },
|
||||
],
|
||||
busy: [],
|
||||
dateOverrides: [],
|
||||
datesOutOfOffice: {},
|
||||
},
|
||||
];
|
||||
|
||||
/* TODO: Make this test more "professional" */
|
||||
it("Sydney and Shiraz can live in harmony 🙏", async () => {
|
||||
expect(getAggregateWorkingHours(HAWAII_AND_NEWYORK_TEAM, "COLLECTIVE")).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"days": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
],
|
||||
"endTime": 360,
|
||||
"startTime": 780,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
6,
|
||||
],
|
||||
"endTime": 180,
|
||||
"startTime": 0,
|
||||
"userId": 2,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
],
|
||||
"endTime": 1260,
|
||||
"startTime": 780,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
5,
|
||||
],
|
||||
"endTime": 1260,
|
||||
"startTime": 780,
|
||||
},
|
||||
]
|
||||
`);
|
||||
|
||||
expect(getAggregateWorkingHours(HAWAII_AND_NEWYORK_TEAM, "ROUND_ROBIN")).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"days": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
],
|
||||
"endTime": 1260,
|
||||
"startTime": 780,
|
||||
"userId": 1,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
],
|
||||
"endTime": 360,
|
||||
"startTime": 0,
|
||||
"userId": 1,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
6,
|
||||
],
|
||||
"endTime": 180,
|
||||
"startTime": 0,
|
||||
"userId": 2,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
],
|
||||
"endTime": 1439,
|
||||
"startTime": 780,
|
||||
"userId": 3,
|
||||
},
|
||||
{
|
||||
"days": [
|
||||
5,
|
||||
],
|
||||
"endTime": 1439,
|
||||
"startTime": 780,
|
||||
"userId": 4,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
71
calcom/apps/web/test/lib/getAvailabilityFromSchedule.test.ts
Normal file
71
calcom/apps/web/test/lib/getAvailabilityFromSchedule.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { Availability } from "@prisma/client";
|
||||
import { expect, it, beforeAll, vi } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
||||
|
||||
beforeAll(() => {
|
||||
vi.setSystemTime(new Date("2021-06-20T11:59:59Z"));
|
||||
});
|
||||
|
||||
//parse "hh:mm-hh:mm" into <Availability> object
|
||||
const parseWorkingHours = (workingHours: string) => {
|
||||
const times = workingHours.split("-").map((time) => dayjs(time, "hh:mm").toDate());
|
||||
return { start: times[0], end: times[1] };
|
||||
};
|
||||
const p = parseWorkingHours;
|
||||
|
||||
// mocked working hours
|
||||
const fulltimeWH = p("09:00-17:00");
|
||||
const morningWH = p("09:00-12:00");
|
||||
const afternoonWH = p("13:00-17:00");
|
||||
|
||||
it("should return an empty availability array when received an empty schedule", async () => {
|
||||
const schedule = [[]];
|
||||
expect(getAvailabilityFromSchedule(schedule)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("should return availability for all workable days from 9:00 to 17:00", async () => {
|
||||
const schedule = [[], [fulltimeWH], [fulltimeWH], [fulltimeWH], [fulltimeWH], [fulltimeWH], []];
|
||||
const expected = [
|
||||
{
|
||||
days: [1, 2, 3, 4, 5],
|
||||
startTime: fulltimeWH.start,
|
||||
endTime: fulltimeWH.end,
|
||||
},
|
||||
] as Availability[];
|
||||
|
||||
expect(getAvailabilityFromSchedule(schedule)).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
it("should return the available days grouped by the available time slots", async () => {
|
||||
const schedule = [
|
||||
[],
|
||||
[afternoonWH],
|
||||
[afternoonWH],
|
||||
[morningWH, afternoonWH],
|
||||
[fulltimeWH],
|
||||
[morningWH],
|
||||
[],
|
||||
];
|
||||
const expected = [
|
||||
{
|
||||
days: [1, 2, 3],
|
||||
startTime: afternoonWH.start,
|
||||
endTime: afternoonWH.end,
|
||||
},
|
||||
{
|
||||
days: [3, 5],
|
||||
startTime: morningWH.start,
|
||||
endTime: morningWH.end,
|
||||
},
|
||||
|
||||
{
|
||||
days: [4],
|
||||
startTime: fulltimeWH.start,
|
||||
endTime: fulltimeWH.end,
|
||||
},
|
||||
] as Availability[];
|
||||
|
||||
expect(getAvailabilityFromSchedule(schedule)).toStrictEqual(expected);
|
||||
});
|
||||
1472
calcom/apps/web/test/lib/getSchedule.test.ts
Normal file
1472
calcom/apps/web/test/lib/getSchedule.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
126
calcom/apps/web/test/lib/getSchedule/expects.ts
Normal file
126
calcom/apps/web/test/lib/getSchedule/expects.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { diff } from "jest-diff";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots/types";
|
||||
|
||||
export const expectedSlotsForSchedule = {
|
||||
IstWorkHours: {
|
||||
interval: {
|
||||
"1hr": {
|
||||
allPossibleSlotsStartingAt430: [
|
||||
"04:30:00.000Z",
|
||||
"05:30:00.000Z",
|
||||
"06:30:00.000Z",
|
||||
"07:30:00.000Z",
|
||||
"08:30:00.000Z",
|
||||
"09:30:00.000Z",
|
||||
"10:30:00.000Z",
|
||||
"11:30:00.000Z",
|
||||
],
|
||||
allPossibleSlotsStartingAt4: [
|
||||
"04:00:00.000Z",
|
||||
"05:00:00.000Z",
|
||||
"06:00:00.000Z",
|
||||
"07:00:00.000Z",
|
||||
"08:00:00.000Z",
|
||||
"09:00:00.000Z",
|
||||
"10:00:00.000Z",
|
||||
"11:00:00.000Z",
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace jest {
|
||||
interface Matchers<R> {
|
||||
toHaveTimeSlots(expectedSlots: string[], date: { dateString: string; doExactMatch?: boolean }): R;
|
||||
/**
|
||||
* Explicitly checks if the date is disabled and fails if date is marked as OOO
|
||||
*/
|
||||
toHaveDateDisabled(date: { dateString: string }): R;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expect.extend({
|
||||
toHaveTimeSlots(
|
||||
schedule: { slots: Record<string, Slot[]> },
|
||||
expectedSlots: string[],
|
||||
{ dateString, doExactMatch }: { dateString: string; doExactMatch: boolean }
|
||||
) {
|
||||
if (!schedule.slots[`${dateString}`]) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `has no timeslots for ${dateString}`,
|
||||
};
|
||||
}
|
||||
|
||||
const expectedSlotHasFullTimestamp = expectedSlots[0].split("-").length === 3;
|
||||
|
||||
if (
|
||||
!schedule.slots[`${dateString}`]
|
||||
.map((slot) => slot.time)
|
||||
.every((actualSlotTime, index) => {
|
||||
const expectedSlotTime = expectedSlotHasFullTimestamp
|
||||
? expectedSlots[index]
|
||||
: `${dateString}T${expectedSlots[index]}`;
|
||||
return expectedSlotTime === actualSlotTime;
|
||||
})
|
||||
) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () =>
|
||||
`has incorrect timeslots for ${dateString}.\n\r ${diff(
|
||||
expectedSlots.map((expectedSlot) => {
|
||||
if (expectedSlotHasFullTimestamp) {
|
||||
return expectedSlot;
|
||||
}
|
||||
return `${dateString}T${expectedSlot}`;
|
||||
}),
|
||||
schedule.slots[`${dateString}`].map((slot) => slot.time)
|
||||
)}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (doExactMatch) {
|
||||
return {
|
||||
pass: expectedSlots.length === schedule.slots[`${dateString}`].length,
|
||||
message: () =>
|
||||
`number of slots don't match for ${dateString}. Expected ${expectedSlots.length} but got ${
|
||||
schedule.slots[`${dateString}`].length
|
||||
}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: () => "has correct timeslots ",
|
||||
};
|
||||
},
|
||||
|
||||
toHaveDateDisabled(schedule: { slots: Record<string, Slot[]> }, { dateString }: { dateString: string }) {
|
||||
// Frontend requires that the date must not be set for that date to be shown as disabled.Because weirdly, if an empty array is provided the date itself isn't shown which we don't want
|
||||
if (!schedule.slots[`${dateString}`]) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => `is not disabled for ${dateString}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (schedule.slots[`${dateString}`].length === 0) {
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `is all day OOO for ${dateString}.`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `has timeslots for ${dateString}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
export { expect } from "vitest";
|
||||
1553
calcom/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts
Normal file
1553
calcom/apps/web/test/lib/getSchedule/futureLimit.timezone.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
24
calcom/apps/web/test/lib/getSchedule/setupAndTeardown.ts
Normal file
24
calcom/apps/web/test/lib/getSchedule/setupAndTeardown.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import { vi, beforeEach, afterEach } from "vitest";
|
||||
|
||||
const cleanup = async () => {
|
||||
await prismock.eventType.deleteMany();
|
||||
await prismock.user.deleteMany();
|
||||
await prismock.schedule.deleteMany();
|
||||
await prismock.selectedCalendar.deleteMany();
|
||||
await prismock.credential.deleteMany();
|
||||
await prismock.booking.deleteMany();
|
||||
await prismock.app.deleteMany();
|
||||
vi.useRealTimers();
|
||||
};
|
||||
|
||||
export function setupAndTeardown() {
|
||||
beforeEach(async () => {
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await cleanup();
|
||||
});
|
||||
}
|
||||
16
calcom/apps/web/test/lib/getSchedule/utils.ts
Normal file
16
calcom/apps/web/test/lib/getSchedule/utils.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getDate } from "../../utils/bookingScenario/bookingScenario";
|
||||
|
||||
import { vi } from "vitest";
|
||||
|
||||
export function timeTravelToTheBeginningOfToday({ utcOffsetInHours = 0 }: { utcOffsetInHours: number }) {
|
||||
const timeInTheUtcOffsetInHours = 24 - utcOffsetInHours;
|
||||
const timeInTheUtcOffsetInMinutes = timeInTheUtcOffsetInHours * 60;
|
||||
const hours = Math.floor(timeInTheUtcOffsetInMinutes / 60);
|
||||
const hoursString = hours < 10 ? `0${hours}` : `${hours}`;
|
||||
const minutes = timeInTheUtcOffsetInMinutes % 60;
|
||||
const minutesString = minutes < 10 ? `0${minutes}` : `${minutes}`;
|
||||
|
||||
const { dateString: yesterdayDateString } = getDate({ dateIncrement: -1 });
|
||||
console.log({ yesterdayDateString, hours, minutes });
|
||||
vi.setSystemTime(`${yesterdayDateString}T${hoursString}:${minutesString}:00.000Z`);
|
||||
}
|
||||
96
calcom/apps/web/test/lib/getTimezone.test.ts
Normal file
96
calcom/apps/web/test/lib/getTimezone.test.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { expect, beforeEach, afterEach, it, vi, describe } from "vitest";
|
||||
|
||||
import { filterByCities, addCitiesToDropdown, handleOptionLabel } from "@calcom/lib/timezone";
|
||||
|
||||
const cityData = [
|
||||
{
|
||||
city: "San Francisco",
|
||||
timezone: "America/Argentina/Cordoba",
|
||||
},
|
||||
{
|
||||
city: "Sao Francisco do Sul",
|
||||
timezone: "America/Sao_Paulo",
|
||||
},
|
||||
{
|
||||
city: "San Francisco de Macoris",
|
||||
timezone: "America/Santo_Domingo",
|
||||
},
|
||||
{
|
||||
city: "San Francisco Gotera",
|
||||
timezone: "America/El_Salvador",
|
||||
},
|
||||
{
|
||||
city: "San Francisco",
|
||||
timezone: "America/Los_Angeles",
|
||||
},
|
||||
];
|
||||
|
||||
const option = {
|
||||
value: "America/Los_Angeles",
|
||||
label: "(GMT-8:00) San Francisco",
|
||||
offset: -8,
|
||||
abbrev: "PST",
|
||||
altName: "Pacific Standard Time",
|
||||
};
|
||||
|
||||
describe("getTimezone", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers().setSystemTime(new Date("2020-01-01"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.runOnlyPendingTimers();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it("should return empty array for an empty string", () => {
|
||||
expect(filterByCities("", cityData)).toMatchInlineSnapshot(`[]`);
|
||||
});
|
||||
|
||||
it("should filter cities for a valid city name", () => {
|
||||
expect(filterByCities("San Francisco", cityData)).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"city": "San Francisco",
|
||||
"timezone": "America/Argentina/Cordoba",
|
||||
},
|
||||
{
|
||||
"city": "San Francisco de Macoris",
|
||||
"timezone": "America/Santo_Domingo",
|
||||
},
|
||||
{
|
||||
"city": "San Francisco Gotera",
|
||||
"timezone": "America/El_Salvador",
|
||||
},
|
||||
{
|
||||
"city": "San Francisco",
|
||||
"timezone": "America/Los_Angeles",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it("should return appropriate timezone(s) for a given city name array", () => {
|
||||
expect(addCitiesToDropdown(cityData)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"America/Argentina/Cordoba": "San Francisco",
|
||||
"America/El_Salvador": "San Francisco Gotera",
|
||||
"America/Los_Angeles": "San Francisco",
|
||||
"America/Santo_Domingo": "San Francisco de Macoris",
|
||||
"America/Sao_Paulo": "Sao Francisco do Sul",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it("should render city name as option label if cityData is not empty", () => {
|
||||
expect(handleOptionLabel(option, cityData)).toMatchInlineSnapshot(`"San Francisco GMT -8:00"`);
|
||||
vi.setSystemTime(new Date("2020-06-01"));
|
||||
expect(handleOptionLabel(option, cityData)).toMatchInlineSnapshot(`"San Francisco GMT -7:00"`);
|
||||
});
|
||||
|
||||
it("should return timezone as option label if cityData is empty", () => {
|
||||
expect(handleOptionLabel(option, [])).toMatchInlineSnapshot(`"America/Los Angeles GMT -8:00"`);
|
||||
vi.setSystemTime(new Date("2020-06-01"));
|
||||
expect(handleOptionLabel(option, [])).toMatchInlineSnapshot(`"America/Los Angeles GMT -7:00"`);
|
||||
});
|
||||
});
|
||||
155
calcom/apps/web/test/lib/getWorkingHours.test.ts
Normal file
155
calcom/apps/web/test/lib/getWorkingHours.test.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import { expect, it, vi, beforeAll } from "vitest";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getWorkingHours } from "@calcom/lib/availability";
|
||||
|
||||
beforeAll(() => {
|
||||
vi.setSystemTime(new Date("2021-06-20T11:59:59Z"));
|
||||
});
|
||||
|
||||
it("correctly translates Availability (UTC+0) to UTC workingHours", async () => {
|
||||
expect(
|
||||
getWorkingHours({ timeZone: "GMT" }, [
|
||||
{
|
||||
days: [0],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 23)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
{
|
||||
days: [0],
|
||||
endTime: 1439,
|
||||
startTime: 1380,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("correctly translates Availability in a positive UTC offset (Pacific/Auckland) to UTC workingHours", async () => {
|
||||
// Take note that (Pacific/Auckland) is UTC+12 on 2021-06-20, NOT +13 like the other half of the year.
|
||||
expect(
|
||||
getWorkingHours({ timeZone: "Pacific/Auckland" }, [
|
||||
{
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 0)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
{
|
||||
days: [1],
|
||||
endTime: 719,
|
||||
startTime: 0,
|
||||
},
|
||||
{
|
||||
days: [0],
|
||||
endTime: 1439,
|
||||
startTime: 720, // 0 (midnight) - 12 * 60 (DST)
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("correctly translates Availability in a negative UTC offset (Pacific/Midway) to UTC workingHours", async () => {
|
||||
// Take note that (Pacific/Midway) is UTC-12 on 2021-06-20, NOT +13 like the other half of the year.
|
||||
expect(
|
||||
getWorkingHours({ timeZone: "Pacific/Midway" }, [
|
||||
{
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 0)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
{
|
||||
days: [2],
|
||||
endTime: 659,
|
||||
startTime: 0,
|
||||
},
|
||||
{
|
||||
days: [1],
|
||||
endTime: 1439,
|
||||
startTime: 660,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("can do the same with UTC offsets", async () => {
|
||||
// Take note that (Pacific/Midway) is UTC-12 on 2021-06-20, NOT +13 like the other half of the year.
|
||||
expect(
|
||||
getWorkingHours({ utcOffset: dayjs().tz("Pacific/Midway").utcOffset() }, [
|
||||
{
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 0)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
{
|
||||
days: [2],
|
||||
endTime: 659,
|
||||
startTime: 0,
|
||||
},
|
||||
{
|
||||
days: [1],
|
||||
endTime: 1439,
|
||||
startTime: 660,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("can also shift UTC into other timeZones", async () => {
|
||||
// UTC+0 time with 23:00 - 23:59 (Sunday) and 00:00 - 16:00 (Monday) when cast into UTC+1 should become 00:00 = 17:00 (Monday)
|
||||
expect(
|
||||
getWorkingHours({ utcOffset: -60 }, [
|
||||
{
|
||||
days: [0],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 23)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
{
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(2021, 11, 17, 0)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 17, 16)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
// TODO: Maybe the desired result is 0-1020 as a single entry, but this requires some post-processing to merge. It may work as is so leaving this as now.
|
||||
{
|
||||
days: [1],
|
||||
endTime: 59,
|
||||
startTime: 0,
|
||||
},
|
||||
{
|
||||
days: [1],
|
||||
endTime: 1020,
|
||||
startTime: 60,
|
||||
},
|
||||
]);
|
||||
// And the other way around; UTC+0 time with 00:00 - 1:00 (Monday) and 21:00 - 24:00 (Sunday) when cast into UTC-1 should become 20:00 = 24:00 (Sunday)
|
||||
expect(
|
||||
getWorkingHours({ utcOffset: 60 }, [
|
||||
{
|
||||
days: [0],
|
||||
startTime: new Date(Date.UTC(2021, 11, 16, 21)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 16, 23, 59)),
|
||||
},
|
||||
{
|
||||
days: [1],
|
||||
startTime: new Date(Date.UTC(2021, 11, 17, 0)),
|
||||
endTime: new Date(Date.UTC(2021, 11, 17, 1)),
|
||||
},
|
||||
])
|
||||
).toStrictEqual([
|
||||
// TODO: Maybe the desired result is 1200-1439 as a single entry, but this requires some post-processing to merge. It may work as is so leaving this as now.
|
||||
{
|
||||
days: [0],
|
||||
endTime: 1379,
|
||||
startTime: 1200,
|
||||
},
|
||||
{
|
||||
days: [0],
|
||||
endTime: 1439,
|
||||
startTime: 1380,
|
||||
},
|
||||
]);
|
||||
});
|
||||
470
calcom/apps/web/test/lib/handleChildrenEventTypes.test.ts
Normal file
470
calcom/apps/web/test/lib/handleChildrenEventTypes.test.ts
Normal file
@@ -0,0 +1,470 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import prismaMock from "../../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import type { EventType } from "@prisma/client";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import updateChildrenEventTypes from "@calcom/features/ee/managed-event-types/lib/handleChildrenEventTypes";
|
||||
import { buildEventType } from "@calcom/lib/test/builder";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import type { CompleteEventType, CompleteWorkflowsOnEventTypes } from "@calcom/prisma/zod";
|
||||
|
||||
const mockFindFirstEventType = (data?: Partial<CompleteEventType>) => {
|
||||
const eventType = buildEventType(data as Partial<EventType>);
|
||||
// const { scheduleId, destinationCalendar, ...restEventType } = eventType;
|
||||
prismaMock.eventType.findFirst.mockResolvedValue(eventType as EventType);
|
||||
|
||||
return eventType;
|
||||
};
|
||||
|
||||
vi.mock("@calcom/emails/email-manager", () => {
|
||||
return {
|
||||
sendSlugReplacementEmail: () => ({}),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/lib/server/i18n", () => {
|
||||
return {
|
||||
getTranslation: (key: string) => key,
|
||||
};
|
||||
});
|
||||
|
||||
describe("handleChildrenEventTypes", () => {
|
||||
describe("Shortcircuits", () => {
|
||||
it("Returns message 'No managed event type'", async () => {
|
||||
mockFindFirstEventType();
|
||||
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [], team: { name: "" } },
|
||||
children: [],
|
||||
updatedEventType: { schedulingType: null, slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
expect(result.deletedUserIds).toEqual(undefined);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
expect(result.message).toBe("No managed event type");
|
||||
});
|
||||
|
||||
it("Returns message 'No managed event metadata'", async () => {
|
||||
mockFindFirstEventType({
|
||||
metadata: {},
|
||||
locations: [],
|
||||
});
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [], team: { name: "" } },
|
||||
children: [],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
expect(result.deletedUserIds).toEqual(undefined);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
expect(result.message).toBe("No managed event metadata");
|
||||
});
|
||||
|
||||
it("Returns message 'Missing event type'", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
prismaMock.eventType.findFirst.mockImplementation(() => {
|
||||
return new Promise((resolve) => {
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [], team: { name: "" } },
|
||||
children: [],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual(undefined);
|
||||
expect(result.oldUserIds).toEqual(undefined);
|
||||
expect(result.deletedUserIds).toEqual(undefined);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
expect(result.message).toBe("Missing event type");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Happy paths", () => {
|
||||
it("Adds new users", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
schedulingType,
|
||||
id,
|
||||
teamId,
|
||||
timeZone,
|
||||
requiresBookerEmailVerification,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
secondaryEmailId,
|
||||
...evType
|
||||
} = mockFindFirstEventType({
|
||||
id: 123,
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
});
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [], team: { name: "" } },
|
||||
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } }],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
parentId: 1,
|
||||
users: { connect: [{ id: 4 }] },
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
userId: 4,
|
||||
},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([4]);
|
||||
expect(result.oldUserIds).toEqual([]);
|
||||
expect(result.deletedUserIds).toEqual([]);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("Updates old users", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
schedulingType,
|
||||
id,
|
||||
teamId,
|
||||
timeZone,
|
||||
locations,
|
||||
parentId,
|
||||
userId,
|
||||
scheduleId,
|
||||
requiresBookerEmailVerification,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
secondaryEmailId,
|
||||
...evType
|
||||
} = mockFindFirstEventType({
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
});
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
|
||||
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } }],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: "somestring",
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {
|
||||
bookingLimits: undefined,
|
||||
},
|
||||
});
|
||||
const { profileId, ...rest } = evType;
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
locations: [],
|
||||
scheduleId: null,
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
hashedLink: { create: { link: expect.any(String) } },
|
||||
},
|
||||
where: {
|
||||
userId_parentId: {
|
||||
userId: 4,
|
||||
parentId: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([]);
|
||||
expect(result.oldUserIds).toEqual([4]);
|
||||
expect(result.deletedUserIds).toEqual([]);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("Deletes old users", async () => {
|
||||
mockFindFirstEventType({ users: [], metadata: { managedEventConfig: {} }, locations: [] });
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
|
||||
children: [],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([]);
|
||||
expect(result.oldUserIds).toEqual([]);
|
||||
expect(result.deletedUserIds).toEqual([4]);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("Adds new users and updates/delete old users", async () => {
|
||||
mockFindFirstEventType({
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
});
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [{ userId: 4 }, { userId: 1 }], team: { name: "" } },
|
||||
children: [
|
||||
{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } },
|
||||
{ hidden: false, owner: { id: 5, name: "", email: "", eventTypeSlugs: [] } },
|
||||
],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
// Have been called
|
||||
expect(result.newUserIds).toEqual([5]);
|
||||
expect(result.oldUserIds).toEqual([4]);
|
||||
expect(result.deletedUserIds).toEqual([1]);
|
||||
expect(result.deletedExistentEventTypes).toEqual(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Slug conflicts", () => {
|
||||
it("Deletes existent event types for new users added", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
schedulingType,
|
||||
id,
|
||||
teamId,
|
||||
timeZone,
|
||||
requiresBookerEmailVerification,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
secondaryEmailId,
|
||||
...evType
|
||||
} = mockFindFirstEventType({
|
||||
id: 123,
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
});
|
||||
prismaMock.eventType.deleteMany.mockResolvedValue([123] as unknown as Prisma.BatchPayload);
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [], team: { name: "" } },
|
||||
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: ["something"] } }],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
parentId: 1,
|
||||
users: { connect: [{ id: 4 }] },
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
hashedLink: undefined,
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
userId: 4,
|
||||
workflows: undefined,
|
||||
},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([4]);
|
||||
expect(result.oldUserIds).toEqual([]);
|
||||
expect(result.deletedUserIds).toEqual([]);
|
||||
expect(result.deletedExistentEventTypes).toEqual([123]);
|
||||
});
|
||||
it("Deletes existent event types for old users updated", async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
const {
|
||||
schedulingType,
|
||||
id,
|
||||
teamId,
|
||||
timeZone,
|
||||
locations,
|
||||
parentId,
|
||||
userId,
|
||||
requiresBookerEmailVerification,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
secondaryEmailId,
|
||||
...evType
|
||||
} = mockFindFirstEventType({
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
});
|
||||
prismaMock.eventType.deleteMany.mockResolvedValue([123] as unknown as Prisma.BatchPayload);
|
||||
const result = await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
|
||||
children: [{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: ["something"] } }],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {
|
||||
length: 30,
|
||||
},
|
||||
});
|
||||
const { profileId, ...rest } = evType;
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
locations: [],
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
},
|
||||
where: {
|
||||
userId_parentId: {
|
||||
userId: 4,
|
||||
parentId: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.newUserIds).toEqual([]);
|
||||
expect(result.oldUserIds).toEqual([4]);
|
||||
expect(result.deletedUserIds).toEqual([]);
|
||||
expect(result.deletedExistentEventTypes).toEqual([123]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Workflows", () => {
|
||||
it("Links workflows to new and existing assigned members", async () => {
|
||||
const {
|
||||
schedulingType: _schedulingType,
|
||||
id: _id,
|
||||
teamId: _teamId,
|
||||
locations: _locations,
|
||||
timeZone: _timeZone,
|
||||
parentId: _parentId,
|
||||
userId: _userId,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
requiresBookerEmailVerification,
|
||||
lockTimeZoneToggleOnBookingPage,
|
||||
useEventTypeDestinationCalendarEmail,
|
||||
secondaryEmailId,
|
||||
...evType
|
||||
} = mockFindFirstEventType({
|
||||
metadata: { managedEventConfig: {} },
|
||||
locations: [],
|
||||
workflows: [
|
||||
{
|
||||
workflowId: 11,
|
||||
} as CompleteWorkflowsOnEventTypes,
|
||||
],
|
||||
});
|
||||
prismaMock.$transaction.mockResolvedValue([{ id: 2 }]);
|
||||
await updateChildrenEventTypes({
|
||||
eventTypeId: 1,
|
||||
oldEventType: { children: [{ userId: 4 }], team: { name: "" } },
|
||||
children: [
|
||||
{ hidden: false, owner: { id: 4, name: "", email: "", eventTypeSlugs: [] } },
|
||||
{ hidden: false, owner: { id: 5, name: "", email: "", eventTypeSlugs: [] } },
|
||||
],
|
||||
updatedEventType: { schedulingType: "MANAGED", slug: "something" },
|
||||
currentUserId: 1,
|
||||
hashedLink: undefined,
|
||||
connectedLink: null,
|
||||
prisma: prismaMock,
|
||||
profileId: null,
|
||||
updatedValues: {},
|
||||
});
|
||||
expect(prismaMock.eventType.create).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...evType,
|
||||
bookingLimits: undefined,
|
||||
durationLimits: undefined,
|
||||
recurringEvent: undefined,
|
||||
hashedLink: undefined,
|
||||
locations: [],
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
parentId: 1,
|
||||
userId: 5,
|
||||
users: {
|
||||
connect: [
|
||||
{
|
||||
id: 5,
|
||||
},
|
||||
],
|
||||
},
|
||||
workflows: {
|
||||
create: [{ workflowId: 11 }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const { profileId, ...rest } = evType;
|
||||
if ("workflows" in rest) delete rest.workflows;
|
||||
expect(prismaMock.eventType.update).toHaveBeenCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
locations: [],
|
||||
lockTimeZoneToggleOnBookingPage: false,
|
||||
requiresBookerEmailVerification: false,
|
||||
hashedLink: undefined,
|
||||
},
|
||||
where: {
|
||||
userId_parentId: {
|
||||
userId: 4,
|
||||
parentId: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(prismaMock.workflowsOnEventTypes.upsert).toHaveBeenCalledWith({
|
||||
create: {
|
||||
eventTypeId: 2,
|
||||
workflowId: 11,
|
||||
},
|
||||
update: {},
|
||||
where: {
|
||||
workflowId_eventTypeId: {
|
||||
eventTypeId: 2,
|
||||
workflowId: 11,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
150
calcom/apps/web/test/lib/next-config.test.ts
Normal file
150
calcom/apps/web/test/lib/next-config.test.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { it, expect, describe, beforeAll } from "vitest";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { getSubdomainRegExp } = require("../../getSubdomainRegExp");
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { match, pathToRegexp } = require("next/dist/compiled/path-to-regexp");
|
||||
type MatcherRes = (path: string) => { params: Record<string, string> };
|
||||
let orgUserTypeRouteMatch: MatcherRes;
|
||||
let orgUserRouteMatch: MatcherRes;
|
||||
|
||||
beforeAll(async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
process.env.NEXT_PUBLIC_WEBAPP_URL = "http://example.com";
|
||||
const {
|
||||
orgUserRoutePath,
|
||||
orgUserTypeRoutePath,
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
} = require("../../pagesAndRewritePaths");
|
||||
|
||||
orgUserTypeRouteMatch = match(orgUserTypeRoutePath);
|
||||
|
||||
orgUserRouteMatch = match(orgUserRoutePath);
|
||||
console.log({
|
||||
regExps: {
|
||||
orgUserTypeRouteMatch: pathToRegexp(orgUserTypeRoutePath),
|
||||
orgUserRouteMatch: pathToRegexp(orgUserRoutePath),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe("next.config.js - Org Rewrite", () => {
|
||||
const orgHostRegExp = (subdomainRegExp: string) =>
|
||||
// RegExp copied from pagesAndRewritePaths.js orgHostPath. Do make the change there as well.
|
||||
new RegExp(`^(?<orgSlug>${subdomainRegExp})\\.(?!vercel\.app).*`);
|
||||
|
||||
describe("Host matching based on NEXT_PUBLIC_WEBAPP_URL", () => {
|
||||
it("https://app.cal.com", () => {
|
||||
const subdomainRegExp = getSubdomainRegExp("https://app.cal.com");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("app.cal.com")).toEqual(null);
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("company.app.cal.com")?.groups?.orgSlug).toEqual("company");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("org.cal.com")?.groups?.orgSlug).toEqual("org");
|
||||
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("localhost:3000")).toEqual(null);
|
||||
});
|
||||
|
||||
it("app.cal.com", () => {
|
||||
const subdomainRegExp = getSubdomainRegExp("app.cal.com");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("app.cal.com")).toEqual(null);
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("company.app.cal.com")?.groups?.orgSlug).toEqual("company");
|
||||
});
|
||||
|
||||
it("https://calcom.app.company.com", () => {
|
||||
const subdomainRegExp = getSubdomainRegExp("https://calcom.app.company.com");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("calcom.app.company.com")).toEqual(null);
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("acme.calcom.app.company.com")?.groups?.orgSlug).toEqual(
|
||||
"acme"
|
||||
);
|
||||
});
|
||||
|
||||
it("https://calcom.example.com", () => {
|
||||
const subdomainRegExp = getSubdomainRegExp("https://calcom.example.com");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("calcom.example.com")).toEqual(null);
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("acme.calcom.example.com")?.groups?.orgSlug).toEqual("acme");
|
||||
// The following also matches which causes anything other than the domain in NEXT_PUBLIC_WEBAPP_URL to give 404
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("some-other.company.com")?.groups?.orgSlug).toEqual(
|
||||
"some-other"
|
||||
);
|
||||
});
|
||||
it("Should ignore Vercel preview URLs", () => {
|
||||
const subdomainRegExp = getSubdomainRegExp("https://cal-xxxxxxxx-cal.vercel.app");
|
||||
expect(
|
||||
orgHostRegExp(subdomainRegExp).exec("https://cal-xxxxxxxx-cal.vercel.app")
|
||||
).toMatchInlineSnapshot("null");
|
||||
expect(orgHostRegExp(subdomainRegExp).exec("cal-xxxxxxxx-cal.vercel.app")).toMatchInlineSnapshot(
|
||||
"null"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Rewrite", () => {
|
||||
it("booking pages", () => {
|
||||
expect(orgUserTypeRouteMatch("/user/type")?.params).toContain({
|
||||
user: "user",
|
||||
type: "type",
|
||||
});
|
||||
|
||||
// User slug starting with 404(which is a page route) will work
|
||||
expect(orgUserTypeRouteMatch("/404a/def")?.params).toEqual({
|
||||
user: "404a",
|
||||
type: "def",
|
||||
});
|
||||
|
||||
// Team Page won't match - There is no /team prefix required for Org team event pages
|
||||
expect(orgUserTypeRouteMatch("/team/abc")).toEqual(false);
|
||||
|
||||
expect(orgUserTypeRouteMatch("/abc")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/abc")?.params).toContain({
|
||||
user: "abc",
|
||||
});
|
||||
|
||||
// Tests that something that starts with 'd' which could accidentally match /d route is correctly identified as a booking page
|
||||
expect(orgUserRouteMatch("/designer")?.params).toContain({
|
||||
user: "designer",
|
||||
});
|
||||
|
||||
// Tests that something that starts with 'apps' which could accidentally match /apps route is correctly identified as a booking page
|
||||
expect(orgUserRouteMatch("/apps-conflict-possibility")?.params).toContain({
|
||||
user: "apps-conflict-possibility",
|
||||
});
|
||||
|
||||
// Tests that something that starts with '_next' which could accidentally match /_next route is correctly identified as a booking page
|
||||
expect(orgUserRouteMatch("/_next-candidate")?.params).toContain({
|
||||
user: "_next-candidate",
|
||||
});
|
||||
|
||||
// Tests that something that starts with 'public' which could accidentally match /public route is correctly identified as a booking page
|
||||
expect(orgUserRouteMatch("/public-person")?.params).toContain({
|
||||
user: "public-person",
|
||||
});
|
||||
});
|
||||
|
||||
it("Non booking pages", () => {
|
||||
expect(orgUserTypeRouteMatch("/_next/def")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/public/def")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/_next/")).toEqual(false);
|
||||
expect(orgUserRouteMatch("/public/")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/event-types/")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/event-types/")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/event-types/?abc=1")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/event-types/?abc=1")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/event-types")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/event-types")).toEqual(false);
|
||||
|
||||
expect(orgUserRouteMatch("/event-types?abc=1")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/event-types?abc=1")).toEqual(false);
|
||||
|
||||
expect(orgUserTypeRouteMatch("/john/avatar.png")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/cancel/abcd")).toEqual(false);
|
||||
expect(orgUserTypeRouteMatch("/success/abcd")).toEqual(false);
|
||||
expect(orgUserRouteMatch("/forms/xdsdf-sd")).toEqual(false);
|
||||
expect(orgUserRouteMatch("/router?form=")).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
10
calcom/apps/web/test/lib/parseZone.test.ts
Normal file
10
calcom/apps/web/test/lib/parseZone.test.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { parseZone } from "@calcom/lib/parse-zone";
|
||||
|
||||
const EXPECTED_DATE_STRING = "2021-06-20T11:59:59+02:00";
|
||||
|
||||
it("has the right utcOffset regardless of the local timeZone", async () => {
|
||||
expect(parseZone(EXPECTED_DATE_STRING)?.utcOffset()).toEqual(120);
|
||||
expect(parseZone(EXPECTED_DATE_STRING)?.format()).toEqual(EXPECTED_DATE_STRING);
|
||||
});
|
||||
193
calcom/apps/web/test/lib/team-event-types.test.ts
Normal file
193
calcom/apps/web/test/lib/team-event-types.test.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import prismaMock from "../../../../tests/libs/__mocks__/prismaMock";
|
||||
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { getLuckyUser } from "@calcom/lib/server";
|
||||
import { buildUser } from "@calcom/lib/test/builder";
|
||||
|
||||
it("can find lucky user with maximize availability", async () => {
|
||||
const user1 = buildUser({
|
||||
id: 1,
|
||||
username: "test1",
|
||||
name: "Test User 1",
|
||||
email: "test@example.com",
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T05:30:00.000Z"),
|
||||
},
|
||||
{
|
||||
createdAt: new Date("2022-01-25T06:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const user2 = buildUser({
|
||||
id: 2,
|
||||
username: "test2",
|
||||
name: "Test User 2",
|
||||
email: "tes2t@example.com",
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T04:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const users = [user1, user2];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(users);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
await expect(
|
||||
getLuckyUser("MAXIMIZE_AVAILABILITY", {
|
||||
availableUsers: users,
|
||||
eventTypeId: 1,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
});
|
||||
|
||||
it("can find lucky user with maximize availability and priority ranking", async () => {
|
||||
const user1 = buildUser({
|
||||
id: 1,
|
||||
username: "test1",
|
||||
name: "Test User 1",
|
||||
email: "test@example.com",
|
||||
priority: 2,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T05:30:00.000Z"),
|
||||
},
|
||||
{
|
||||
createdAt: new Date("2022-01-25T06:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const user2 = buildUser({
|
||||
id: 2,
|
||||
username: "test2",
|
||||
name: "Test User 2",
|
||||
email: "tes2t@example.com",
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T04:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const users = [user1, user2];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(users);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
const test = await getLuckyUser("MAXIMIZE_AVAILABILITY", {
|
||||
availableUsers: users,
|
||||
eventTypeId: 1,
|
||||
});
|
||||
|
||||
// both users have medium priority (one user has no priority set, default to medium) so pick least recently booked
|
||||
await expect(
|
||||
getLuckyUser("MAXIMIZE_AVAILABILITY", {
|
||||
availableUsers: users,
|
||||
eventTypeId: 1,
|
||||
})
|
||||
).resolves.toStrictEqual(users[1]);
|
||||
|
||||
const userLowest = buildUser({
|
||||
id: 1,
|
||||
username: "test1",
|
||||
name: "Test User 1",
|
||||
email: "test@example.com",
|
||||
priority: 0,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T03:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const userMedium = buildUser({
|
||||
id: 2,
|
||||
username: "test2",
|
||||
name: "Test User 2",
|
||||
email: "tes2t@example.com",
|
||||
priority: 2,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T04:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const userHighest = buildUser({
|
||||
id: 2,
|
||||
username: "test2",
|
||||
name: "Test User 2",
|
||||
email: "tes2t@example.com",
|
||||
priority: 4,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T05:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const usersWithPriorities = [userLowest, userMedium, userHighest];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(usersWithPriorities);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
// pick the user with the highest priority
|
||||
await expect(
|
||||
getLuckyUser("MAXIMIZE_AVAILABILITY", {
|
||||
availableUsers: usersWithPriorities,
|
||||
eventTypeId: 1,
|
||||
})
|
||||
).resolves.toStrictEqual(usersWithPriorities[2]);
|
||||
|
||||
const userLow = buildUser({
|
||||
id: 1,
|
||||
username: "test1",
|
||||
name: "Test User 1",
|
||||
email: "test@example.com",
|
||||
priority: 0,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T02:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
const userHighLeastRecentBooking = buildUser({
|
||||
id: 2,
|
||||
username: "test2",
|
||||
name: "Test User 2",
|
||||
email: "tes2t@example.com",
|
||||
priority: 3,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T03:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const userHighRecentBooking = buildUser({
|
||||
id: 3,
|
||||
username: "test3",
|
||||
name: "Test User 3",
|
||||
email: "test3t@example.com",
|
||||
priority: 3,
|
||||
bookings: [
|
||||
{
|
||||
createdAt: new Date("2022-01-25T04:30:00.000Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const usersWithSamePriorities = [userLow, userHighLeastRecentBooking, userHighRecentBooking];
|
||||
// TODO: we may be able to use native prisma generics somehow?
|
||||
prismaMock.user.findMany.mockResolvedValue(usersWithSamePriorities);
|
||||
prismaMock.booking.findMany.mockResolvedValue([]);
|
||||
|
||||
// pick the least recently booked user of the two with the highest priority
|
||||
await expect(
|
||||
getLuckyUser("MAXIMIZE_AVAILABILITY", {
|
||||
availableUsers: usersWithSamePriorities,
|
||||
eventTypeId: 1,
|
||||
})
|
||||
).resolves.toStrictEqual(usersWithSamePriorities[1]);
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
import prismaMock from "../../../../../tests/libs/__mocks__/prisma";
|
||||
|
||||
import type { Payment, Prisma, PaymentOption, Booking } from "@prisma/client";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import "vitest-fetch-mock";
|
||||
|
||||
import { sendAwaitingPaymentEmail } from "@calcom/emails";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
|
||||
|
||||
export function getMockPaymentService() {
|
||||
function createPaymentLink(/*{ paymentUid, name, email, date }*/) {
|
||||
return "http://mock-payment.example.com/";
|
||||
}
|
||||
const paymentUid = uuidv4();
|
||||
const externalId = uuidv4();
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
class MockPaymentService implements IAbstractPaymentService {
|
||||
// TODO: We shouldn't need to implement adding a row to Payment table but that's a requirement right now.
|
||||
// We should actually delegate table creation to the core app. Here, only the payment app specific logic should come
|
||||
async create(
|
||||
payment: Pick<Prisma.PaymentUncheckedCreateInput, "amount" | "currency">,
|
||||
bookingId: Booking["id"],
|
||||
userId: Booking["userId"],
|
||||
username: string | null,
|
||||
bookerName: string | null,
|
||||
bookerEmail: string,
|
||||
paymentOption: PaymentOption
|
||||
) {
|
||||
const paymentCreateData = {
|
||||
id: 1,
|
||||
uid: paymentUid,
|
||||
appId: null,
|
||||
bookingId,
|
||||
// booking Booking? @relation(fields: [bookingId], references: [id], onDelete: Cascade)
|
||||
fee: 10,
|
||||
success: true,
|
||||
refunded: false,
|
||||
data: {},
|
||||
externalId,
|
||||
paymentOption,
|
||||
amount: payment.amount,
|
||||
currency: payment.currency,
|
||||
};
|
||||
|
||||
const paymentData = prismaMock.payment.create({
|
||||
data: paymentCreateData,
|
||||
});
|
||||
logger.silly("Created mock payment", JSON.stringify({ paymentData }));
|
||||
|
||||
return paymentData;
|
||||
}
|
||||
async afterPayment(
|
||||
event: CalendarEvent,
|
||||
booking: {
|
||||
user: { email: string | null; name: string | null; timeZone: string } | null;
|
||||
id: number;
|
||||
startTime: { toISOString: () => string };
|
||||
uid: string;
|
||||
},
|
||||
paymentData: Payment
|
||||
): Promise<void> {
|
||||
// TODO: App implementing PaymentService is supposed to send email by itself at the moment.
|
||||
await sendAwaitingPaymentEmail({
|
||||
...event,
|
||||
paymentInfo: {
|
||||
link: createPaymentLink(/*{
|
||||
paymentUid: paymentData.uid,
|
||||
name: booking.user?.name,
|
||||
email: booking.user?.email,
|
||||
date: booking.startTime.toISOString(),
|
||||
}*/),
|
||||
paymentOption: paymentData.paymentOption || "ON_BOOKING",
|
||||
amount: paymentData.amount,
|
||||
currency: paymentData.currency,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return {
|
||||
paymentUid,
|
||||
externalId,
|
||||
MockPaymentService,
|
||||
};
|
||||
}
|
||||
1782
calcom/apps/web/test/utils/bookingScenario/bookingScenario.ts
Normal file
1782
calcom/apps/web/test/utils/bookingScenario/bookingScenario.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
import { createMocks } from "node-mocks-http";
|
||||
|
||||
import type {
|
||||
CustomNextApiRequest,
|
||||
CustomNextApiResponse,
|
||||
} from "@calcom/features/bookings/lib/handleNewBooking/test/fresh-booking.test";
|
||||
|
||||
export function createMockNextJsRequest(...args: Parameters<typeof createMocks>) {
|
||||
return createMocks<CustomNextApiRequest, CustomNextApiResponse>(...args);
|
||||
}
|
||||
1247
calcom/apps/web/test/utils/bookingScenario/expects.ts
Normal file
1247
calcom/apps/web/test/utils/bookingScenario/expects.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
import { getDate } from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
|
||||
import type { SchedulingType } from "@calcom/prisma/client";
|
||||
|
||||
export const DEFAULT_TIMEZONE_BOOKER = "Asia/Kolkata";
|
||||
export function getBasicMockRequestDataForBooking() {
|
||||
return {
|
||||
start: `${getDate({ dateIncrement: 1 }).dateString}T04:00:00.000Z`,
|
||||
end: `${getDate({ dateIncrement: 1 }).dateString}T04:30:00.000Z`,
|
||||
eventTypeSlug: "no-confirmation",
|
||||
timeZone: DEFAULT_TIMEZONE_BOOKER,
|
||||
language: "en",
|
||||
user: "teampro",
|
||||
metadata: {},
|
||||
hasHashedBookingLink: false,
|
||||
hashedLink: null,
|
||||
};
|
||||
}
|
||||
export function getMockRequestDataForBooking({
|
||||
data,
|
||||
}: {
|
||||
data: Partial<ReturnType<typeof getBasicMockRequestDataForBooking>> & {
|
||||
eventTypeId: number;
|
||||
user?: string;
|
||||
rescheduleUid?: string;
|
||||
bookingUid?: string;
|
||||
recurringEventId?: string;
|
||||
recurringCount?: number;
|
||||
schedulingType?: SchedulingType;
|
||||
responses: {
|
||||
email: string;
|
||||
name: string;
|
||||
location: { optionValue: ""; value: string };
|
||||
smsReminderNumber?: string;
|
||||
};
|
||||
};
|
||||
}) {
|
||||
return {
|
||||
...getBasicMockRequestDataForBooking(),
|
||||
...data,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type z from "zod";
|
||||
|
||||
import type { schemaBookingCancelParams } from "@calcom/prisma/zod-utils";
|
||||
|
||||
export function getMockRequestDataForCancelBooking(data: z.infer<typeof schemaBookingCancelParams>) {
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { UserPermissionRole } from "@calcom/prisma/client";
|
||||
import { IdentityProvider } from "@calcom/prisma/enums";
|
||||
|
||||
export const getSampleUserInSession = function () {
|
||||
return {
|
||||
locale: "",
|
||||
avatar: "",
|
||||
organization: {
|
||||
isOrgAdmin: false,
|
||||
metadata: null,
|
||||
id: 1,
|
||||
requestedSlug: null,
|
||||
},
|
||||
profile: null,
|
||||
defaultScheduleId: null,
|
||||
name: "",
|
||||
defaultBookerLayouts: null,
|
||||
timeZone: "Asia/Kolkata",
|
||||
selectedCalendars: [],
|
||||
destinationCalendar: null,
|
||||
emailVerified: new Date(),
|
||||
allowDynamicBooking: false,
|
||||
bio: "",
|
||||
weekStart: "",
|
||||
startTime: 0,
|
||||
endTime: 0,
|
||||
bufferTime: 0,
|
||||
hideBranding: false,
|
||||
timeFormat: 12,
|
||||
twoFactorEnabled: false,
|
||||
identityProvider: IdentityProvider.CAL,
|
||||
brandColor: "#292929",
|
||||
darkBrandColor: "#fafafa",
|
||||
metadata: null,
|
||||
role: UserPermissionRole.USER,
|
||||
disableImpersonation: false,
|
||||
organizationId: null,
|
||||
theme: "",
|
||||
appTheme: "",
|
||||
createdDate: new Date(),
|
||||
trialEndsAt: new Date(),
|
||||
completedOnboarding: false,
|
||||
allowSEOIndexing: false,
|
||||
receiveMonthlyDigestEmail: false,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
enableEmailFeature,
|
||||
mockNoTranslations,
|
||||
} from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
|
||||
import { beforeEach, afterEach } from "vitest";
|
||||
|
||||
export function setupAndTeardown() {
|
||||
beforeEach(() => {
|
||||
// Required to able to generate token in email in some cases
|
||||
//@ts-expect-error - It is a readonly variable
|
||||
process.env.CALENDSO_ENCRYPTION_KEY = "abcdefghjnmkljhjklmnhjklkmnbhjui";
|
||||
//@ts-expect-error - It is a readonly variable
|
||||
process.env.STRIPE_WEBHOOK_SECRET = "MOCK_STRIPE_WEBHOOK_SECRET";
|
||||
// We are setting it in vitest.config.ts because otherwise it's too late to set it.
|
||||
// process.env.DAILY_API_KEY = "MOCK_DAILY_API_KEY";
|
||||
|
||||
// Ensure that Rate Limiting isn't enforced for tests
|
||||
delete process.env.UNKEY_ROOT_KEY;
|
||||
mockNoTranslations();
|
||||
// mockEnableEmailFeature();
|
||||
enableEmailFeature();
|
||||
globalThis.testEmails = [];
|
||||
fetchMock.resetMocks();
|
||||
});
|
||||
afterEach(() => {
|
||||
//@ts-expect-error - It is a readonly variable
|
||||
delete process.env.CALENDSO_ENCRYPTION_KEY;
|
||||
//@ts-expect-error - It is a readonly variable
|
||||
delete process.env.STRIPE_WEBHOOK_SECRET;
|
||||
delete process.env.DAILY_API_KEY;
|
||||
globalThis.testEmails = [];
|
||||
fetchMock.resetMocks();
|
||||
// process.env.DAILY_API_KEY = "MOCK_DAILY_API_KEY";
|
||||
});
|
||||
}
|
||||
81
calcom/apps/web/test/utils/bookingScenario/test.ts
Normal file
81
calcom/apps/web/test/utils/bookingScenario/test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { createOrganization } from "@calcom/web/test/utils/bookingScenario/bookingScenario";
|
||||
|
||||
import type { TestFunction } from "vitest";
|
||||
|
||||
import { WEBSITE_URL } from "@calcom/lib/constants";
|
||||
import { test } from "@calcom/web/test/fixtures/fixtures";
|
||||
import type { Fixtures } from "@calcom/web/test/fixtures/fixtures";
|
||||
|
||||
const WEBSITE_PROTOCOL = new URL(WEBSITE_URL).protocol;
|
||||
const _testWithAndWithoutOrg = (
|
||||
description: Parameters<typeof testWithAndWithoutOrg>[0],
|
||||
fn: Parameters<typeof testWithAndWithoutOrg>[1],
|
||||
timeout: Parameters<typeof testWithAndWithoutOrg>[2],
|
||||
mode: "only" | "skip" | "run" = "run"
|
||||
) => {
|
||||
const t = mode === "only" ? test.only : mode === "skip" ? test.skip : test;
|
||||
t(
|
||||
`${description} - With org`,
|
||||
async ({ emails, sms, meta, task, onTestFailed, expect, skip }) => {
|
||||
const org = await createOrganization({
|
||||
name: "Test Org",
|
||||
slug: "testorg",
|
||||
});
|
||||
|
||||
await fn({
|
||||
meta,
|
||||
task,
|
||||
onTestFailed,
|
||||
expect,
|
||||
emails,
|
||||
sms,
|
||||
skip,
|
||||
org: {
|
||||
organization: org,
|
||||
urlOrigin: `${WEBSITE_PROTOCOL}//${org.slug}.cal.local:3000`,
|
||||
},
|
||||
});
|
||||
},
|
||||
timeout
|
||||
);
|
||||
|
||||
t(
|
||||
`${description}`,
|
||||
async ({ emails, sms, meta, task, onTestFailed, expect, skip }) => {
|
||||
await fn({
|
||||
emails,
|
||||
sms,
|
||||
meta,
|
||||
task,
|
||||
onTestFailed,
|
||||
expect,
|
||||
skip,
|
||||
org: null,
|
||||
});
|
||||
},
|
||||
timeout
|
||||
);
|
||||
};
|
||||
|
||||
export const testWithAndWithoutOrg = (
|
||||
description: string,
|
||||
fn: TestFunction<
|
||||
Fixtures & {
|
||||
org: {
|
||||
organization: { id: number | null };
|
||||
urlOrigin?: string;
|
||||
} | null;
|
||||
}
|
||||
>,
|
||||
timeout?: number
|
||||
) => {
|
||||
_testWithAndWithoutOrg(description, fn, timeout, "run");
|
||||
};
|
||||
|
||||
testWithAndWithoutOrg.only = ((description, fn, timeout) => {
|
||||
_testWithAndWithoutOrg(description, fn, timeout, "only");
|
||||
}) as typeof _testWithAndWithoutOrg;
|
||||
|
||||
testWithAndWithoutOrg.skip = ((description, fn, timeout) => {
|
||||
_testWithAndWithoutOrg(description, fn, timeout, "skip");
|
||||
}) as typeof _testWithAndWithoutOrg;
|
||||
Reference in New Issue
Block a user