From 5592f257a37f42cd01b099c0486a87d7479f146b Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 17:52:13 +0900 Subject: [PATCH 01/22] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/entities/profile.entity.ts | 1 - 1 file changed, 1 deletion(-) delete mode 100644 nestjs-BE/server/src/profiles/entities/profile.entity.ts diff --git a/nestjs-BE/server/src/profiles/entities/profile.entity.ts b/nestjs-BE/server/src/profiles/entities/profile.entity.ts deleted file mode 100644 index b4a8829d..00000000 --- a/nestjs-BE/server/src/profiles/entities/profile.entity.ts +++ /dev/null @@ -1 +0,0 @@ -export class Profile {} From d2544d9781a55bc9aa74d4293831310c6e654474 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 18:15:29 +0900 Subject: [PATCH 02/22] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=95=A8=EC=88=98=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/profiles.service.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.service.ts b/nestjs-BE/server/src/profiles/profiles.service.ts index 42c57130..c2a1391d 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.ts @@ -29,12 +29,6 @@ export class ProfilesService { return this.prisma.profile.findUnique({ where: { uuid } }); } - async findProfiles(profileUuids: string[]): Promise { - return this.prisma.profile.findMany({ - where: { uuid: { in: profileUuids } }, - }); - } - async getOrCreateProfile(data: CreateProfileDto): Promise { return this.prisma.profile.upsert({ where: { userUuid: data.userUuid }, From adfe9893916512875288f8f1a3f55e550309dee5 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 19:54:58 +0900 Subject: [PATCH 03/22] =?UTF-8?q?fix:=20=EC=9C=A0=EC=A0=80=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EB=A7=A4=EC=B9=98=20=EB=8F=99=EC=9E=91=20?= =?UTF-8?q?=EA=B0=80=EB=93=9C=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/profiles/profiles.controller.spec.ts | 1 - .../src/profiles/profiles.controller.ts | 4 +- .../src/profiles/profiles.service.spec.ts | 65 +------------------ .../server/src/profiles/profiles.service.ts | 18 +---- 4 files changed, 6 insertions(+), 82 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts index e2389df1..1c016b37 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts @@ -100,7 +100,6 @@ describe('ProfilesController', () => { }); expect(profilesService.updateProfile).toHaveBeenCalledWith( userUuidMock, - bodyMock.uuid, imageMock, bodyMock, ); diff --git a/nestjs-BE/server/src/profiles/profiles.controller.ts b/nestjs-BE/server/src/profiles/profiles.controller.ts index c8fa1fb4..829fe10d 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.ts @@ -7,12 +7,14 @@ import { UploadedFile, ValidationPipe, HttpStatus, + UseGuards, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { ProfilesService } from './profiles.service'; import { UpdateProfileDto } from './dto/update-profile.dto'; import { User } from '../auth/decorators/user.decorator'; +import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; @Controller('profiles') @ApiTags('profiles') @@ -35,6 +37,7 @@ export class ProfilesController { } @Patch() + @UseGuards(MatchUserProfileGuard) @UseInterceptors(FileInterceptor('image')) @ApiOperation({ summary: 'Update profile' }) @ApiResponse({ @@ -53,7 +56,6 @@ export class ProfilesController { ) { const profile = await this.profilesService.updateProfile( userUuid, - updateProfileDto.uuid, image, updateProfileDto, ); diff --git a/nestjs-BE/server/src/profiles/profiles.service.spec.ts b/nestjs-BE/server/src/profiles/profiles.service.spec.ts index 938bee15..f96a22b5 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { ForbiddenException, NotFoundException } from '@nestjs/common'; +import { NotFoundException } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { ProfilesService } from './profiles.service'; import { PrismaService } from '../prisma/prisma.service'; @@ -70,7 +70,6 @@ describe('ProfilesService', () => { const imageUrl = 'www.test.com/image'; beforeEach(() => { - jest.spyOn(profilesService, 'verifyUserProfile').mockResolvedValue(true); (uploadService.uploadFile as jest.Mock).mockResolvedValue(imageUrl); (prisma.profile.update as jest.Mock).mockImplementation(async (args) => { return { @@ -87,12 +86,7 @@ describe('ProfilesService', () => { it('updated', async () => { const data = { nickname: 'new nickname' }; - const profile = profilesService.updateProfile( - userUuid, - profileUuid, - image, - data, - ); + const profile = profilesService.updateProfile(userUuid, image, data); await expect(profile).resolves.toEqual({ uuid: profileUuid, @@ -101,60 +95,5 @@ describe('ProfilesService', () => { nickname: data.nickname, }); }); - - it('wrong user uuid', async () => { - const data = {}; - - jest - .spyOn(profilesService, 'verifyUserProfile') - .mockRejectedValue(new ForbiddenException()); - - const profile = profilesService.updateProfile( - userUuid, - profileUuid, - image, - data, - ); - - await expect(profile).rejects.toThrow(ForbiddenException); - }); - }); - - describe('verifyUserProfile', () => { - const userUuid = 'user uuid'; - const profileUuid = 'profile uuid'; - const image = 'www.test.com'; - const nickname = 'test nickname'; - - beforeEach(() => { - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue({ uuid: profileUuid, userUuid, image, nickname }); - }); - - it('verified', async () => { - const res = profilesService.verifyUserProfile(userUuid, profileUuid); - - await expect(res).resolves.toBeTruthy(); - }); - - it('profile not found', async () => { - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(null); - - const res = profilesService.verifyUserProfile(userUuid, profileUuid); - - await expect(res).rejects.toThrow(ForbiddenException); - }); - - it('profile user not own', async () => { - const res = profilesService.verifyUserProfile( - 'other user uuid', - profileUuid, - ); - - await expect(res).rejects.toThrow(ForbiddenException); - }); }); }); diff --git a/nestjs-BE/server/src/profiles/profiles.service.ts b/nestjs-BE/server/src/profiles/profiles.service.ts index c2a1391d..0e4ce238 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.ts @@ -1,8 +1,4 @@ -import { - ForbiddenException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { Profile, Prisma } from '@prisma/client'; import { v4 as uuid } from 'uuid'; import { UpdateProfileDto } from './dto/update-profile.dto'; @@ -44,11 +40,9 @@ export class ProfilesService { async updateProfile( userUuid: string, - profileUuid: string, image: Express.Multer.File, updateProfileDto: UpdateProfileDto, ): Promise { - await this.verifyUserProfile(userUuid, profileUuid); if (image) { updateProfileDto.image = await this.uploadService.uploadFile(image); } @@ -65,14 +59,4 @@ export class ProfilesService { } } } - - async verifyUserProfile( - userUuid: string, - profileUuid: string, - ): Promise { - const profile = await this.findProfileByProfileUuid(profileUuid); - if (!profile) throw new ForbiddenException(); - if (userUuid !== profile.userUuid) throw new ForbiddenException(); - return true; - } } From b01bb7193c82eba20a0ae006ab5f82a360ebae44 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 20:37:08 +0900 Subject: [PATCH 04/22] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EB=A7=A4=EC=B9=98=20=EA=B0=80?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=ED=94=84=EB=A1=9C=ED=95=84=20=EB=AA=A8?= =?UTF-8?q?=EB=93=88=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/auth/auth.module.ts | 3 --- .../server/src/invite-codes/invite-codes.controller.spec.ts | 2 +- nestjs-BE/server/src/invite-codes/invite-codes.controller.ts | 2 +- nestjs-BE/server/src/invite-codes/invite-codes.module.ts | 3 ++- .../{auth => profiles}/guards/match-user-profile.guard.ts | 0 nestjs-BE/server/src/profiles/profiles.controller.ts | 2 +- nestjs-BE/server/src/profiles/profiles.module.ts | 5 +++-- nestjs-BE/server/src/spaces/spaces.controller.spec.ts | 2 +- nestjs-BE/server/src/spaces/spaces.controller.ts | 2 +- nestjs-BE/server/src/spaces/spaces.module.ts | 3 ++- 10 files changed, 12 insertions(+), 12 deletions(-) rename nestjs-BE/server/src/{auth => profiles}/guards/match-user-profile.guard.ts (100%) diff --git a/nestjs-BE/server/src/auth/auth.module.ts b/nestjs-BE/server/src/auth/auth.module.ts index 2a6b5b0a..1622c181 100644 --- a/nestjs-BE/server/src/auth/auth.module.ts +++ b/nestjs-BE/server/src/auth/auth.module.ts @@ -6,7 +6,6 @@ import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { JwtStrategy } from './strategies/jwt.strategy'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; -import { MatchUserProfileGuard } from './guards/match-user-profile.guard'; import { IsProfileInSpaceGuard } from './guards/is-profile-in-space.guard'; import { UsersModule } from '../users/users.module'; import { ProfilesModule } from '../profiles/profiles.module'; @@ -27,12 +26,10 @@ import { ProfileSpaceModule } from '../profile-space/profile-space.module'; AuthService, JwtStrategy, { provide: APP_GUARD, useClass: JwtAuthGuard }, - MatchUserProfileGuard, IsProfileInSpaceGuard, ], exports: [ AuthService, - MatchUserProfileGuard, ProfilesModule, IsProfileInSpaceGuard, ProfileSpaceModule, diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.controller.spec.ts b/nestjs-BE/server/src/invite-codes/invite-codes.controller.spec.ts index 41882343..0c6b6961 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.controller.spec.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.controller.spec.ts @@ -8,8 +8,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { InviteCode, Space } from '@prisma/client'; import { InviteCodesController } from './invite-codes.controller'; import { InviteCodesService } from './invite-codes.service'; -import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; import { ProfilesService } from '../profiles/profiles.service'; +import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard'; import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts b/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts index a0796bcc..4acf7067 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.controller.ts @@ -9,8 +9,8 @@ import { import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; import { InviteCodesService } from './invite-codes.service'; import { CreateInviteCodeDto } from './dto/create-invite-code.dto'; -import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard'; +import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard'; @Controller('inviteCodes') @ApiTags('inviteCodes') diff --git a/nestjs-BE/server/src/invite-codes/invite-codes.module.ts b/nestjs-BE/server/src/invite-codes/invite-codes.module.ts index 2467d984..1e1e583c 100644 --- a/nestjs-BE/server/src/invite-codes/invite-codes.module.ts +++ b/nestjs-BE/server/src/invite-codes/invite-codes.module.ts @@ -3,9 +3,10 @@ import { InviteCodesService } from './invite-codes.service'; import { InviteCodesController } from './invite-codes.controller'; import { SpacesModule } from '../spaces/spaces.module'; import { AuthModule } from '../auth/auth.module'; +import { ProfilesModule } from '../profiles/profiles.module'; @Module({ - imports: [AuthModule, SpacesModule], + imports: [AuthModule, SpacesModule, ProfilesModule], controllers: [InviteCodesController], providers: [InviteCodesService], }) diff --git a/nestjs-BE/server/src/auth/guards/match-user-profile.guard.ts b/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts similarity index 100% rename from nestjs-BE/server/src/auth/guards/match-user-profile.guard.ts rename to nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts diff --git a/nestjs-BE/server/src/profiles/profiles.controller.ts b/nestjs-BE/server/src/profiles/profiles.controller.ts index 829fe10d..cbe9c0d8 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.ts @@ -14,7 +14,7 @@ import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { ProfilesService } from './profiles.service'; import { UpdateProfileDto } from './dto/update-profile.dto'; import { User } from '../auth/decorators/user.decorator'; -import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; +import { MatchUserProfileGuard } from './guards/match-user-profile.guard'; @Controller('profiles') @ApiTags('profiles') diff --git a/nestjs-BE/server/src/profiles/profiles.module.ts b/nestjs-BE/server/src/profiles/profiles.module.ts index 0515229e..f5a07137 100644 --- a/nestjs-BE/server/src/profiles/profiles.module.ts +++ b/nestjs-BE/server/src/profiles/profiles.module.ts @@ -3,11 +3,12 @@ import { ProfilesService } from './profiles.service'; import { ProfilesController } from './profiles.controller'; import { UploadModule } from '../upload/upload.module'; import { PrismaModule } from '../prisma/prisma.module'; +import { MatchUserProfileGuard } from './guards/match-user-profile.guard'; @Module({ imports: [UploadModule, PrismaModule], controllers: [ProfilesController], - providers: [ProfilesService], - exports: [ProfilesService], + providers: [ProfilesService, MatchUserProfileGuard], + exports: [ProfilesService, MatchUserProfileGuard], }) export class ProfilesModule {} diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index 1b9d1b04..a756bb0d 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -5,9 +5,9 @@ import { SpacesController } from './spaces.controller'; import { SpacesService } from './spaces.service'; import { UpdateSpaceDto } from './dto/update-space.dto'; import { CreateSpaceDto } from './dto/create-space.dto'; -import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard'; import { ProfilesService } from '../profiles/profiles.service'; +import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; describe('SpacesController', () => { diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index 0c4732a1..cf00b0dd 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -17,7 +17,7 @@ import { SpacesService } from './spaces.service'; import { CreateSpaceDto } from './dto/create-space.dto'; import { UpdateSpaceDto } from './dto/update-space.dto'; import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard'; -import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard'; +import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard'; @Controller('spaces') @ApiTags('spaces') diff --git a/nestjs-BE/server/src/spaces/spaces.module.ts b/nestjs-BE/server/src/spaces/spaces.module.ts index 63214130..c4e3e8c3 100644 --- a/nestjs-BE/server/src/spaces/spaces.module.ts +++ b/nestjs-BE/server/src/spaces/spaces.module.ts @@ -10,9 +10,10 @@ import { UploadModule } from '../upload/upload.module'; import { ProfileSpaceModule } from '../profile-space/profile-space.module'; import { AuthModule } from '../auth/auth.module'; import { MulterFileMiddleware } from '../common/middlewares/multer-file.middleware'; +import { ProfilesModule } from '../profiles/profiles.module'; @Module({ - imports: [ProfileSpaceModule, UploadModule, AuthModule], + imports: [ProfileSpaceModule, UploadModule, AuthModule, ProfilesModule], controllers: [SpacesController], providers: [SpacesService], exports: [SpacesService], From 95dc3e97db030070c6f9fc38dd22ad8ce6368012 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 21:04:30 +0900 Subject: [PATCH 05/22] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/profiles/profiles.service.spec.ts | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.service.spec.ts b/nestjs-BE/server/src/profiles/profiles.service.spec.ts index f96a22b5..1647f160 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.spec.ts @@ -6,7 +6,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { UploadService } from '../upload/upload.service'; describe('ProfilesService', () => { - let profilesService: ProfilesService; + let service: ProfilesService; let prisma: PrismaService; let configService: ConfigService; let uploadService: UploadService; @@ -16,23 +16,12 @@ describe('ProfilesService', () => { imports: [ConfigModule], providers: [ ProfilesService, - { - provide: PrismaService, - useValue: { - profile: { - findUnique: jest.fn(), - update: jest.fn(), - }, - }, - }, - { - provide: UploadService, - useValue: { uploadFile: jest.fn() }, - }, + { provide: PrismaService, useValue: { profile: {} } }, + { provide: UploadService, useValue: {} }, ], }).compile(); - profilesService = module.get(ProfilesService); + service = module.get(ProfilesService); prisma = module.get(PrismaService); configService = module.get(ConfigService); uploadService = module.get(UploadService); @@ -43,11 +32,11 @@ describe('ProfilesService', () => { const profile = { uuid: 'profile uuid', userUuid }; beforeEach(() => { - (prisma.profile.findUnique as jest.Mock).mockResolvedValue(profile); + (prisma.profile.findUnique as jest.Mock) = jest.fn(async () => profile); }); it('found', async () => { - const res = profilesService.findProfileByUserUuid(userUuid); + const res = service.findProfileByUserUuid(userUuid); await expect(res).resolves.toEqual(profile); }); @@ -57,7 +46,7 @@ describe('ProfilesService', () => { new NotFoundException(), ); - const res = profilesService.findProfileByUserUuid(userUuid); + const res = service.findProfileByUserUuid(userUuid); await expect(res).rejects.toThrow(NotFoundException); }); @@ -70,23 +59,21 @@ describe('ProfilesService', () => { const imageUrl = 'www.test.com/image'; beforeEach(() => { - (uploadService.uploadFile as jest.Mock).mockResolvedValue(imageUrl); - (prisma.profile.update as jest.Mock).mockImplementation(async (args) => { - return { - uuid: profileUuid, - userUuid, - nickname: args.data.nickname ? args.data.nickname : 'test nickname', - image: args.data.image - ? args.data.image - : configService.get('BASE_IMAGE_URL'), - }; - }); + uploadService.uploadFile = jest.fn(async () => imageUrl); + (prisma.profile.update as jest.Mock) = jest.fn(async (args) => ({ + uuid: profileUuid, + userUuid, + nickname: args.data.nickname ? args.data.nickname : 'test nickname', + image: args.data.image + ? args.data.image + : configService.get('BASE_IMAGE_URL'), + })); }); it('updated', async () => { const data = { nickname: 'new nickname' }; - const profile = profilesService.updateProfile(userUuid, image, data); + const profile = service.updateProfile(userUuid, image, data); await expect(profile).resolves.toEqual({ uuid: profileUuid, From 98d48452c611be8a0cc39aeb9bf6d45b6a5e1e40 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 21:11:54 +0900 Subject: [PATCH 06/22] =?UTF-8?q?refactor:=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EB=8F=99=EC=9E=91=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/profiles/profiles.controller.spec.ts | 30 +------------------ 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts index 1c016b37..de72aded 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts @@ -1,8 +1,4 @@ -import { - ForbiddenException, - HttpStatus, - NotFoundException, -} from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ProfilesController } from './profiles.controller'; import { ProfilesService } from './profiles.service'; @@ -55,16 +51,6 @@ describe('ProfilesController', () => { userUuidMock, ); }); - - it('not found profile', async () => { - jest - .spyOn(profilesService, 'findProfileByUserUuid') - .mockRejectedValue(new NotFoundException()); - - const response = controller.findProfileByUserUuid(userUuidMock); - - await expect(response).rejects.toThrow(NotFoundException); - }); }); describe('update', () => { @@ -104,19 +90,5 @@ describe('ProfilesController', () => { bodyMock, ); }); - - it('not found user', async () => { - jest - .spyOn(profilesService, 'updateProfile') - .mockRejectedValue(new ForbiddenException()); - - const response = controller.updateProfile( - imageMock, - userUuidMock, - bodyMock, - ); - - await expect(response).rejects.toThrow(ForbiddenException); - }); }); }); From 395213c9190a574cc1d2ec785b06c0ce7bd8a8d6 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 17 Feb 2025 21:16:48 +0900 Subject: [PATCH 07/22] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/profiles/profiles.controller.spec.ts | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts index de72aded..cb446e4c 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts @@ -10,15 +10,7 @@ describe('ProfilesController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [ProfilesController], - providers: [ - { - provide: ProfilesService, - useValue: { - findProfileByUserUuid: jest.fn(), - updateProfile: jest.fn(), - }, - }, - ], + providers: [{ provide: ProfilesService, useValue: {} }], }).compile(); controller = module.get(ProfilesController); @@ -36,9 +28,7 @@ describe('ProfilesController', () => { nickname: 'test nickname', }; - jest - .spyOn(profilesService, 'findProfileByUserUuid') - .mockResolvedValue(testProfile); + profilesService.findProfileByUserUuid = jest.fn(async () => testProfile); const response = controller.findProfileByUserUuid(userUuidMock); @@ -69,9 +59,7 @@ describe('ProfilesController', () => { nickname: 'test nickname', }; - jest - .spyOn(profilesService, 'updateProfile') - .mockResolvedValue(testProfile); + profilesService.updateProfile = jest.fn(async () => testProfile); const response = controller.updateProfile( imageMock, From 231b86143a54d4eb099154c344edce86a940c406 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 18 Feb 2025 21:21:31 +0900 Subject: [PATCH 08/22] =?UTF-8?q?test:=20JwtAuthGuard=20=EC=A0=81=EC=9A=A9?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 nestjs-BE/server/test/profiles.e2e-spec.ts diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts new file mode 100644 index 00000000..1ddc4ca7 --- /dev/null +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -0,0 +1,53 @@ +import { HttpStatus } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { AuthModule } from '../src/auth/auth.module'; +import { ProfilesModule } from '../src/profiles/profiles.module'; + +import type { INestApplication } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; + +describe('ProfilesController (e2e)', () => { + let app: INestApplication; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ProfilesModule, + AuthModule, + ConfigModule.forRoot({ isGlobal: true }), + ], + }).compile(); + + app = module.createNestApplication(); + + await app.init(); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('Checking if JwtAuthGuard is applied', () => { + it('/profiles (GET)', () => { + return request(app.getHttpServer()) + .get('/profiles') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + statusCode: HttpStatus.UNAUTHORIZED, + message: 'Unauthorized', + }); + }); + + it('/profiles (PATCH)', () => { + return request(app.getHttpServer()) + .patch('/profiles') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + statusCode: HttpStatus.UNAUTHORIZED, + message: 'Unauthorized', + }); + }); + }); +}); From 2535c5d839774350b923e82ddfd20f1e87d6275c Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Tue, 18 Feb 2025 21:37:52 +0900 Subject: [PATCH 09/22] =?UTF-8?q?test:=20MatchUserProfileGuard=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 34 +++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index 1ddc4ca7..c547561a 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -1,8 +1,11 @@ import { HttpStatus } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; +import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; +import { sign } from 'jsonwebtoken'; import * as request from 'supertest'; +import { v4 as uuid } from 'uuid'; import { AuthModule } from '../src/auth/auth.module'; +import { PrismaService } from '../src/prisma/prisma.service'; import { ProfilesModule } from '../src/profiles/profiles.module'; import type { INestApplication } from '@nestjs/common'; @@ -10,6 +13,8 @@ import type { TestingModule } from '@nestjs/testing'; describe('ProfilesController (e2e)', () => { let app: INestApplication; + let prisma: PrismaService; + let config: ConfigService; beforeAll(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -23,6 +28,9 @@ describe('ProfilesController (e2e)', () => { app = module.createNestApplication(); await app.init(); + + prisma = module.get(PrismaService); + config = module.get(ConfigService); }); afterAll(async () => { @@ -50,4 +58,28 @@ describe('ProfilesController (e2e)', () => { }); }); }); + + describe('Checking if MatchUserProfileGuard is applied', () => { + let testToken: string; + + beforeEach(async () => { + const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + testToken = sign( + { sub: testUser.uuid }, + config.get('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + }); + + it('/profiles (PATCH)', () => { + return request(app.getHttpServer()) + .patch('/profiles') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.BAD_REQUEST) + .expect({ + statusCode: HttpStatus.BAD_REQUEST, + message: 'Bad Request', + }); + }); + }); }); From fd533b15b44fa2de1d77be9f61643bf2a019b775 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 11:23:00 +0900 Subject: [PATCH 10/22] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index c547561a..0ad9c0f4 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -63,7 +63,7 @@ describe('ProfilesController (e2e)', () => { let testToken: string; beforeEach(async () => { - const testUser = await prisma.user.create({ data: { uuid: uuid() } }); + const testUser = await createUser(prisma); testToken = sign( { sub: testUser.uuid }, config.get('JWT_ACCESS_SECRET'), @@ -83,3 +83,7 @@ describe('ProfilesController (e2e)', () => { }); }); }); + +async function createUser(prisma: PrismaService) { + return prisma.user.create({ data: { uuid: uuid() } }); +} From e88a7cb8ca877ca973e1428e3a59a6281d56eea6 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 11:26:25 +0900 Subject: [PATCH 11/22] =?UTF-8?q?refactor:=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=ED=95=A8=EC=88=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index 0ad9c0f4..cad6c46f 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -64,11 +64,7 @@ describe('ProfilesController (e2e)', () => { beforeEach(async () => { const testUser = await createUser(prisma); - testToken = sign( - { sub: testUser.uuid }, - config.get('JWT_ACCESS_SECRET'), - { expiresIn: '5m' }, - ); + testToken = createToken(testUser.uuid, config); }); it('/profiles (PATCH)', () => { @@ -87,3 +83,9 @@ describe('ProfilesController (e2e)', () => { async function createUser(prisma: PrismaService) { return prisma.user.create({ data: { uuid: uuid() } }); } + +function createToken(userUuid: string, config: ConfigService) { + return sign({ sub: userUuid }, config.get('JWT_ACCESS_SECRET'), { + expiresIn: '5m', + }); +} From 90d98d4b9d9382714820816063400973d6431c8c Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 11:51:53 +0900 Subject: [PATCH 12/22] =?UTF-8?q?test:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index cad6c46f..8377c95d 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -10,6 +10,7 @@ import { ProfilesModule } from '../src/profiles/profiles.module'; import type { INestApplication } from '@nestjs/common'; import type { TestingModule } from '@nestjs/testing'; +import type { Profile } from '@prisma/client'; describe('ProfilesController (e2e)', () => { let app: INestApplication; @@ -78,6 +79,30 @@ describe('ProfilesController (e2e)', () => { }); }); }); + + describe('/profiles (GET)', () => { + const path = '/profiles'; + let testToken: string; + let testProfile: Profile; + + beforeEach(async () => { + const testUser = await createUser(prisma); + testToken = createToken(testUser.uuid, config); + testProfile = await createProfile(testUser.uuid, prisma); + }); + + it('success', () => { + return request(app.getHttpServer()) + .get(path) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect({ + statusCode: HttpStatus.OK, + message: 'Success', + data: testProfile, + }); + }); + }); }); async function createUser(prisma: PrismaService) { @@ -89,3 +114,14 @@ function createToken(userUuid: string, config: ConfigService) { expiresIn: '5m', }); } + +async function createProfile(userUuid: string, prisma: PrismaService) { + return prisma.profile.create({ + data: { + uuid: uuid(), + userUuid, + image: 'test image', + nickname: 'test nickname', + }, + }); +} From 71d45f25288d0ca504a86cebdb2431bfbb8b1018 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 18:39:11 +0900 Subject: [PATCH 13/22] =?UTF-8?q?chore:=20lodash=20=ED=8C=A8=ED=82=A4?= =?UTF-8?q?=EC=A7=80=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/package-lock.json | 8 ++++++++ nestjs-BE/server/package.json | 2 ++ 2 files changed, 10 insertions(+) diff --git a/nestjs-BE/server/package-lock.json b/nestjs-BE/server/package-lock.json index 65779b59..55ec2be7 100644 --- a/nestjs-BE/server/package-lock.json +++ b/nestjs-BE/server/package-lock.json @@ -25,6 +25,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", + "lodash": "^4.17.21", "mongoose": "^8.0.2", "passport": "^0.6.0", "passport-jwt": "^4.0.1", @@ -38,6 +39,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/lodash": "^4.17.15", "@types/multer": "^1.4.11", "@types/node": "^20.3.1", "@types/passport-jwt": "^3.0.13", @@ -3916,6 +3918,12 @@ "@types/node": "*" } }, + "node_modules/@types/lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==", + "dev": true + }, "node_modules/@types/luxon": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.3.6.tgz", diff --git a/nestjs-BE/server/package.json b/nestjs-BE/server/package.json index 1d751a11..30530bbf 100644 --- a/nestjs-BE/server/package.json +++ b/nestjs-BE/server/package.json @@ -36,6 +36,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "dotenv": "^16.3.1", + "lodash": "^4.17.21", "mongoose": "^8.0.2", "passport": "^0.6.0", "passport-jwt": "^4.0.1", @@ -49,6 +50,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", + "@types/lodash": "^4.17.15", "@types/multer": "^1.4.11", "@types/node": "^20.3.1", "@types/passport-jwt": "^3.0.13", From 76685a662eecd1f485848f0e969de760490f77d2 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 18:36:34 +0900 Subject: [PATCH 14/22] =?UTF-8?q?fix:=20=ED=8C=A8=EC=8A=A4=20=ED=8C=A8?= =?UTF-8?q?=EB=9F=AC=EB=AF=B8=ED=84=B0=EB=A1=9C=20=ED=94=84=EB=A1=9C?= =?UTF-8?q?=ED=95=84=20uuid=20=EB=B0=9B=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/profiles/dto/update-profile.dto.ts | 10 +++------- .../src/profiles/profiles.controller.spec.ts | 15 ++++++++------- .../server/src/profiles/profiles.controller.ts | 7 ++++--- .../server/src/profiles/profiles.service.spec.ts | 6 ++++-- nestjs-BE/server/src/profiles/profiles.service.ts | 15 +++++++++++---- 5 files changed, 30 insertions(+), 23 deletions(-) diff --git a/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts b/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts index 5ab2b2b4..0864d62b 100644 --- a/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts +++ b/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts @@ -1,24 +1,20 @@ -import { PartialType } from '@nestjs/mapped-types'; import { ApiProperty } from '@nestjs/swagger'; import { MaxLength } from 'class-validator'; -import { CreateProfileDto } from './create-profile.dto'; import { MAX_NAME_LENGTH } from '../../config/constants'; -export class UpdateProfileDto extends PartialType(CreateProfileDto) { +export class UpdateProfileDto { @MaxLength(MAX_NAME_LENGTH) @ApiProperty({ example: 'new nickname', description: 'Updated nickname of the profile', required: false, }) - nickname?: string; + nickname: string; @ApiProperty({ example: 'new image.png', description: 'Updated Profile image file', required: false, }) - image?: string; - - uuid?: string; + image: Express.Multer.File; } diff --git a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts index cb446e4c..329477c2 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.spec.ts @@ -3,6 +3,8 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ProfilesController } from './profiles.controller'; import { ProfilesService } from './profiles.service'; +import type { UpdateProfileDto } from './dto/update-profile.dto'; + describe('ProfilesController', () => { let controller: ProfilesController; let profilesService: ProfilesService; @@ -45,16 +47,15 @@ describe('ProfilesController', () => { describe('update', () => { const imageMock = {} as Express.Multer.File; - const userUuidMock = 'user uuid'; + const profileUuidMock = 'profile uuid'; const bodyMock = { - uuid: 'profile test uuid', nickname: 'test nickname', - }; + } as UpdateProfileDto; it('updated profile', async () => { const testProfile = { - uuid: 'profile test uuid', - userUuid: userUuidMock, + uuid: profileUuidMock, + userUuid: 'user uuid', image: 'www.test.com/image', nickname: 'test nickname', }; @@ -63,7 +64,7 @@ describe('ProfilesController', () => { const response = controller.updateProfile( imageMock, - userUuidMock, + profileUuidMock, bodyMock, ); @@ -73,7 +74,7 @@ describe('ProfilesController', () => { data: testProfile, }); expect(profilesService.updateProfile).toHaveBeenCalledWith( - userUuidMock, + profileUuidMock, imageMock, bodyMock, ); diff --git a/nestjs-BE/server/src/profiles/profiles.controller.ts b/nestjs-BE/server/src/profiles/profiles.controller.ts index cbe9c0d8..99ffac8a 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.ts @@ -8,6 +8,7 @@ import { ValidationPipe, HttpStatus, UseGuards, + Param, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; @@ -36,7 +37,7 @@ export class ProfilesController { return { statusCode: HttpStatus.OK, message: 'Success', data: profile }; } - @Patch() + @Patch(':profile_uuid') @UseGuards(MatchUserProfileGuard) @UseInterceptors(FileInterceptor('image')) @ApiOperation({ summary: 'Update profile' }) @@ -50,12 +51,12 @@ export class ProfilesController { }) async updateProfile( @UploadedFile() image: Express.Multer.File, - @User('uuid') userUuid: string, + @Param('profile_uuid') profileUuid: string, @Body(new ValidationPipe({ whitelist: true })) updateProfileDto: UpdateProfileDto, ) { const profile = await this.profilesService.updateProfile( - userUuid, + profileUuid, image, updateProfileDto, ); diff --git a/nestjs-BE/server/src/profiles/profiles.service.spec.ts b/nestjs-BE/server/src/profiles/profiles.service.spec.ts index 1647f160..da5dd474 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.spec.ts @@ -5,6 +5,8 @@ import { ProfilesService } from './profiles.service'; import { PrismaService } from '../prisma/prisma.service'; import { UploadService } from '../upload/upload.service'; +import type { UpdateProfileDto } from './dto/update-profile.dto'; + describe('ProfilesService', () => { let service: ProfilesService; let prisma: PrismaService; @@ -71,9 +73,9 @@ describe('ProfilesService', () => { }); it('updated', async () => { - const data = { nickname: 'new nickname' }; + const data = { nickname: 'new nickname' } as UpdateProfileDto; - const profile = service.updateProfile(userUuid, image, data); + const profile = service.updateProfile(profileUuid, image, data); await expect(profile).resolves.toEqual({ uuid: profileUuid, diff --git a/nestjs-BE/server/src/profiles/profiles.service.ts b/nestjs-BE/server/src/profiles/profiles.service.ts index 0e4ce238..c9c17234 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.ts @@ -1,11 +1,17 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { Profile, Prisma } from '@prisma/client'; +import { isUndefined, omitBy } from 'lodash'; import { v4 as uuid } from 'uuid'; import { UpdateProfileDto } from './dto/update-profile.dto'; import { CreateProfileDto } from './dto/create-profile.dto'; import { PrismaService } from '../prisma/prisma.service'; import { UploadService } from '../upload/upload.service'; +type UpdateData = { + nickname?: string; + image?: string; +}; + @Injectable() export class ProfilesService { constructor( @@ -39,17 +45,18 @@ export class ProfilesService { } async updateProfile( - userUuid: string, + profileUuid: string, image: Express.Multer.File, updateProfileDto: UpdateProfileDto, ): Promise { + const updateData: UpdateData = { nickname: updateProfileDto.nickname }; if (image) { - updateProfileDto.image = await this.uploadService.uploadFile(image); + updateData.image = await this.uploadService.uploadFile(image); } try { return await this.prisma.profile.update({ - where: { userUuid }, - data: updateProfileDto, + where: { uuid: profileUuid }, + data: omitBy(updateData, isUndefined), }); } catch (err) { if (err instanceof Prisma.PrismaClientKnownRequestError) { From a9fde790ef71d7f99094de7bf7648a98398aa196 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 18:47:14 +0900 Subject: [PATCH 15/22] =?UTF-8?q?test:=20=EB=8F=99=EC=9E=91=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=9D=BC=EC=84=9C=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index 8377c95d..cc99d8fa 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -49,9 +49,12 @@ describe('ProfilesController (e2e)', () => { }); }); - it('/profiles (PATCH)', () => { + it('/profiles (PATCH)', async () => { + const testUser = await createUser(prisma); + const testProfile = await createProfile(testUser.uuid, prisma); + return request(app.getHttpServer()) - .patch('/profiles') + .patch(`/profiles/${testProfile.uuid}`) .expect(HttpStatus.UNAUTHORIZED) .expect({ statusCode: HttpStatus.UNAUTHORIZED, @@ -66,16 +69,17 @@ describe('ProfilesController (e2e)', () => { beforeEach(async () => { const testUser = await createUser(prisma); testToken = createToken(testUser.uuid, config); + await createProfile(testUser.uuid, prisma); }); - it('/profiles (PATCH)', () => { + it('/profiles (PATCH)', async () => { return request(app.getHttpServer()) - .patch('/profiles') + .patch(`/profiles/${uuid()}`) .auth(testToken, { type: 'bearer' }) - .expect(HttpStatus.BAD_REQUEST) + .expect(HttpStatus.FORBIDDEN) .expect({ - statusCode: HttpStatus.BAD_REQUEST, - message: 'Bad Request', + statusCode: HttpStatus.FORBIDDEN, + message: 'Forbidden', }); }); }); From 2ad1922262cf352466606b762b4bb26ea2d7732a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 21:01:43 +0900 Subject: [PATCH 16/22] =?UTF-8?q?fix:=20userUuid=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=ED=95=98=EA=B3=A0=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/profiles.service.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.service.ts b/nestjs-BE/server/src/profiles/profiles.service.ts index c9c17234..90e5585a 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.ts @@ -1,6 +1,6 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { Profile, Prisma } from '@prisma/client'; -import { isUndefined, omitBy } from 'lodash'; +import { isUndefined, omit, omitBy } from 'lodash'; import { v4 as uuid } from 'uuid'; import { UpdateProfileDto } from './dto/update-profile.dto'; import { CreateProfileDto } from './dto/create-profile.dto'; @@ -19,12 +19,14 @@ export class ProfilesService { private uploadService: UploadService, ) {} - async findProfileByUserUuid(userUuid: string): Promise { + async findProfileByUserUuid( + userUuid: string, + ): Promise | null> { const profile = await this.prisma.profile.findUnique({ where: { userUuid }, }); if (!profile) throw new NotFoundException(); - return profile; + return omit(profile, ['userUuid']); } async findProfileByProfileUuid(uuid: string): Promise { @@ -48,16 +50,17 @@ export class ProfilesService { profileUuid: string, image: Express.Multer.File, updateProfileDto: UpdateProfileDto, - ): Promise { + ): Promise | null> { const updateData: UpdateData = { nickname: updateProfileDto.nickname }; if (image) { updateData.image = await this.uploadService.uploadFile(image); } try { - return await this.prisma.profile.update({ + const profile = await this.prisma.profile.update({ where: { uuid: profileUuid }, data: omitBy(updateData, isUndefined), }); + return omit(profile, ['userUuid']); } catch (err) { if (err instanceof Prisma.PrismaClientKnownRequestError) { return null; From 2b5f10a263087da21d5607c442f97a1abe275968 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 21:02:27 +0900 Subject: [PATCH 17/22] =?UTF-8?q?test:=20/profiles/:profile=5Fuuid=20PATCH?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/profiles.e2e-spec.ts | 98 +++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/test/profiles.e2e-spec.ts b/nestjs-BE/server/test/profiles.e2e-spec.ts index cc99d8fa..c4f04210 100644 --- a/nestjs-BE/server/test/profiles.e2e-spec.ts +++ b/nestjs-BE/server/test/profiles.e2e-spec.ts @@ -1,7 +1,10 @@ import { HttpStatus } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { Test } from '@nestjs/testing'; +import { readFile } from 'fs/promises'; import { sign } from 'jsonwebtoken'; +import { omit } from 'lodash'; +import { resolve } from 'path'; import * as request from 'supertest'; import { v4 as uuid } from 'uuid'; import { AuthModule } from '../src/auth/auth.module'; @@ -103,7 +106,100 @@ describe('ProfilesController (e2e)', () => { .expect({ statusCode: HttpStatus.OK, message: 'Success', - data: testProfile, + data: omit(testProfile, ['userUuid']), + }); + }); + }); + + describe('/profiles (PATCH)', () => { + let testToken: string; + let testProfile: Profile; + let path: string; + + beforeEach(async () => { + const testUser = await createUser(prisma); + testToken = createToken(testUser.uuid, config); + testProfile = await createProfile(testUser.uuid, prisma); + path = `/profiles/${testProfile.uuid}`; + }); + + it('success without update', () => { + return request(app.getHttpServer()) + .patch(path) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect({ + statusCode: HttpStatus.OK, + message: 'Success', + data: omit(testProfile, ['userUuid']), + }); + }); + + it('success nickname update', () => { + const newNickname = 'new nickname'; + + return request(app.getHttpServer()) + .patch(path) + .auth(testToken, { type: 'bearer' }) + .send({ nickname: newNickname }) + .expect(HttpStatus.OK) + .expect({ + statusCode: HttpStatus.OK, + message: 'Success', + data: { + ...omit(testProfile, ['userUuid']), + nickname: newNickname, + }, + }); + }); + + it('success image update', async () => { + const imageUrlPattern = `^https\\:\\/\\/${config.get( + 'S3_BUCKET_NAME', + )}\\.s3\\.${config.get( + 'AWS_REGION', + )}\\.amazonaws\\.com\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-`; + const imageRegExp = new RegExp(imageUrlPattern); + const testImage = await readFile(resolve(__dirname, './base_image.png')); + + return request(app.getHttpServer()) + .patch(path) + .auth(testToken, { type: 'bearer' }) + .attach('image', testImage, 'base_image.png') + .expect(HttpStatus.OK) + .expect((res) => { + expect(res.body.message).toBe('Success'); + expect(res.body.statusCode).toBe(HttpStatus.OK); + expect(res.body.data.uuid).toBe(testProfile.uuid); + expect(res.body.data.userUuid).toBeUndefined(); + expect(res.body.data.image).toMatch(imageRegExp); + expect(res.body.data.nickname).toBe(testProfile.nickname); + }); + }); + + it('success update all', async () => { + const newNickname = 'new nickname'; + const imageUrlPattern = `^https\\:\\/\\/${config.get( + 'S3_BUCKET_NAME', + )}\\.s3\\.${config.get( + 'AWS_REGION', + )}\\.amazonaws\\.com\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-`; + const imageRegExp = new RegExp(imageUrlPattern); + const testImage = await readFile(resolve(__dirname, './base_image.png')); + + return request(app.getHttpServer()) + .patch(path) + .auth(testToken, { type: 'bearer' }) + .attach('image', testImage, 'base_image.png') + .field('nickname', newNickname) + .expect(HttpStatus.OK) + .expect((res) => { + expect(res.body.message).toBe('Success'); + expect(res.body.statusCode).toBe(HttpStatus.OK); + expect(res.body.data.uuid).toBe(testProfile.uuid); + expect(res.body.data.userUuid).toBeUndefined(); + expect(res.body.data.image).toMatch(imageRegExp); + expect(res.body.data.nickname).toBe(newNickname); }); }); }); From 3c1cb648525fa1a10ba188546c08fc82bd0acbb2 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 21:02:55 +0900 Subject: [PATCH 18/22] =?UTF-8?q?fix:=20IsOptional=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/dto/update-profile.dto.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts b/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts index 0864d62b..2e6e908a 100644 --- a/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts +++ b/nestjs-BE/server/src/profiles/dto/update-profile.dto.ts @@ -1,8 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { MaxLength } from 'class-validator'; +import { IsOptional, MaxLength } from 'class-validator'; import { MAX_NAME_LENGTH } from '../../config/constants'; export class UpdateProfileDto { + @IsOptional() @MaxLength(MAX_NAME_LENGTH) @ApiProperty({ example: 'new nickname', From 7bb1d24d50da1e72d1449e16111a836021f6af6a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Wed, 19 Feb 2025 21:09:57 +0900 Subject: [PATCH 19/22] =?UTF-8?q?docs:=20swagger=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/profiles.controller.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/nestjs-BE/server/src/profiles/profiles.controller.ts b/nestjs-BE/server/src/profiles/profiles.controller.ts index 99ffac8a..6d4f7197 100644 --- a/nestjs-BE/server/src/profiles/profiles.controller.ts +++ b/nestjs-BE/server/src/profiles/profiles.controller.ts @@ -49,6 +49,10 @@ export class ProfilesController { status: HttpStatus.UNAUTHORIZED, description: 'Unauthorized.', }) + @ApiResponse({ + status: HttpStatus.FORBIDDEN, + description: 'Inappropriate profile uuid requested.', + }) async updateProfile( @UploadedFile() image: Express.Multer.File, @Param('profile_uuid') profileUuid: string, From 19673bca9b3500f03527f12a90571eac98e8bef2 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 20 Feb 2025 12:08:46 +0900 Subject: [PATCH 20/22] =?UTF-8?q?test:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=EA=B0=92=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/profiles/profiles.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/src/profiles/profiles.service.spec.ts b/nestjs-BE/server/src/profiles/profiles.service.spec.ts index da5dd474..145e527d 100644 --- a/nestjs-BE/server/src/profiles/profiles.service.spec.ts +++ b/nestjs-BE/server/src/profiles/profiles.service.spec.ts @@ -1,6 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { NotFoundException } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { omit } from 'lodash'; import { ProfilesService } from './profiles.service'; import { PrismaService } from '../prisma/prisma.service'; import { UploadService } from '../upload/upload.service'; @@ -40,7 +41,7 @@ describe('ProfilesService', () => { it('found', async () => { const res = service.findProfileByUserUuid(userUuid); - await expect(res).resolves.toEqual(profile); + await expect(res).resolves.toEqual(omit(profile, ['userUuid'])); }); it('not found', async () => { @@ -79,7 +80,6 @@ describe('ProfilesService', () => { await expect(profile).resolves.toEqual({ uuid: profileUuid, - userUuid, image: imageUrl, nickname: data.nickname, }); From 132438f662aca9dbf21ea922cad9a1d67efca1e8 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 20 Feb 2025 12:51:30 +0900 Subject: [PATCH 21/22] =?UTF-8?q?test:=20MatchUserProfileGuard=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../guards/match-user-profile.guard.spec.ts | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 nestjs-BE/server/src/profiles/guards/match-user-profile.guard.spec.ts diff --git a/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.spec.ts b/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.spec.ts new file mode 100644 index 00000000..7a437869 --- /dev/null +++ b/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.spec.ts @@ -0,0 +1,116 @@ +import { BadRequestException, ForbiddenException } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import { MatchUserProfileGuard } from './match-user-profile.guard'; +import { ProfilesService } from '../profiles.service'; + +import type { ExecutionContext } from '@nestjs/common'; +import type { TestingModule } from '@nestjs/testing'; +import type { Profile } from '@prisma/client'; + +describe('MatchUserProfileGuard', () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + let guard: MatchUserProfileGuard; + let profilesService: ProfilesService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [{ provide: ProfilesService, useValue: {} }], + }).compile(); + + profilesService = module.get(ProfilesService); + + guard = new MatchUserProfileGuard(profilesService); + }); + + it('throw bad request when profile uuid not include', async () => { + const context = createExecutionContext({ user: { uuid: userUuid } }); + + await expect(guard.canActivate(context)).rejects.toThrow( + BadRequestException, + ); + }); + + it('throw bad request when user uuid not include (body)', async () => { + const context = createExecutionContext({ + body: { profile_uuid: profileUuid }, + }); + + await expect(guard.canActivate(context)).rejects.toThrow( + BadRequestException, + ); + }); + + it('throw bad request when user uuid not include (query)', async () => { + const context = createExecutionContext({ + query: { profile_uuid: profileUuid }, + }); + + await expect(guard.canActivate(context)).rejects.toThrow( + BadRequestException, + ); + }); + + it('throw bad request when user uuid not include (params)', async () => { + const context = createExecutionContext({ + params: { profile_uuid: profileUuid }, + }); + + await expect(guard.canActivate(context)).rejects.toThrow( + BadRequestException, + ); + }); + + it('throw forbidden when profile not found', async () => { + const context = createExecutionContext({ + user: { uuid: userUuid }, + params: { profile_uuid: profileUuid }, + }); + profilesService.findProfileByProfileUuid = jest.fn(async () => null); + + await expect(guard.canActivate(context)).rejects.toThrow( + ForbiddenException, + ); + }); + + it("throw forbidden when profile not user's profile", async () => { + const context = createExecutionContext({ + user: { uuid: userUuid }, + params: { profile_uuid: profileUuid }, + }); + profilesService.findProfileByProfileUuid = jest.fn( + async () => ({ userUuid: 'other user uuid' }) as Profile, + ); + + await expect(guard.canActivate(context)).rejects.toThrow( + ForbiddenException, + ); + }); + + it('return true when valid user', async () => { + const context = createExecutionContext({ + user: { uuid: userUuid }, + params: { profile_uuid: profileUuid }, + }); + profilesService.findProfileByProfileUuid = jest.fn( + async () => ({ userUuid }) as Profile, + ); + + await expect(guard.canActivate(context)).resolves.toBeTruthy(); + }); +}); + +function createExecutionContext(request: object): ExecutionContext { + const innerRequest = { + body: {}, + params: {}, + query: {}, + ...request, + }; + const context: ExecutionContext = { + switchToHttp: () => ({ + getRequest: () => innerRequest, + }), + } as ExecutionContext; + return context; +} From be877915bbe06dda9d7f961a6aa07b80b0451d5a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Thu, 20 Feb 2025 12:52:39 +0900 Subject: [PATCH 22/22] =?UTF-8?q?fix:=20=3F.=20=EC=98=B5=EC=85=94=EB=84=90?= =?UTF-8?q?=20=EC=B2=B4=EC=9D=B4=EB=8B=9D=20=EC=82=AC=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit request.user는 JwtAuthGuard에서 추가하는 것이므로 request.user가 존재하지 않는 경우 처리 --- .../server/src/profiles/guards/match-user-profile.guard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts b/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts index ff9f01f9..0a2471f2 100644 --- a/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts +++ b/nestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts @@ -13,7 +13,7 @@ export class MatchUserProfileGuard implements CanActivate { async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); - const userUuid = request.user.uuid; + const userUuid = request.user?.uuid; const profileUuid = request.body.profile_uuid || request.query.profile_uuid ||