Skip to content

Commit

Permalink
Merge pull request #379 from HyoJongPark/be/test/images-with-auth
Browse files Browse the repository at this point in the history
[BE] auth/images 단위 테스트 작성
HyoJongPark authored Jan 22, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents d760d41 + 718f12b commit cb95732
Showing 6 changed files with 336 additions and 21 deletions.
2 changes: 1 addition & 1 deletion backend/src/auth/auth.repository.ts
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ import { v4 as uuidv4 } from 'uuid';
export class AuthRepository {
constructor(@InjectRedis() private readonly redis: Redis) {}

setRefreshToken(accessToken: string): void {
setRefreshToken(accessToken: string) {
const refreshToken = uuidv4();
this.redis.set(accessToken, refreshToken, 'EX', REFRESH_TOKEN_EXPIRE_DATE);
}
266 changes: 266 additions & 0 deletions backend/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
import { Test, TestingModule } from '@nestjs/testing';
import { JwtService } from '@nestjs/jwt';
import { AuthService } from './auth.service';
import { AuthRepository } from './auth.repository';
import { UsersRepository } from 'src/users/users.repository';
import { GET_NAVER_PROFILE_URL, NAVER_OAUTH_URL } from './utils/auth.constant';
import { SocialType } from 'src/users/entity/socialType';
import { OAuthLoginDto } from './dto/auth.dto';
import {
ForbiddenException,
InternalServerErrorException,
UnauthorizedException,
} from '@nestjs/common';

jest.mock('@nestjs/jwt');
jest.mock('./auth.repository');
jest.mock('../users/users.repository');

describe('FriendsService Test', () => {
let jwtService: JwtService;
let authService: AuthService;
let authRepository: AuthRepository;
let usersRepository: UsersRepository;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [JwtService, AuthService, AuthRepository, UsersRepository],
}).compile();

jwtService = module.get<JwtService>(JwtService);
authService = module.get<AuthService>(AuthService);
authRepository = module.get<AuthRepository>(AuthRepository);
usersRepository = module.get<UsersRepository>(UsersRepository);
});

describe('login 테스트', () => {
const mockDto: OAuthLoginDto = {
code: 'testCode',
state: 'testState',
socialType: SocialType.NAVER,
};

const profile = {
id: '1',
email: '[email protected]',
nickname: 'testNickname',
profile_image: 'testImage',
};
const mockUser = {
id: 1,
nickname: 'testNickname',
};
const tokenResponse = {
status: 200,
json: function () {
return { access_token: 'mocked_token' };
},
};
const profileResponse = {
status: 200,
json: function () {
return { response: profile };
},
};

beforeEach(() => jest.clearAllMocks());

it('DB에 존재하지 않는 사용자 로그인 요청 시 회원가입 후 결과 반환', async () => {
//given
tokenResponse.status = 200;
profileResponse.status = 200;

(usersRepository.findBySocialIdAndSocialType as jest.Mock).mockResolvedValue(null);
(usersRepository.createUser as jest.Mock).mockResolvedValue(mockUser);
(jwtService.sign as jest.Mock).mockReturnValue('mocked_token');
global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
if (url === GET_NAVER_PROFILE_URL) {
return profileResponse;
}
});

//when
const result = await authService.login(mockDto);

//then
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(usersRepository.findBySocialIdAndSocialType).toHaveBeenCalledTimes(1);
expect(usersRepository.createUser).toHaveBeenCalledTimes(1);
expect(authRepository.setRefreshToken).toHaveBeenCalledTimes(1);
expect(jwtService.sign).toHaveBeenCalledTimes(1);
expect(result).toEqual({ token: 'mocked_token', userId: 1 });
});

it('DB에 존재하는 사용자 로그인 요청 시 회원가입 진행 x', async () => {
//given
tokenResponse.status = 200;
profileResponse.status = 200;

(usersRepository.findBySocialIdAndSocialType as jest.Mock).mockResolvedValue(mockUser);
(jwtService.sign as jest.Mock).mockReturnValue('mocked_token');
global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
if (url === GET_NAVER_PROFILE_URL) {
return profileResponse;
}
});

