Skip to content

Commit

Permalink
Some fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
hariombalhara committed Nov 6, 2023
1 parent 31fd536 commit 7c03444
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 86 deletions.
16 changes: 10 additions & 6 deletions apps/web/pages/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type SignupProps = inferSSRProps<typeof getServerSideProps>;
const checkValidEmail = (email: string) => z.string().email().safeParse(email).success;

const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) => {
const [emailUser, emailDomain] = email.split("@");
const [emailUser, emailDomain = ""] = email.split("@");
const username =
emailDomain === autoAcceptEmailDomain
? slugify(emailUser)
Expand All @@ -57,10 +57,9 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
const telemetry = useTelemetry();
const { t, i18n } = useLocale();
const flags = useFlagMap();
const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username;
const methods = useForm<FormValues>({
mode: "onChange",
resolver: zodResolver(isOrgInviteByLink ? signupSchema.omit({ username: true }) : signupSchema),
resolver: zodResolver(signupSchema),
defaultValues: prepopulateFormValues,
});
const {
Expand All @@ -75,10 +74,9 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
}
};

const isOrgInviteByLink = orgSlug && !prepopulateFormValues?.username;

const signUp: SubmitHandler<FormValues> = async (data) => {
if (data.username === undefined && isOrgInviteByLink && orgAutoAcceptEmail) {
data = { ...data, username: getOrgUsernameFromEmail(methods.getValues().email, orgAutoAcceptEmail) };
}
await fetch("/api/auth/signup", {
body: JSON.stringify({
...data,
Expand Down Expand Up @@ -145,6 +143,12 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
methods.clearErrors("apiError");
}

if (!methods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) {
methods.setValue(
"username",
getOrgUsernameFromEmail(methods.getValues().email, orgAutoAcceptEmail)
);
}
methods.handleSubmit(signUp)(event);
}}
className="bg-default space-y-6">
Expand Down
34 changes: 34 additions & 0 deletions apps/web/playwright/fixtures/clipboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Page } from "@playwright/test";

declare global {
interface Window {
E2E_CLIPBOARD_VALUE?: string;
}
}

export type Window = typeof window;
// creates the single server fixture
export const createClipboardFixture = (page: Page) => {
return {
reset: async () => {
await page.evaluate(() => {
delete window.E2E_CLIPBOARD_VALUE;
});
},
get: async () => {
return getClipboardValue({ page });
},
};
};

function getClipboardValue({ page }: { page: Page }) {
return page.evaluate(() => {
return new Promise<string>((resolve, reject) => {
setInterval(() => {
if (!window.E2E_CLIPBOARD_VALUE) return;
resolve(window.E2E_CLIPBOARD_VALUE);
}, 500);
setTimeout(() => reject(new Error("Timeout")), 1000);
});
});
}
3 changes: 3 additions & 0 deletions apps/web/playwright/fixtures/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
routingForms: user.routingForms,
self,
apiLogin: async () => apiLogin({ ...(await self()), password: user.username }, store.page),
/**
* @deprecated use apiLogin instead
*/
login: async () => login({ ...(await self()), password: user.username }, store.page),
logout: async () => {
await page.goto("/auth/logout");
Expand Down
6 changes: 6 additions & 0 deletions apps/web/playwright/lib/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import prisma from "@calcom/prisma";

import type { ExpectedUrlDetails } from "../../../../playwright.config";
import { createBookingsFixture } from "../fixtures/bookings";
import { createClipboardFixture } from "../fixtures/clipboard";
import { createEmbedsFixture } from "../fixtures/embeds";
import { createOrgsFixture } from "../fixtures/orgs";
import { createPaymentsFixture } from "../fixtures/payments";
Expand All @@ -28,6 +29,7 @@ export interface Fixtures {
emails?: API;
routingForms: ReturnType<typeof createRoutingFormsFixture>;
bookingPage: ReturnType<typeof createBookingPageFixture>;
clipboard: ReturnType<typeof createClipboardFixture>;
}

declare global {
Expand Down Expand Up @@ -92,4 +94,8 @@ export const test = base.extend<Fixtures>({
const bookingPage = createBookingPageFixture(page);
await use(bookingPage);
},
clipboard: async ({ page }, use) => {
const clipboard = createClipboardFixture(page);
await use(clipboard);
},
});
32 changes: 0 additions & 32 deletions apps/web/playwright/lib/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { createHash } from "crypto";
import EventEmitter from "events";
import type { IncomingMessage, ServerResponse } from "http";
import { createServer } from "http";
import { JSDOM } from "jsdom";
// eslint-disable-next-line no-restricted-imports
import { noop } from "lodash";
import type { API, Messages } from "mailhog";
Expand Down Expand Up @@ -282,37 +281,6 @@ export async function createUserWithSeatedEventAndAttendees(
return { user, eventType, booking };
}

export async function expectInvitationEmailToBeReceived(
page: Page,
emails: API | undefined,
userEmail: string,
subject: string,
returnLink?: string
) {
if (!emails) return null;
// We need to wait for the email to go through, otherwise it will fail
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(2000);
const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail });
expect(receivedEmails?.total).toBe(1);
const [firstReceivedEmail] = (receivedEmails as Messages).items;
expect(firstReceivedEmail.subject).toBe(subject);
if (!returnLink) return;
const dom = new JSDOM(firstReceivedEmail.html);
const anchor = dom.window.document.querySelector(`a[href*="${returnLink}"]`);
return anchor?.getAttribute("href");
}

