Skip to content

Commit

Permalink
�test: 인증 단위 테스트 구현 (#202)
Browse files Browse the repository at this point in the history
* refactor: JwtAuthenticationFilter 불필요한 로직 제거 (#198)

* refactor: SecurityValue 패키지 이동 (#198)

* test: 인증 관련 단위 테스트 구현 (#198)

* test: 인증 관련 단위 테스트 구현 (#198)

* test: JwtAuthenticationFilterTest 리팩토링 (#198)
  • Loading branch information
choidongkuen authored Feb 12, 2024
1 parent d812722 commit e5a4953
Show file tree
Hide file tree
Showing 8 changed files with 215 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

Expand All @@ -31,7 +30,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProperty jwtProperty;

@Override
protected void doFilterInternal(HttpServletRequest request,
public void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (request.getMethod().equals("OPTIONS")) {
Expand Down Expand Up @@ -73,10 +72,9 @@ private void saveUserAuthentication(User user) {

private String resolveTokenFromRequest(HttpServletRequest request) {
String token = request.getHeader(jwtProperty.getAccess().getHeader());
if (!ObjectUtils.isEmpty(token) && token.toLowerCase().startsWith(jwtProperty.getBearer().toLowerCase())) {
if (token.toLowerCase().startsWith(jwtProperty.getBearer().toLowerCase())) {
return token.substring(7);
}
setRequestAttribute(request, "요청에 대한 JWT 파싱 과정에서 문제가 발생했습니다.");
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package net.teumteum.unit.auth.controller;


import static net.teumteum.unit.auth.common.SecurityValue.INVALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.INVALID_REFRESH_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.unit.common.SecurityValue.INVALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.INVALID_REFRESH_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_REFRESH_TOKEN;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.http.HttpHeaders.AUTHORIZATION;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package net.teumteum.unit.auth.service;

import static net.teumteum.core.security.Authenticated.네이버;
import static net.teumteum.unit.auth.common.SecurityValue.INVALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.unit.common.SecurityValue.INVALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_REFRESH_TOKEN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package net.teumteum.unit.auth.common;
package net.teumteum.unit.common;

public final class SecurityValue {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package net.teumteum.unit.core.security;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import net.teumteum.core.error.ErrorResponse;
import net.teumteum.core.security.filter.JwtAuthenticationEntryPoint;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.DelegatingServletOutputStream;
import org.springframework.security.core.AuthenticationException;

@ExtendWith(MockitoExtension.class)
@DisplayName("JwtAuthenticationEntryPoint 단위 테스트의")
public class JwtAuthenticationEntryPointTest {

private static final String ATTRIBUTE_NAME = "exception";
@Mock
private ObjectMapper objectMapper;
@Mock
private AuthenticationException authenticationException;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@InjectMocks
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

@Nested
@DisplayName("JwtAuthenticationFilter 에서 인증 예외가 발생시")
class When_authentication_error_occurs_from_filter {

@Test
@DisplayName("알맞은 예외 메시지와 관련 응답을 반환한다.")
void Return_error_response_with_message() throws IOException {
// given
var errorMessage = "Authentication Exception Occurred";
var outputStream = new ByteArrayOutputStream();

given(request.getAttribute(ATTRIBUTE_NAME)).willReturn(errorMessage);
given(response.getOutputStream()).willReturn(new DelegatingServletOutputStream(outputStream));

// when
jwtAuthenticationEntryPoint.commence(request, response, authenticationException);

// then
verify(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
verify(objectMapper, times(1)).writeValue(any(OutputStream.class), any(ErrorResponse.class));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package net.teumteum.unit.core.security;

import static net.teumteum.unit.common.SecurityValue.INVALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_ACCESS_TOKEN;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import java.io.IOException;
import net.teumteum.auth.service.AuthService;
import net.teumteum.core.property.JwtProperty;
import net.teumteum.core.security.UserAuthentication;
import net.teumteum.core.security.filter.JwtAuthenticationFilter;
import net.teumteum.core.security.service.JwtService;
import net.teumteum.user.domain.UserFixture;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;

@ExtendWith(MockitoExtension.class)
@DisplayName("JwtAuthenticationFilter 단위 테스트의")
public class JwtAuthenticationFilterTest {

private static final String ATTRIBUTE_NAME = "exception";
@Mock
private JwtService jwtService;
@Mock
private AuthService authService;
@Mock
private JwtProperty jwtProperty;
@Mock
private JwtProperty.Access access;
@Mock
private FilterChain filterChain;
@InjectMocks
private JwtAuthenticationFilter jwtAuthenticationFilter;

@Nested
@DisplayName("API 요청시 JWT 파싱 및 회원 조회 로직은")
class Api_request_with_valid_jwt_unit {

@BeforeEach
@AfterEach
void clearSecurityContextHolder() {
SecurityContextHolder.clearContext();
}

@Test
@DisplayName("유효한 JWT 인 경우, JWT 을 파싱하고 성공적으로 UserAuthentication 을 SecurityContext 에 저장한다.")
void Parsing_jwt_and_save_user_in_security_context() throws ServletException, IOException {
// given
var request = new MockHttpServletRequest();
var response = new MockHttpServletResponse();

given(jwtProperty.getAccess()).willReturn(access);
given(jwtProperty.getAccess().getHeader()).willReturn("Authorization");
given(jwtProperty.getBearer()).willReturn("Bearer");

request.addHeader(jwtProperty.getAccess().getHeader(),
jwtProperty.getBearer() + " " + VALID_ACCESS_TOKEN);

var user = UserFixture.getIdUser();

given(jwtService.validateToken(anyString())).willReturn(true);
given(authService.findUserByAccessToken(anyString())).willReturn(user);

// when
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);

// then
var authentication = SecurityContextHolder.getContext().getAuthentication();
assertThat(authentication).isInstanceOf(UserAuthentication.class);
}

@Test
@DisplayName("유효하지 않은 JWT 와 함께 요청이 들어오면, 요청 처리를 중단하고 에러 메세지를 반환한다.")
void Return_error_when_jwt_is_invalid() throws ServletException, IOException {
// given
var request = new MockHttpServletRequest();
var response = new MockHttpServletResponse();

given(jwtProperty.getAccess()).willReturn(access);
given(jwtProperty.getAccess().getHeader()).willReturn("Authorization");
given(jwtProperty.getBearer()).willReturn("Bearer");

request.addHeader(jwtProperty.getAccess().getHeader(),
jwtProperty.getBearer() + " " + INVALID_ACCESS_TOKEN);

given(jwtService.validateToken(anyString())).willReturn(false);

// when
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);

// then
assertThat(request.getAttribute(ATTRIBUTE_NAME)).isEqualTo("요청에 대한 JWT 가 유효하지 않습니다.");
var authentication = SecurityContextHolder.getContext().getAuthentication();
assertThat(authentication).isNull();
verify(filterChain, times(1)).doFilter(request, response);
}

@Test
@DisplayName("JWT 가 존재하지 않는 경우, 요청 처리를 중단하고 에러 메세지를 반환한다.")
void Return_error_when_jwt_is_not_exist() throws ServletException, IOException {
// given
var request = new MockHttpServletRequest();
var response = new MockHttpServletResponse();

given(jwtProperty.getAccess()).willReturn(access);
given(jwtProperty.getAccess().getHeader()).willReturn("Authorization");
given(jwtProperty.getBearer()).willReturn("Bearer");

request.addHeader(jwtProperty.getAccess().getHeader(),
jwtProperty.getBearer() + " ");

// when
jwtAuthenticationFilter.doFilterInternal(request, response, filterChain);

// then
assertThat(request.getAttribute(ATTRIBUTE_NAME)).isEqualTo("요청에 대한 JWT 정보가 존재하지 않습니다.");
verify(jwtService, times(0)).validateToken(anyString());
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package net.teumteum.unit.user.controller;

import static net.teumteum.unit.auth.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.최고에요;
import static org.hamcrest.Matchers.hasSize;
Expand Down Expand Up @@ -267,11 +267,9 @@ void Get_user_reviews_with_200_ok() throws Exception {
class Logout_user_api_unit {

@Test
@DisplayName("")
@DisplayName("로그인한 회원의 로그아웃을 진행하고, 200 OK 을 반환합니다.")
void Logout_user_with_200_ok() throws Exception {
// given
var userId = 1L;

doNothing().when(userService).logout(anyLong());

// when && then
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package net.teumteum.unit.user.service;

import static net.teumteum.unit.auth.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.auth.common.SecurityValue.VALID_REFRESH_TOKEN;

import static net.teumteum.unit.common.SecurityValue.VALID_ACCESS_TOKEN;
import static net.teumteum.unit.common.SecurityValue.VALID_REFRESH_TOKEN;
import static net.teumteum.user.domain.Review.별로에요;
import static net.teumteum.user.domain.Review.최고에요;
import static org.assertj.core.api.Assertions.assertThat;
Expand Down

0 comments on commit e5a4953

Please sign in to comment.