//when
const result = await authService.login(mockDto);

//then
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(usersRepository.findBySocialIdAndSocialType).toHaveBeenCalledTimes(1);
expect(usersRepository.createUser).toHaveBeenCalledTimes(0);
expect(authRepository.setRefreshToken).toHaveBeenCalledTimes(1);
expect(jwtService.sign).toHaveBeenCalledTimes(1);
expect(result).toEqual({ token: 'mocked_token', userId: 1 });
});

it('유효하지 않은 토큰 응답 시 예외 발생', async () => {
//given
tokenResponse.status = 401;

global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
});

//when
await expect(async () => await authService.login(mockDto)).rejects.toThrow(
new UnauthorizedException('유효하지 않은 인가 코드입니다.'),
);
expect(global.fetch).toHaveBeenCalledTimes(1);
});

it('유효하지 않은 access 토큰으로 프로필 요청 시 예외 발생', async () => {
//given
tokenResponse.status = 200;
profileResponse.status = 401;

global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
if (url === GET_NAVER_PROFILE_URL) {
return profileResponse;
}
});

//when
await expect(async () => await authService.login(mockDto)).rejects.toThrow(
new UnauthorizedException('유효하지 않은 accessToken입니다.'),
);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(usersRepository.findBySocialIdAndSocialType).toHaveBeenCalledTimes(0);
});

it('호출 권한이 존재하지 않는 프로필 요청 시 예외 발생', async () => {
//given
tokenResponse.status = 200;
profileResponse.status = 403;

global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
if (url === GET_NAVER_PROFILE_URL) {
return profileResponse;
}
});

//when
await expect(async () => await authService.login(mockDto)).rejects.toThrow(
new ForbiddenException('데이터 호출 권한이 없습니다.'),
);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(usersRepository.findBySocialIdAndSocialType).toHaveBeenCalledTimes(0);
});

it('프로필 요청 시 정상 응답 외 응답 시 예외 발생', async () => {
//given
tokenResponse.status = 200;
profileResponse.status = 404;

global.fetch = jest.fn().mockImplementation((url: string) => {
if (url === NAVER_OAUTH_URL) {
return tokenResponse;
}
if (url === GET_NAVER_PROFILE_URL) {
return profileResponse;
}
});

//when
await expect(async () => await authService.login(mockDto)).rejects.toThrow(
new InternalServerErrorException('Naver 서버 에러입니다.'),
);
expect(global.fetch).toHaveBeenCalledTimes(2);
expect(usersRepository.findBySocialIdAndSocialType).toHaveBeenCalledTimes(0);
});
});

describe('refreshAccessToken 테스트', () => {
const mockReq: any = {
cookies: { utk: 'mock_token' },
};
const mockPayload = {
id: 1,
nickname: 'testNickname',
accessKey: 'testKey',
};

beforeEach(() => jest.clearAllMocks());

it('저장소에 리프레시 토큰이 존재하면 새 access token 발급', async () => {
//given
(jwtService.decode as jest.Mock).mockResolvedValue(mockPayload);
(authRepository.getRefreshToken as jest.Mock).mockResolvedValue('mock_token');

//when
const result = await authService.refreshAccessToken(mockReq);

//then
expect(jwtService.decode).toHaveBeenCalledTimes(1);
expect(authRepository.getRefreshToken).toHaveBeenCalledTimes(1);
expect(jwtService.sign).toHaveBeenCalledTimes(1);
});

it('저장소에 리프레시 토큰이 존재하지 않으면 예외 발생', async () => {
//given
(jwtService.decode as jest.Mock).mockResolvedValue(mockPayload);
(authRepository.getRefreshToken as jest.Mock).mockResolvedValue(null);

//when-then
await expect(async () => await authService.refreshAccessToken(mockReq)).rejects.toThrow(
new UnauthorizedException('로그인이 필요합니다.'),
);
expect(jwtService.decode).toHaveBeenCalledTimes(1);
expect(authRepository.getRefreshToken).toHaveBeenCalledTimes(1);
expect(jwtService.sign).toHaveBeenCalledTimes(0);
});
});

