Files
c28eb90928 chore: Upgrade prisma to 6.7.0 (#21264)
* chore: Upgrade prisma to 6.7.0

* Build fixes

* type fixes

Signed-off-by: Omar López <[email protected]>

* Update schema.prisma

* Patching

* Revert "Update schema.prisma"

This reverts commit 47d8618bf89ef4d007b30084df766f17281e21a1.

* Revert "Patching"

This reverts commit a1d2e3040e71690a44d4324db95d73b4d68c6adb.

* Revert schema changes

Signed-off-by: Omar López <[email protected]>

* WIP

Signed-off-by: Omar López <[email protected]>

* Update getPublicEvent.ts

* Update imports

Signed-off-by: Omar López <[email protected]>

* Update gitignore

Signed-off-by: Omar López <[email protected]>

* update remaining imports

Signed-off-by: Omar López <[email protected]>

* Delete .cursor/config.json

* Discard changes to packages/features/eventtypes/lib/getPublicEvent.ts

* Update _get.ts

* Update user.ts

* Update .gitignore

* update

* Update WorkflowStepContainer.tsx

* Update next-auth-custom-adapter.ts

* Update getPublicEvent.ts

* Update workflow.ts

* Update next-auth-custom-adapter.ts

* Update next-auth-options.ts

* Update bookingScenario.ts

* fix missing imports

* upgrades prismock

Signed-off-by: Omar López <[email protected]>

* patches prismock

Signed-off-by: Omar López <[email protected]>

* Update reschedule.test.ts

* Update prisma.ts

* patch prismock

Signed-off-by: Omar López <[email protected]>

* fix enums imports

Signed-off-by: Omar López <[email protected]>

* Revert "Update prisma.ts"

This reverts commit 64edcf8db54171ff4456c209d563b5d431d99619.

* Revert "patch prismock"

This reverts commit e95819113dc9d88e7130947aa120cd42710977c8.

* fix patch

* Fix test that overrun the boundary, it shouldn't test too much

* Move prisma import to changeSMSLockState

* Bring back broken test without illegal imports

* Merge with main and fix filter hosts by same round robin host

* Fixed buildDryRunBooking fn tests

* Fix and move ooo create or update handler test

* Fix packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts

* Fix packages/trpc/server/routers/viewer/organizations/listMembers.handler.test.ts

* Mock @calcom/prisma

* Fix: verify-email.test.ts

* fix: Moved WebhookService test and fixed default import mock

* Fix: Added missing prisma mock, handleNewBooking uses that of course

* We're not testing createContext here

* fix: Prisma mock fix for listMembers.test.ts

* More fixes to broken testcases

* Forgot to remove borked test

* Prevent the need to mock a lot of dependencies by moving out buildBaseWhereCondition to its own file

* Temporarily skip getCalendarEvents, needs a rewrite

* Fix: turns out you can access protected in testcases

* fix further mocks

* Added packages/features/insights/server/buildBaseWhereCondition.ts, types

* Always great to have a mock and then not use it

* And one less again.

* fix: confirm.handler.test, didn't mock prisma

* fix: Address minor nit by @eunjae & fix ImpersonationProvider test

* Updated isPrismaAvailableCheck that doesn't crash on import

* fix: Get Prisma directly from the client, it usually involves the Validator and does not need 'local' inclusion

* Add zod-prisma-types without the generator enabled (commented out)

* Uncomment and see what happens

* Change method of import as imports did not work in Input Schemas

* Remove custom 'zod' booking model, it does not belong with Prisma

* Fix all other global Model imports

* Rewrite most schema includes AND remove barrel file

* Add bookingCreateBodySchema to features/bookings

* Flurry of type fixes for compatibility with new zod gen

* Refactor out the custom prisma type createEventTypeInput

* Work around nullable eventTypeLocations

* HandlePayment type fix

* More fixes, final fix remaining is CompleteEventType

* Should fix a bunch more booking related type errors

* Missed one

* Some props missing from BookingCreateBodySchema

* Fix location type in handleChildrenEventTypes

* Little bit hacky imo but it works

* Final type error \o/

* Forgot to include Prisma

* Do not include zod-utils in booker/types

* Oops, was already including Booker/types

* Fix membership type, also disallow updating createdAt/updatedAt, make part of patch/post

* Fix api v1 type errors

* Fix EventTypeDescription typings

* Remove getParserWithGeneric, use userBodySchema with UserSchema

* use centralized timeZoneSchema

* Implement feedback by @zomars

* Couple of WIP pushes

* Fix tests

* Type fixes in `handleChildrenEventTypes` test

* Try and parse metadata before use

* Change zod-prisma-types configuration for optimal performance

* Fix prisma validator error in `prisma/selects/credential`

* Disable seperate relations model, hits a bug

* Import absolute - this makes rollup work in @platform/libraries

* Attempt at removing resolutions override

* Refactor using `Prisma.validator` to `satisfies`

* Build atoms using @calcom/prisma/client

* Build atoms using @calcom/prisma/client

* fixes

* Update eventTypeSelect.ts

* Adjust `eventTypeMetaDataSchemaWithUntypedApps` from `unknown` to `record(any)`

* `EventTypeDescription` rely on `descriptionAsSafeHTML` instead of `description`

* Add `seatsPerTimeSlot` to event type public select

* Fix typing in `users-public-view` getServerSide props

* Add missing `schedulingType` to prop

* chore: bump platform libraries

* Function return type is illegal, not sure how this passed eslint (#21567)

* Merged with main

* Update updateTokenObject.ts

* Update handleResponse.ts

* Update index.ts

* Update handleChildrenEventTypes.ts

* Update booking-idempotency-key.ts

* Update WebhookService.test.ts

* Update events.test.ts

* Update queued-response.test.ts

* Update events.test.ts

* Update getRoutedUrl.test.ts

* fix: type checks

Signed-off-by: Omar López <[email protected]>

* fixes

Signed-off-by: Omar López <[email protected]>

* chore: bump platform libraries

* Update yarn.lock

* more fixes

Signed-off-by: Omar López <[email protected]>

* fixes

Signed-off-by: Omar López <[email protected]>

* biuld fixes

* chore: bump platform libraries

* Update conferencing.repository.ts

* Update conferencing.repository.ts

* Update getCalendarsEvents.test.ts

* Update vite.config.js

* chore: bump platform libraries

* Update users.ts

* Discard changes to docs/api-reference/v2/openapi.json

* Update vite.config.ts

* updated platform libraries

* Update get.handler.test.ts

* Update get.handler.test.ts

* Update schema.prisma

* Discard changes to docs/api-reference/v2/openapi.json

* Update next-auth-custom-adapter.ts

* Update team.ts

* Flurry of type fixes

* Fix majority of insight related type errors

* Type fixes for unlink of account

* Make user nullable again

* Fixed a bunch of unit tests and one type error

* Attempted mock fix

* Attempted fix for Attribute type

* Ensure default import becomes prisma, but not direct usage

* Import default as prisma in prisma.module

* Add attributeOption to attribute type

* Fix calcom/prisma mock

* Refactor Prisma client imports to @calcom/prisma/client

Updated all imports from '@prisma/client' to '@calcom/prisma/client' across tests and repository files for consistency and to use the correct Prisma client package. This change improves maintainability and ensures the correct client is referenced throughout the codebase.

* Undo removal of max-warnings=0 to get main to merge

* Remove unit tests for e2e fixtures, provide new prisma mock

* Mock @calcom/prisma in event manager

* Mock @calcom/prisma in event manager

* Add correct format even with --no-verify

* Mock prisma in CalendarManager

* Add mock for permission-check.service

* Better injection in PrismaApiKeyRepository imports

* More mock fixes :)

* Fix listMembers.handler.test

* Fix User import

* Appropriately adjust all types to be imported as types, there were a lot of types imported as normal deps

* Why was this a thing?

* Strictly speaking; Not using prismock anymore

* Ditched patch file for prismock

* Fix output.service.ts platform type imports, need concrete for plainToClass

* Better typing and tests for unlinkConnectedAccount.handler

* Small type fix

* Disable calendar cache tests as they are dependent on prismock

* chore: bump platform lib

* getRoutedUrl test remove of unused import

* Extract select to external const on getEventTypesFromDB

* Direct select of userSelect from selects/user

* fix type error from merging 23653

* Fixed integration tests by removing hardcoded values that were possible due to mocking, but as its now directly hitting the db no longer

* fix: vite config atoms prisma client type location

* revert: example app prisma client

* revert: example app prisma client

* bump platform libs

* fix: use class instead of type for DI of PlatformBookingsService

* update platform libs

* remove unused variable

* chore: generate prisma client for api v2

* fix: api v2 e2e

* fix: atoms e2e

* fix: atoms e2e

* fix: atoms e2e

* fix: api v2 e2e

* fix: tsconfig apiv2 enums

* publish libraries

* Simplify check for existence teamId

---------

Signed-off-by: Omar López <[email protected]>
Co-authored-by: Alex van Andel <[email protected]>
Co-authored-by: Joe Au-Yeung <[email protected]>
Co-authored-by: supalarry <[email protected]>
Co-authored-by: cal.com <[email protected]>
Co-authored-by: Morgan <[email protected]>
Co-authored-by: Benny Joo <[email protected]>
2025-09-11 15:27:50 +01:00

565 lines
16 KiB
TypeScript

/**
* This file is deprecated in favour of inviteMember.handler.integration-test.ts
*
* It mocks a lot of things that are untested and integration tests make more sense for this handler
*/
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 "../../../../types";
import inviteMemberHandler from "./inviteMember.handler";
import { INVITE_STATUS } from "./types";
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;
}
},
};
});
vi.mock("@calcom/prisma", () => {
return {
prisma: vi.fn(),
};
});
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: "[email protected]",
},
{
id: 2,
email: "[email protected]",
},
];
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", () => {
// TODO: Fix this test
it.skip("should call appropriate utilities to add users and update in stripe. It should return `numUsersInvited=2`", async () => {
const usersToBeInvited = [
buildExistingUser({
id: 1,
email: "[email protected]",
username: "user1",
}),
{
id: null,
email: "[email protected]",
},
] 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: "[email protected]",
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("With one email that is already a member and isDirectUserAction=false, it should NOT throw error", async () => {
const userToBeInvited = buildExistingUser({
id: 1,
email: "[email protected]",
username: "user1",
});
const loggedInUser = getLoggedInUser();
const team = {
id: 1,
name: "Team 1",
parent: null,
isOrganization: false,
};
inviteMemberUtilsScenarios.getTeamOrThrow.fakeReturnTeam(team, {
teamId: team.id,
});
inviteMemberUtilsScenarios.findUsersWithInviteStatus.useAdvancedMock(
[
{
...userToBeInvited,
canBeInvited: INVITE_STATUS.USER_ALREADY_INVITED_OR_MEMBER,
newRole: MembershipRole.MEMBER,
},
],
{
invitations: [
{
role: MembershipRole.MEMBER,
usernameOrEmail: userToBeInvited.email,
},
],
team,
}
);
// Call inviteMembersWithNoInviterPermissionCheck directly with isDirectUserAction=false
const { inviteMembersWithNoInviterPermissionCheck } = await import("./inviteMember.handler");
const result = await inviteMembersWithNoInviterPermissionCheck({
inviterName: loggedInUser.name,
teamId: team.id,
language: "en",
creationSource: "WEBAPP" as const,
orgSlug: null,
invitations: [
{
usernameOrEmail: userToBeInvited.email,
role: MembershipRole.MEMBER,
},
],
isDirectUserAction: false,
});
// Should not throw error, should return successfully with 0 users invited
expect(result).toEqual({
usernameOrEmail: userToBeInvited.email,
numUsersInvited: 0,
});
// Verify that handleNewUsersInvites and handleExistingUsersInvites were not called
// since the user is already a member
expect(inviteMemberUtilsMock.handleNewUsersInvites).not.toHaveBeenCalled();
expect(inviteMemberUtilsMock.handleExistingUsersInvites).not.toHaveBeenCalled();
});
it("With multiple emails where some are already members and isDirectUserAction=false, it should NOT throw error and invite only eligible users", async () => {
const existingMember = buildExistingUser({
id: 1,
email: "[email protected]",
username: "existing",
});
const newUser = {
id: null,
email: "[email protected]",
};
const team = {
id: 1,
name: "Team 1",
parent: null,
isOrganization: false,
};
inviteMemberUtilsScenarios.getTeamOrThrow.fakeReturnTeam(team, {
teamId: team.id,
});
const allInvitations = [
{
role: MembershipRole.MEMBER,
usernameOrEmail: existingMember.email,
},
{
role: MembershipRole.MEMBER,
usernameOrEmail: newUser.email,
},
];
inviteMemberUtilsScenarios.findUsersWithInviteStatus.useAdvancedMock(
[
{
...existingMember,
canBeInvited: INVITE_STATUS.USER_ALREADY_INVITED_OR_MEMBER,
newRole: MembershipRole.MEMBER,
},
],
{
invitations: allInvitations,
team,
}
);
// Call inviteMembersWithNoInviterPermissionCheck directly with isDirectUserAction=false
const { inviteMembersWithNoInviterPermissionCheck } = await import("./inviteMember.handler");
const result = await inviteMembersWithNoInviterPermissionCheck({
inviterName: "Test Inviter",
teamId: team.id,
language: "en",
creationSource: "WEBAPP" as const,
orgSlug: null,
invitations: allInvitations,
isDirectUserAction: false,
});
// Should not throw error, should return successfully with 1 user invited (the new user)
expect(result).toEqual({
usernameOrEmail: [existingMember.email, newUser.email],
numUsersInvited: 1,
});
// Verify that only new user was invited
expect(inviteMemberUtilsMock.handleNewUsersInvites).toHaveBeenCalledWith(
expect.objectContaining({
invitationsForNewUsers: [
{
role: MembershipRole.MEMBER,
usernameOrEmail: newUser.email,
},
],
})
);
});
});
it("When rate limit exceeded, it should throw error", async () => {
const userToBeInvited = buildExistingUser({
id: 1,
email: "[email protected]",
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);
}
});
});