import { describe, it, vi, expect } from "vitest"; import { isTeamAdmin } from "@calcom/lib/server/queries"; import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations"; import type { User } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; import { TRPCError } from "@trpc/server"; import type { TeamWithParent } from "./types"; import { checkInputEmailIsValid, checkPermissions, getEmailsToInvite, getIsOrgVerified, getOrgConnectionInfo, throwIfInviteIsToOrgAndUserExists, createAndAutoJoinIfInOrg, } from "./utils"; vi.mock("@calcom/lib/server/queries", () => { return { isTeamAdmin: vi.fn(), }; }); vi.mock("@calcom/lib/server/queries/organisations", () => { return { isOrganisationAdmin: vi.fn(), }; }); const mockedReturnSuccessCheckPerms = { accepted: true, disableImpersonation: false, id: 1, role: MembershipRole.ADMIN, userId: 1, teamId: 1, }; const mockedTeam: TeamWithParent = { id: 1, name: "Team A", slug: null, logo: null, appLogo: null, appIconLogo: null, bio: null, hideBranding: false, hideBookATeamMember: false, createdAt: new Date(), brandColor: "#292929", darkBrandColor: "#fafafa", timeZone: "Europe/London", weekStart: "Sunday", theme: null, timeFormat: null, metadata: null, parentId: null, parent: null, isPrivate: false, }; const mockUser: User = { id: 4, username: "pro", name: "Pro Example", email: "pro@example.com", emailVerified: new Date(), password: "", bio: null, avatar: null, timeZone: "Europe/London", weekStart: "Sunday", startTime: 0, endTime: 1440, bufferTime: 0, hideBranding: false, theme: null, createdDate: new Date(), trialEndsAt: null, defaultScheduleId: null, completedOnboarding: true, locale: "en", timeFormat: 12, twoFactorSecret: null, twoFactorEnabled: false, identityProvider: "CAL", identityProviderId: null, invitedTo: null, brandColor: "#292929", darkBrandColor: "#fafafa", away: false, allowDynamicBooking: true, metadata: null, verified: false, role: "USER", disableImpersonation: false, organizationId: null, }; describe("Invite Member Utils", () => { describe("checkPermissions", () => { it("It should throw an error if the user is not an admin of the ORG", async () => { vi.mocked(isOrganisationAdmin).mockResolvedValue(false); await expect(checkPermissions({ userId: 1, teamId: 1, isOrg: true })).rejects.toThrow(); }); it("It should NOT throw an error if the user is an admin of the ORG", async () => { vi.mocked(isOrganisationAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms); await expect(checkPermissions({ userId: 1, teamId: 1, isOrg: true })).resolves.not.toThrow(); }); it("It should throw an error if the user is not an admin of the team", async () => { vi.mocked(isTeamAdmin).mockResolvedValue(false); await expect(checkPermissions({ userId: 1, teamId: 1 })).rejects.toThrow(); }); it("It should NOT throw an error if the user is an admin of a team", async () => { vi.mocked(isTeamAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms); await expect(checkPermissions({ userId: 1, teamId: 1 })).resolves.not.toThrow(); }); }); describe("getEmailsToInvite", () => { it("should throw a TRPCError with code BAD_REQUEST if no emails are provided", async () => { await expect(getEmailsToInvite([])).rejects.toThrow(TRPCError); }); it("should return an array with one email if a string is provided", async () => { const result = await getEmailsToInvite("test@example.com"); expect(result).toEqual(["test@example.com"]); }); it("should return an array with multiple emails if an array is provided", async () => { const result = await getEmailsToInvite(["test1@example.com", "test2@example.com"]); expect(result).toEqual(["test1@example.com", "test2@example.com"]); }); }); describe("checkInputEmailIsValid", () => { it("should throw a TRPCError with code BAD_REQUEST if the email is invalid", () => { const invalidEmail = "invalid-email"; expect(() => checkInputEmailIsValid(invalidEmail)).toThrow(TRPCError); expect(() => checkInputEmailIsValid(invalidEmail)).toThrowError( "Invite failed because invalid-email is not a valid email address" ); }); it("should not throw an error if the email is valid", () => { const validEmail = "valid-email@example.com"; expect(() => checkInputEmailIsValid(validEmail)).not.toThrow(); }); }); describe("getOrgConnectionInfo", () => { const orgAutoAcceptDomain = "example.com"; const usersEmail = "user@example.com"; it("should return orgId and autoAccept as true if team has parent and usersEmail domain matches orgAutoAcceptDomain and orgVerified is true", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, orgVerified: true, usersEmail, team: { ...mockedTeam, parentId: 2, }, isOrg: false, }); expect(result).toEqual({ orgId: 2, autoAccept: true }); }); it("should return orgId and autoAccept as false if team has parent and usersEmail domain does not match orgAutoAcceptDomain", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, usersEmail: "user@other.com", team: { ...mockedTeam, parentId: 2, }, isOrg: false, }); expect(result).toEqual({ orgId: undefined, autoAccept: false }); }); it("should return orgId and autoAccept as false if team has parent and usersEmail domain matches orgAutoAcceptDomain but orgVerified is false", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, orgVerified: false, usersEmail, team: { ...mockedTeam }, isOrg: false, }); expect(result).toEqual({ orgId: undefined, autoAccept: false }); }); it("should return orgId and autoAccept as true if team has no parent and isOrg is true and usersEmail domain matches orgAutoAcceptDomain and orgVerified is true", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, orgVerified: true, usersEmail, team: { ...mockedTeam, parentId: null }, isOrg: true, }); expect(result).toEqual({ orgId: 1, autoAccept: true }); }); it("should return orgId and autoAccept as false if team has no parent and isOrg is true and usersEmail domain does not match orgAutoAcceptDomain", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, usersEmail: "user@other.com", team: { ...mockedTeam, parentId: null }, isOrg: true, }); expect(result).toEqual({ orgId: undefined, autoAccept: false }); }); it("should return orgId and autoAccept as false if team has no parent and isOrg is true and usersEmail domain matches orgAutoAcceptDomain but orgVerified is false", () => { const result = getOrgConnectionInfo({ orgAutoAcceptDomain, orgVerified: false, usersEmail, team: { ...mockedTeam, parentId: null }, isOrg: true, }); expect(result).toEqual({ orgId: mockedTeam.id, autoAccept: false }); }); }); describe("getIsOrgVerified", () => { it("should return the correct values when isOrg is true and teamMetadata.orgAutoAcceptEmail is true", () => { const team = { metadata: { isOrganizationVerified: true, orgAutoAcceptEmail: "example.com", }, parent: null, }; const result = getIsOrgVerified(true, { ...mockedTeam, ...team }); expect(result).toEqual({ isInOrgScope: true, orgVerified: true, autoAcceptEmailDomain: "example.com", }); }); it("should return the correct values when orgMetadataIfExists.orgAutoAcceptEmail is true", () => { const team = { metadata: {}, parent: { ...mockedTeam, metadata: { isOrganizationVerified: false, orgAutoAcceptEmail: "example.com", }, }, }; const result = getIsOrgVerified(false, { ...mockedTeam, ...team }); expect(result).toEqual({ isInOrgScope: true, orgVerified: false, autoAcceptEmailDomain: "example.com", }); }); it("should return the correct values when neither isOrg nor orgMetadataIfExists.orgAutoAcceptEmail is true", () => { const team = { metadata: {}, parent: null, }; const result = getIsOrgVerified(false, { ...mockedTeam, ...team }); expect(result).toEqual({ isInOrgScope: false, }); }); }); describe("throwIfInviteIsToOrgAndUserExists", () => { const invitee: User = { ...mockUser, id: 1, username: "testuser", email: "testuser@example.com", organizationId: null, }; const isOrg = false; it("should not throw when inviting an existing user to the same organization", () => { const inviteeWithOrg: User = { ...invitee, organizationId: 2, }; const teamWithOrg = { ...mockedTeam, parentId: 2, }; expect(() => throwIfInviteIsToOrgAndUserExists(inviteeWithOrg, teamWithOrg, isOrg)).not.toThrow(); }); it("should throw a TRPCError with code FORBIDDEN if the invitee is already a member of another organization", () => { const inviteeWithOrg: User = { ...invitee, organizationId: 2, }; const teamWithOrg = { ...mockedTeam, parentId: 3, }; expect(() => throwIfInviteIsToOrgAndUserExists(inviteeWithOrg, teamWithOrg, isOrg)).toThrow(TRPCError); }); it("should throw a TRPCError with code FORBIDDEN if the invitee already exists in Cal.com and is being invited to an organization", () => { const isOrg = true; expect(() => throwIfInviteIsToOrgAndUserExists(invitee, mockedTeam, isOrg)).toThrow(TRPCError); }); it("should not throw an error if the invitee does not already belong to another organization and is not being invited to an organization", () => { expect(() => throwIfInviteIsToOrgAndUserExists(invitee, mockedTeam, isOrg)).not.toThrow(); }); }); describe("createAndAutoJoinIfInOrg", () => { it("should return autoJoined: false if the user is not in the same organization as the team", async () => { const result = await createAndAutoJoinIfInOrg({ team: mockedTeam, role: MembershipRole.ADMIN, invitee: mockUser, }); expect(result).toEqual({ autoJoined: false }); }); it("should return autoJoined: false if the team does not have a parent organization", async () => { const result = await createAndAutoJoinIfInOrg({ team: { ...mockedTeam, parentId: null }, role: MembershipRole.ADMIN, invitee: mockUser, }); expect(result).toEqual({ autoJoined: false }); }); // TODO: Add test for when the user is already a member of the organization - need to mock prisma response value }); });