describe('removeRefreshToken 테스트', () => {
beforeEach(() => jest.clearAllMocks());

it('존재 유무에 상관없이 refreshToken 삭제 요청 가능', async () => {
//given
const mockReq: any = {
cookies: { utk: 'mock_token' },
};

//when
await authService.removeRefreshToken(mockReq);

//then
expect(authRepository.removeRefreshToken).toHaveBeenCalledTimes(1);
});
});
});
2 changes: 1 addition & 1 deletion backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ export class AuthService {
};
}

async signUp(user: AuthUserDto, socialType: SocialType): Promise<User> {
private async signUp(user: AuthUserDto, socialType: SocialType): Promise<User> {
return await this.usersRepository.createUser(user, socialType);
}

19 changes: 0 additions & 19 deletions backend/src/images/images.controller.ts
Original file line number Diff line number Diff line change
@@ -36,23 +36,4 @@ export class ImagesController {

return { imageURL: uploadedFile.Location };
}

@Post('/profile')
@UseGuards(JwtAuthGuard)
@UseInterceptors(FileInterceptor('image'))
@ApiOperation({ description: '프로필 이미지 업로드 API' })
@ApiCreatedResponse({ description: '이미지 업로드 성공' })
async uploadProfileImage(
@User() user: UserEntity,
@UploadedFile(
new ParseFilePipe({
validators: [new FileTypeValidator({ fileType: IMAGE_TYPE_REGEX })],
}),
)
file: Express.Multer.File,
): Promise<Record<string, string>> {
const uploadedFile = await this.imagesService.uploadProfileImage(user.id, file);

return { imageURL: uploadedFile.Location };
}
}
68 changes: 68 additions & 0 deletions backend/src/images/images.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ImagesService } from './images.service';
import { ImagesRepository } from './images.repository';
import * as fs from 'fs';
import { ManagedUpload } from 'aws-sdk/clients/s3';

jest.mock('./images.repository');

describe('FriendsService Test', () => {
let imagesService: ImagesService;
let imagesRepository: ImagesRepository;

const buffer = fs.readFileSync('./test/testImage.png');
const originalname = 'testImage.jpeg';
const file = { originalname, buffer } as Express.Multer.File;

beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ImagesService, ImagesRepository],
}).compile();

imagesService = module.get<ImagesService>(ImagesService);
imagesRepository = module.get<ImagesRepository>(ImagesRepository);
});

describe('일기 이미지 업로드 테스트', () => {
beforeEach(() => jest.clearAllMocks());

it('일기 이미지 정상 업로드', async () => {
//given
const userId = 1;
const date = new Date();
const data = {
Location: `https://dandi-object-storage.kr.object.ncloudstorage.com/${userId}/${date.getFullYear()}/${date.getMonth()}/${originalname}`,
} as ManagedUpload.SendData;

(imagesRepository.uploadImage as jest.Mock).mockResolvedValue(data);

//when
const result = await imagesService.uploadDiaryImage(userId, file);

//then
expect(imagesRepository.uploadImage).toHaveBeenCalledTimes(1);
expect(result.Location).toEqual(data.Location);
});
});

describe('프로필 이미지 업로드 테스트', () => {
beforeEach(() => jest.clearAllMocks());

it('프로필 이미지 정상 업로드', async () => {
//given
const userId = 1;
const data = {
Location: `https://dandi-object-storage.kr.object.ncloudstorage.com/${userId}/profile/${originalname}`,
} as ManagedUpload.SendData;

(imagesRepository.uploadImage as jest.Mock).mockResolvedValue(data);

//when
const result = await imagesService.uploadProfileImage(userId, file);

//then
expect(imagesRepository.uploadImage).toHaveBeenCalledTimes(1);
expect(result.Location).toEqual(data.Location);
});
});
});
Binary file added backend/test/testImage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit cb95732

Please sign in to comment.