feat: mailhog fixture (#10606)

* feat: mailhog fixture

* fix: nodemailer to dispatch emails with e2e env

* fix: remove space from email subject

* feat: fixture getFirstEventAsOwner

* feat: assert email subjects
This commit is contained in:
Shivam Kalra 2023-08-12 00:26:27 +05:30 committed by GitHub
parent 851a388853
commit afe180a0ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 56 additions and 19 deletions

View File

@ -1,4 +1,7 @@
import { expect } from "@playwright/test";
import type { Messages } from "mailhog";
import { randomString } from "@calcom/lib/random";
import { test } from "./lib/fixtures";
import {
@ -10,30 +13,47 @@ import {
testName,
} from "./lib/testUtils";
const freeUserObj = { name: `Free-user-${randomString(3)}` };
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users }) => users.deleteAll());
test.afterEach(async ({ users }) => {
await users.deleteAll();
});
test.describe("free user", () => {
test.beforeEach(async ({ page, users }) => {
const free = await users.create();
const free = await users.create(freeUserObj);
await page.goto(`/${free.username}`);
});
test("cannot book same slot multiple times", async ({ page }) => {
test("cannot book same slot multiple times", async ({ page, users, emails }) => {
const [user] = users.get();
const bookerObj = { email: `testEmail-${randomString(4)}@example.com`, name: "testBooker" };
// Click first event type
await page.click('[data-testid="event-type-link"]');
await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page);
await bookTimeSlot(page, bookerObj);
// save booking url
const bookingUrl: string = page.url();
// Make sure we're navigated to the success page
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
const { title: eventTitle } = await user.getFirstEventAsOwner();
// TODO: follow DRY
const emailsOrganiserReceived = await emails.search(user.email, "to");
const emailsBookerReceived = await emails.search(bookerObj.email, "to");
expect(emailsOrganiserReceived?.total).toBe(1);
expect(emailsBookerReceived?.total).toBe(1);
const [organizerFirstEmail] = (emailsOrganiserReceived as Messages).items;
const [bookerFirstEmail] = (emailsBookerReceived as Messages).items;
const emailSubject = `${eventTitle} between ${user.name} and ${bookerObj.name}`;
expect(organizerFirstEmail.subject).toBe(emailSubject);
expect(bookerFirstEmail.subject).toBe(emailSubject);
// return to same time spot booking page
await page.goto(bookingUrl);
// book same time spot again

View File

@ -2,6 +2,7 @@ import type { Page, WorkerInfo } from "@playwright/test";
import type Prisma from "@prisma/client";
import { Prisma as PrismaType } from "@prisma/client";
import { hashSync as hash } from "bcryptjs";
import type { API } from "mailhog";
import dayjs from "@calcom/dayjs";
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
@ -116,7 +117,7 @@ const createTeamAndAddUser = async (
};
// creates a user fixture instance and stores the collection
export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
export const createUsersFixture = (page: Page, emails: API, workerInfo: WorkerInfo) => {
const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
return {
create: async (
@ -330,7 +331,19 @@ export const createUsersFixture = (page: Page, workerInfo: WorkerInfo) => {
await page.goto("/auth/logout");
},
deleteAll: async () => {
const emailMessageIds: string[] = [];
const ids = store.users.map((u) => u.id);
for (const user of store.users) {
const emailMessages = await emails.search(user.email);
if (emailMessages && emailMessages.count > 0) {
emailMessages.items.forEach((item) => {
emailMessageIds.push(item.ID);
});
}
}
for (const id of emailMessageIds) {
await emails.deleteMessage(id);
}
await prisma.user.deleteMany({ where: { id: { in: ids } } });
store.users = [];
},
@ -387,6 +400,12 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
include: { team: { select: { children: true, metadata: true, name: true } } },
});
},
getFirstEventAsOwner: async () =>
prisma.eventType.findFirstOrThrow({
where: {
userId: user.id,
},
}),
getFirstTeamEvent: async (teamId: number) => {
return prisma.eventType.findFirstOrThrow({
where: {

View File

@ -1,5 +1,7 @@
import type { Page } from "@playwright/test";
import { test as base } from "@playwright/test";
import type { API } from "mailhog";
import mailhog from "mailhog";
import prisma from "@calcom/prisma";
@ -19,6 +21,7 @@ export interface Fixtures {
getActionFiredDetails: ReturnType<typeof createGetActionFiredDetails>;
servers: ReturnType<typeof createServersFixture>;
prisma: typeof prisma;
emails: API;
}
declare global {
@ -40,8 +43,8 @@ declare global {
* @see https://playwright.dev/docs/test-fixtures
*/
export const test = base.extend<Fixtures>({
users: async ({ page, context }, use, workerInfo) => {
const usersFixture = createUsersFixture(page, workerInfo);
users: async ({ page, context, emails }, use, workerInfo) => {
const usersFixture = createUsersFixture(page, emails, workerInfo);
await use(usersFixture);
},
bookings: async ({ page }, use) => {
@ -67,4 +70,8 @@ export const test = base.extend<Fixtures>({
prisma: async ({}, use) => {
await use(prisma);
},
emails: async ({}, use) => {
const mailhogAPI = mailhog();
await use(mailhogAPI);
},
});

View File

@ -85,6 +85,7 @@
"jest-diff": "^29.5.0",
"jsdom": "^22.0.0",
"lint-staged": "^12.5.0",
"mailhog": "^4.16.0",
"prettier": "^2.8.6",
"tsc-absolute": "^1.0.0",
"typescript": "^4.9.4",

View File

@ -8,10 +8,6 @@ import { getErrorFromUnknown } from "@calcom/lib/errors";
import { serverConfig } from "@calcom/lib/serverConfig";
import prisma from "@calcom/prisma";
declare let global: {
E2E_EMAILS?: Record<string, unknown>[];
};
export default class BaseEmail {
name = "";
@ -37,12 +33,6 @@ export default class BaseEmail {
console.warn("Skipped Sending Email due to active Kill Switch");
return new Promise((r) => r("Skipped Sending Email due to active Kill Switch"));
}
if (process.env.NEXT_PUBLIC_IS_E2E) {
global.E2E_EMAILS = global.E2E_EMAILS || [];
global.E2E_EMAILS.push(this.getNodeMailerPayload());
console.log("Skipped Sending Email as NEXT_PUBLIC_IS_E2E==1");
return new Promise((r) => r("Skipped sendEmail for E2E"));
}
const payload = this.getNodeMailerPayload();
const parseSubject = z.string().safeParse(payload?.subject);

View File

@ -80,7 +80,7 @@ export default class OrganizerScheduledEmail extends BaseEmail {
},
from: `${APP_NAME} <${this.getMailerOptions().from}>`,
to: toAddresses.join(","),
subject: `${this.newSeat ? this.t("new_attendee") + ":" : ""} ${this.calEvent.title}`,
subject: `${this.newSeat ? this.t("new_attendee") + ": " : ""}${this.calEvent.title}`,
html: renderEmail("OrganizerScheduledEmail", {
calEvent: clonedCalEvent,
attendee: this.calEvent.organizer,