diff --git a/apps/api/v2/jest-e2e.json b/apps/api/v2/jest-e2e.json index 5329e06aad..3f5b257332 100644 --- a/apps/api/v2/jest-e2e.json +++ b/apps/api/v2/jest-e2e.json @@ -2,7 +2,8 @@ "moduleFileExtensions": ["js", "json", "ts"], "rootDir": ".", "moduleNameMapper": { - "@/(.*)": "/src/$1" + "@/(.*)": "/src/$1", + "test/(.*)": "/test/$1" }, "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", diff --git a/apps/api/v2/src/modules/auth/guard/oauth/access-token.guard.ts b/apps/api/v2/src/modules/auth/guard/oauth/access-token.guard.ts index 396cc5f557..f1840ff151 100644 --- a/apps/api/v2/src/modules/auth/guard/oauth/access-token.guard.ts +++ b/apps/api/v2/src/modules/auth/guard/oauth/access-token.guard.ts @@ -9,7 +9,7 @@ export class AccessTokenGuard implements CanActivate { const request = context.switchToHttp().getRequest(); const authHeader = request.headers.authorization; - const bearer = authHeader.replace("Bearer ", "").trim(); + const bearer = authHeader?.replace("Bearer ", "").trim(); if (!bearer) { throw new UnauthorizedException(); } diff --git a/apps/api/v2/src/modules/auth/guard/organization-roles/organization-roles.guard.ts b/apps/api/v2/src/modules/auth/guard/organization-roles/organization-roles.guard.ts index 50a3b91a6e..576771c72a 100644 --- a/apps/api/v2/src/modules/auth/guard/organization-roles/organization-roles.guard.ts +++ b/apps/api/v2/src/modules/auth/guard/organization-roles/organization-roles.guard.ts @@ -9,7 +9,8 @@ export class OrganizationRolesGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const requiredRoles = this.reflector.get(Roles, context.getHandler()); - if (!requiredRoles) { + + if (!requiredRoles.length || !Object.keys(requiredRoles).length) { return true; } diff --git a/apps/api/v2/src/modules/auth/strategy/api-key-auth/api-key-auth.strategy.ts b/apps/api/v2/src/modules/auth/strategy/api-key-auth/api-key-auth.strategy.ts index a1e85275a3..c11915dc5f 100644 --- a/apps/api/v2/src/modules/auth/strategy/api-key-auth/api-key-auth.strategy.ts +++ b/apps/api/v2/src/modules/auth/strategy/api-key-auth/api-key-auth.strategy.ts @@ -1,6 +1,6 @@ import { ApiKeyService } from "@/modules/api-key/api-key.service"; import { UserRepository } from "@/modules/user/user.repository"; -import { Injectable, UnauthorizedException } from "@nestjs/common"; +import { Injectable, NotFoundException, UnauthorizedException } from "@nestjs/common"; import { PassportStrategy } from "@nestjs/passport"; import type { Request } from "express"; @@ -31,6 +31,10 @@ export class ApiKeyAuthStrategy extends PassportStrategy(BaseStrategy, "api-key" } const user = await this.userRepository.findById(apiKey.userId); + if (!user) { + throw new NotFoundException("User not found"); + } + this.success(user); } catch (error) { if (error instanceof Error) return this.error(error); diff --git a/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.spec.ts b/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.spec.ts index 5cf9b88082..38d2f49a4d 100644 --- a/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.spec.ts +++ b/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.spec.ts @@ -1,19 +1,22 @@ import { AppModule } from "@/app.module"; import { OAuthClientModule } from "@/modules/oauth/oauth-client.module"; import { createMock } from "@golevelup/ts-jest"; -import { ExecutionContext } from "@nestjs/common"; +import { ExecutionContext, UnauthorizedException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; -import { PlatformOAuthClient } from "@prisma/client"; +import { PlatformOAuthClient, Team } from "@prisma/client"; import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; +import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; -import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants"; +import { X_CAL_SECRET_KEY } from "@calcom/platform-constants"; import { OAuthClientGuard } from "./oauth-client.guard"; describe("OAuthClientGuard", () => { let guard: OAuthClientGuard; let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let teamRepositoryFixture: TeamRepositoryFixture; let oauthClient: PlatformOAuthClient; + let organization: Team; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -21,9 +24,11 @@ describe("OAuthClientGuard", () => { }).compile(); guard = module.get(OAuthClientGuard); + teamRepositoryFixture = new TeamRepositoryFixture(module); oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(module); - const organizationId = 1; + organization = await teamRepositoryFixture.create({ name: "organization" }); + const data = { logo: "logo-url", name: "name", @@ -32,7 +37,7 @@ describe("OAuthClientGuard", () => { }; const secret = "secret"; - oauthClient = await oauthClientRepositoryFixture.create(organizationId, data, secret); + oauthClient = await oauthClientRepositoryFixture.create(organization.id, data, secret); }); it("should be defined", () => { @@ -41,41 +46,46 @@ describe("OAuthClientGuard", () => { }); it("should return true if client ID and secret are valid", async () => { - const mockContext = createMockExecutionContext({ - [X_CAL_CLIENT_ID]: oauthClient.id, - [X_CAL_SECRET_KEY]: oauthClient.secret, - }); + const mockContext = createMockExecutionContext( + { [X_CAL_SECRET_KEY]: oauthClient.secret }, + { clientId: oauthClient.id } + ); await expect(guard.canActivate(mockContext)).resolves.toBe(true); }); it("should return false if client ID is invalid", async () => { - const mockContext = createMockExecutionContext({ - [X_CAL_CLIENT_ID]: "invalid id", - [X_CAL_SECRET_KEY]: oauthClient.secret, - }); + const mockContext = createMockExecutionContext( + { [X_CAL_SECRET_KEY]: oauthClient.secret }, + { clientId: "invalid id" } + ); - await expect(guard.canActivate(mockContext)).resolves.toBe(false); + await expect(guard.canActivate(mockContext)).rejects.toThrow(UnauthorizedException); }); it("should return false if secret key is invalid", async () => { - const mockContext = createMockExecutionContext({ - [X_CAL_CLIENT_ID]: oauthClient.id, - [X_CAL_SECRET_KEY]: "invalid secret", - }); + const mockContext = createMockExecutionContext( + { [X_CAL_SECRET_KEY]: "invalid secret" }, + { clientId: oauthClient.id } + ); - await expect(guard.canActivate(mockContext)).resolves.toBe(false); + await expect(guard.canActivate(mockContext)).rejects.toThrow(UnauthorizedException); }); afterAll(async () => { await oauthClientRepositoryFixture.delete(oauthClient.id); + await teamRepositoryFixture.delete(organization.id); }); - function createMockExecutionContext(headers: Record): ExecutionContext { + function createMockExecutionContext( + headers: Record, + params: Record + ): ExecutionContext { return createMock({ switchToHttp: () => ({ getRequest: () => ({ headers, + params, }), }), }); diff --git a/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.ts b/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.ts index b39f3fce15..14e92b3e0c 100644 --- a/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.ts +++ b/apps/api/v2/src/modules/oauth/guard/oauth-client/oauth-client.guard.ts @@ -1,27 +1,31 @@ import { OAuthClientRepository } from "@/modules/oauth/oauth-client.repository"; -import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common"; +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common"; -import { X_CAL_CLIENT_ID, X_CAL_SECRET_KEY } from "@calcom/platform-constants"; +import { X_CAL_SECRET_KEY } from "@calcom/platform-constants"; @Injectable() export class OAuthClientGuard implements CanActivate { constructor(private readonly oauthRepository: OAuthClientRepository) {} - canActivate(context: ExecutionContext): Promise { + async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - const { headers } = request; + const { headers, params } = request; - const oauthClientId = headers[X_CAL_CLIENT_ID]; + const oauthClientId = params.clientId; const oauthClientSecret = headers[X_CAL_SECRET_KEY]; - return this.validateOauthClient(oauthClientId, oauthClientSecret); - } + if (!oauthClientId) { + throw new UnauthorizedException("Missing client ID"); + } - private async validateOauthClient(oauthClientId: string, oauthClientSecret: string): Promise { - const oauthClient = await this.oauthRepository.getOAuthClient(oauthClientId); + if (!oauthClientSecret) { + throw new UnauthorizedException("Missing client secret"); + } - if (!oauthClient || oauthClient.secret !== oauthClientSecret) { - return false; + const client = await this.oauthRepository.getOAuthClient(oauthClientId); + + if (!client || client.secret !== oauthClientSecret) { + throw new UnauthorizedException(); } return true; diff --git a/apps/api/v2/src/modules/oauth/oauth-client.module.ts b/apps/api/v2/src/modules/oauth/oauth-client.module.ts index 09ae4532c3..afde1f60d2 100644 --- a/apps/api/v2/src/modules/oauth/oauth-client.module.ts +++ b/apps/api/v2/src/modules/oauth/oauth-client.module.ts @@ -1,10 +1,13 @@ import { getEnv } from "@/env"; import { AuthModule } from "@/modules/auth/auth.module"; import { MembershipModule } from "@/modules/membership/membership.module"; +import { OAuthFlowModule } from "@/modules/oauth/flow/oauth-flow.module"; import { OAuthClientGuard } from "@/modules/oauth/guard/oauth-client/oauth-client.guard"; import { OAuthClientController } from "@/modules/oauth/oauth-client.controller"; import { OAuthClientRepository } from "@/modules/oauth/oauth-client.repository"; +import { OAuthUserController } from "@/modules/oauth/user/oauth-user.controller"; import { PrismaModule } from "@/modules/prisma/prisma.module"; +import { TokensModule } from "@/modules/tokens/tokens.module"; import { UserModule } from "@/modules/user/user.module"; import { Global, Module } from "@nestjs/common"; import { JwtModule } from "@nestjs/jwt"; @@ -17,9 +20,11 @@ import { JwtModule } from "@nestjs/jwt"; UserModule, MembershipModule, JwtModule.register({ secret: getEnv("JWT_SECRET") }), + OAuthFlowModule, + TokensModule, ], providers: [OAuthClientRepository, OAuthClientGuard], - controllers: [OAuthClientController], + controllers: [OAuthClientController, OAuthUserController], exports: [OAuthClientRepository, OAuthClientGuard], }) export class OAuthClientModule {} diff --git a/apps/api/v2/src/modules/oauth/user/oauth-user.controller.e2e-spec.ts b/apps/api/v2/src/modules/oauth/user/oauth-user.controller.e2e-spec.ts new file mode 100644 index 0000000000..25b8c0fbfb --- /dev/null +++ b/apps/api/v2/src/modules/oauth/user/oauth-user.controller.e2e-spec.ts @@ -0,0 +1,187 @@ +import { bootstrap } from "@/app"; +import { AppModule } from "@/app.module"; +import { HttpExceptionFilter } from "@/filters/http-exception.filter"; +import { PrismaExceptionFilter } from "@/filters/prisma-exception.filter"; +import { CreateUserResponse } from "@/modules/oauth/user/oauth-user.controller"; +import { CreateUserInput } from "@/modules/user/input/create-user"; +import { UpdateUserInput } from "@/modules/user/input/update-user"; +import { UserModule } from "@/modules/user/user.module"; +import { INestApplication } from "@nestjs/common"; +import { NestExpressApplication } from "@nestjs/platform-express"; +import { Test } from "@nestjs/testing"; +import { PlatformOAuthClient, Team, User } from "@prisma/client"; +import * as request from "supertest"; +import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture"; +import { TeamRepositoryFixture } from "test/fixtures/repository/team.repository.fixture"; +import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture"; + +import { SUCCESS_STATUS } from "@calcom/platform-constants"; +import { ApiSuccessResponse } from "@calcom/platform-types"; + +describe("User Endpoints", () => { + describe("Not authenticated", () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [PrismaExceptionFilter, HttpExceptionFilter], + imports: [AppModule, UserModule], + }).compile(); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + await app.init(); + }); + + describe("secret header not set", () => { + it(`/POST`, () => { + return request(app.getHttpServer()) + .post("/api/v2/oauth-clients/100/users") + .send({ email: "bob@gmail.com" }) + .expect(401); + }); + }); + + describe("Bearer access token not set", () => { + it(`/GET/:id`, () => { + return request(app.getHttpServer()).get("/api/v2/oauth-clients/100/users/200").expect(401); + }); + it(`/PUT/:id`, () => { + return request(app.getHttpServer()).put("/api/v2/oauth-clients/100/users/200").expect(401); + }); + }); + + afterAll(async () => { + await app.close(); + }); + }); + + describe("User Authenticated", () => { + let app: INestApplication; + + let oAuthClient: PlatformOAuthClient; + let organization: Team; + let userRepositoryFixture: UserRepositoryFixture; + let oauthClientRepositoryFixture: OAuthClientRepositoryFixture; + let teamRepositoryFixture: TeamRepositoryFixture; + + let postResponseData: CreateUserResponse; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + providers: [PrismaExceptionFilter, HttpExceptionFilter], + imports: [AppModule, UserModule], + }).compile(); + + app = moduleRef.createNestApplication(); + bootstrap(app as NestExpressApplication); + + oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef); + userRepositoryFixture = new UserRepositoryFixture(moduleRef); + teamRepositoryFixture = new TeamRepositoryFixture(moduleRef); + organization = await teamRepositoryFixture.create({ name: "organization" }); + oAuthClient = await createOAuthClient(organization.id); + + await app.init(); + }); + + async function createOAuthClient(organizationId: number) { + const data = { + logo: "logo-url", + name: "name", + redirect_uris: ["redirect-uri"], + permissions: 32, + }; + const secret = "secret"; + + const client = await oauthClientRepositoryFixture.create(organizationId, data, secret); + return client; + } + + it("should be defined", () => { + expect(oauthClientRepositoryFixture).toBeDefined(); + expect(userRepositoryFixture).toBeDefined(); + expect(oAuthClient).toBeDefined(); + }); + + it(`/POST`, async () => { + const requestBody: CreateUserInput = { + email: "user-e2e-spec@gmail.com", + }; + + const response = await request(app.getHttpServer()) + .post(`/api/v2/oauth-clients/${oAuthClient.id}/users`) + .set("x-cal-secret-key", oAuthClient.secret) + .send(requestBody) + .expect(201); + + const responseBody: ApiSuccessResponse<{ + user: Omit; + accessToken: string; + refreshToken: string; + }> = response.body; + + postResponseData = responseBody.data; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.user.email).toEqual(requestBody.email); + expect(responseBody.data.accessToken).toBeDefined(); + expect(responseBody.data.refreshToken).toBeDefined(); + + await userConnectedToOAuth(responseBody.data.user.email); + }); + + async function userConnectedToOAuth(userEmail: string) { + const oAuthUsers = await oauthClientRepositoryFixture.getUsers(oAuthClient.id); + const newOAuthUser = oAuthUsers?.find((user) => user.email === userEmail); + + expect(oAuthUsers?.length).toEqual(1); + expect(newOAuthUser?.email).toEqual(userEmail); + } + + it(`/GET/:id`, async () => { + const response = await request(app.getHttpServer()) + .get(`/api/v2/oauth-clients/${oAuthClient.id}/users/${postResponseData.user.id}`) + .set("Authorization", `Bearer ${postResponseData.accessToken}`) + .expect(200); + + const responseBody: ApiSuccessResponse> = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.email).toEqual(postResponseData.user.email); + }); + + it(`/PUT/:id`, async () => { + const userUpdatedEmail = "pineapple-pizza@gmail.com"; + const body: UpdateUserInput = { email: userUpdatedEmail }; + + const response = await request(app.getHttpServer()) + .put(`/api/v2/oauth-clients/${oAuthClient.id}/users/${postResponseData.user.id}`) + .set("Authorization", `Bearer ${postResponseData.accessToken}`) + .send(body) + .expect(200); + + const responseBody: ApiSuccessResponse> = response.body; + + expect(responseBody.status).toEqual(SUCCESS_STATUS); + expect(responseBody.data).toBeDefined(); + expect(responseBody.data.email).toEqual(userUpdatedEmail); + }); + + afterAll(async () => { + if (postResponseData?.user.id) { + await userRepositoryFixture.delete(postResponseData.user.id); + } + if (oAuthClient) { + await oauthClientRepositoryFixture.delete(oAuthClient.id); + } + if (organization) { + await teamRepositoryFixture.delete(organization.id); + } + + await app.close(); + }); + }); +}); diff --git a/apps/api/v2/src/modules/oauth/user/oauth-user.controller.ts b/apps/api/v2/src/modules/oauth/user/oauth-user.controller.ts new file mode 100644 index 0000000000..6ebfa964e0 --- /dev/null +++ b/apps/api/v2/src/modules/oauth/user/oauth-user.controller.ts @@ -0,0 +1,96 @@ +import { AccessTokenGuard } from "@/modules/auth/guard/oauth/access-token.guard"; +import { OAuthClientGuard } from "@/modules/oauth/guard/oauth-client/oauth-client.guard"; +import { TokensRepository } from "@/modules/tokens/tokens.repository"; +import { CreateUserInput } from "@/modules/user/input/create-user"; +import { UpdateUserInput } from "@/modules/user/input/update-user"; +import { UserRepository } from "@/modules/user/user.repository"; +import { + Body, + Controller, + Post, + Logger, + UseGuards, + Get, + HttpCode, + HttpStatus, + NotFoundException, + Param, + Put, + BadRequestException, +} from "@nestjs/common"; +import { User } from "@prisma/client"; + +import { DUPLICATE_RESOURCE, SUCCESS_STATUS } from "@calcom/platform-constants"; +import { ApiResponse } from "@calcom/platform-types"; + +@Controller({ + path: "oauth-clients/:clientId/users", + version: "2", +}) +export class OAuthUserController { + private readonly logger = new Logger("UserController"); + + constructor( + private readonly userRepository: UserRepository, + private readonly tokensRepository: TokensRepository + ) {} + + @Post("/") + @UseGuards(OAuthClientGuard) + async createUser( + @Param("clientId") oAuthClientId: string, + @Body() body: CreateUserInput + ): Promise> { + this.logger.log( + `Creating user with data: ${JSON.stringify(body, null, 2)} for OAuth Client with ID ${oAuthClientId}` + ); + + const existingUser = await this.userRepository.findByEmail(body.email); + + if (existingUser) { + throw new BadRequestException(DUPLICATE_RESOURCE); + } + + const user = await this.userRepository.create(body, oAuthClientId); + const { access_token, refresh_token } = await this.tokensRepository.createOAuthTokens( + oAuthClientId, + user.id! + ); + + return { + status: SUCCESS_STATUS, + data: { + user, + accessToken: access_token, + refreshToken: refresh_token, + }, + }; + } + + @Get("/:userId") + @HttpCode(HttpStatus.OK) + @UseGuards(AccessTokenGuard) + async getUserById(@Param("userId") userId: number): Promise>> { + const user = await this.userRepository.findById(userId); + if (!user) { + throw new NotFoundException("User not found"); + } + + return { status: SUCCESS_STATUS, data: user }; + } + + @Put("/:userId") + @HttpCode(HttpStatus.OK) + @UseGuards(AccessTokenGuard) + async updateUser( + @Param("userId") userId: number, + @Body() body: UpdateUserInput + ): Promise>> { + this.logger.log(`Updating user with ID ${userId}: ${JSON.stringify(body, null, 2)}`); + + const user = await this.userRepository.update(userId, body); + return { status: SUCCESS_STATUS, data: user }; + } +} + +export type CreateUserResponse = { user: Partial; accessToken: string; refreshToken: string }; diff --git a/apps/api/v2/src/modules/user/input/create-user.ts b/apps/api/v2/src/modules/user/input/create-user.ts new file mode 100644 index 0000000000..806ecf523d --- /dev/null +++ b/apps/api/v2/src/modules/user/input/create-user.ts @@ -0,0 +1,6 @@ +import { IsString } from "class-validator"; + +export class CreateUserInput { + @IsString() + email!: string; +} diff --git a/apps/api/v2/src/modules/user/input/update-user.ts b/apps/api/v2/src/modules/user/input/update-user.ts new file mode 100644 index 0000000000..e8878c9df8 --- /dev/null +++ b/apps/api/v2/src/modules/user/input/update-user.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from "class-validator"; + +export class UpdateUserInput { + @IsString() + @IsOptional() + email?: string; +} diff --git a/apps/api/v2/src/modules/user/user.repository.ts b/apps/api/v2/src/modules/user/user.repository.ts index f91cae062b..4b95f7bf87 100644 --- a/apps/api/v2/src/modules/user/user.repository.ts +++ b/apps/api/v2/src/modules/user/user.repository.ts @@ -1,27 +1,67 @@ import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; +import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; +import { CreateUserInput } from "@/modules/user/input/create-user"; +import { UpdateUserInput } from "@/modules/user/input/update-user"; import { Injectable } from "@nestjs/common"; import type { User } from "@prisma/client"; @Injectable() export class UserRepository { - constructor(private readonly dbRead: PrismaReadService) {} + constructor(private readonly dbRead: PrismaReadService, private readonly dbWrite: PrismaWriteService) {} + + async create(user: CreateUserInput, oAuthClientId: string) { + const newUser = await this.dbRead.prisma.user.create({ + data: { + ...user, + platformOAuthClients: { + connect: { id: oAuthClientId }, + }, + }, + }); + + return this.sanitize(newUser); + } async findById(userId: number) { - const user = await this.dbRead.prisma.user.findUniqueOrThrow({ + const user = await this.dbRead.prisma.user.findUnique({ where: { id: userId, }, }); - return this.sanitize(user); + + if (user) { + return this.sanitize(user); + } + + return null; } async findByEmail(email: string) { - const user = await this.dbRead.prisma.user.findUniqueOrThrow({ + const user = await this.dbRead.prisma.user.findUnique({ where: { email, }, }); - return this.sanitize(user); + + if (user) { + return this.sanitize(user); + } + + return null; + } + + async update(userId: number, updateData: UpdateUserInput) { + const updatedUser = await this.dbWrite.prisma.user.update({ + where: { id: userId }, + data: updateData, + }); + return this.sanitize(updatedUser); + } + + async delete(userId: number): Promise { + return this.dbWrite.prisma.user.delete({ + where: { id: userId }, + }); } sanitize(user: User): Partial { diff --git a/apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts b/apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts index e97c28e4e7..4aeb2868b0 100644 --- a/apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts +++ b/apps/api/v2/test/fixtures/repository/oauth-client.repository.fixture.ts @@ -1,9 +1,10 @@ -import { CreateOAuthClientInput } from "@/modules/oauth/input/create-oauth-client"; import { PrismaReadService } from "@/modules/prisma/prisma-read.service"; import { PrismaWriteService } from "@/modules/prisma/prisma-write.service"; import { TestingModule } from "@nestjs/testing"; import { PlatformOAuthClient } from "@prisma/client"; +import { CreateOAuthClientInput } from "@calcom/platform-types"; + export class OAuthClientRepositoryFixture { private prismaReadClient: PrismaReadService["prisma"]; private prismaWriteClient: PrismaWriteService["prisma"]; @@ -17,6 +18,17 @@ export class OAuthClientRepositoryFixture { return this.prismaReadClient.platformOAuthClient.findFirst({ where: { id: clientId } }); } + async getUsers(clientId: PlatformOAuthClient["id"]) { + const response = await this.prismaReadClient.platformOAuthClient.findFirst({ + where: { id: clientId }, + include: { + users: true, + }, + }); + + return response?.users; + } + async create(organizationId: number, data: CreateOAuthClientInput, secret: string) { return this.prismaWriteClient.platformOAuthClient.create({ data: { diff --git a/apps/api/v2/test/oauth.e2e-spec.ts b/apps/api/v2/test/oauth.e2e-spec.ts index bbc2cc2ea5..3d5ab00d48 100644 --- a/apps/api/v2/test/oauth.e2e-spec.ts +++ b/apps/api/v2/test/oauth.e2e-spec.ts @@ -213,7 +213,7 @@ describe("OAuth Client Endpoints", () => { }); }); it(`/DELETE/:id`, () => { - return request(app.getHttpServer()).delete(`/api/v2/oauth-clients/${client.client_id}`).expect(204); + return request(app.getHttpServer()).delete(`/api/v2/oauth-clients/${client.client_id}`).expect(200); }); afterAll(async () => { @@ -295,7 +295,7 @@ describe("OAuth Client Endpoints", () => { }); }); it(`/DELETE/:id`, () => { - return request(app.getHttpServer()).delete(`/api/v2/oauth-clients/${client.client_id}`).expect(204); + return request(app.getHttpServer()).delete(`/api/v2/oauth-clients/${client.client_id}`).expect(200); }); afterAll(async () => {