Skip to content

Commit f7c1580

Browse files
authored
Merge pull request #381 from boostcampwm2023/BE-feature/profiles-module
ํ”„๋กœํ•„ ๋ชจ๋“ˆ ๊ฐœ์„ 
2 parents 4ee04fa + be87791 commit f7c1580

19 files changed

+434
-202
lines changed

โ€Žnestjs-BE/server/package-lock.json

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

โ€Žnestjs-BE/server/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"class-transformer": "^0.5.1",
3737
"class-validator": "^0.14.0",
3838
"dotenv": "^16.3.1",
39+
"lodash": "^4.17.21",
3940
"mongoose": "^8.0.2",
4041
"passport": "^0.6.0",
4142
"passport-jwt": "^4.0.1",
@@ -49,6 +50,7 @@
4950
"@nestjs/testing": "^10.0.0",
5051
"@types/express": "^4.17.17",
5152
"@types/jest": "^29.5.2",
53+
"@types/lodash": "^4.17.15",
5254
"@types/multer": "^1.4.11",
5355
"@types/node": "^20.3.1",
5456
"@types/passport-jwt": "^3.0.13",

โ€Žnestjs-BE/server/src/auth/auth.module.ts

-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { AuthController } from './auth.controller';
66
import { AuthService } from './auth.service';
77
import { JwtStrategy } from './strategies/jwt.strategy';
88
import { JwtAuthGuard } from './guards/jwt-auth.guard';
9-
import { MatchUserProfileGuard } from './guards/match-user-profile.guard';
109
import { IsProfileInSpaceGuard } from './guards/is-profile-in-space.guard';
1110
import { UsersModule } from '../users/users.module';
1211
import { ProfilesModule } from '../profiles/profiles.module';
@@ -27,12 +26,10 @@ import { ProfileSpaceModule } from '../profile-space/profile-space.module';
2726
AuthService,
2827
JwtStrategy,
2928
{ provide: APP_GUARD, useClass: JwtAuthGuard },
30-
MatchUserProfileGuard,
3129
IsProfileInSpaceGuard,
3230
],
3331
exports: [
3432
AuthService,
35-
MatchUserProfileGuard,
3633
ProfilesModule,
3734
IsProfileInSpaceGuard,
3835
ProfileSpaceModule,

โ€Žnestjs-BE/server/src/invite-codes/invite-codes.controller.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { Test, TestingModule } from '@nestjs/testing';
88
import { InviteCode, Space } from '@prisma/client';
99
import { InviteCodesController } from './invite-codes.controller';
1010
import { InviteCodesService } from './invite-codes.service';
11-
import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard';
1211
import { ProfilesService } from '../profiles/profiles.service';
12+
import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard';
1313
import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard';
1414
import { ProfileSpaceService } from '../profile-space/profile-space.service';
1515

โ€Žnestjs-BE/server/src/invite-codes/invite-codes.controller.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
1010
import { InviteCodesService } from './invite-codes.service';
1111
import { CreateInviteCodeDto } from './dto/create-invite-code.dto';
12-
import { MatchUserProfileGuard } from '../auth/guards/match-user-profile.guard';
1312
import { IsProfileInSpaceGuard } from '../auth/guards/is-profile-in-space.guard';
13+
import { MatchUserProfileGuard } from '../profiles/guards/match-user-profile.guard';
1414

1515
@Controller('inviteCodes')
1616
@ApiTags('inviteCodes')

โ€Žnestjs-BE/server/src/invite-codes/invite-codes.module.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { InviteCodesService } from './invite-codes.service';
33
import { InviteCodesController } from './invite-codes.controller';
44
import { SpacesModule } from '../spaces/spaces.module';
55
import { AuthModule } from '../auth/auth.module';
6+
import { ProfilesModule } from '../profiles/profiles.module';
67

78
@Module({
8-
imports: [AuthModule, SpacesModule],
9+
imports: [AuthModule, SpacesModule, ProfilesModule],
910
controllers: [InviteCodesController],
1011
providers: [InviteCodesService],
1112
})
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
1-
import { PartialType } from '@nestjs/mapped-types';
21
import { ApiProperty } from '@nestjs/swagger';
3-
import { MaxLength } from 'class-validator';
4-
import { CreateProfileDto } from './create-profile.dto';
2+
import { IsOptional, MaxLength } from 'class-validator';
53
import { MAX_NAME_LENGTH } from '../../config/constants';
64

7-
export class UpdateProfileDto extends PartialType(CreateProfileDto) {
5+
export class UpdateProfileDto {
6+
@IsOptional()
87
@MaxLength(MAX_NAME_LENGTH)
98
@ApiProperty({
109
example: 'new nickname',
1110
description: 'Updated nickname of the profile',
1211
required: false,
1312
})
14-
nickname?: string;
13+
nickname: string;
1514

1615
@ApiProperty({
1716
example: 'new image.png',
1817
description: 'Updated Profile image file',
1918
required: false,
2019
})
21-
image?: string;
22-
23-
uuid?: string;
20+
image: Express.Multer.File;
2421
}

โ€Žnestjs-BE/server/src/profiles/entities/profile.entity.ts

-1
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { BadRequestException, ForbiddenException } from '@nestjs/common';
2+
import { Test } from '@nestjs/testing';
3+
import { MatchUserProfileGuard } from './match-user-profile.guard';
4+
import { ProfilesService } from '../profiles.service';
5+
6+
import type { ExecutionContext } from '@nestjs/common';
7+
import type { TestingModule } from '@nestjs/testing';
8+
import type { Profile } from '@prisma/client';
9+
10+
describe('MatchUserProfileGuard', () => {
11+
const userUuid = 'user uuid';
12+
const profileUuid = 'profile uuid';
13+
let guard: MatchUserProfileGuard;
14+
let profilesService: ProfilesService;
15+
16+
beforeAll(async () => {
17+
const module: TestingModule = await Test.createTestingModule({
18+
providers: [{ provide: ProfilesService, useValue: {} }],
19+
}).compile();
20+
21+
profilesService = module.get<ProfilesService>(ProfilesService);
22+
23+
guard = new MatchUserProfileGuard(profilesService);
24+
});
25+
26+
it('throw bad request when profile uuid not include', async () => {
27+
const context = createExecutionContext({ user: { uuid: userUuid } });
28+
29+
await expect(guard.canActivate(context)).rejects.toThrow(
30+
BadRequestException,
31+
);
32+
});
33+
34+
it('throw bad request when user uuid not include (body)', async () => {
35+
const context = createExecutionContext({
36+
body: { profile_uuid: profileUuid },
37+
});
38+
39+
await expect(guard.canActivate(context)).rejects.toThrow(
40+
BadRequestException,
41+
);
42+
});
43+
44+
it('throw bad request when user uuid not include (query)', async () => {
45+
const context = createExecutionContext({
46+
query: { profile_uuid: profileUuid },
47+
});
48+
49+
await expect(guard.canActivate(context)).rejects.toThrow(
50+
BadRequestException,
51+
);
52+
});
53+
54+
it('throw bad request when user uuid not include (params)', async () => {
55+
const context = createExecutionContext({
56+
params: { profile_uuid: profileUuid },
57+
});
58+
59+
await expect(guard.canActivate(context)).rejects.toThrow(
60+
BadRequestException,
61+
);
62+
});
63+
64+
it('throw forbidden when profile not found', async () => {
65+
const context = createExecutionContext({
66+
user: { uuid: userUuid },
67+
params: { profile_uuid: profileUuid },
68+
});
69+
profilesService.findProfileByProfileUuid = jest.fn(async () => null);
70+
71+
await expect(guard.canActivate(context)).rejects.toThrow(
72+
ForbiddenException,
73+
);
74+
});
75+
76+
it("throw forbidden when profile not user's profile", async () => {
77+
const context = createExecutionContext({
78+
user: { uuid: userUuid },
79+
params: { profile_uuid: profileUuid },
80+
});
81+
profilesService.findProfileByProfileUuid = jest.fn(
82+
async () => ({ userUuid: 'other user uuid' }) as Profile,
83+
);
84+
85+
await expect(guard.canActivate(context)).rejects.toThrow(
86+
ForbiddenException,
87+
);
88+
});
89+
90+
it('return true when valid user', async () => {
91+
const context = createExecutionContext({
92+
user: { uuid: userUuid },
93+
params: { profile_uuid: profileUuid },
94+
});
95+
profilesService.findProfileByProfileUuid = jest.fn(
96+
async () => ({ userUuid }) as Profile,
97+
);
98+
99+
await expect(guard.canActivate(context)).resolves.toBeTruthy();
100+
});
101+
});
102+
103+
function createExecutionContext(request: object): ExecutionContext {
104+
const innerRequest = {
105+
body: {},
106+
params: {},
107+
query: {},
108+
...request,
109+
};
110+
const context: ExecutionContext = {
111+
switchToHttp: () => ({
112+
getRequest: () => innerRequest,
113+
}),
114+
} as ExecutionContext;
115+
return context;
116+
}

โ€Žnestjs-BE/server/src/auth/guards/match-user-profile.guard.ts โ€Žnestjs-BE/server/src/profiles/guards/match-user-profile.guard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class MatchUserProfileGuard implements CanActivate {
1313

1414
async canActivate(context: ExecutionContext): Promise<boolean> {
1515
const request = context.switchToHttp().getRequest();
16-
const userUuid = request.user.uuid;
16+
const userUuid = request.user?.uuid;
1717
const profileUuid =
1818
request.body.profile_uuid ||
1919
request.query.profile_uuid ||
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,18 @@
1-
import {
2-
ForbiddenException,
3-
HttpStatus,
4-
NotFoundException,
5-
} from '@nestjs/common';
1+
import { HttpStatus } from '@nestjs/common';
62
import { Test, TestingModule } from '@nestjs/testing';
73
import { ProfilesController } from './profiles.controller';
84
import { ProfilesService } from './profiles.service';
95

6+
import type { UpdateProfileDto } from './dto/update-profile.dto';
7+
108
describe('ProfilesController', () => {
119
let controller: ProfilesController;
1210
let profilesService: ProfilesService;
1311

1412
beforeEach(async () => {
1513
const module: TestingModule = await Test.createTestingModule({
1614
controllers: [ProfilesController],
17-
providers: [
18-
{
19-
provide: ProfilesService,
20-
useValue: {
21-
findProfileByUserUuid: jest.fn(),
22-
updateProfile: jest.fn(),
23-
},
24-
},
25-
],
15+
providers: [{ provide: ProfilesService, useValue: {} }],
2616
}).compile();
2717

2818
controller = module.get<ProfilesController>(ProfilesController);
@@ -40,9 +30,7 @@ describe('ProfilesController', () => {
4030
nickname: 'test nickname',
4131
};
4232

43-
jest
44-
.spyOn(profilesService, 'findProfileByUserUuid')
45-
.mockResolvedValue(testProfile);
33+
profilesService.findProfileByUserUuid = jest.fn(async () => testProfile);
4634

4735
const response = controller.findProfileByUserUuid(userUuidMock);
4836

@@ -55,41 +43,28 @@ describe('ProfilesController', () => {
5543
userUuidMock,
5644
);
5745
});
58-
59-
it('not found profile', async () => {
60-
jest
61-
.spyOn(profilesService, 'findProfileByUserUuid')
62-
.mockRejectedValue(new NotFoundException());
63-
64-
const response = controller.findProfileByUserUuid(userUuidMock);
65-
66-
await expect(response).rejects.toThrow(NotFoundException);
67-
});
6846
});
6947

7048
describe('update', () => {
7149
const imageMock = {} as Express.Multer.File;
72-
const userUuidMock = 'user uuid';
50+
const profileUuidMock = 'profile uuid';
7351
const bodyMock = {
74-
uuid: 'profile test uuid',
7552
nickname: 'test nickname',
76-
};
53+
} as UpdateProfileDto;
7754

7855
it('updated profile', async () => {
7956
const testProfile = {
80-
uuid: 'profile test uuid',
81-
userUuid: userUuidMock,
57+
uuid: profileUuidMock,
58+
userUuid: 'user uuid',
8259
image: 'www.test.com/image',
8360
nickname: 'test nickname',
8461
};
8562

86-
jest
87-
.spyOn(profilesService, 'updateProfile')
88-
.mockResolvedValue(testProfile);
63+
profilesService.updateProfile = jest.fn(async () => testProfile);
8964

9065
const response = controller.updateProfile(
9166
imageMock,
92-
userUuidMock,
67+
profileUuidMock,
9368
bodyMock,
9469
);
9570

@@ -99,25 +74,10 @@ describe('ProfilesController', () => {
9974
data: testProfile,
10075
});
10176
expect(profilesService.updateProfile).toHaveBeenCalledWith(
102-
userUuidMock,
103-
bodyMock.uuid,
104-
imageMock,
105-
bodyMock,
106-
);
107-
});
108-
109-
it('not found user', async () => {
110-
jest
111-
.spyOn(profilesService, 'updateProfile')
112-
.mockRejectedValue(new ForbiddenException());
113-
114-
const response = controller.updateProfile(
77+
profileUuidMock,
11578
imageMock,
116-
userUuidMock,
11779
bodyMock,
11880
);
119-
120-
await expect(response).rejects.toThrow(ForbiddenException);
12181
});
12282
});
12383
});

0 commit comments

Comments
ย (0)