export async function getInviteLinkFromConsole(page: Page): Promise<string> {
return new Promise((resolve) => {
page.on("console", (msg) => {
if (msg.text().indexOf("signup?token") > -1) {
resolve(msg.text());
}
});
});
}

export function generateTotpCode(email: string) {
const secret = createHash("md5")
.update(email + process.env.CALENDSO_ENCRYPTION_KEY)
Expand Down
28 changes: 28 additions & 0 deletions apps/web/playwright/organization/expects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { JSDOM } from "jsdom";
// eslint-disable-next-line no-restricted-imports
import type { API, Messages } from "mailhog";

import { getEmailsReceivedByUser } from "../lib/testUtils";

export async function expectInvitationEmailToBeReceived(
page: Page,
emails: API | undefined,
userEmail: string,
subject: string,
returnLink?: string
) {
if (!emails) return null;
// We need to wait for the email to go through, otherwise it will fail
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(2000);
const receivedEmails = await getEmailsReceivedByUser({ emails, userEmail });
expect(receivedEmails?.total).toBe(1);
const [firstReceivedEmail] = (receivedEmails as Messages).items;
expect(firstReceivedEmail.subject).toBe(subject);
if (!returnLink) return;
const dom = new JSDOM(firstReceivedEmail.html);
const anchor = dom.window.document.querySelector(`a[href*="${returnLink}"]`);
return anchor?.getAttribute("href");
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import { expect } from "@playwright/test";
import path from "path";

import { test } from "./lib/fixtures";
import { expectInvitationEmailToBeReceived, generateTotpCode } from "./lib/testUtils";
import { test } from "../lib/fixtures";
import { generateTotpCode } from "../lib/testUtils";
import { expectInvitationEmailToBeReceived } from "./expects";

test.afterAll(({ users, emails }) => {
users.deleteAll();
emails?.deleteAll();
});

function capitalize(text: string) {
if (!text) {
return text;
}
return text.charAt(0).toUpperCase() + text.slice(1);
}

test.describe("Organization", () => {
test("Creation", async ({ page, users, emails }) => {
test("should be able to create an organization and complete onboarding", async ({
page,
users,
emails,
}) => {
const orgOwner = await users.create();
const orgName = `${orgOwner.username}-org`.charAt(0).toUpperCase() + `${orgOwner.username}-org`.slice(1);
const orgDomain = `${orgOwner.username}-org`;
await orgOwner.login();
const orgName = capitalize(`${orgOwner.username}-org`);
await orgOwner.apiLogin();
await page.goto("/settings/organizations/new");
await page.waitForLoadState("networkidle");

Expand Down Expand Up @@ -90,7 +102,6 @@ test.describe("Organization", () => {
await test.step("On-board administrators", async () => {
// Required field
await page.locator("button[type=submit]").click();
await page.locator('textarea[name="emails"]:invalid');

// Happy path
await page.locator('textarea[name="emails"]').fill(`rick@${orgDomain}.com`);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { expect } from "@playwright/test";

import { test } from "./lib/fixtures";
import { getInviteLinkFromConsole, expectInvitationEmailToBeReceived } from "./lib/testUtils";
import { test } from "../lib/fixtures";
import { expectInvitationEmailToBeReceived } from "./expects";

test.describe.configure({ mode: "parallel" });

test.afterAll(({ users, emails }) => {
users.deleteAll();
test.afterEach(async ({ users, emails, clipboard }) => {
clipboard.reset();
await users.deleteAll();
emails?.deleteAll();
});

test.describe("Organization", () => {
test("Invitation (non verified)", async ({ browser, page, users, emails }) => {
test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
const { team: org } = await orgOwner.getOrg();
await orgOwner.login();
await orgOwner.apiLogin();
await page.goto("/settings/organizations/members");
await page.waitForLoadState("networkidle");

Expand Down Expand Up @@ -68,31 +69,31 @@ test.describe("Organization", () => {
// Get the invite link
await page.locator('button:text("Add")').click();
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
const inviteLink = await getInviteLinkFromConsole(page);
const inviteLink = await clipboard.get();
await page.waitForLoadState("networkidle");

// Follow invite link in new window
const context = await browser.newContext();
const newPage = await context.newPage();
newPage.goto(inviteLink);
await newPage.waitForLoadState("networkidle");
const inviteLinkPage = await context.newPage();
await inviteLinkPage.goto(inviteLink);
await inviteLinkPage.waitForLoadState("networkidle");

// Check required fields
await newPage.locator("button[type=submit]").click();
await expect(newPage.locator(".text-red-700")).toHaveCount(4); // email + 3 password hints
await inviteLinkPage.locator("button[type=submit]").click();
await expect(inviteLinkPage.locator(".text-red-700")).toHaveCount(4); // email + 3 password hints

// Happy path
await newPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`);
await newPage.locator("input[name=password]").fill(`P4ssw0rd!`);
await newPage.locator("button[type=submit]").click();
await newPage.waitForURL("/getting-started");
await inviteLinkPage.locator("input[name=email]").fill(`rick@domain-${Date.now()}.com`);
await inviteLinkPage.locator("input[name=password]").fill(`P4ssw0rd!`);
await inviteLinkPage.locator("button[type=submit]").click();
await inviteLinkPage.waitForURL("/getting-started");
});
});

test("Invitation (verified)", async ({ browser, page, users, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
const { team: org } = await orgOwner.getOrg();
await orgOwner.login();
await orgOwner.apiLogin();
await page.goto("/settings/organizations/members");
await page.waitForLoadState("networkidle");

Expand Down
18 changes: 0 additions & 18 deletions packages/features/ee/organizations/pages/settings/members.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,6 @@ const MembersView = () => {
<LicenseRequired>
<Meta title={t("organization_members")} description={t("organization_description")} />
<div>
{/* {team && (
<>
{isInviteOpen && (
<TeamInviteList
teams={[
{
id: team.id,
accepted: team.membership.accepted || false,
logo: team.logo,
name: team.name,
slug: team.slug,
role: team.membership.role,
},
]}
/>
)}
</>
)} */}
<UserListTable />
</div>
</LicenseRequired>
Expand Down
11 changes: 6 additions & 5 deletions packages/features/ee/teams/components/MemberInvitationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
TextAreaField,
} from "@calcom/ui";
import { Link } from "@calcom/ui/components/icon";
import type { Window as WindowWithClipboardValue } from "@calcom/web/playwright/fixtures/clipboard";

import type { PendingMember } from "../lib/types";
import { GoogleWorkspaceInviteButton } from "./GoogleWorkspaceInviteButton";
Expand Down Expand Up @@ -92,15 +93,15 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)

const inviteLink =
isOrgInvite || (props?.orgMembers && props.orgMembers?.length > 0) ? orgInviteLink : teamInviteLink;

try {
await navigator.clipboard.writeText(inviteLink);
console.log(inviteLink);
showToast(t("invite_link_copied"), "success");
} catch (e) {
console.log(inviteLink);
if (process.env.NEXT_PUBLIC_IS_E2E) {
(window as WindowWithClipboardValue).E2E_CLIPBOARD_VALUE = inviteLink;
}
console.error(e);
}

showToast(t("invite_link_copied"), "success");
};

const options: MembershipRoleOption[] = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const createHandler = async ({ input, ctx }: CreateOptions) => {

return { user: { ...createOwnerOrg, password } };
} else {
if (!IS_PRODUCTION && !process.env.NEXT_PUBLIC_IS_E2E) return { checked: true };
if (!IS_PRODUCTION || process.env.NEXT_PUBLIC_IS_E2E) return { checked: true };
const language = await getTranslation(input.language ?? "en", "common");

const secret = createHash("md5")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const verifyCodeHandler = async ({ ctx, input }: VerifyCodeOptions) => {

if (!user || !email || !code) throw new TRPCError({ code: "BAD_REQUEST" });

if (!IS_PRODUCTION) return true;
if (!IS_PRODUCTION || process.env.NEXT_PUBLIC_IS_E2E) return true;
await checkRateLimitAndThrowError({
rateLimitingType: "core",
identifier: email,
Expand Down
Loading

0 comments on commit 7c03444

Please sign in to comment.