* Add profile table Copy to profile table Update schema Migrations * wip * Get switcher working * Add organization seeder * wip * Remove dead code * Fix unit tests * relevantProfile -> profile * cache bust * One more comment * Squash migrations * Copy to profile table * Update schema * Fix existing user invite flow * Add profile table * Copy to profile table * Update schema * Format * Add profile table Copy to profile table Update schema Migrations * Format * wip Get switcher working Add organization seeder wip Remove dead code Fix unit tests relevantProfile -> profile cache bust One more comment Squash migrations Copy to profile table Update schema Fix existing user invite flow Add profile table Copy to profile table Update schema chore: small improvements to signup (#13227) * removed features on mobile, better background color * nit * dark mode nits New Crowdin translations by Github Action fix: event reschedule workflow trigger (#13122) * fix event reschedule workflow trigger * uncomment previously failed test * review fix --------- Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com> chore: added community support to help (#13213) refactor: Abstract `handleSeats` from `handleNewBooking` (#12062) * Refactor createBooking * Type fix * Abstract handleSeats * Create Invitee type * Create OrganizerUser type * Abstract addVideoCallDataToEvt * Abstract createLoggerWithEventDetails * Abstract `handleAppStatus` from handler * Create ReqAppsStatus type * Move `deleteMeeting` and `getCalendar` * Set parameters for `handleSeats` * Typescript refactor * Change function params from req * Type fix --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Erik <erik@erosemberg.com> fix: Dynamic group booking org-migration redirect (#13223) * fix: Dynamic group booking redirect * Fix flaky private team test test: Check availability tab and all your functions (teste2e-availability) (#12434) * add changes * add changes * add changes * add changes * Requested changes * Change way to grab element --------- Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com> Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com> Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> fix: Reschedule a cancelled booking (#13170) * fix: Reschedule a cancelled booking * canceled instead of cancelled --------- Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> v3.7.0 test: In Team Event type when location is Organizer Default App and U… (#12939) * test: In Team Event type when location is Organizer Default App and User Metadata is emoty * chore: use variable * fix: type error test: add test for invalid cal video (#13103) * test: add test for invalid cal video * chore: add confirm * chore: fix type error * chore: update code * test: add unit test * chore: type error * chore * chore: type * chore: fix test * chore --------- Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> chore: add error message for no availability (#13230) * chore: add error message for no default user availability * chore: check only availability * chore: change message * chore: add eventType fix: Team revert with members (#13228) refactor: Refactor seats logic (#12905) * Refactor createBooking * Type fix * Abstract handleSeats * Create Invitee type * Create OrganizerUser type * Abstract addVideoCallDataToEvt * Abstract createLoggerWithEventDetails * Abstract `handleAppStatus` from handler * Create ReqAppsStatus type * Move `deleteMeeting` and `getCalendar` * Set parameters for `handleSeats` * Typescript refactor * Change function params from req * Type fix * Move handleSeats * Abstract lastAttendeeDeleteBooking * Create function for rescheduling seated events * Fix imports on reschedule seats function * Fix imports * Import handleSeats function * Fix rescheduleUid type * Refactor owner reschedule to new time slot * Refactor combine two booking times together * Reschedule as an attendee * Refactor createNewSeat * Remove old handleSeats * Remove lastAttendeeDeleteBooking from handleNewBooking * Test for new attendee right params are passed * Unit test params for reschedule * Typo fix * Clean up * Create new seat test * Test when attendee already signs up for booking * Type fix * Test reschedule move attendee to existing booking * On reschedule create new booking * Test on last attendee cancel booking * Owner reschedule to new time slot * Owner rescheduling, merge two bookings together * Test: when merging more than available seats, then fail * Test: fail when event is full * Remove duplicate E2E tests * Clean up * Rename `addVideoCallDataToEvt` to `addVideoCallDataToEvent` * Refactor `calcAppsStatus` * Assign `evt` to resutl of `addVideoCallDataToEvent` * Use prisma.transaction when moving attendees * Clean create seat call * Use ErrorCode enum * Use attendeeRescheduledSeatedBooking function * Await function * Prevent double triggering of workflows * Use inviteeToAdd in createNewSeat * Remove unused error code * Remove old handleSeats file * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix * Type fix --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Erik <erik@erosemberg.com> feat: Allow admins to filter bookings only by attendee emails (#13236) * chore: Allow admins to filter bookings only by attendee emails * Fix type error Fixed [CAL-2930] Org branding isnt centered in shell #13225 (#13237) Add profile table Copy to profile table Update schema Migrations Format * wip * Add profile relation everywhere * Fix schema * Fix invite handler * Add profileId during eventTypec reation * Get dashboard working with profile relatioon * Add workflow create repository * Add profileId to host * Add profileId to credential table * Add destinationCalendar repository * Add profileId to membership * Revert all profile related changes * Add acme and dunder * Fix membership profileId usgae * Various fixes * Some renaming * Fix failing tests * Debugging next-aut * Consolidate migrations * Testing fixes * Fix unit tests * Self review feedback * PR feedback * Fix orgMigration as well * Add profile schema and copy users to it * Remove migrations from here * Fix type issues * Fix type error after main merge * Remove movedFromUserId * Add missing migration file * Self review addressed * Joe's review addressed * Fix profile switcher --------- Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
1677 lines
44 KiB
TypeScript
1677 lines
44 KiB
TypeScript
import prismock from "../../../tests/libs/__mocks__/prisma";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import type { z } from "zod";
|
|
|
|
import { WEBSITE_URL } from "@calcom/lib/constants";
|
|
import logger from "@calcom/lib/logger";
|
|
import { safeStringify } from "@calcom/lib/safeStringify";
|
|
import type { MembershipRole, Prisma } from "@calcom/prisma/client";
|
|
import { RedirectType } from "@calcom/prisma/enums";
|
|
import type { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
|
|
|
import { moveTeamToOrg, moveUserToOrg, removeTeamFromOrg, removeUserFromOrg } from "./orgMigration";
|
|
|
|
const WEBSITE_PROTOCOL = new URL(WEBSITE_URL).protocol;
|
|
describe("orgMigration", () => {
|
|
describe("moveUserToOrg", () => {
|
|
describe("when user email does not match orgAutoAcceptEmail", () => {
|
|
it(`should migrate a user to become a part of an organization with ADMIN role
|
|
- username in the organization should be automatically derived from email when it isn't passed`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "ADMIN",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
const team1 = await createTeamOutsideOrg({
|
|
name: "Team-1",
|
|
slug: "team-1",
|
|
});
|
|
|
|
// Make userToMigrate part of team-1
|
|
await addMemberShipOfUserWithTeam({
|
|
teamId: team1.id,
|
|
userId: dbUserToMigrate.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
await expectTeamToBeNotPartOfAnyOrganization({
|
|
teamId: team1.id,
|
|
});
|
|
|
|
expectUserRedirectToBeEnabled({
|
|
from: {
|
|
username: data.userToMigrate.username,
|
|
},
|
|
to: data.userToMigrate.expectedUsernameInOrg,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
});
|
|
|
|
it("should migrate a user to become a part of an organization(which has slug set) with MEMBER role", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
id: 1,
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbOrg.id,
|
|
},
|
|
targetOrg: {
|
|
id: data.targetOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: data.targetOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
});
|
|
|
|
it(`should migrate a user to become a part of an organization(which has no slug but requestedSlug) with MEMBER role
|
|
1. Should set the slug as requestedSlug for the organization(so that the redirect doesnt take to an unpublished organization page)`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
requestedSlug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: "user-1@example.com",
|
|
username: "user-1",
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
requestedSlug: data.targetOrg.requestedSlug,
|
|
},
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
const organization = await prismock.team.findUnique({
|
|
where: {
|
|
id: dbOrg.id,
|
|
},
|
|
});
|
|
|
|
expect(organization?.slug).toBe(data.targetOrg.requestedSlug);
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
});
|
|
|
|
it("should migrate a user along with its teams(without other members)", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
teams: [
|
|
{
|
|
name: "Team 100",
|
|
slug: "team-100",
|
|
},
|
|
{
|
|
name: "Team 101",
|
|
slug: "team-101",
|
|
},
|
|
],
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
userPartOfTeam1: {
|
|
username: "user-2",
|
|
email: "user-2@example.com",
|
|
},
|
|
userPartOfTeam2: {
|
|
username: "user-3",
|
|
email: "user-3@example.com",
|
|
},
|
|
};
|
|
|
|
// Create user to migrate
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
// Create another user that would be part of team-1 along with userToMigrate
|
|
const userPartOfTeam1 = await createUserOutsideOrg({
|
|
email: data.userPartOfTeam1.email,
|
|
username: data.userPartOfTeam1.username,
|
|
});
|
|
|
|
// Create another user that would be part of team-2 along with userToMigrate
|
|
const userPartOfTeam2 = await createUserOutsideOrg({
|
|
email: data.userPartOfTeam2.email,
|
|
username: data.userPartOfTeam2.username,
|
|
});
|
|
|
|
const team1 = await createTeamOutsideOrg({
|
|
name: data.userToMigrate.teams[0].name,
|
|
slug: data.userToMigrate.teams[0].slug,
|
|
});
|
|
|
|
const team2 = await createTeamOutsideOrg({
|
|
name: data.userToMigrate.teams[1].name,
|
|
slug: data.userToMigrate.teams[1].slug,
|
|
});
|
|
|
|
// Make userToMigrate part of team-1
|
|
await addMemberShipOfUserWithTeam({
|
|
teamId: team1.id,
|
|
userId: dbUserToMigrate.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
// Make userToMigrate part of team-2
|
|
await addMemberShipOfUserWithTeam({
|
|
teamId: team2.id,
|
|
userId: dbUserToMigrate.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
// Make userPartofTeam1 part of team-1
|
|
await addMemberShipOfUserWithTeam({
|
|
teamId: team1.id,
|
|
userId: userPartOfTeam1.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
// Make userPartofTeam2 part of team2
|
|
await addMemberShipOfUserWithTeam({
|
|
teamId: team2.id,
|
|
userId: userPartOfTeam2.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: true,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
await expectTeamToBeAPartOfOrg({
|
|
teamId: team1.id,
|
|
orgId: dbOrg.id,
|
|
teamSlugInOrg: team1.slug,
|
|
});
|
|
|
|
await expectTeamToBeAPartOfOrg({
|
|
teamId: team2.id,
|
|
orgId: dbOrg.id,
|
|
teamSlugInOrg: team2.slug,
|
|
});
|
|
|
|
await expectUserToBeNotAPartOfTheOrg({
|
|
userId: userPartOfTeam1.id,
|
|
orgId: dbOrg.id,
|
|
username: data.userPartOfTeam1.username,
|
|
});
|
|
|
|
await expectUserToBeNotAPartOfTheOrg({
|
|
userId: userPartOfTeam2.id,
|
|
orgId: dbOrg.id,
|
|
username: data.userPartOfTeam2.username,
|
|
});
|
|
});
|
|
|
|
it(`should migrate a user to become a part of an organization
|
|
- username in the organization should same as provided to moveUserToOrg`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
usernameInOrgThatWeWant: "user-1-in-org",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "ADMIN",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
username: data.userToMigrate.usernameInOrgThatWeWant,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.usernameInOrgThatWeWant,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
expectUserRedirectToBeEnabled({
|
|
from: {
|
|
username: data.userToMigrate.username,
|
|
},
|
|
to: data.userToMigrate.usernameInOrgThatWeWant,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
});
|
|
|
|
it(`should be able to re-migrate an already migrated user fixing things as we do it
|
|
- Redirect should correctly determine the nonOrgUsername so that on repeat migrations, the original user link doesn't break`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
usernameWeWantInOrg: "user-1-in-org",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
const dbUserToMigrate = await createUserInsideTheOrg(
|
|
{
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
},
|
|
dbOrg.id
|
|
);
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
username: data.userToMigrate.usernameWeWantInOrg,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.usernameWeWantInOrg,
|
|
// membership role should be updated
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
username: data.userToMigrate.usernameWeWantInOrg,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.usernameWeWantInOrg,
|
|
// membership role should be updated
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
expectUserRedirectToBeEnabled({
|
|
from: {
|
|
username: data.userToMigrate.username,
|
|
},
|
|
to: data.userToMigrate.usernameWeWantInOrg,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
|
|
console.log(await prismock.tempOrgRedirect.findMany({}));
|
|
});
|
|
|
|
describe("Failures handling", () => {
|
|
it("migration should fail if the username already exists in the organization", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
await createUserInsideTheOrg(
|
|
{
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
},
|
|
dbOrg.id
|
|
);
|
|
|
|
// User with same username exists outside the org as well as inside the org
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
expect(() => {
|
|
return moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
username: data.userToMigrate.username,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
}).rejects.toThrowError("already exists");
|
|
});
|
|
|
|
it("should fail the migration if the target org doesn't exist", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
await createUserInsideTheOrg(
|
|
{
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
},
|
|
dbOrg.id
|
|
);
|
|
|
|
// User with same username exists outside the org as well as inside the org
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
expect(() => {
|
|
return moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
// Non existent teamId
|
|
id: 1001,
|
|
username: data.userToMigrate.username,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
}).rejects.toThrowError(/Org .* not found/);
|
|
});
|
|
|
|
it("should fail the migration if the target teamId is not an organization", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
// Create a team instead of an organization
|
|
const dbOrgWhichIsActuallyATeam = await createTeamOutsideOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
expect(() => {
|
|
return moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
// Non existent teamId
|
|
id: dbOrgWhichIsActuallyATeam.id,
|
|
username: data.userToMigrate.username,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
}).rejects.toThrowError(/is not an Org/);
|
|
});
|
|
|
|
it("should fail the migration if the user is part of any other organization", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@example.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1-example",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
const dbOrgOther = await createOrg({
|
|
name: data.targetOrg.name,
|
|
slug: data.targetOrg.slug,
|
|
});
|
|
|
|
const dbUserToMigrate = await createUserInsideTheOrg(
|
|
{
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
},
|
|
dbOrgOther.id
|
|
);
|
|
|
|
expect(() => {
|
|
return moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
// Non existent teamId
|
|
id: dbOrg.id,
|
|
username: data.userToMigrate.username,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
}).rejects.toThrowError(/already a part of different organization/);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("when user email matches orgAutoAcceptEmail", () => {
|
|
const orgMetadata = {
|
|
orgAutoAcceptEmail: "org1.com",
|
|
} as const;
|
|
|
|
it(`should migrate a user to become a part of an organization with ADMIN role`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@org1.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "ADMIN",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
...orgMetadata,
|
|
},
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
|
|
expectUserRedirectToBeEnabled({
|
|
from: {
|
|
username: data.userToMigrate.username,
|
|
},
|
|
to: data.userToMigrate.expectedUsernameInOrg,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
});
|
|
|
|
it("should migrate a user to become a part of an organization(which has slug set) with MEMBER role", async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@org1.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
...orgMetadata,
|
|
},
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
});
|
|
|
|
it(`should migrate a user to become a part of an organization(which has no slug but requestedSlug) with MEMBER role
|
|
1. Should set the slug as requestedSlug for the organization(so that the redirect doesnt take to an unpublished organization page)`, async () => {
|
|
const data = {
|
|
userToMigrate: {
|
|
username: "user-1",
|
|
email: "user-1@org1.com",
|
|
// Because example.com isn't the orgAutoAcceptEmail
|
|
expectedUsernameInOrg: "user-1",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
requestedSlug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "MEMBER",
|
|
} as const,
|
|
};
|
|
|
|
const dbUserToMigrate = await createUserOutsideOrg({
|
|
email: data.userToMigrate.email,
|
|
username: data.userToMigrate.username,
|
|
});
|
|
|
|
const dbOrg = await createOrg({
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
requestedSlug: data.targetOrg.requestedSlug,
|
|
...orgMetadata,
|
|
},
|
|
});
|
|
|
|
await moveUserToOrg({
|
|
user: {
|
|
id: dbUserToMigrate.id,
|
|
},
|
|
targetOrg: {
|
|
id: dbOrg.id,
|
|
membership: {
|
|
role: data.membershipWeWant.role,
|
|
},
|
|
},
|
|
shouldMoveTeams: false,
|
|
});
|
|
|
|
const organization = await prismock.team.findUnique({
|
|
where: {
|
|
id: dbOrg.id,
|
|
},
|
|
});
|
|
|
|
expect(organization?.slug).toBe(data.targetOrg.requestedSlug);
|
|
|
|
await expectUserToBeAPartOfOrg({
|
|
userId: dbUserToMigrate.id,
|
|
orgId: dbOrg.id,
|
|
usernameInOrg: data.userToMigrate.expectedUsernameInOrg,
|
|
expectedMembership: { role: data.membershipWeWant.role, accepted: true },
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("moveTeamToOrg", () => {
|
|
it(`should migrate a team to become a part of an organization`, async () => {
|
|
const data = {
|
|
teamToMigrate: {
|
|
id: 1,
|
|
name: "Team 1",
|
|
slug: "team1",
|
|
newSlug: "team1-new-slug",
|
|
},
|
|
targetOrg: {
|
|
id: 2,
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
};
|
|
|
|
await prismock.team.create({
|
|
data: {
|
|
id: data.teamToMigrate.id,
|
|
slug: data.teamToMigrate.slug,
|
|
name: data.teamToMigrate.name,
|
|
},
|
|
});
|
|
|
|
await prismock.team.create({
|
|
data: {
|
|
id: data.targetOrg.id,
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
isOrganization: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
await moveTeamToOrg({
|
|
teamId: data.teamToMigrate.id,
|
|
targetOrg: {
|
|
id: data.targetOrg.id,
|
|
teamSlug: data.teamToMigrate.newSlug,
|
|
},
|
|
});
|
|
|
|
await expectTeamToBeAPartOfOrg({
|
|
teamId: data.teamToMigrate.id,
|
|
orgId: data.targetOrg.id,
|
|
teamSlugInOrg: data.teamToMigrate.newSlug,
|
|
});
|
|
|
|
expectTeamRedirectToBeEnabled({
|
|
from: {
|
|
teamSlug: data.teamToMigrate.slug,
|
|
},
|
|
to: data.teamToMigrate.newSlug,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
});
|
|
it.todo("should migrate a team with members");
|
|
it.todo("Try migrating an already migrated team");
|
|
});
|
|
|
|
describe("removeUserFromOrg", () => {
|
|
it(`should remove a user from an organization but he should remain in team`, async () => {
|
|
const data = {
|
|
userToUnmigrate: {
|
|
username: "user1-in-org1",
|
|
email: "user-1@example.com",
|
|
usernameBeforeMovingToOrg: "user1",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
membershipWeWant: {
|
|
role: "ADMIN",
|
|
} as const,
|
|
};
|
|
|
|
const dbOrg = await createOrg({
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
});
|
|
|
|
const dbTeamOutsideOrg = await createTeamOutsideOrg({
|
|
slug: "team-1",
|
|
name: "Team 1",
|
|
});
|
|
|
|
const dbUser = await createUserInsideTheOrg(
|
|
{
|
|
email: data.userToUnmigrate.email,
|
|
username: data.userToUnmigrate.username,
|
|
metadata: {
|
|
migratedToOrgFrom: {
|
|
username: data.userToUnmigrate.usernameBeforeMovingToOrg,
|
|
},
|
|
},
|
|
},
|
|
dbOrg.id
|
|
);
|
|
|
|
await addMemberShipOfUserWithOrg({
|
|
userId: dbUser.id,
|
|
teamId: dbTeamOutsideOrg.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
await addMemberShipOfUserWithTeam({
|
|
userId: dbUser.id,
|
|
teamId: dbOrg.id,
|
|
role: data.membershipWeWant.role,
|
|
accepted: true,
|
|
});
|
|
|
|
const userToUnmigrate = await prismock.user.findUnique({
|
|
where: {
|
|
id: dbUser.id,
|
|
},
|
|
include: {
|
|
organization: true,
|
|
},
|
|
});
|
|
|
|
if (!userToUnmigrate?.organizationId || !userToUnmigrate.organization) {
|
|
throw new Error(
|
|
`Couldn't setup user to unmigrate properly userToUnMigrate: ${{
|
|
organizationId: userToUnmigrate?.organizationId,
|
|
organization: !!userToUnmigrate?.organization,
|
|
}}`
|
|
);
|
|
}
|
|
|
|
await removeUserFromOrg({
|
|
userId: dbUser.id,
|
|
targetOrgId: dbOrg.id,
|
|
});
|
|
|
|
await expectUserToBeNotAPartOfTheOrg({
|
|
userId: dbUser.id,
|
|
orgId: dbOrg.id,
|
|
username: data.userToUnmigrate.usernameBeforeMovingToOrg,
|
|
});
|
|
|
|
await expectUserToBeAPartOfTeam({
|
|
userId: dbUser.id,
|
|
teamId: dbTeamOutsideOrg.id,
|
|
expectedMembership: {
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
expectUserRedirectToBeNotEnabled({
|
|
from: {
|
|
username: data.userToUnmigrate.username,
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("removeTeamFromOrg", () => {
|
|
it(`should remove a team from an organization`, async () => {
|
|
const data = {
|
|
teamToUnmigrate: {
|
|
name: "Team 1",
|
|
slug: "team1",
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
};
|
|
|
|
const targetOrg = await prismock.team.create({
|
|
data: {
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
isOrganization: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { id: teamToUnMigrateId } = await prismock.team.create({
|
|
data: {
|
|
slug: data.teamToUnmigrate.slug,
|
|
name: data.teamToUnmigrate.name,
|
|
parent: {
|
|
connect: {
|
|
id: targetOrg.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const teamToUnmigrate = await prismock.team.findUnique({
|
|
where: {
|
|
id: teamToUnMigrateId,
|
|
},
|
|
include: {
|
|
parent: true,
|
|
},
|
|
});
|
|
|
|
if (!teamToUnmigrate?.parent || !teamToUnmigrate.parentId) {
|
|
throw new Error(`Couldn't setup team to unmigrate properly ID:${teamToUnMigrateId}`);
|
|
}
|
|
|
|
await removeTeamFromOrg({
|
|
teamId: teamToUnMigrateId,
|
|
targetOrgId: targetOrg.id,
|
|
});
|
|
|
|
await expectTeamToBeNotPartOfAnyOrganization({
|
|
teamId: teamToUnMigrateId,
|
|
});
|
|
|
|
expectTeamRedirectToBeNotEnabled({
|
|
from: {
|
|
teamSlug: data.teamToUnmigrate.slug,
|
|
},
|
|
to: data.teamToUnmigrate.slug,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
});
|
|
|
|
it(`should remove a team from an organization with it's members`, async () => {
|
|
const data = {
|
|
teamToUnmigrate: {
|
|
name: "Team 1",
|
|
slug: "team1",
|
|
members: [
|
|
{
|
|
username: "user1-example",
|
|
email: "user1@example.com",
|
|
usernameBeforeMovingToOrg: "user1",
|
|
},
|
|
],
|
|
},
|
|
targetOrg: {
|
|
name: "Org 1",
|
|
slug: "org1",
|
|
},
|
|
};
|
|
|
|
const targetOrg = await prismock.team.create({
|
|
data: {
|
|
slug: data.targetOrg.slug,
|
|
name: data.targetOrg.name,
|
|
metadata: {
|
|
isOrganization: true,
|
|
},
|
|
},
|
|
});
|
|
|
|
const { id: teamToUnMigrateId } = await prismock.team.create({
|
|
data: {
|
|
slug: data.teamToUnmigrate.slug,
|
|
name: data.teamToUnmigrate.name,
|
|
parent: {
|
|
connect: {
|
|
id: targetOrg.id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const teamToUnmigrate = await prismock.team.findUnique({
|
|
where: {
|
|
id: teamToUnMigrateId,
|
|
},
|
|
include: {
|
|
parent: true,
|
|
},
|
|
});
|
|
|
|
if (!teamToUnmigrate?.parent || !teamToUnmigrate.parentId) {
|
|
throw new Error(`Couldn't setup team to unmigrate properly ID:${teamToUnMigrateId}`);
|
|
}
|
|
|
|
const member1OfTeam = await createUserInsideTheOrg(
|
|
{
|
|
email: data.teamToUnmigrate.members[0].email,
|
|
username: data.teamToUnmigrate.members[0].username,
|
|
metadata: {
|
|
migratedToOrgFrom: {
|
|
username: data.teamToUnmigrate.members[0].usernameBeforeMovingToOrg,
|
|
},
|
|
},
|
|
},
|
|
targetOrg.id
|
|
);
|
|
|
|
addMemberShipOfUserWithOrg({
|
|
userId: member1OfTeam.id,
|
|
teamId: targetOrg.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
addMemberShipOfUserWithTeam({
|
|
teamId: teamToUnmigrate.id,
|
|
userId: member1OfTeam.id,
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
});
|
|
|
|
expectUserToBeAPartOfOrg({
|
|
userId: member1OfTeam.id,
|
|
orgId: targetOrg.id,
|
|
usernameInOrg: data.teamToUnmigrate.members[0].username,
|
|
expectedMembership: {
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
expectUserToBeAPartOfTeam({
|
|
userId: member1OfTeam.id,
|
|
teamId: teamToUnmigrate.id,
|
|
expectedMembership: {
|
|
role: "MEMBER",
|
|
accepted: true,
|
|
},
|
|
});
|
|
|
|
await removeTeamFromOrg({
|
|
teamId: teamToUnMigrateId,
|
|
targetOrgId: targetOrg.id,
|
|
});
|
|
|
|
await expectTeamToBeNotPartOfAnyOrganization({
|
|
teamId: teamToUnMigrateId,
|
|
});
|
|
|
|
expectTeamRedirectToBeNotEnabled({
|
|
from: {
|
|
teamSlug: data.teamToUnmigrate.slug,
|
|
},
|
|
to: data.teamToUnmigrate.slug,
|
|
orgSlug: data.targetOrg.slug,
|
|
});
|
|
|
|
expectUserRedirectToBeNotEnabled({
|
|
from: {
|
|
username: data.teamToUnmigrate.members[0].usernameBeforeMovingToOrg,
|
|
},
|
|
});
|
|
|
|
expectUserToBeNotAPartOfTheOrg({
|
|
userId: member1OfTeam.id,
|
|
orgId: targetOrg.id,
|
|
username: data.teamToUnmigrate.members[0].usernameBeforeMovingToOrg,
|
|
});
|
|
|
|
expectUserToBeNotAPartOfTheTeam({
|
|
userId: member1OfTeam.id,
|
|
teamId: targetOrg.id,
|
|
username: data.teamToUnmigrate.members[0].usernameBeforeMovingToOrg,
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
async function expectUserToBeAPartOfOrg({
|
|
userId,
|
|
orgId,
|
|
expectedMembership,
|
|
usernameInOrg,
|
|
}: {
|
|
userId: number;
|
|
orgId: number;
|
|
usernameInOrg: string;
|
|
expectedMembership: {
|
|
role: MembershipRole;
|
|
accepted: boolean;
|
|
};
|
|
}) {
|
|
const migratedUser = await prismock.user.findUnique({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
teams: true,
|
|
},
|
|
});
|
|
if (!migratedUser) {
|
|
throw new Error(`User with id ${userId} does not exist`);
|
|
}
|
|
|
|
expect(migratedUser.username).toBe(usernameInOrg);
|
|
expect(migratedUser.organizationId).toBe(orgId);
|
|
|
|
const membership = migratedUser.teams.find(
|
|
(membership) => membership.teamId === orgId && membership.userId === userId
|
|
);
|
|
|
|
expect(membership).not.toBeNull();
|
|
if (!membership) {
|
|
throw new Error(`User with id ${userId} is not a part of org with id ${orgId}`);
|
|
}
|
|
expect(membership.role).toBe(expectedMembership.role);
|
|
expect(membership.accepted).toBe(expectedMembership.accepted);
|
|
}
|
|
|
|
async function expectUserToBeAPartOfTeam({
|
|
userId,
|
|
teamId,
|
|
expectedMembership,
|
|
}: {
|
|
userId: number;
|
|
teamId: number;
|
|
expectedMembership: {
|
|
role: MembershipRole;
|
|
accepted: boolean;
|
|
};
|
|
}) {
|
|
const user = await prismock.user.findUnique({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
teams: true,
|
|
},
|
|
});
|
|
if (!user) {
|
|
throw new Error(`User with id ${userId} does not exist`);
|
|
}
|
|
|
|
const membership = user.teams.find(
|
|
(membership) => membership.teamId === teamId && membership.userId === userId
|
|
);
|
|
|
|
expect(membership).not.toBeNull();
|
|
if (!membership) {
|
|
throw new Error(`User with id ${userId} is not a part of team with id ${teamId}`);
|
|
}
|
|
expect(membership.role).toBe(expectedMembership.role);
|
|
expect(membership.accepted).toBe(expectedMembership.accepted);
|
|
}
|
|
|
|
async function expectUserToBeNotAPartOfTheOrg({
|
|
userId,
|
|
orgId,
|
|
username,
|
|
}: {
|
|
userId: number;
|
|
orgId: number;
|
|
username: string;
|
|
}) {
|
|
expectUserToBeNotAPartOfTheTeam({
|
|
userId,
|
|
teamId: orgId,
|
|
username,
|
|
});
|
|
}
|
|
|
|
async function expectUserToBeNotAPartOfTheTeam({
|
|
userId,
|
|
teamId,
|
|
username,
|
|
}: {
|
|
userId: number;
|
|
teamId: number;
|
|
username: string;
|
|
}) {
|
|
const user = await prismock.user.findUnique({
|
|
where: {
|
|
id: userId,
|
|
},
|
|
include: {
|
|
teams: true,
|
|
},
|
|
});
|
|
if (!user) {
|
|
throw new Error(`User with id ${userId} does not exist`);
|
|
}
|
|
|
|
expect(user.username).toBe(username);
|
|
expect(user.organizationId).toBe(null);
|
|
|
|
const membership = user.teams.find(
|
|
(membership) => membership.teamId === teamId && membership.userId === userId
|
|
);
|
|
|
|
expect(membership).toBeUndefined();
|
|
}
|
|
|
|
async function expectTeamToBeAPartOfOrg({
|
|
teamId,
|
|
orgId,
|
|
teamSlugInOrg,
|
|
}: {
|
|
teamId: number;
|
|
orgId: number;
|
|
teamSlugInOrg: string | null;
|
|
}) {
|
|
const migratedTeam = await prismock.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
});
|
|
if (!migratedTeam) {
|
|
throw new Error(`Team with id ${teamId} does not exist`);
|
|
}
|
|
|
|
if (!teamSlugInOrg) {
|
|
throw new Error(`teamSlugInOrg should be defined`);
|
|
}
|
|
expect(migratedTeam.parentId).toBe(orgId);
|
|
expect(migratedTeam.slug).toBe(teamSlugInOrg);
|
|
}
|
|
|
|
async function expectTeamToBeNotPartOfAnyOrganization({ teamId }: { teamId: number }) {
|
|
const team = await prismock.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
},
|
|
});
|
|
if (!team) {
|
|
throw new Error(`Team with id ${teamId} does not exist`);
|
|
}
|
|
|
|
expect(team.parentId).toBe(null);
|
|
}
|
|
|
|
async function expectUserRedirectToBeEnabled({
|
|
from,
|
|
to,
|
|
orgSlug,
|
|
}: {
|
|
from: { username: string } | { teamSlug: string };
|
|
to: string;
|
|
orgSlug: string;
|
|
}) {
|
|
expectRedirectToBeEnabled({
|
|
from,
|
|
to,
|
|
orgSlug,
|
|
redirectType: RedirectType.User,
|
|
});
|
|
}
|
|
|
|
async function expectTeamRedirectToBeEnabled({
|
|
from,
|
|
to,
|
|
orgSlug,
|
|
}: {
|
|
from: { username: string } | { teamSlug: string };
|
|
to: string;
|
|
orgSlug: string;
|
|
}) {
|
|
expectRedirectToBeEnabled({
|
|
from,
|
|
to,
|
|
orgSlug,
|
|
redirectType: RedirectType.Team,
|
|
});
|
|
}
|
|
|
|
async function expectUserRedirectToBeNotEnabled({
|
|
from,
|
|
}: {
|
|
from: { username: string } | { teamSlug: string };
|
|
}) {
|
|
expectRedirectToBeNotEnabled({
|
|
from,
|
|
});
|
|
}
|
|
|
|
async function expectTeamRedirectToBeNotEnabled({
|
|
from,
|
|
}: {
|
|
from: { username: string } | { teamSlug: string };
|
|
to: string;
|
|
orgSlug: string;
|
|
}) {
|
|
expectRedirectToBeNotEnabled({
|
|
from,
|
|
});
|
|
}
|
|
|
|
async function expectRedirectToBeEnabled({
|
|
from,
|
|
to,
|
|
orgSlug,
|
|
redirectType,
|
|
}: {
|
|
from: { username: string } | { teamSlug: string };
|
|
to: string;
|
|
orgSlug: string;
|
|
redirectType: RedirectType;
|
|
}) {
|
|
let tempOrgRedirectWhere = null;
|
|
let tempOrgRedirectThatShouldNotExistWhere = null;
|
|
if ("username" in from) {
|
|
tempOrgRedirectWhere = {
|
|
from_type_fromOrgId: {
|
|
from: from.username,
|
|
type: RedirectType.User,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
|
|
// Normally with user migration `from.username != to`
|
|
if (from.username !== to) {
|
|
// There must not be a redirect setup from=To to something else as that would cause double redirection
|
|
tempOrgRedirectThatShouldNotExistWhere = {
|
|
from_type_fromOrgId: {
|
|
from: to,
|
|
type: RedirectType.User,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
}
|
|
} else if ("teamSlug" in from) {
|
|
tempOrgRedirectWhere = {
|
|
from_type_fromOrgId: {
|
|
from: from.teamSlug,
|
|
type: RedirectType.Team,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
|
|
if (from.teamSlug !== to) {
|
|
// There must not be a redirect setup from=To to something else as that would cause double redirection
|
|
tempOrgRedirectThatShouldNotExistWhere = {
|
|
from_type_fromOrgId: {
|
|
from: to,
|
|
type: RedirectType.Team,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
}
|
|
} else {
|
|
throw new Error("Atleast one of userId or teamId should be present in from");
|
|
}
|
|
const redirect = await prismock.tempOrgRedirect.findUnique({
|
|
where: tempOrgRedirectWhere,
|
|
});
|
|
|
|
if (tempOrgRedirectThatShouldNotExistWhere) {
|
|
const redirectThatShouldntBeThere = await prismock.tempOrgRedirect.findUnique({
|
|
where: tempOrgRedirectThatShouldNotExistWhere,
|
|
});
|
|
expect(redirectThatShouldntBeThere).toBeNull();
|
|
}
|
|
|
|
expect(redirect).not.toBeNull();
|
|
expect(redirect?.toUrl).toBe(`${WEBSITE_PROTOCOL}//${orgSlug}.cal.local:3000/${to}`);
|
|
if (!redirect) {
|
|
throw new Error(`Redirect doesn't exist for ${JSON.stringify(tempOrgRedirectWhere)}`);
|
|
}
|
|
expect(redirect.type).toBe(redirectType);
|
|
}
|
|
|
|
async function expectRedirectToBeNotEnabled({ from }: { from: { username: string } | { teamSlug: string } }) {
|
|
let tempOrgRedirectWhere = null;
|
|
if ("username" in from) {
|
|
tempOrgRedirectWhere = {
|
|
from_type_fromOrgId: {
|
|
from: from.username,
|
|
type: RedirectType.User,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
} else if ("teamSlug" in from) {
|
|
tempOrgRedirectWhere = {
|
|
from_type_fromOrgId: {
|
|
from: from.teamSlug,
|
|
type: RedirectType.Team,
|
|
fromOrgId: 0,
|
|
},
|
|
};
|
|
} else {
|
|
throw new Error("Atleast one of userId or teamId should be present in from");
|
|
}
|
|
const redirect = await prismock.tempOrgRedirect.findUnique({
|
|
where: tempOrgRedirectWhere,
|
|
});
|
|
expect(redirect).toBeNull();
|
|
}
|
|
|
|
async function createOrg(
|
|
data: Omit<Prisma.TeamCreateArgs["data"], "metadata" | "parent"> & {
|
|
metadata?: z.infer<typeof teamMetadataSchema>;
|
|
}
|
|
) {
|
|
return await prismock.team.create({
|
|
data: {
|
|
...data,
|
|
metadata: {
|
|
...(data.metadata || {}),
|
|
isOrganization: true,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function createTeamOutsideOrg(
|
|
data: Omit<Prisma.TeamCreateArgs["data"], "metadata" | "parent"> & {
|
|
metadata?: z.infer<typeof teamMetadataSchema>;
|
|
}
|
|
) {
|
|
return await prismock.team.create({
|
|
data: {
|
|
...data,
|
|
parentId: null,
|
|
metadata: {
|
|
...(data.metadata || {}),
|
|
isOrganization: false,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function createUserOutsideOrg(
|
|
data: Omit<Prisma.UserCreateArgs["data"], "organization" | "movedToProfile">
|
|
) {
|
|
return await prismock.user.create({
|
|
data: {
|
|
...data,
|
|
movedToProfileId: null,
|
|
organizationId: null,
|
|
},
|
|
});
|
|
}
|
|
|
|
async function createUserInsideTheOrg(
|
|
data: Omit<Prisma.UserUncheckedCreateInput, "organization" | "organizationId" | "id" | "movedToProfileId">,
|
|
orgId: number
|
|
) {
|
|
const org = await prismock.team.findUnique({
|
|
where: {
|
|
id: orgId,
|
|
},
|
|
});
|
|
if (!org) {
|
|
throw new Error(`Org with id ${orgId} does not exist`);
|
|
}
|
|
logger.debug(
|
|
`Creating user inside org`,
|
|
safeStringify({
|
|
orgId,
|
|
data,
|
|
})
|
|
);
|
|
return await prismock.user.create({
|
|
data: {
|
|
...data,
|
|
organization: {
|
|
connect: {
|
|
id: orgId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
async function addMemberShipOfUserWithTeam({
|
|
teamId,
|
|
userId,
|
|
role,
|
|
accepted,
|
|
}: {
|
|
teamId: number;
|
|
userId: number;
|
|
role: MembershipRole;
|
|
accepted: boolean;
|
|
}) {
|
|
await prismock.membership.create({
|
|
data: {
|
|
role,
|
|
accepted,
|
|
team: {
|
|
connect: {
|
|
id: teamId,
|
|
},
|
|
},
|
|
user: {
|
|
connect: {
|
|
id: userId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const membership = await prismock.membership.findUnique({
|
|
where: {
|
|
userId_teamId: {
|
|
teamId,
|
|
userId,
|
|
},
|
|
},
|
|
});
|
|
if (!membership) {
|
|
throw new Error(`Membership between teamId ${teamId} and userId ${userId} couldn't be created`);
|
|
}
|
|
}
|
|
|
|
const addMemberShipOfUserWithOrg = addMemberShipOfUserWithTeam;
|