feat: v2 api allow switching event type between collective and round robin (#25045)

* refactor: create team event type hosts

* refactor: update team event type hosts

* feat: allow switching between collective and round robin

* fix: make schedulingType optional when updating

* fix: e2e tests

* fix: e2e and add more tests

* test: only hosts update

* fix: remove test that makes no sense
This commit is contained in:
Lauris Skraucis
2025-11-11 09:40:21 +01:00
committed by GitHub
parent 53a931da4e
commit 8b4f675cee
4 changed files with 686 additions and 20 deletions
@@ -15,6 +15,10 @@ import {
HostPriority,
EmailSettings_2024_06_14,
} from "@calcom/platform-types";
import type { EventType } from "@calcom/prisma/client";
export const HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR =
"Hosts required when switching schedulingType. Please provide 'hosts' or set 'assignAllTeamMembers: true' to specify how hosts should be configured for the new scheduling type.";
export type TransformedCreateTeamEventTypeInput = Awaited<
ReturnType<InstanceType<typeof InputOrganizationsEventTypesService>["transformInputCreateTeamEventType"]>
@@ -116,10 +120,12 @@ export class InputOrganizationsEventTypesService {
teamId: number,
inputEventType: CreateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const eventType = this.inputEventTypesService.transformInputCreateEventType(rest);
const isManagedEventType = rest.schedulingType === "MANAGED";
const defaultLocations: CreateTeamEventTypeInput_2024_06_14["locations"] = [
{
type: "integration",
@@ -127,12 +133,13 @@ export class InputOrganizationsEventTypesService {
},
];
const children = await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId);
const children = isManagedEventType
? await this.getChildEventTypesForManagedEventTypeCreate(inputEventType, teamId)
: undefined;
let metadata =
rest.schedulingType === "MANAGED"
? { managedEventConfig: {}, ...eventType.metadata }
: eventType.metadata;
let metadata = isManagedEventType
? { managedEventConfig: {}, ...eventType.metadata }
: eventType.metadata;
if (emailSettings) {
metadata = this.addEmailSettingsToMetadata(emailSettings, metadata);
@@ -140,12 +147,7 @@ export class InputOrganizationsEventTypesService {
const teamEventType = {
...eventType,
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
hosts: !(rest.schedulingType === "MANAGED")
? assignAllTeamMembers
? await this.getAllTeamMembers(teamId, inputEventType.schedulingType)
: this.transformInputHosts(hosts, inputEventType.schedulingType)
: undefined,
hosts: await this.transformInputCreateTeamEventTypeHosts(teamId, inputEventType),
assignAllTeamMembers,
locations: this.transformInputTeamLocations(locations || defaultLocations),
metadata,
@@ -155,6 +157,24 @@ export class InputOrganizationsEventTypesService {
return teamEventType;
}
private async transformInputCreateTeamEventTypeHosts(
teamId: number,
inputEventType: CreateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, schedulingType } = inputEventType;
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children - each child managed event type is associated with
// a specific user and hosts property is only for team event types e.g round robin and collective.
if (schedulingType === "MANAGED") {
return undefined;
}
if (assignAllTeamMembers) {
return await this.getAllTeamMembers(teamId, inputEventType.schedulingType);
}
return this.transformInputHosts(hosts, inputEventType.schedulingType);
}
private addEmailSettingsToMetadata(
emailSettings: EmailSettings_2024_06_14,
metadata: NonNullable<EventTypeMetadata>
@@ -190,7 +210,7 @@ export class InputOrganizationsEventTypesService {
teamId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const { assignAllTeamMembers, locations, emailSettings, ...rest } = inputEventType;
const eventType = await this.inputEventTypesService.transformInputUpdateEventType(rest, eventTypeId);
const dbEventType = await this.teamsEventTypesRepository.getTeamEventType(teamId, eventTypeId);
@@ -213,11 +233,11 @@ export class InputOrganizationsEventTypesService {
const teamEventType = {
...eventType,
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the children
hosts: !children
? assignAllTeamMembers
? await this.getAllTeamMembers(teamId, dbEventType.schedulingType)
: this.transformInputHosts(hosts, dbEventType.schedulingType)
: undefined,
hosts: await this.transformInputUpdateTeamEventTypeHosts(
teamId,
dbEventType.schedulingType,
inputEventType
),
assignAllTeamMembers,
children,
locations: locations ? this.transformInputTeamLocations(locations) : undefined,
@@ -227,6 +247,33 @@ export class InputOrganizationsEventTypesService {
return teamEventType;
}
private async transformInputUpdateTeamEventTypeHosts(
teamId: number,
dbEventTypeSchedulingType: EventType["schedulingType"],
inputEventType: UpdateTeamEventTypeInput_2024_06_14
) {
const { hosts, assignAllTeamMembers } = inputEventType;
if (dbEventTypeSchedulingType === "MANAGED") {
// note(Lauris): we don't populate hosts for managed event-types because they are handled by the event type children
return undefined;
}
const isSchedulingTypeChanging =
inputEventType.schedulingType && inputEventType.schedulingType !== dbEventTypeSchedulingType;
if (isSchedulingTypeChanging && !assignAllTeamMembers && !hosts) {
throw new BadRequestException(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
}
const nextSchedulingType = inputEventType.schedulingType || dbEventTypeSchedulingType;
if (assignAllTeamMembers) {
return await this.getAllTeamMembers(teamId, nextSchedulingType);
}
return this.transformInputHosts(hosts, nextSchedulingType);
}
async getChildEventTypesForManagedEventTypeUpdate(
eventTypeId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
@@ -273,7 +320,7 @@ export class InputOrganizationsEventTypesService {
}
async getChildEventTypesForManagedEventTypeCreate(
inputEventType: UpdateTeamEventTypeInput_2024_06_14,
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">,
teamId: number
) {
const ownersIds = await this.getOwnersIdsForManagedEventTypeCreate(teamId, inputEventType);
@@ -289,7 +336,7 @@ export class InputOrganizationsEventTypesService {
async getOwnersIdsForManagedEventTypeCreate(
teamId: number,
inputEventType: UpdateTeamEventTypeInput_2024_06_14
inputEventType: Pick<CreateTeamEventTypeInput_2024_06_14, "assignAllTeamMembers" | "hosts">
) {
if (inputEventType.assignAllTeamMembers) {
return await this.getTeamUsersIds(teamId);
@@ -1,5 +1,6 @@
import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR } from "@/modules/organizations/event-types/services/input.service";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
@@ -758,6 +759,586 @@ describe("Organizations Event Types Endpoints", () => {
expect(expected.priority).toEqual(received?.priority);
}
describe("updating scheduling type", () => {
it("should return 400 error if schedulingType: managed is passed", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "managed",
};
await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(400);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should require hosts when changing round robin event type to collective without providing hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
{
userId: teamMember2.id,
priority: "low",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody);
expect(response.status).toBe(400);
expect(response.body.error.message).toBe(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should require hosts when changing collective event type to roundRobin without providing hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
{
userId: teamMember2.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody);
expect(response.status).toBe(400);
expect(response.body.error.message).toBe(HOSTS_REQUIRED_WHEN_SWITCHING_SCHEDULING_TYPE_ERROR);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change round robin event type to collective and pass new hosts", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
hosts: [
{
userId: teamMember2.id,
},
],
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change collective event type to roundRobin and pass new hosts", async () => {
const createBody = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "collective",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
hosts: [
{
userId: teamMember2.id,
priority: "medium",
},
],
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change collective event type to roundRobin with assignAllTeamMembers: true", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-collective-${randomString()}`,
slug: `teams-event-types-scheduling-collective-${randomString()}`,
description: "Test collective event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "COLLECTIVE",
hosts: [
{
userId: teamMember1.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "roundRobin",
assignAllTeamMembers: true,
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(3);
const hostUserIds = responseBody.data.hosts.map((host) => host.userId);
expect(hostUserIds).toContain(teamMember1.id);
expect(hostUserIds).toContain(teamMember2.id);
expect(hostUserIds).toContain(userAdmin.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should change round robin event type to collective with assignAllTeamMembers: true", async () => {
const createBody: CreateTeamEventTypeInput_2024_06_14 = {
title: `teams-event-types-scheduling-roundrobin-${randomString()}`,
slug: `teams-event-types-scheduling-roundrobin-${randomString()}`,
description: "Test round robin event type",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "ROUND_ROBIN",
hosts: [
{
userId: teamMember1.id,
priority: "high",
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
schedulingType: "collective",
assignAllTeamMembers: true,
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(3);
const hostUserIds = responseBody.data.hosts.map((host) => host.userId);
expect(hostUserIds).toContain(teamMember1.id);
expect(hostUserIds).toContain(teamMember2.id);
expect(hostUserIds).toContain(userAdmin.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
it("should preserve existing hosts when updating without changing scheduling type", async () => {
const createBody = {
title: `teams-event-types-scheduling-preserve-${randomString()}`,
slug: `teams-event-types-scheduling-preserve-${randomString()}`,
description: "Test preserve hosts",
lengthInMinutes: 60,
locations: [
{
type: "integration",
integration: "cal-video",
},
],
schedulingType: "collective",
hosts: [
{
userId: teamMember1.id,
},
{
userId: teamMember2.id,
},
],
};
const createResponse = await request(app.getHttpServer())
.post(`/v2/teams/${team.id}/event-types`)
.send(createBody)
.expect(201);
const createdEventType: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = createResponse.body;
const updateBody = {
title: "Updated title",
};
const updateResponse = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${createdEventType.data.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = updateResponse.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual("Updated title");
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(createdEventType.data.id);
});
});
describe("should update event type title", () => {
it("should preserve hosts when updating collective event type title", async () => {
const collectiveEventType = await eventTypesRepositoryFixture.create(
{
title: `collective-preserve-hosts-${randomString()}`,
slug: `collective-preserve-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "COLLECTIVE",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
},
{
userId: teamMember2.id,
isFixed: true,
},
],
},
},
userAdmin.id
);
const newTitle = `updated-collective-title-${randomString()}`;
const updateBody = {
title: newTitle,
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${collectiveEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual(newTitle);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(collectiveEventType.id);
});
it("should preserve hosts when updating round robin event type title", async () => {
const roundRobinEventType = await eventTypesRepositoryFixture.create(
{
title: `roundrobin-preserve-hosts-${randomString()}`,
slug: `roundrobin-preserve-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "ROUND_ROBIN",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: false,
priority: 2,
},
{
userId: teamMember2.id,
isFixed: true,
priority: 1,
},
],
},
},
userAdmin.id
);
const newTitle = `updated-roundrobin-title-${randomString()}`;
const updateBody = {
title: newTitle,
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${roundRobinEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.title).toEqual(newTitle);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(2);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember1.id);
expect(responseBody.data.hosts.map((h) => h.userId)).toContain(teamMember2.id);
await eventTypesRepositoryFixture.delete(roundRobinEventType.id);
});
});
describe("should update event type hosts", () => {
it("should update collective event type hosts", async () => {
const collectiveEventType = await eventTypesRepositoryFixture.create(
{
title: `collective-update-hosts-${randomString()}`,
slug: `collective-update-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "COLLECTIVE",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
},
],
},
},
userAdmin.id
);
const updateBody = {
hosts: [
{
userId: teamMember2.id,
},
],
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${collectiveEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("collective");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(collectiveEventType.id);
});
it("should update round robin event type hosts", async () => {
const roundRobinEventType = await eventTypesRepositoryFixture.create(
{
title: `roundrobin-update-hosts-${randomString()}`,
slug: `roundrobin-update-hosts-${randomString()}`,
length: 60,
team: {
connect: {
id: team.id,
},
},
schedulingType: "ROUND_ROBIN",
hosts: {
create: [
{
userId: teamMember1.id,
isFixed: true,
priority: 1,
},
],
},
},
userAdmin.id
);
const updateBody = {
hosts: [
{
userId: teamMember2.id,
priority: "high",
},
],
};
const response = await request(app.getHttpServer())
.patch(`/v2/teams/${team.id}/event-types/${roundRobinEventType.id}`)
.send(updateBody)
.expect(200);
const responseBody: ApiSuccessResponse<TeamEventTypeOutput_2024_06_14> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data.schedulingType).toEqual("roundRobin");
expect(responseBody.data.hosts).toHaveLength(1);
expect(responseBody.data.hosts[0].userId).toEqual(teamMember2.id);
await eventTypesRepositoryFixture.delete(roundRobinEventType.id);
});
});
afterAll(async () => {
await userRepositoryFixture.deleteByEmail(userAdmin.email);
await userRepositoryFixture.deleteByEmail(teamMember1.email);