Files
calendar/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.test.ts
T
+13 a4a3eb5ea9 refactor: Billing service (#15366)
* WIP: Billing service

* WIP

* Update __handler.ts

* Update index.ts

* WIP

* WIP

Signed-off-by: zomars <zomars@me.com>

* WIP

Signed-off-by: zomars <zomars@me.com>

* Update delete.handler.ts

* Update delete.handler.ts

* Update internal-team-billing.ts

* WIP

* Type fixes

Signed-off-by: zomars <zomars@me.com>

* WIP

Signed-off-by: zomars <zomars@me.com>

* Discard changes to apps/api/v2/src/ee/event-types/event-types_2024_04_15/controllers/event-types.controller.ts

* Discard changes to packages/app-store/skype/config.json

* style: update text area (#15934)

* chore: update text-editor input

* chore: update text-editor input

* final update

* Update Editor.tsx

---------

Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* fix: #15697 Assignment warning modal poping up ,even when hosts are added . (#15699)

* removed reduntand check on assignedusers

* update for managed eventtype

* formatted

* comments

* Updated comments

* extracted checkForEmptyAssignment and added tests

* updated comments

* updated to show dialog also when not yet saved

* update translations

* updated comments

* updated translation

* updated types

* chore: update naming and comments

* Delete apps/web/lib/checkForEmptyAssignment.ts

deleted since file was renamed

* Delete apps/web/test/lib/checkForEmptyAssignment.test.ts

deleted as the file is renamed to be more descriptive

* update to do teamcheck before

* chore: revert name to CheckForEmptyAssignment

* updated check conditions

* updated useEffect dependencies

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* fix: getting an error when a team member tries to leave (#15925)

a team

Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>

* feat: add transitions to more components (#15916)

* add transitions to more components

* chore: Remove duplicate class

* fix: Change minimum number of teams required for org self-serve (#15955)

* fix: Change minimum number of teams required for org self-serve

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* refactor: Use a new constant for displaying org helper text dialogue

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>

* Update packages/lib/constants.ts

* Update turbo.json

---------

Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* feat: pwa ready and add shortcut for quick access (#15957)

* chore: Update site.webmanifest with additional display properties

* chore: Update site.webmanifest with additional display properties

* Update apps/web/public/site.webmanifest

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* i18n: update translations for "by proceeding, ..." (#15921)

* feat: Add title attribute to iframe in embed.ts (#15944)

* feat: Add title attribute to iframe in embed.ts

* Update packages/embeds/embed-core/src/embed.ts

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* fix: Assignment reminder modal appears when deleting a team event type (#15544)

* fix: Assignment reminder modal appears when deleting a team event type

* update

* Update EventTypeSingleLayout.tsx

* Update event-types-single-view.tsx

* update

* Early return https://handbook.cal.com/engineering/best-practices/prefer-early-returns

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>

* fix: Add CORP and COEP headers to embedded pages (#15275)

* Add CORP headers

* Improve test

* Add unit tests as well

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* feat: No-show bookings - do not count towards a member's round robin assignment (#15323)

* feat: No-show bookings - do not count towards a member's round robin assignment

* filter no show organizer

* remove logs

* fix: filter bookings where host attendee is no show

* fix: use not condition for noShow

* chore: test

* add default false for noShowHost

* Revert "chore: test"

This reverts commit c914a8fc4a14ca9c7d8c4a08ff8b0f463789171c.

* chore: integration test

* fix: noShowHost not working for null

* Update packages/lib/server/getLuckyUser.ts

* chore: update test description

---------

Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: gh0st91848 <gh0st91848@gmail.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* refactor: v1 orgs unlimited teams (#15908)

* refactor: v1 orgs unlimited teams

* fix: response message ownerId

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* fix: refresh connect atoms on token change (#15933)

* added dropdown

* change comment

* fix: added types

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>

* fix: Next.js bundle analysis (#15903)

* fix: Next.js bundle analysis

* Added the main checkout back

* fix: type in webhook payload (#15912)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* fix: auto accept (#15975)

* fix: auto accept

* Update packages/lib/server/repository/organization.ts

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

---------

Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* fix: Broken GitHub workflows (#15979)

* test: Delete credential endpoint (#15322)

* Init

* Add mock trpc context

* Intro mockDatabaseClient

* Introduce mockDatabaseClient class

* Add delete video app test

* Add calendar test

* Remove unused func from bookingScenario

* Remove console.log

* Add app repository

* Add createMany method to event type repository

* Remove instance of MockDatabaseClient from video test

* Add destination calendar repository

* Remove instances of MockDatabaseClient

* abstract logic to own handler

* Remove dev dependency

* Clean up

* Type fix

* Pull yarn.lock from main

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* fix: #15967 Show better error when wrong variable is used in custom event name (#15974)

* update to show exact err msg with custom event name

* updated tests for validateCustomEventName

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>

* WIP

Signed-off-by: zomars <zomars@me.com>

* Update yarn.lock

* Update publish.handler.ts

* Update internal-team-billing.test.ts

* rename create to init

* remove sync services

* wip tests for delete team

* wip tets

* wip tests

* tests/internal-test

* remove return that is inaccessable

* remove team calls to use repository

* remove redudant file

* update team repo

* add stripe mocks and test

* fix constants

* tests to main stripe handler

* remove logs

* fix constants

* Update packages/features/ee/billing/api/webhook/_customer.subscription.deleted.team-plan.ts

Co-authored-by: Omar López <zomars@me.com>

* move workflow logic to a service

* use static method > creating new class

* fix: remove http code deep in repo + tests

* Feedback

* Update index.ts

* Type fix

* Lazy load fixes

---------

Signed-off-by: zomars <zomars@me.com>
Signed-off-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Rohan Advani <122167726+RNAdvani@users.noreply.github.com>
Co-authored-by: unknown <adhabal2002@gmail.com>
Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
Co-authored-by: Vijay <vijayraghav22@gmail.com>
Co-authored-by: Somay Chauhan <somaychauhan98@gmail.com>
Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com>
Co-authored-by: Hichem Fantar <hichemfantar2049@gmail.com>
Co-authored-by: Souptik Datta <souptikdatta2001@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Johannes Maendle <141119154+jomaendle2@users.noreply.github.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: gh0st91848 <gh0st91848@gmail.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean <sean@brydon.io>
2024-10-04 13:44:50 -07:00

403 lines
11 KiB
TypeScript

import { scenarios as checkRateLimitAndThrowErrorScenarios } from "../../../../../../../tests/libs/__mocks__/checkRateLimitAndThrowError";
import { mock as getTranslationMock } from "../../../../../../../tests/libs/__mocks__/getTranslation";
import {
inviteMemberutilsScenarios as inviteMemberUtilsScenarios,
default as inviteMemberUtilsMock,
} from "./__mocks__/inviteMemberUtils";
import { constantsScenarios } from "@calcom/lib/__mocks__/constants";
import { describe, it, expect, beforeEach, vi } from "vitest";
import type { Profile } from "@calcom/prisma/client";
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
import { TRPCError } from "@trpc/server";
import type { TrpcSessionUser } from "../../../../trpc";
import inviteMemberHandler from "./inviteMember.handler";
import { INVITE_STATUS } from "./utils";
vi.mock("@trpc/server", () => {
return {
TRPCError: class TRPCError {
code: string;
message: unknown;
constructor({ code, message }: { code: string; message: unknown }) {
this.code = code;
this.message = message;
}
},
};
});
function fakeNoUsersFoundMatchingInvitations(args: {
team: any;
invitations: {
role: MembershipRole;
usernameOrEmail: string;
}[];
}) {
inviteMemberUtilsScenarios.findUsersWithInviteStatus.useAdvancedMock([], args);
}
function getPersonalProfile({ username }: { username: string }) {
return {
id: null,
upId: "abc",
organization: null,
organizationId: null,
username,
startTime: 0,
endTime: 0,
avatarUrl: "",
name: "",
bufferTime: 0,
};
}
function getLoggedInUser() {
return {
id: 123,
name: "John Doe",
organization: {
id: 456,
isOrgAdmin: true,
metadata: null,
requestedSlug: null,
},
profile: getPersonalProfile({ username: "john_doe" }),
} as NonNullable<TrpcSessionUser>;
}
function buildExistingUser(user: { id: number; email: string; username: string }) {
return {
password: {
hash: "hash",
userId: 1,
},
identityProvider: IdentityProvider.CAL,
profiles: [] as Profile[],
completedOnboarding: false,
...user,
};
}
describe("inviteMemberHandler", () => {
beforeEach(async () => {
await inviteMemberUtilsScenarios.getOrgState.useActual();
await inviteMemberUtilsScenarios.getUniqueInvitationsOrThrowIfEmpty.useActual();
await inviteMemberUtilsScenarios.getOrgConnectionInfo.useActual();
checkRateLimitAndThrowErrorScenarios.fakeRateLimitPassed();
getTranslationMock.fakeIdentityFn();
inviteMemberUtilsScenarios.checkPermissions.fakePassed();
constantsScenarios.enableTeamBilling();
});
describe("Regular Team", () => {
describe("with 2 emails in input and when there are no users matching the emails", () => {
it("should call appropriate utilities to send email, add users and update in stripe. It should return `numUsersInvited`=2", async () => {
const usersToBeInvited = [
{
id: 1,
email: "user1@example.com",
},
{
id: 2,
email: "user2@example.com",
},
];
const loggedInUser = getLoggedInUser();
const input = {
teamId: 1,
role: MembershipRole.MEMBER,
isOrg: false,
language: "en",
usernameOrEmail: usersToBeInvited.map((u) => u.email),
};
const team = {
id: input.teamId,
name: "Team 1",
parent: null,
};
const retValueOfGetTeamOrThrowError = inviteMemberUtilsScenarios.getTeamOrThrow.fakeReturnTeam(team, {
teamId: input.teamId,
});
const allExpectedInvitations = [
{
role: input.role,
usernameOrEmail: usersToBeInvited[0].email,
},
{
role: input.role,
usernameOrEmail: usersToBeInvited[1].email,
},
];
fakeNoUsersFoundMatchingInvitations({
team,
invitations: allExpectedInvitations,
});
const ctx = {
user: loggedInUser,
};
// Call the inviteMemberHandler function
const result = await inviteMemberHandler({ ctx, input });
const expectedConnectionInfoMap = {
[usersToBeInvited[0].email]: {
orgId: undefined,
autoAccept: false,
},
[usersToBeInvited[1].email]: {
orgId: undefined,
autoAccept: false,
},
};
const inviter = {
name: loggedInUser.name,
};
expect(inviteMemberUtilsMock.handleNewUsersInvites).toHaveBeenCalledWith({
invitationsForNewUsers: allExpectedInvitations,
team: retValueOfGetTeamOrThrowError,
orgConnectInfoByUsernameOrEmail: expectedConnectionInfoMap,
teamId: input.teamId,
language: input.language,
inviter,
autoAcceptEmailDomain: null,
});
// TODO: Fix this test
// expect(paymentsMock.updateQuantitySubscriptionFromStripe).toHaveBeenCalledWith(input.teamId);
expect(inviteMemberUtilsMock.handleExistingUsersInvites).not.toHaveBeenCalled();
expect(inviteMemberUtilsMock.getUniqueInvitationsOrThrowIfEmpty).toHaveBeenCalledWith([
{
role: input.role,
usernameOrEmail: usersToBeInvited[0].email,
},
{
role: input.role,
usernameOrEmail: usersToBeInvited[1].email,
},
]);
// Assert the result
expect(result).toEqual({
usernameOrEmail: input.usernameOrEmail,
numUsersInvited: 2,
});
});
});
describe("with 2 emails in input and when there is one user matching the email", () => {
it("should call appropriate utilities to add users and update in stripe. It should return `numUsersInvited=2`", async () => {
const usersToBeInvited = [
buildExistingUser({
id: 1,
email: "user1@example.com",
username: "user1",
}),
{
id: null,
email: "user2@example.com",
},
] as const;
const loggedInUser = getLoggedInUser();
const input = {
teamId: 1,
role: MembershipRole.MEMBER,
language: "en",
usernameOrEmail: usersToBeInvited.map((u) => u.email),
};
const team = {
id: input.teamId,
name: "Team 1",
parent: null,
isOrganization: false,
};
const retValueOfGetTeamOrThrowError = inviteMemberUtilsScenarios.getTeamOrThrow.fakeReturnTeam(team, {
teamId: input.teamId,
});
const allExpectedInvitations = [
{
role: input.role,
usernameOrEmail: usersToBeInvited[0].email,
},
{
role: input.role,
usernameOrEmail: usersToBeInvited[1].email,
},
];
const retValueOfFindUsersWithInviteStatus =
await inviteMemberUtilsScenarios.findUsersWithInviteStatus.useAdvancedMock(
[
{
...usersToBeInvited[0],
canBeInvited: INVITE_STATUS.CAN_BE_INVITED,
newRole: input.role,
},
],
{
invitations: allExpectedInvitations,
team,
}
);
const ctx = {
user: loggedInUser,
};
const result = await inviteMemberHandler({ ctx, input });
const expectedConnectionInfoMap = {
[usersToBeInvited[0].email]: {
orgId: undefined,
autoAccept: false,
},
[usersToBeInvited[1].email]: {
orgId: undefined,
autoAccept: false,
},
};
const inviter = {
name: loggedInUser.name,
};
expect(inviteMemberUtilsMock.handleNewUsersInvites).toHaveBeenCalledWith({
invitationsForNewUsers: allExpectedInvitations.slice(1),
team: retValueOfGetTeamOrThrowError,
orgConnectInfoByUsernameOrEmail: expectedConnectionInfoMap,
teamId: input.teamId,
language: input.language,
inviter,
autoAcceptEmailDomain: null,
isOrg: false,
});
// TODO: Fix this test
// expect(paymentsMock.updateQuantitySubscriptionFromStripe).toHaveBeenCalledWith(input.teamId);
expect(inviteMemberUtilsMock.handleExistingUsersInvites).toHaveBeenCalledWith({
invitableExistingUsers: retValueOfFindUsersWithInviteStatus,
teamId: input.teamId,
language: input.language,
inviter,
orgConnectInfoByUsernameOrEmail: expectedConnectionInfoMap,
orgSlug: null,
team: retValueOfGetTeamOrThrowError,
isOrg: false,
});
// Assert the result
expect(result).toEqual({
usernameOrEmail: input.usernameOrEmail,
numUsersInvited: 2,
});
});
});
it("With one email in input and that email is already a member of the team, it should throw error", async () => {
const userToBeInvited = buildExistingUser({
id: 1,
email: "user1@example.com",
username: "user1",
});
const loggedInUser = getLoggedInUser();
const input = {
teamId: 1,
role: MembershipRole.MEMBER,
isOrg: false,
language: "en",
usernameOrEmail: userToBeInvited.email,
};
const team = {
id: input.teamId,
name: "Team 1",
parent: null,
};
inviteMemberUtilsScenarios.getTeamOrThrow.fakeReturnTeam(team, {
teamId: input.teamId,
});
inviteMemberUtilsScenarios.findUsersWithInviteStatus.useAdvancedMock(
[
{
...userToBeInvited,
canBeInvited: INVITE_STATUS.USER_ALREADY_INVITED_OR_MEMBER,
newRole: input.role,
},
],
{
invitations: [
{
newRole: input.role,
usernameOrEmail: userToBeInvited.email,
},
],
team,
}
);
const ctx = {
user: loggedInUser,
};
try {
await inviteMemberHandler({ ctx, input });
throw new Error("Expected an error to be thrown");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
expect(e).toBeInstanceOf(TRPCError);
expect(e.code).toEqual("BAD_REQUEST");
expect(e.message).toBe(INVITE_STATUS.USER_ALREADY_INVITED_OR_MEMBER);
}
});
});
it("When rate limit exceeded, it should throw error", async () => {
const userToBeInvited = buildExistingUser({
id: 1,
email: "user1@example.com",
username: "user1",
});
const errorMessageForRateLimit = checkRateLimitAndThrowErrorScenarios.fakeRateLimitFailed();
const loggedInUser = getLoggedInUser();
const input = {
teamId: 1,
role: MembershipRole.MEMBER,
isOrg: false,
language: "en",
usernameOrEmail: userToBeInvited.email,
};
const ctx = {
user: loggedInUser,
};
try {
await inviteMemberHandler({ ctx, input });
throw new Error("Expected an error to be thrown");
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (e: any) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toEqual(errorMessageForRateLimit);
}
});
});