From ff6db3c572541848bd7c3cf9505cb9c116cfbc62 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Fri, 16 Feb 2024 18:20:07 +0530 Subject: [PATCH] fix: team invite signup not working (#13717) * Fix team invite signup not working * Add unit test --------- Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> --- packages/lib/server/username.test.ts | 92 +++++++++++++++++++ packages/lib/server/username.ts | 6 +- .../viewer/teams/inviteMember/utils.ts | 15 ++- 3 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 packages/lib/server/username.test.ts diff --git a/packages/lib/server/username.test.ts b/packages/lib/server/username.test.ts new file mode 100644 index 0000000000..480dd3ef0c --- /dev/null +++ b/packages/lib/server/username.test.ts @@ -0,0 +1,92 @@ +import prismaMock from "../../../tests/libs/__mocks__/prismaMock"; + +import { describe, expect, it, beforeEach } from "vitest"; + +import { usernameCheckForSignup } from "./username"; + +describe("usernameCheckForSignup ", async () => { + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + prismaMock.user.findUnique.mockImplementation(() => { + return null; + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + prismaMock.user.findMany.mockImplementation(() => { + return []; + }); + }); + + it("should return available true for an email that doesn't exist", async () => { + const res = await usernameCheckForSignup({ username: "johnny", email: "johnny@example.com" }); + expect(res).toEqual({ + available: true, + premium: false, + suggestedUsername: "", + }); + }); + + it("should return available false for an email that exists and a different username is provided", async () => { + mockUserInDB({ + id: 1, + email: "john@example.com", + username: "john", + }); + const res = await usernameCheckForSignup({ username: "johnny", email: "john@example.com" }); + expect(res).toEqual({ + available: false, + premium: false, + suggestedUsername: "johnny001", + }); + }); + + it("should return available true for an email that exists but the user is signing up for an organization", async () => { + const userId = 1; + mockUserInDB({ + id: userId, + email: "john@example.com", + username: "john", + }); + mockMembership({ userId }); + const res = await usernameCheckForSignup({ username: "john", email: "john@example.com" }); + expect(res).toEqual({ + available: true, + // An organization can't have premium username + premium: false, + suggestedUsername: "", + }); + }); +}); + +function mockUserInDB({ id, email, username }: { id: number; email: string; username: string }) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + prismaMock.user.findUnique.mockImplementation((arg) => { + if (arg.where.email === email) { + return { + id, + email, + username, + }; + } + return null; + }); +} + +function mockMembership({ userId }: { userId: number }) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + prismaMock.membership.findFirst.mockImplementation((arg) => { + const isOrganizationWhereClause = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + arg?.where?.team?.metadata?.path[0] === "isOrganization" && arg?.where?.team?.metadata?.equals === true; + if (arg?.where?.userId === userId && isOrganizationWhereClause) { + return { + userId, + teamId: 1, + }; + } + }); +} diff --git a/packages/lib/server/username.ts b/packages/lib/server/username.ts index cc5abc0176..e8531e205c 100644 --- a/packages/lib/server/username.ts +++ b/packages/lib/server/username.ts @@ -178,7 +178,7 @@ const usernameCheckForSignup = async ({ const username = slugify(usernameRaw); - const user = await prisma.user.findFirst({ + const user = await prisma.user.findUnique({ where: { email, }, @@ -207,7 +207,9 @@ const usernameCheckForSignup = async ({ // The only way to differentiate b/w 'a new email that was invited to an Org' and 'a user that was created using regular signup' is to check if the user is a member of an org. // If username is in global namespace if (!userIsAMemberOfAnOrg) { - response.available = false; + const isClaimingAlreadySetUsername = user.username === username; + const isClaimingUnsetUsername = !user.username; + response.available = isClaimingUnsetUsername || isClaimingAlreadySetUsername; // There are no premium users outside an organization only response.premium = await isPremiumUserName(username); } diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts index bfe6a35453..a4953162d5 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts @@ -227,22 +227,29 @@ export async function createNewUsersConnectToOrgIfExists({ }) { // fail if we have invalid emails usernamesOrEmails.forEach((usernameOrEmail) => checkInputEmailIsValid(usernameOrEmail)); - // from this point we know usernamesOrEmails contains only emails await prisma.$transaction( async (tx) => { for (let index = 0; index < usernamesOrEmails.length; index++) { const usernameOrEmail = usernamesOrEmails[index]; + // Weird but orgId is defined only if the invited user email matches orgAutoAcceptEmail const { orgId, autoAccept } = connectionInfoMap[usernameOrEmail]; const [emailUser, emailDomain] = usernameOrEmail.split("@"); - const username = + + // An org member can't change username during signup, so we set the username + const orgMemberUsername = emailDomain === autoAcceptEmailDomain ? slugify(emailUser) : slugify(`${emailUser}-${emailDomain.split(".")[0]}`); + // As a regular team member is allowed to change username during signup, we don't set any username for him + const regularTeamMemberUsername = null; + + const isBecomingAnOrgMember = parentId || input.isOrg; + const createdUser = await tx.user.create({ data: { - username, + username: isBecomingAnOrgMember ? orgMemberUsername : regularTeamMemberUsername, email: usernameOrEmail, verified: true, invitedTo: input.teamId, @@ -254,7 +261,7 @@ export async function createNewUsersConnectToOrgIfExists({ data: [ { uid: ProfileRepository.generateProfileUid(), - username, + username: orgMemberUsername, organizationId: orgId, }, ],