2022-05-14 03:02:10 +00:00
|
|
|
import type { Page, WorkerInfo } from "@playwright/test";
|
2022-03-28 20:06:41 +00:00
|
|
|
import type Prisma from "@prisma/client";
|
2022-05-14 03:02:10 +00:00
|
|
|
import { Prisma as PrismaType, UserPlan } from "@prisma/client";
|
2022-08-26 18:44:02 +00:00
|
|
|
import { hash } from "bcryptjs";
|
2022-03-28 20:06:41 +00:00
|
|
|
|
2022-08-22 23:53:51 +00:00
|
|
|
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
2022-03-28 20:06:41 +00:00
|
|
|
import { prisma } from "@calcom/prisma";
|
|
|
|
|
2022-04-06 15:13:09 +00:00
|
|
|
import { TimeZoneEnum } from "./types";
|
2022-03-28 20:06:41 +00:00
|
|
|
|
2022-08-26 18:44:02 +00:00
|
|
|
// Don't import hashPassword from app as that ends up importing next-auth and initializing it before NEXTAUTH_URL can be updated during tests.
|
|
|
|
export async function hashPassword(password: string) {
|
|
|
|
const hashedPassword = await hash(password, 12);
|
|
|
|
return hashedPassword;
|
|
|
|
}
|
|
|
|
|
2022-04-06 15:13:09 +00:00
|
|
|
type UserFixture = ReturnType<typeof createUserFixture>;
|
2022-03-28 20:06:41 +00:00
|
|
|
|
2022-05-14 03:02:10 +00:00
|
|
|
const userIncludes = PrismaType.validator<PrismaType.UserInclude>()({
|
|
|
|
eventTypes: true,
|
|
|
|
credentials: true,
|
|
|
|
});
|
|
|
|
|
|
|
|
const userWithEventTypes = PrismaType.validator<PrismaType.UserArgs>()({
|
|
|
|
include: userIncludes,
|
|
|
|
});
|
|
|
|
|
|
|
|
type UserWithIncludes = PrismaType.UserGetPayload<typeof userWithEventTypes>;
|
|
|
|
|
2022-03-28 20:06:41 +00:00
|
|
|
// creates a user fixture instance and stores the collection
|
2022-05-14 03:02:10 +00:00
|
|
|
export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
|
2022-05-18 16:05:25 +00:00
|
|
|
const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
|
2022-03-28 20:06:41 +00:00
|
|
|
return {
|
2022-04-06 15:13:09 +00:00
|
|
|
create: async (opts?: CustomUserOpts) => {
|
2022-05-14 03:02:10 +00:00
|
|
|
const _user = await prisma.user.create({
|
|
|
|
data: await createUser(workerInfo, opts),
|
|
|
|
});
|
|
|
|
await prisma.eventType.create({
|
|
|
|
data: {
|
|
|
|
users: {
|
|
|
|
connect: {
|
|
|
|
id: _user.id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
title: "Paid",
|
|
|
|
slug: "paid",
|
|
|
|
length: 30,
|
|
|
|
price: 1000,
|
|
|
|
},
|
|
|
|
});
|
2022-08-05 17:08:47 +00:00
|
|
|
await prisma.eventType.create({
|
|
|
|
data: {
|
|
|
|
users: {
|
|
|
|
connect: {
|
|
|
|
id: _user.id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
title: "Opt in",
|
|
|
|
slug: "opt-in",
|
|
|
|
requiresConfirmation: true,
|
|
|
|
length: 30,
|
|
|
|
},
|
|
|
|
});
|
2022-05-14 03:02:10 +00:00
|
|
|
const user = await prisma.user.findUnique({
|
|
|
|
rejectOnNotFound: true,
|
|
|
|
where: { id: _user.id },
|
|
|
|
include: userIncludes,
|
2022-03-28 20:06:41 +00:00
|
|
|
});
|
|
|
|
const userFixture = createUserFixture(user, store.page!);
|
|
|
|
store.users.push(userFixture);
|
|
|
|
return userFixture;
|
|
|
|
},
|
|
|
|
get: () => store.users,
|
|
|
|
logout: async () => {
|
2022-08-26 18:44:02 +00:00
|
|
|
await page.goto(`${process.env.PLAYWRIGHT_TEST_BASE_URL}/auth/logout`);
|
2022-03-28 20:06:41 +00:00
|
|
|
},
|
2022-05-14 03:02:10 +00:00
|
|
|
deleteAll: async () => {
|
|
|
|
const ids = store.users.map((u) => u.id);
|
|
|
|
await prisma.user.deleteMany({ where: { id: { in: ids } } });
|
|
|
|
store.users = [];
|
|
|
|
},
|
|
|
|
delete: async (id: number) => {
|
|
|
|
await prisma.user.delete({ where: { id } });
|
|
|
|
store.users = store.users.filter((b) => b.id !== id);
|
|
|
|
},
|
2022-03-28 20:06:41 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-04-06 15:13:09 +00:00
|
|
|
type JSONValue = string | number | boolean | { [x: string]: JSONValue } | Array<JSONValue>;
|
|
|
|
|
2022-03-28 20:06:41 +00:00
|
|
|
// creates the single user fixture
|
2022-05-14 03:02:10 +00:00
|
|
|
const createUserFixture = (user: UserWithIncludes, page: Page) => {
|
2022-03-28 20:06:41 +00:00
|
|
|
const store = { user, page };
|
|
|
|
|
|
|
|
// self is a reflective method that return the Prisma object that references this fixture.
|
2022-05-14 03:02:10 +00:00
|
|
|
const self = async () =>
|
|
|
|
(await prisma.user.findUnique({ where: { id: store.user.id }, include: { eventTypes: true } }))!;
|
2022-03-28 20:06:41 +00:00
|
|
|
return {
|
|
|
|
id: user.id,
|
2022-05-14 03:02:10 +00:00
|
|
|
username: user.username,
|
|
|
|
eventTypes: user.eventTypes!,
|
2022-03-28 20:06:41 +00:00
|
|
|
self,
|
2022-04-06 15:13:09 +00:00
|
|
|
login: async () => login({ ...(await self()), password: user.username }, store.page),
|
2022-05-14 03:02:10 +00:00
|
|
|
getPaymentCredential: async () => getPaymentCredential(store.page),
|
2022-03-28 20:06:41 +00:00
|
|
|
// ths is for developemnt only aimed to inject debugging messages in the metadata field of the user
|
2022-04-06 15:13:09 +00:00
|
|
|
debug: async (message: string | Record<string, JSONValue>) => {
|
2022-03-28 20:06:41 +00:00
|
|
|
await prisma.user.update({ where: { id: store.user.id }, data: { metadata: { debug: message } } });
|
|
|
|
},
|
2022-05-14 03:02:10 +00:00
|
|
|
delete: async () => (await prisma.user.delete({ where: { id: store.user.id } }))!,
|
2022-03-28 20:06:41 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-04-06 15:13:09 +00:00
|
|
|
type CustomUserOptsKeys = "username" | "password" | "plan" | "completedOnboarding" | "locale";
|
|
|
|
type CustomUserOpts = Partial<Pick<Prisma.User, CustomUserOptsKeys>> & { timeZone?: TimeZoneEnum };
|
2022-03-28 20:06:41 +00:00
|
|
|
|
|
|
|
// creates the actual user in the db.
|
2022-05-14 03:02:10 +00:00
|
|
|
const createUser = async (
|
|
|
|
workerInfo: WorkerInfo,
|
|
|
|
opts?: CustomUserOpts
|
|
|
|
): Promise<PrismaType.UserCreateInput> => {
|
2022-03-28 20:06:41 +00:00
|
|
|
// build a unique name for our user
|
2022-05-14 03:02:10 +00:00
|
|
|
const uname = `${opts?.username ?? opts?.plan?.toLocaleLowerCase() ?? UserPlan.PRO.toLowerCase()}-${
|
|
|
|
workerInfo.workerIndex
|
|
|
|
}-${Date.now()}`;
|
2022-03-28 20:06:41 +00:00
|
|
|
return {
|
|
|
|
username: uname,
|
|
|
|
name: (opts?.username ?? opts?.plan ?? UserPlan.PRO).toUpperCase(),
|
|
|
|
plan: opts?.plan ?? UserPlan.PRO,
|
|
|
|
email: `${uname}@example.com`,
|
|
|
|
password: await hashPassword(uname),
|
|
|
|
emailVerified: new Date(),
|
|
|
|
completedOnboarding: opts?.completedOnboarding ?? true,
|
2022-04-06 15:13:09 +00:00
|
|
|
timeZone: opts?.timeZone ?? TimeZoneEnum.UK,
|
2022-03-28 20:06:41 +00:00
|
|
|
locale: opts?.locale ?? "en",
|
2022-08-22 23:53:51 +00:00
|
|
|
schedules:
|
|
|
|
opts?.completedOnboarding ?? true
|
|
|
|
? {
|
|
|
|
create: {
|
|
|
|
name: "Working Hours",
|
|
|
|
availability: {
|
|
|
|
createMany: {
|
|
|
|
data: getAvailabilityFromSchedule(DEFAULT_SCHEDULE),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
: undefined,
|
2022-05-14 03:02:10 +00:00
|
|
|
eventTypes: {
|
|
|
|
create: {
|
|
|
|
title: "30 min",
|
|
|
|
slug: "30-min",
|
|
|
|
length: 30,
|
|
|
|
},
|
|
|
|
},
|
2022-03-28 20:06:41 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
// login using a replay of an E2E routine.
|
2022-04-06 15:13:09 +00:00
|
|
|
export async function login(
|
|
|
|
user: Pick<Prisma.User, "username"> & Partial<Pick<Prisma.User, "password" | "email">>,
|
|
|
|
page: Page
|
|
|
|
) {
|
|
|
|
// get locators
|
2022-05-14 03:02:10 +00:00
|
|
|
const loginLocator = page.locator("[data-testid=login-form]");
|
|
|
|
const emailLocator = loginLocator.locator("#email");
|
|
|
|
const passwordLocator = loginLocator.locator("#password");
|
|
|
|
const signInLocator = loginLocator.locator('[type="submit"]');
|
2022-04-06 15:13:09 +00:00
|
|
|
|
|
|
|
//login
|
2022-08-26 18:44:02 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
await page.goto(process.env.PLAYWRIGHT_TEST_BASE_URL!);
|
2022-04-06 15:13:09 +00:00
|
|
|
await emailLocator.fill(user.email ?? `${user.username}@example.com`);
|
2022-08-26 18:44:02 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
2022-04-06 15:13:09 +00:00
|
|
|
await passwordLocator.fill(user.password ?? user.username!);
|
|
|
|
await signInLocator.click();
|
2022-03-28 20:06:41 +00:00
|
|
|
|
2022-04-06 15:13:09 +00:00
|
|
|
// 2 seconds of delay to give the session enough time for a clean load
|
2022-05-17 19:31:49 +00:00
|
|
|
// eslint-disable-next-line playwright/no-wait-for-timeout
|
2022-03-28 20:06:41 +00:00
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
}
|
2022-05-14 03:02:10 +00:00
|
|
|
|
|
|
|
export async function getPaymentCredential(page: Page) {
|
2022-06-01 17:24:41 +00:00
|
|
|
await page.goto("/apps/stripe");
|
2022-05-14 03:02:10 +00:00
|
|
|
|
|
|
|
/** We start the Stripe flow */
|
|
|
|
await Promise.all([
|
|
|
|
page.waitForNavigation({ url: "https://connect.stripe.com/oauth/v2/authorize?*" }),
|
2022-06-01 17:24:41 +00:00
|
|
|
page.click('[data-testid="install-app-button"]'),
|
2022-05-14 03:02:10 +00:00
|
|
|
]);
|
|
|
|
|
|
|
|
await Promise.all([
|
|
|
|
page.waitForNavigation({ url: "/apps/installed" }),
|
|
|
|
/** We skip filling Stripe forms (testing mode only) */
|
|
|
|
page.click('[id="skip-account-app"]'),
|
|
|
|
]);
|
|
|
|
}
|