diff --git a/components/team/MemberInvitationModal.tsx b/components/team/MemberInvitationModal.tsx index 2ba1d2dba2..30f43f2214 100644 --- a/components/team/MemberInvitationModal.tsx +++ b/components/team/MemberInvitationModal.tsx @@ -125,7 +125,7 @@ export default function MemberInvitationModal(props: { team: TeamWithMembers | n

)}
-
diff --git a/package.json b/package.json index ea3ca758c9..7027c96977 100644 --- a/package.json +++ b/package.json @@ -116,6 +116,7 @@ "@types/jest": "^27.0.3", "@types/lodash": "^4.14.177", "@types/micro": "^7.3.6", + "@types/module-alias": "^2.0.1", "@types/node": "^16.11.10", "@types/nodemailer": "^6.4.4", "@types/qrcode": "^1.4.1", @@ -140,6 +141,7 @@ "jest": "^26.0.0", "lint-staged": "^11.1.2", "mockdate": "^3.0.5", + "module-alias": "^2.2.2", "npm-run-all": "^4.1.5", "postcss": "^8.4.4", "prettier": "^2.3.2", diff --git a/pages/api/auth/signup.ts b/pages/api/auth/signup.ts index 45d351a487..5c92ddf1d1 100644 --- a/pages/api/auth/signup.ts +++ b/pages/api/auth/signup.ts @@ -31,21 +31,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) return; } + // There is actually an existingUser if username matches + // OR if email matches and both username and password are set const existingUser = await prisma.user.findFirst({ where: { OR: [ + { username }, { - username: username, - }, - { - email: userEmail, - }, - ], - AND: [ - { - emailVerified: { - not: null, - }, + AND: [{ email: userEmail }, { password: { not: null } }, { username: { not: null } }], }, ], }, diff --git a/pages/settings/teams/[id].tsx b/pages/settings/teams/[id].tsx index 6064fdc974..beb44ed2bf 100644 --- a/pages/settings/teams/[id].tsx +++ b/pages/settings/teams/[id].tsx @@ -72,7 +72,8 @@ export function TeamSettingsPage() { type="button" color="secondary" StartIcon={PlusIcon} - onClick={() => setShowMemberInvitationModal(true)}> + onClick={() => setShowMemberInvitationModal(true)} + data-testid="new-member-button"> {t("new_member")}
diff --git a/playwright.config.ts b/playwright.config.ts index 82c824f154..1ee266ae47 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,4 +1,17 @@ import { PlaywrightTestConfig, devices } from "@playwright/test"; +import { addAliases } from "module-alias"; + +// Add aliases for the paths specified in the tsconfig.json file. +// This is needed because playwright does not consider tsconfig.json +// For more info, see: +// https://stackoverflow.com/questions/69023682/typescript-playwright-error-cannot-find-module +// https://github.com/microsoft/playwright/issues/7066#issuecomment-983984496 +addAliases({ + "@components": __dirname + "/components", + "@lib": __dirname + "/lib", + "@server": __dirname + "/server", + "@ee": __dirname + "/ee", +}); const config: PlaywrightTestConfig = { forbidOnly: !!process.env.CI, diff --git a/playwright/auth/auth-index.test.ts b/playwright/auth/auth-index.test.ts index 6b46733d6d..674a1d2970 100644 --- a/playwright/auth/auth-index.test.ts +++ b/playwright/auth/auth-index.test.ts @@ -1,5 +1,105 @@ +import { expect, test } from "@playwright/test"; + +import { BASE_URL } from "@lib/config/constants"; +import prisma from "@lib/prisma"; + import { todo } from "../lib/testUtils"; -todo("Can signup from a team invite"); +test.describe("Can signup from a team invite", async () => { + let page; + let token: string | undefined; + let signupFromInviteURL = ""; + const team = { name: "Seeded Team", slug: "seeded-team" }; + const testUser = { + email: "test@test.com", + password: "secretpassword123", + validUsername: "test-user", + }; + const usernameAlreadyTaken = "teampro"; + const emailAlreadyTaken = "teampro@example.com"; + + test.use({ storageState: "playwright/artifacts/teamproStorageState.json" }); + test.beforeAll(async ({ browser }) => { + page = await browser.newPage(); + + await page.goto("/settings/teams"); + + await page.waitForSelector(`a[title="${team.name}"]`); + await page.click(`a[title="${team.name}"]`); + + // Send invite to team + await page.click('[data-testid="new-member-button"]'); + await page.fill('input[id="inviteUser"]', testUser.email); + await page.click('[data-testid="invite-new-member-button"]'); + + // Wait for the invite to be sent + await page.waitForSelector(`[data-testid="member-email"][data-email="${testUser.email}"]`); + + const tokenObj = await prisma.verificationRequest.findFirst({ + where: { identifier: testUser.email }, + select: { token: true }, + }); + token = tokenObj?.token; + signupFromInviteURL = `/auth/signup?token=${token}&callbackUrl=${BASE_URL}/settings/teams`; + }); + + test.afterAll(async () => { + // Delete test user + await prisma.user.delete({ + where: { email: testUser.email }, + }); + // Delete verification request + await prisma.verificationRequest.delete({ + where: { token }, + }); + }); + + test("Username already taken", async ({ page }) => { + expect(token).toBeDefined(); + await page.goto(signupFromInviteURL); + // Fill in form + await page.fill('input[name="username"]', usernameAlreadyTaken); + await page.fill('input[name="email"]', testUser.email); + await page.fill('input[name="password"]', testUser.password); + await page.fill('input[name="passwordcheck"]', testUser.password); + await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit + + await expect(page.locator('text="Username already taken"')).toBeVisible(); + }); + + test("Email address is already registered", async ({ page }) => { + expect(token).toBeDefined(); + await page.goto(signupFromInviteURL); + // Fill in form + await page.fill('input[name="username"]', testUser.validUsername); + await page.fill('input[name="email"]', emailAlreadyTaken); + await page.fill('input[name="password"]', testUser.password); + await page.fill('input[name="passwordcheck"]', testUser.password); + await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit + + await expect(page.locator('text="Email address is already registered"')).toBeVisible(); + }); + + test("Successful signup", async ({ page }) => { + expect(token).toBeDefined(); + await page.goto(signupFromInviteURL); + // Fill in form + await page.fill('input[name="username"]', testUser.validUsername); + await page.fill('input[name="email"]', testUser.email); + await page.fill('input[name="password"]', testUser.password); + await page.fill('input[name="passwordcheck"]', testUser.password); + await page.press('input[name="passwordcheck"]', "Enter"); // Press Enter to submit + + await page.waitForNavigation(); + + const createdUser = await prisma.user.findUnique({ + where: { email: testUser.email }, + }); + expect(createdUser).not.toBeNull(); + expect(createdUser?.username).toBe(testUser.validUsername); + expect(createdUser?.password).not.toBeNull(); + expect(createdUser?.emailVerified).not.toBeNull(); + }); +}); todo("Can login using 2FA"); diff --git a/playwright/lib/globalSetup.ts b/playwright/lib/globalSetup.ts index 368ec7c6be..91e86ebe0c 100644 --- a/playwright/lib/globalSetup.ts +++ b/playwright/lib/globalSetup.ts @@ -33,7 +33,7 @@ async function globalSetup(/* config: FullConfig */) { await loginAsUser("free", browser); // await loginAsUser("usa", browser); // await loginAsUser("teamfree", browser); - // await loginAsUser("teampro", browser); + await loginAsUser("teampro", browser); await browser.close(); } diff --git a/yarn.lock b/yarn.lock index a73431481c..f185c3ee6e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2589,6 +2589,11 @@ "@types/node" "*" "@types/socket.io" "2.1.13" +"@types/module-alias@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/module-alias/-/module-alias-2.0.1.tgz#e5893236ce922152d57c5f3f978f764f4deeb45f" + integrity sha512-DN/CCT1HQG6HquBNJdLkvV+4v5l/oEuwOHUPLxI+Eub0NED+lk0YUfba04WGH90EINiUrNgClkNnwGmbICeWMQ== + "@types/ms@*": version "0.7.31" resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" @@ -7548,6 +7553,11 @@ mockdate@^3.0.5: resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== +module-alias@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.2.tgz#151cdcecc24e25739ff0aa6e51e1c5716974c0e0" + integrity sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q== + mongodb-connection-string-url@^2.3.2: version "2.4.1" resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.4.1.tgz#6b3c6c40133a0ad059fe9a0abda64b2a1cb4e8b4"