Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NDD-337]: 면접용 닉네임 반환 API Controller 테스트 (1h / 1h) #159

Merged
merged 3 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 156 additions & 40 deletions BE/src/member/controller/member.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { Test, TestingModule } from '@nestjs/testing';
import { MemberController } from './member.controller';
import { MemberResponse } from '../dto/memberResponse';
import { Request } from 'express';
import { ManipulatedTokenNotFiltered } from 'src/token/exception/token.exception';
import {
InvalidTokenException,
ManipulatedTokenNotFiltered,
TokenExpiredException,
} from 'src/token/exception/token.exception';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AuthModule } from 'src/auth/auth.module';
Expand All @@ -18,13 +22,22 @@ import { TokenModule } from '../../token/token.module';
import { MemberModule } from '../member.module';
import { Member } from '../entity/member';
import { Token } from '../../token/entity/token';
import { createIntegrationTestModule } from '../../util/test.util';
import {
addAppModules,
createIntegrationTestModule,
} from '../../util/test.util';
import { Category } from '../../category/entity/category';
import { MemberNicknameResponse } from '../dto/memberNicknameResponse';
import { MemberRepository } from '../repository/member.repository';
import { JwtService } from '@nestjs/jwt';
import { TokenPayload } from 'src/token/interface/token.interface';

