feat: add forcedSlowMode rate limit to Team and Organization DELETE endpoints (#27258)
* feat: add forcedSlowMode rate limit to Team and Organization DELETE endpoints Co-Authored-By: alex@cal.com <me@alexvanandel.com> * test: add NoOpThrottlerGuard to disable rate limiting in tests - Create NoOpThrottlerGuard class that always allows requests through - Create withNoThrottler helper function to override CustomThrottlerGuard - Update teams.controller.e2e-spec.ts to use withNoThrottler - Update organizations-teams.controller.e2e-spec.ts to use withNoThrottler - Update organizations-organizations.controller.e2e-spec.ts to use withNoThrottler This prevents race conditions when tests fire rapidly against rate-limited endpoints. Co-Authored-By: alex@cal.com <me@alexvanandel.com> * fix: use jest.spyOn to mock throttler guard in tests Changed approach from overrideGuard to jest.spyOn for mocking CustomThrottlerGuard.handleRequest to always return true. This properly disables rate limiting in tests for the DELETE endpoints. Co-Authored-By: alex@cal.com <me@alexvanandel.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
This commit is contained in:
co-authored by
Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Morgan
parent
0bcb110d20
commit
7ce1ee5cea
+8
-5
@@ -29,6 +29,7 @@ import { OrganizationRepositoryFixture } from "test/fixtures/repository/organiza
|
||||
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { mockThrottlerGuard } from "test/utils/withNoThrottler";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { getEnv } from "@/env";
|
||||
@@ -91,12 +92,14 @@ describe("Organizations Organizations Endpoints", () => {
|
||||
|
||||
const newDate = new Date(2035, 0, 9, 15, 0, 0);
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
}).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
}).compile();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
platformBillingRepositoryFixture = new PlatformBillingRepositoryFixture(moduleRef);
|
||||
|
||||
+21
-21
@@ -1,5 +1,23 @@
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { X_CAL_CLIENT_ID_HEADER, X_CAL_SECRET_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { Throttle } from "@/lib/endpoint-throttler-decorator";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
@@ -17,24 +35,6 @@ import { CreateManagedOrganizationOutput } from "@/modules/organizations/organiz
|
||||
import { GetManagedOrganizationOutput } from "@/modules/organizations/organizations/outputs/get-managed-organization.output";
|
||||
import { GetManagedOrganizationsOutput } from "@/modules/organizations/organizations/outputs/get-managed-organizations.output";
|
||||
import { ManagedOrganizationsService } from "@/modules/organizations/organizations/services/managed-organizations.service";
|
||||
import {
|
||||
Controller,
|
||||
UseGuards,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Post,
|
||||
Body,
|
||||
Get,
|
||||
Patch,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
Delete,
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
|
||||
const SCALE = "SCALE";
|
||||
|
||||
@@ -142,6 +142,7 @@ export class OrganizationsOrganizationsController {
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan(SCALE)
|
||||
@Delete("/:managedOrganizationId")
|
||||
@Throttle({ limit: 1, ttl: 30000, blockDuration: 30000, name: "organizations_delete" })
|
||||
@ApiOperation({
|
||||
summary: "Delete an organization within an organization",
|
||||
description:
|
||||
@@ -151,9 +152,8 @@ export class OrganizationsOrganizationsController {
|
||||
async deleteOrganization(
|
||||
@Param("managedOrganizationId", ParseIntPipe) managedOrganizationId: number
|
||||
): Promise<GetManagedOrganizationOutput> {
|
||||
const organization = await this.managedOrganizationsService.deleteManagedOrganization(
|
||||
managedOrganizationId
|
||||
);
|
||||
const organization =
|
||||
await this.managedOrganizationsService.deleteManagedOrganization(managedOrganizationId);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: organization,
|
||||
|
||||
+224
-215
@@ -12,6 +12,7 @@ import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { withApiAuth } from "test/utils/withApiAuth";
|
||||
import { mockThrottlerGuard } from "test/utils/withNoThrottler";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { CreateOrgTeamDto } from "@/modules/organizations/teams/index/inputs/create-organization-team.input";
|
||||
@@ -39,42 +40,44 @@ describe("Organizations Team Endpoints", () => {
|
||||
const userEmail = `organizations-teams-admin-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
@@ -300,93 +303,95 @@ describe("Organizations Team Endpoints", () => {
|
||||
const userEmail = `organizations-teams-member-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(teamsRepositoryFixture).toBeDefined();
|
||||
expect(user).toBeDefined();
|
||||
expect(org).toBeDefined();
|
||||
});
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it("should deny get all the teams of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams`).expect(403);
|
||||
});
|
||||
it("should be defined", () => {
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(teamsRepositoryFixture).toBeDefined();
|
||||
expect(user).toBeDefined();
|
||||
expect(org).toBeDefined();
|
||||
});
|
||||
|
||||
it("should deny get all the teams of the org paginated", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams?skip=1&take=1`).expect(403);
|
||||
});
|
||||
it("should deny get all the teams of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams`).expect(403);
|
||||
});
|
||||
|
||||
it("should deny get the team of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/${team.id}`).expect(403);
|
||||
});
|
||||
it("should deny get all the teams of the org paginated", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams?skip=1&take=1`).expect(403);
|
||||
});
|
||||
|
||||
it("should deny create the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
it("should deny get the team of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/${team.id}`).expect(403);
|
||||
});
|
||||
|
||||
it("should deny update the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
it("should deny create the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should deny delete the team of the org we created via api", async () => {
|
||||
return request(app.getHttpServer()).delete(`/v2/organizations/${org.id}/teams/${team2.id}`).expect(403);
|
||||
});
|
||||
it("should deny update the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should deny delete the team of the org we created via api", async () => {
|
||||
return request(app.getHttpServer()).delete(`/v2/organizations/${org.id}/teams/${team2.id}`).expect(403);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
@@ -414,105 +419,107 @@ describe("Organizations Team Endpoints", () => {
|
||||
const userEmail = `organizations-teams-owner-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
org = await organizationsRepositoryFixture.create({
|
||||
name: `organizations-teams-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "MEMBER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
team = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team1-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: team.id } },
|
||||
});
|
||||
team2 = await teamsRepositoryFixture.create({
|
||||
name: `organizations-teams-team2-${randomString()}`,
|
||||
isOrganization: false,
|
||||
parent: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: team2.id } },
|
||||
});
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: team.id } },
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "OWNER",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: team2.id } },
|
||||
});
|
||||
|
||||
await app.init();
|
||||
});
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(organizationsRepositoryFixture).toBeDefined();
|
||||
expect(user).toBeDefined();
|
||||
expect(org).toBeDefined();
|
||||
});
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it("should deny get all the teams of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams`).expect(403);
|
||||
});
|
||||
it("should be defined", () => {
|
||||
expect(userRepositoryFixture).toBeDefined();
|
||||
expect(organizationsRepositoryFixture).toBeDefined();
|
||||
expect(user).toBeDefined();
|
||||
expect(org).toBeDefined();
|
||||
});
|
||||
|
||||
it("should deny get all the teams of the org paginated", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams?skip=1&take=1`).expect(403);
|
||||
});
|
||||
it("should deny get all the teams of the org", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams`).expect(403);
|
||||
});
|
||||
|
||||
it("should get the team of the org for which the user is team owner", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/${team.id}`).expect(200);
|
||||
});
|
||||
it("should deny get all the teams of the org paginated", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams?skip=1&take=1`).expect(403);
|
||||
});
|
||||
|
||||
it("should deny create the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
it("should get the team of the org for which the user is team owner", async () => {
|
||||
return request(app.getHttpServer()).get(`/v2/organizations/${org.id}/teams/${team.id}`).expect(200);
|
||||
});
|
||||
|
||||
it("should deny update the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
it("should deny create the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.post(`/v2/organizations/${org.id}/teams`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should deny delete the team of the org we created via api", async () => {
|
||||
return request(app.getHttpServer()).delete(`/v2/organizations/${org.id}/teams/${team2.id}`).expect(403);
|
||||
});
|
||||
it("should deny update the team of the org", async () => {
|
||||
return request(app.getHttpServer())
|
||||
.patch(`/v2/organizations/${org.id}/teams/${team.id}`)
|
||||
.send({
|
||||
name: `organizations-teams-api-team1-${randomString()}-updated`,
|
||||
} satisfies CreateOrgTeamDto)
|
||||
.expect(403);
|
||||
});
|
||||
|
||||
it("should deny delete the team of the org we created via api", async () => {
|
||||
return request(app.getHttpServer()).delete(`/v2/organizations/${org.id}/teams/${team2.id}`).expect(403);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await userRepositoryFixture.deleteByEmail(user.email);
|
||||
@@ -544,43 +551,45 @@ describe("Organizations Team Endpoints", () => {
|
||||
const userEmail = `organizations-teams-platform-owner-${randomString()}@api.com`;
|
||||
let user: User;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
orgRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
const moduleRef = await withApiAuth(
|
||||
userEmail,
|
||||
Test.createTestingModule({
|
||||
imports: [AppModule, PrismaModule, UsersModule, TokensModule],
|
||||
})
|
||||
).compile();
|
||||
|
||||
org = await orgRepositoryFixture.create({
|
||||
name: `organizations-teams-platform-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
teamsRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
|
||||
oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
|
||||
orgRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
|
||||
user = await userRepositoryFixture.create({
|
||||
email: userEmail,
|
||||
username: userEmail,
|
||||
});
|
||||
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
org = await orgRepositoryFixture.create({
|
||||
name: `organizations-teams-platform-organization-${randomString()}`,
|
||||
isOrganization: true,
|
||||
});
|
||||
|
||||
oAuthClient1 = await createOAuthClient(org.id);
|
||||
oAuthClient2 = await createOAuthClient(org.id);
|
||||
await membershipsRepositoryFixture.create({
|
||||
role: "ADMIN",
|
||||
user: { connect: { id: user.id } },
|
||||
team: { connect: { id: org.id } },
|
||||
});
|
||||
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
oAuthClient1 = await createOAuthClient(org.id);
|
||||
oAuthClient2 = await createOAuthClient(org.id);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
app = moduleRef.createNestApplication();
|
||||
bootstrap(app as NestExpressApplication);
|
||||
|
||||
await app.init();
|
||||
});
|
||||
|
||||
async function createOAuthClient(organizationId: number) {
|
||||
const data = {
|
||||
|
||||
+21
-20
@@ -1,9 +1,29 @@
|
||||
import { SUCCESS_STATUS, X_CAL_CLIENT_ID } from "@calcom/platform-constants";
|
||||
import { OrgTeamOutputDto, SkipTakePagination } from "@calcom/platform-types";
|
||||
import type { Team } from "@calcom/prisma/client";
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Patch,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
import { Request } from "express";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import {
|
||||
OPTIONAL_API_KEY_HEADER,
|
||||
OPTIONAL_X_CAL_CLIENT_ID_HEADER,
|
||||
OPTIONAL_X_CAL_SECRET_KEY_HEADER,
|
||||
} from "@/lib/docs/headers";
|
||||
import { Throttle } from "@/lib/endpoint-throttler-decorator";
|
||||
import { PlatformPlan } from "@/modules/auth/decorators/billing/platform-plan.decorator";
|
||||
import { GetTeam } from "@/modules/auth/decorators/get-team/get-team.decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
@@ -25,26 +45,6 @@ import {
|
||||
} from "@/modules/organizations/teams/index/outputs/organization-team.output";
|
||||
import { OrganizationsTeamsService } from "@/modules/organizations/teams/index/services/organizations-teams.service";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import {
|
||||
Controller,
|
||||
UseGuards,
|
||||
Get,
|
||||
Param,
|
||||
ParseIntPipe,
|
||||
Query,
|
||||
Delete,
|
||||
Patch,
|
||||
Post,
|
||||
Body,
|
||||
Req,
|
||||
} from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
import { Request } from "express";
|
||||
|
||||
import { SUCCESS_STATUS, X_CAL_CLIENT_ID } from "@calcom/platform-constants";
|
||||
import { OrgTeamOutputDto, SkipTakePagination } from "@calcom/platform-types";
|
||||
import type { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId/teams",
|
||||
@@ -121,6 +121,7 @@ export class OrganizationsTeamsController {
|
||||
@Roles("ORG_ADMIN")
|
||||
@PlatformPlan("ESSENTIALS")
|
||||
@Delete("/:teamId")
|
||||
@Throttle({ limit: 1, ttl: 30000, blockDuration: 30000, name: "org_teams_delete" })
|
||||
@ApiOperation({ summary: "Delete a team" })
|
||||
async deleteTeam(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MembershipRepositoryFixture } from "test/fixtures/repository/membership
|
||||
import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture";
|
||||
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
|
||||
import { randomString } from "test/utils/randomString";
|
||||
import { mockThrottlerGuard } from "test/utils/withNoThrottler";
|
||||
import { AppModule } from "@/app.module";
|
||||
import { bootstrap } from "@/bootstrap";
|
||||
import { StripeService } from "@/modules/stripe/stripe.service";
|
||||
@@ -40,12 +41,14 @@ describe("Teams endpoint", () => {
|
||||
let team1: TeamOutputDto;
|
||||
let team2: TeamOutputDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, TeamsModule],
|
||||
}).compile();
|
||||
beforeAll(async () => {
|
||||
mockThrottlerGuard();
|
||||
|
||||
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(() => ({}) as unknown as Stripe);
|
||||
const moduleRef = await Test.createTestingModule({
|
||||
imports: [AppModule, TeamsModule],
|
||||
}).compile();
|
||||
|
||||
jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(() => ({}) as unknown as Stripe);
|
||||
|
||||
userRepositoryFixture = new UserRepositoryFixture(moduleRef);
|
||||
teamRepositoryFixture = new TeamRepositoryFixture(moduleRef);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { TeamOutputDto } from "@calcom/platform-types";
|
||||
import { Body, Controller, Delete, Get, Param, ParseIntPipe, Patch, Post, UseGuards } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
import { API_VERSIONS_VALUES } from "@/lib/api-versions";
|
||||
import { API_KEY_HEADER } from "@/lib/docs/headers";
|
||||
import { Throttle } from "@/lib/endpoint-throttler-decorator";
|
||||
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
|
||||
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
|
||||
import { ApiAuthGuard } from "@/modules/auth/guards/api-auth/api-auth.guard";
|
||||
@@ -14,12 +20,6 @@ import { UpdateTeamOutput } from "@/modules/teams/teams/outputs/teams/update-tea
|
||||
import { TeamsService } from "@/modules/teams/teams/services/teams.service";
|
||||
import { TeamsRepository } from "@/modules/teams/teams/teams.repository";
|
||||
import { UserWithProfile } from "@/modules/users/users.repository";
|
||||
import { Controller, UseGuards, Get, Param, ParseIntPipe, Delete, Patch, Post, Body } from "@nestjs/common";
|
||||
import { ApiHeader, ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { TeamOutputDto } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/teams",
|
||||
@@ -29,7 +29,10 @@ import { TeamOutputDto } from "@calcom/platform-types";
|
||||
@DocsTags("Teams")
|
||||
@ApiHeader(API_KEY_HEADER)
|
||||
export class TeamsController {
|
||||
constructor(private teamsService: TeamsService, private teamsRepository: TeamsRepository) {}
|
||||
constructor(
|
||||
private teamsService: TeamsService,
|
||||
private teamsRepository: TeamsRepository
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@ApiOperation({ summary: "Create a team" })
|
||||
@@ -95,6 +98,7 @@ export class TeamsController {
|
||||
|
||||
@UseGuards(RolesGuard)
|
||||
@Delete("/:teamId")
|
||||
@Throttle({ limit: 1, ttl: 30000, blockDuration: 30000, name: "teams_delete" })
|
||||
@ApiOperation({ summary: "Delete a team" })
|
||||
@Roles("TEAM_OWNER")
|
||||
async deleteTeam(@Param("teamId", ParseIntPipe) teamId: number): Promise<OrgTeamOutputResponseDto> {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { CustomThrottlerGuard } from "@/lib/throttler-guard";
|
||||
|
||||
export const mockThrottlerGuard = (): void => {
|
||||
jest.spyOn(CustomThrottlerGuard.prototype, "handleRequest").mockResolvedValue(true);
|
||||
};
|
||||
Reference in New Issue
Block a user