전체 애플리케이션 흐름: 컨트롤러부터 데이터베이스까지 전체 애플리케이션의 흐름을 시뮬레이션하여 테스트합니다.
데이터베이스 통합: 실제 또는 인메모리 데이터베이스를 사용하여 JPA 및 QueryDSL의 통합을 검증합니다.
보안 통합: JWT 기반의 인증 및 권한 부여가 전체 시스템에서 올바르게 작동하는지 확인합니다.
요청 실패 사유 목록
/**
* 사용자 Access Token 을 활용한 로그아웃 실패
* - 실패 사유 : 요청 시, Header 에 Authorization 정보 (Access Token) 를 추가하지 않음
*/
@Test
public void 로그아웃_실패_Header_Authorization_존재() throws Exception {
// given
// when
final ResultActions resultActions = requestLogoutWithOutAccessToken();
// then
assertError(UserErrorCode.MISSING_JWT, resultActions);
}
private ResultActions requestLogoutWithOutAccessToken() throws Exception {
return mvc.perform(post("/user/logout"))
.andDo(print());
}
/**
* 사용자 Access Token 을 활용한 로그아웃 실패
* - 실패 사유 : 요청 시, Header 에 있는 Authorization 정보 (Access Token) 에 권한 정보가 없음
*/
@Test
public void 로그아웃_실패_Unauthorized_Access_Token() throws Exception {
// given
final String unauthorizedAccessToken = jwtBuilder.unauthorizedAccessJwtBuild();
// when
final ResultActions resultActions = requestLogout(unauthorizedAccessToken);
// then
assertError(UserErrorCode.UNAUTHORIZED_JWT, resultActions);
}
/**
* 사용자 Access Token 을 활용한 로그아웃 실패
* - 실패 사유 : 요청 시, Header 에 다른 타입의 Authorization 정보 (Refresh Token) 를 추가함
*/
@Test
public void 로그아웃_실패_Token_Type() throws Exception {
// given
final String refreshToken = jwtBuilder.refreshJwtBuildOfCustomer();
// when
final ResultActions resultActions = requestLogout(refreshToken);
// then
assertError(UserErrorCode.INVALID_TOKEN_TYPE, resultActions);
}
/**
* 사용자 Access Token 을 활용한 로그아웃 실패
* - 실패 사유 : 요청 시, Header 에 있는 Authorization(Access Token) 의 유효기간 만료
*/
@Test
public void 로그아웃_실패_Expired_Access_Token() throws Exception {
// given
final String expiredAccessToken = jwtBuilder.expiredAccessJwtBuild();
// when
final ResultActions resultActions = requestLogout(expiredAccessToken);
// then
assertError(UserErrorCode.EXPIRED_JWT, resultActions);
}
/**
* 사용자 Access Token 을 활용한 로그아웃 실패
* - 실패 사유 : 요청 시, Header 에 있는 Authorization(JWT) 가 유효하지 않음
*/
@Test
public void 로그아웃_실패_Invalid_Token() throws Exception {
// given
final String invalidToken = jwtBuilder.invalidJwtBuild();
// when
final ResultActions resultActions = requestLogout(invalidToken);
// then
assertError(UserErrorCode.INVALID_JWT, resultActions);
}
예외 및 에러 핸들링: 시스템 전체에서 예외 및 에러가 적절히 처리되고, 적절한 사용자 피드백이 제공되는지 확인합니다.
/**
* 거래의 상세 정보 조회 실패
* - 실패 사유 : 거래에 대한 접근 권한이 없음
*/
@Test
public void 거래_상세_정보_조회_실패_거래_접근_권한() throws Exception {
// given
final String accessTokenOfUser2 = jwtBuilder.accessJwtBuildOfSocialCustomer();
final String orderId = "1";
// when
final ResultActions resultActions = requestGetOrderDetail(accessTokenOfUser2, orderId);
// then
assertError(OrderErrorCode.DENIED_ACCESS_TO_ORDER, resultActions);
}
/**
* 사용자 기본 로그인 성공
*/
@Test
public void 기본_로그인_성공() throws Exception {
// given
final LoginRequest request = LoginRequestBuilder.build();
final Authentication expectedAuthentication = new UsernamePasswordAuthenticationToken(request.email(), null, Collections.singleton(new SimpleGrantedAuthority(Role.ROLE_CUSTOMER.name())));
final TokenInfoResponse expectedResponse = TokenInfoResponseBuilder.build();
// stub
when(userAuthService.authenticateBasic(any())).thenReturn(expectedAuthentication);
when(userAuthService.login(any(), any())).thenReturn(expectedResponse);
// when
final ResultActions resultActions = requestLoginBasic(request);
// then
final String responseString = resultActions
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsString(StandardCharsets.UTF_8);
final TokenInfoResponse actualResponse = objectMapper.readValue(responseString, TokenInfoResponse.class);
TokenInfoResponseBuilder.assertTokenInfoResponse(actualResponse, expectedResponse);
}
private ResultActions requestLoginBasic(LoginRequest request) throws Exception {
return mvc.perform(post("/user/login")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andDo(print());
}
데이터 검증: 요청 데이터가 유효성 검증을 거치는지 확인합니다 (예: 필드 검증).
Request Parameter 검증
/**
* 이메일 찾기 실패
* - 실패 사유 : name 파라미터 - empty
*/
@Test
public void 이메일_찾기_실패_name_파라미터_empty() throws Exception {
// given
final String emptyName = "";
final String phone = "01011111111";
// when
final ResultActions resultActions = requestGetUserEmail(emptyName, phone);
// then
assertErrorWithMessage(CommonErrorCode.INVALID_PARAMETER, resultActions, "이름(상호)는 필수 입력값입니다.");
}
Request Body 필드 검증
/**
* 사용자 기본 로그인 실패
* - 실패 사유 : 이메일 필드 null
*/
@Test
public void 기본_로그인_실패_이메일_필드_null() throws Exception {
// given
final LoginRequest request = LoginRequestBuilder.nullEmailBuild();
// when
final ResultActions resultActions = requestLoginBasic(request);
// then
assertErrorWithMessage(CommonErrorCode.INVALID_REQUEST_BODY_FIELDS, resultActions, "이메일은 필수 입력값입니다.");
}
인증 및 권한 부여: JWT를 사용한 인증이 올바르게 적용되었는지, 적절한 사용자가 엔드포인트에 접근할 수 있는지 테스트합니다.
@WithMockUser
또는 @WithUserDetails
어노테이션을 사용하여 테스트 메소드 또는 클래스 레벨에서 인증된 사용자를 모방합니다./**
* 도면 파일 업로드 실패
* - 실패 사유 : 공장(FACTORY) 사용자에 의한 요청
*/
@Test
@WithMockUser(roles = "FACTORY")
public void 도면_파일_업로드_실패_사용자_역할() throws Exception {
// given
final String accessToken = "access-token";
final String filePath = "src/test/resources/drawing/drawing.dwg";
final MockMultipartFile file = new MockMultipartFile(
"file",
"drawing.dwg",
MediaType.MULTIPART_FORM_DATA_VALUE,
new FileInputStream(filePath)
);
// when
final ResultActions resultActions = requestUploadDrawingFile(accessToken, file);
// then
assertError(UserErrorCode.DENIED_ACCESS, resultActions);
}
예외 처리: 잘못된 요청이나 서버 오류 시 적절한 HTTP 상태 코드와 메시지를 반환하는지 확인합니다.
/**
* 거래의 상세 정보 조회 실패
* - 실패 사유 : 거래에 대한 접근 권한이 없음
*/
@Test
@WithMockUser
public void 거래_상세_정보_조회_실패_거래접근권한() throws Exception {
// given
final String accessToken = "access-token";
final String orderId = "1";
// stub
doThrow(new CustomCommonException(OrderErrorCode.DENIED_ACCESS_TO_ORDER)).when(orderService).checkAuthorityCustomerOfOrderOrFactory(any(), any());
// when
final ResultActions resultActions = requestGetOrderDetail(accessToken, orderId);
// then
assertError(OrderErrorCode.DENIED_ACCESS_TO_ORDER, resultActions);
}
/**
* 거래 DB id 에 해당하는 거래의 접근 권한 확인 성공 - 공장 사용자
*/
@Test
public void checkAuthorityCustomerOfOrderOrFactory_성공_Factory() {
// given
final UserEntity userEntity = UserEntityBuilder.factoryAdminUserBuild();
final Long orderId = 1L;
// stub
when(userAuthService.getUserByEmail(userEntity.getEmail())).thenReturn(userEntity);
// when
orderService.checkAuthorityCustomerOfOrderOrFactory(userEntity.getEmail(), orderId);
}
/**
* 사용자 Refresh Token 을 활용한 Access Token 재발급 성공
*/
@Test
public void reissue_성공() {
// given
final RefreshToken refreshToken = RefreshToken.builder()
.id("[email protected]")
.ip(NetworkUtil.getClientIp(httpServletRequest))
.authorityList(List.of(Role.ROLE_CUSTOMER.name(), Authority.AUTHORITY_ADMIN.name()))
.refreshToken("refreshToken")
.build();
final TokenInfoResponse expectedResponse = TokenInfoResponseBuilder.build();
// stub
when(jwtProvider.validateToken(refreshToken.getRefreshToken())).thenReturn(true);
when(refreshTokenRedisRepository.findByRefreshToken(refreshToken.getRefreshToken())).thenReturn(Optional.of(refreshToken));
when(jwtProvider.generateToken(refreshToken.getId(), refreshToken.getAuthorityList())).thenReturn(expectedResponse);
// when
final TokenInfoResponse actualResponse = userAuthService.reissue(httpServletRequest, refreshToken.getRefreshToken());
// then
Assertions.assertThat(actualResponse).isEqualTo(expectedResponse);
}
/**
* 거래 DB id 에 해당하는 거래의 접근 권한 확인 실패
* - 실패 사유 : 접근 권한 없음
*/
@Test
public void checkAuthorityCustomerOfOrderOrFactory_실패_DENIED_ACCESS_TO_ORDER() {
// given
final UserEntity userEntity = UserEntityBuilder.build();
final Long orderId = 0L;
final String userEmailOfOrder = "[email protected]";
// stub
when(userAuthService.getUserByEmail(userEntity.getEmail())).thenReturn(userEntity);
when(orderRepository.findUserEmailById(orderId)).thenReturn(Optional.of(userEmailOfOrder));
// when & then
Assertions.assertThatThrownBy(() -> orderService.checkAuthorityCustomerOfOrderOrFactory(userEntity.getEmail(), orderId))
.isInstanceOf(CustomCommonException.class)
.hasMessage(OrderErrorCode.DENIED_ACCESS_TO_ORDER.getMessage());
}