describe('MemberController', () => {
describe('MemberController 단위 테스트', () => {
let memberController: MemberController;

const mockMemberService = {};
const mockMemberService = {
getNameForInterview: jest.fn(),
};
const mockTokenService = {};

beforeEach(async () => {
Expand All @@ -44,31 +57,85 @@ describe('MemberController', () => {
expect(memberController).toBeDefined();
});

it('should return member information as MemberResponse type', async () => {
const result = memberController.getMyInfo(
mockReqWithMemberFixture as unknown as Request,
);
describe('getMyInfo', () => {
it('should return member information as MemberResponse type', async () => {
const result = memberController.getMyInfo(
mockReqWithMemberFixture as unknown as Request,
);

expect(result).toBeInstanceOf(MemberResponse);
expect(result).toEqual(MemberResponse.from(memberFixture));
expect(result['id']).toBe(1);
expect(result['email']).toBe('test@example.com');
expect(result['nickname']).toBe('TestUser');
expect(result['profileImg']).toBe('https://example.com');
});

expect(result).toBeInstanceOf(MemberResponse);
expect(result).toEqual(MemberResponse.from(memberFixture));
expect(result['id']).toBe(1);
expect(result['email']).toBe('test@example.com');
expect(result['nickname']).toBe('TestUser');
expect(result['profileImg']).toBe('https://example.com');
it('should handle invalid user in the request', async () => {
const mockUser = undefined;
const mockReq = { user: mockUser };
expect(() =>
memberController.getMyInfo(mockReq as unknown as Request),
).toThrow(ManipulatedTokenNotFiltered);
});
});

it('should handle invalid user in the request', async () => {
const mockUser = undefined;
const mockReq = { user: mockUser };
expect(() =>
memberController.getMyInfo(mockReq as unknown as Request),
).toThrow(ManipulatedTokenNotFiltered);
describe('getNameForInterview', () => {
const nickname = 'fakeNickname';

it('면접 화면에 표출할 닉네임 반환 성공 시에는 MemberNicknameResponse 형태로 반환한다.', async () => {
// given
const mockReq = mockReqWithMemberFixture;

// when
mockMemberService.getNameForInterview.mockResolvedValue(
new MemberNicknameResponse(nickname),
);

// then
const result = await memberController.getNameForInterview(mockReq);

expect(result).toBeInstanceOf(MemberNicknameResponse);
expect(result.nickname).toBe(nickname);
});

it('면접 화면에 표출할 닉네임 반환 시 토큰이 만료되었으면 TokenExpiredException을 반환한다.', async () => {
// given
const mockReq = mockReqWithMemberFixture;

// when
mockMemberService.getNameForInterview.mockRejectedValue(
new TokenExpiredException(),
);

// then
expect(memberController.getNameForInterview(mockReq)).rejects.toThrow(
TokenExpiredException,
);
});

it('면접 화면에 표출할 닉네임 반환 시 토큰이 유효하지 않으면 InvalidTokenException을 반환한다.', async () => {
// given
const mockReq = mockReqWithMemberFixture;

// when
mockMemberService.getNameForInterview.mockRejectedValue(
new InvalidTokenException(),
);

// then
expect(memberController.getNameForInterview(mockReq)).rejects.toThrow(
InvalidTokenException,
);
});
});
});

describe('MemberController (E2E Test)', () => {
describe('MemberController 통합 테스트', () => {
let app: INestApplication;
let authService: AuthService;
let jwtService: JwtService;
let memberRepository: MemberRepository;

beforeAll(async () => {
const modules = [AuthModule, TokenModule, MemberModule];
Expand All @@ -80,37 +147,86 @@ describe('MemberController (E2E Test)', () => {
);

app = moduleFixture.createNestApplication();
addAppModules(app);
await app.init();

authService = moduleFixture.get<AuthService>(AuthService);
jwtService = moduleFixture.get<JwtService>(JwtService);
memberRepository = moduleFixture.get<MemberRepository>(MemberRepository);
});

it('GET /api/member (회원 정보 반환 성공)', (done) => {
authService.login(oauthRequestFixture).then((validToken) => {
describe('getMyInfo', () => {
it('쿠키를 가지고 회원 정보 반환 요청을 하면 200 상태 코드와 회원 정보가 반환된다.)', (done) => {
authService.login(oauthRequestFixture).then((validToken) => {
const agent = request.agent(app.getHttpServer());
agent
.get('/api/member')
.set('Cookie', [`accessToken=${validToken}`])
.expect(200)
.then((response) => {
expect(response.body.email).toBe(oauthRequestFixture.email);
expect(response.body.nickname).toBe(oauthRequestFixture.name);
expect(response.body.profileImg).toBe(oauthRequestFixture.img);
done();
});
});
});

it('유효하지 않은 토큰으로 회원 정보 반환을 요청하면 401 상태코드가 반환된다.)', () => {
const agent = request.agent(app.getHttpServer());
agent
.get('/api/member')
.set('Cookie', [`accessToken=${validToken}`])
.expect(200)
.then((response) => {
expect(response.body.email).toBe(oauthRequestFixture.email);
expect(response.body.nickname).toBe(oauthRequestFixture.name);
expect(response.body.profileImg).toBe(oauthRequestFixture.img);
done();
});
.set('Cookie', [`accessToken=Bearer INVALID_TOKEN`])
.expect(401);
});
});

it('GET /api/member (유효하지 않은 토큰 사용으로 인한 회원 정보 반환 실패)', (done) => {
const agent = request.agent(app.getHttpServer());
agent
.get('/api/member')
.set('Cookie', [`accessToken=Bearer INVALID_TOKEN`])
.expect(401)
.then(() => done());
describe('getNameForInterview', () => {
it('쿠키를 가지고 면접 화면에 표출할 닉네임 반환 요청을 하면 200 상태 코드와 회원 닉네임이 들어간 채로 반환된다.)', (done) => {
authService.login(oauthRequestFixture).then((validToken) => {
const agent = request.agent(app.getHttpServer());
agent
.get('/api/member/name')
.set('Cookie', [`accessToken=${validToken}`])
.expect(200)
.then((response) => {
expect(
response.body.nickname.endsWith(oauthRequestFixture.name),
).toBeTruthy();
done();
});
});
});

it('유효하지 않은 토큰으로 면접 화면에 표출할 닉네임 반환을 요청하면 401 상태코드가 반환된다.)', () => {
const agent = request.agent(app.getHttpServer());
agent
.get('/api/member/name')
.set('Cookie', [`accessToken=Bearer INVALID_TOKEN`])
.expect(401);
});

it('만료된 토큰으로 면접 화면에 표출할 닉네임 반환을 요청하면 410 상태코드가 반환된다.)', async () => {
const expirationTime = Math.floor(Date.now() / 1000) - 1;
const expiredToken = await jwtService.signAsync(
{ id: memberFixture.id } as TokenPayload,
{
expiresIn: expirationTime,
},
);

const agent = request.agent(app.getHttpServer());
agent
.get('/api/member/name')
.set('Cookie', [`accessToken=${expiredToken}`])
.expect(410);
});
});

afterAll(async () => {
await app.close();
afterEach(async () => {
await memberRepository.query('delete from Member');
await memberRepository.query('delete from Token');
});

afterAll(async () => await app.close());
});
2 changes: 1 addition & 1 deletion BE/src/member/dto/memberNicknameResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createPropertyOption } from 'src/util/swagger.util';

export class MemberNicknameResponse {
@ApiProperty(createPropertyOption('foobar', '회원의 닉네임', String))
private nickname: string;
nickname: string;

constructor(nickname: string) {
this.nickname = nickname;
Expand Down
4 changes: 4 additions & 0 deletions BE/src/member/repository/member.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ export class MemberRepository {
async findByEmail(email: string) {
return await this.memberRepository.findOneBy({ email: email });
}

async query(query: string) {
return await this.memberRepository.query(query);
}
}
Loading