diff --git a/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java new file mode 100644 index 000000000..d54a0ece6 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/application/AuthenticationService.java @@ -0,0 +1,30 @@ +package com.codesoom.assignment.application; + +import com.codesoom.assignment.domain.User; +import com.codesoom.assignment.domain.UserRepository; +import com.codesoom.assignment.dto.UserLoginData; +import com.codesoom.assignment.errors.InvalidLoginException; +import com.codesoom.assignment.errors.UserNotFoundException; +import com.codesoom.assignment.utils.JwtUtil; +import org.springframework.stereotype.Service; + +@Service +public class AuthenticationService { + private final UserRepository userRepository; + private final JwtUtil jwtUtil; + + public AuthenticationService(UserRepository userRepository, JwtUtil jwtUtil) { + this.userRepository = userRepository; + this.jwtUtil = jwtUtil; + } + + public String login(UserLoginData loginData) { + User loginUser = userRepository.findByEmail(loginData.getEmail()) + .orElseThrow(() -> new UserNotFoundException(loginData.getEmail())); + + if (!loginUser.getPassword().equals(loginData.getPassword())) { + throw new InvalidLoginException("Check your password"); + } + return jwtUtil.encode(loginUser.getId()); + } +} diff --git a/app/src/main/java/com/codesoom/assignment/config/WebConfig.java b/app/src/main/java/com/codesoom/assignment/config/WebConfig.java new file mode 100644 index 000000000..bc467b7fb --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/config/WebConfig.java @@ -0,0 +1,22 @@ +package com.codesoom.assignment.config; + +import com.codesoom.assignment.interceptor.AuthInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final AuthInterceptor authInterceptor; + + public WebConfig(AuthInterceptor authInterceptor) { + this.authInterceptor = authInterceptor; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/products/**"); + } + +} diff --git a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java index 099939d4e..7c7939142 100644 --- a/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java +++ b/app/src/main/java/com/codesoom/assignment/controllers/ControllerErrorAdvice.java @@ -1,9 +1,7 @@ package com.codesoom.assignment.controllers; import com.codesoom.assignment.dto.ErrorResponse; -import com.codesoom.assignment.errors.ProductNotFoundException; -import com.codesoom.assignment.errors.UserEmailDuplicationException; -import com.codesoom.assignment.errors.UserNotFoundException; +import com.codesoom.assignment.errors.*; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -30,4 +28,22 @@ public ErrorResponse handleUserNotFound() { public ErrorResponse handleUserEmailIsAlreadyExisted() { return new ErrorResponse("User's email address is already existed"); } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(InvalidLoginException.class) + public ErrorResponse handleInvalidLogin() { + return new ErrorResponse("Invalid login request"); + } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(AccessTokenNotFoundException.class) + public ErrorResponse handleAccessTokenNotFound() { + return new ErrorResponse("Access token not found"); + } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(InvalidAccessTokenException.class) + public ErrorResponse handleInvalidAccessToken() { + return new ErrorResponse("Invalid access token"); + } } diff --git a/app/src/main/java/com/codesoom/assignment/controllers/SessionController.java b/app/src/main/java/com/codesoom/assignment/controllers/SessionController.java new file mode 100644 index 000000000..fc7b908d4 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/controllers/SessionController.java @@ -0,0 +1,32 @@ +package com.codesoom.assignment.controllers; + +import com.codesoom.assignment.application.AuthenticationService; +import com.codesoom.assignment.dto.SessionResponse; +import com.codesoom.assignment.dto.UserLoginData; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@RestController +@RequestMapping("/session") +@CrossOrigin +public class SessionController { + + private final AuthenticationService authenticationService; + + public SessionController(AuthenticationService authenticationService) { + this.authenticationService = authenticationService; + } + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public SessionResponse login(@RequestBody @Valid UserLoginData userLoginData) { + String accessToken = authenticationService.login(userLoginData); + + return SessionResponse.builder() + .accessToken(accessToken) + .build(); + } + +} diff --git a/app/src/main/java/com/codesoom/assignment/domain/UserRepository.java b/app/src/main/java/com/codesoom/assignment/domain/UserRepository.java index 995d6395a..5019fae54 100644 --- a/app/src/main/java/com/codesoom/assignment/domain/UserRepository.java +++ b/app/src/main/java/com/codesoom/assignment/domain/UserRepository.java @@ -12,4 +12,6 @@ public interface UserRepository { Optional findByIdAndDeletedIsFalse(Long id); Optional findByEmail(String email); + + void deleteAll(); } diff --git a/app/src/main/java/com/codesoom/assignment/dto/SessionResponse.java b/app/src/main/java/com/codesoom/assignment/dto/SessionResponse.java new file mode 100644 index 000000000..249b63479 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/dto/SessionResponse.java @@ -0,0 +1,13 @@ +package com.codesoom.assignment.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +@AllArgsConstructor +public class SessionResponse { + + private String accessToken; +} diff --git a/app/src/main/java/com/codesoom/assignment/dto/UserLoginData.java b/app/src/main/java/com/codesoom/assignment/dto/UserLoginData.java new file mode 100644 index 000000000..695e8f04c --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/dto/UserLoginData.java @@ -0,0 +1,26 @@ +package com.codesoom.assignment.dto; + +import com.github.dozermapper.core.Mapping; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class UserLoginData { + @NotBlank + @Size(min = 3) + @Mapping("email") + private String email; + + @NotBlank + @Size(min = 4, max = 1024) + @Mapping("password") + private String password; +} diff --git a/app/src/main/java/com/codesoom/assignment/errors/AccessTokenNotFoundException.java b/app/src/main/java/com/codesoom/assignment/errors/AccessTokenNotFoundException.java new file mode 100644 index 000000000..1579813bd --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/errors/AccessTokenNotFoundException.java @@ -0,0 +1,8 @@ +package com.codesoom.assignment.errors; + +public class AccessTokenNotFoundException extends RuntimeException { + + public AccessTokenNotFoundException() { + super("Access token not found"); + } +} diff --git a/app/src/main/java/com/codesoom/assignment/errors/InvalidAccessTokenException.java b/app/src/main/java/com/codesoom/assignment/errors/InvalidAccessTokenException.java new file mode 100644 index 000000000..8216143a2 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/errors/InvalidAccessTokenException.java @@ -0,0 +1,14 @@ +package com.codesoom.assignment.errors; + +public class InvalidAccessTokenException extends RuntimeException { + + public InvalidAccessTokenException(String message) { + super("Invalid access token : " + message); + } + + public InvalidAccessTokenException() { + super("Invalid access token"); + } + + +} diff --git a/app/src/main/java/com/codesoom/assignment/errors/InvalidLoginException.java b/app/src/main/java/com/codesoom/assignment/errors/InvalidLoginException.java new file mode 100644 index 000000000..9809f0c93 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/errors/InvalidLoginException.java @@ -0,0 +1,8 @@ +package com.codesoom.assignment.errors; + +public class InvalidLoginException extends RuntimeException { + public InvalidLoginException(String message) { + super("Invalid login Exception: " + message); + } + +} diff --git a/app/src/main/java/com/codesoom/assignment/errors/UserNotFoundException.java b/app/src/main/java/com/codesoom/assignment/errors/UserNotFoundException.java index 870dfd123..b4a65e7b7 100644 --- a/app/src/main/java/com/codesoom/assignment/errors/UserNotFoundException.java +++ b/app/src/main/java/com/codesoom/assignment/errors/UserNotFoundException.java @@ -4,4 +4,8 @@ public class UserNotFoundException extends RuntimeException { public UserNotFoundException(Long id) { super("User not found: " + id); } + + public UserNotFoundException(String email) { + super("User not found: " + email); + } } diff --git a/app/src/main/java/com/codesoom/assignment/infra/JpaUserRepository.java b/app/src/main/java/com/codesoom/assignment/infra/JpaUserRepository.java index 7e8205836..4e3995820 100644 --- a/app/src/main/java/com/codesoom/assignment/infra/JpaUserRepository.java +++ b/app/src/main/java/com/codesoom/assignment/infra/JpaUserRepository.java @@ -17,4 +17,6 @@ public interface JpaUserRepository Optional findByIdAndDeletedIsFalse(Long id); Optional findByEmail(String email); + + void deleteAll(); } diff --git a/app/src/main/java/com/codesoom/assignment/interceptor/AuthInterceptor.java b/app/src/main/java/com/codesoom/assignment/interceptor/AuthInterceptor.java new file mode 100644 index 000000000..acd166433 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/interceptor/AuthInterceptor.java @@ -0,0 +1,65 @@ +package com.codesoom.assignment.interceptor; + +import com.codesoom.assignment.errors.AccessTokenNotFoundException; +import com.codesoom.assignment.errors.InvalidAccessTokenException; +import com.codesoom.assignment.utils.JwtUtil; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private final JwtUtil jwtUtil; + + public AuthInterceptor(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + if (isGetMethod(request)) { + return true; + } + + if (isPostMethod(request) || isPatchMethod(request) || isDeleteMethod(request)) { + return checkAccessToken(request); + } + + return true; + } + + private boolean isGetMethod(HttpServletRequest request) { + return request.getMethod().equals("GET"); + } + + private boolean isPostMethod(HttpServletRequest request) { + return request.getMethod().equals("POST"); + } + + private boolean isPatchMethod(HttpServletRequest request) { + return request.getMethod().equals("PATCH"); + } + + private boolean isDeleteMethod(HttpServletRequest request) { + return request.getMethod().equals("DELETE"); + } + + private boolean checkAccessToken(HttpServletRequest request) throws InvalidAccessTokenException, AccessTokenNotFoundException { + String authorization = request.getHeader("Authorization"); + if (authorization == null) { + throw new AccessTokenNotFoundException(); + } + String accessToken = authorization.substring("Bearer ".length()); + try { + jwtUtil.decode(accessToken); + return true; + } catch (Exception e) { + throw new InvalidAccessTokenException(e.getMessage()); + } + } + +} diff --git a/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java b/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java new file mode 100644 index 000000000..f7719be11 --- /dev/null +++ b/app/src/main/java/com/codesoom/assignment/utils/JwtUtil.java @@ -0,0 +1,33 @@ +package com.codesoom.assignment.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; + +@Component +public class JwtUtil { + private final Key key; + + public JwtUtil(@Value("${jwt.secret}") String secret) { + this.key = Keys.hmacShaKeyFor(secret.getBytes()); + } + + public String encode(Long userId) { + return Jwts.builder() + .claim("userId", userId) + .signWith(key) + .compact(); + } + + public Claims decode(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } +} diff --git a/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java new file mode 100644 index 000000000..39910001f --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/application/AuthenticationServiceTest.java @@ -0,0 +1,73 @@ +package com.codesoom.assignment.application; + +import com.codesoom.assignment.domain.User; +import com.codesoom.assignment.dto.UserLoginData; +import com.codesoom.assignment.errors.InvalidLoginException; +import com.codesoom.assignment.errors.UserNotFoundException; +import com.codesoom.assignment.utils.TestHelper; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; + +import static com.codesoom.assignment.utils.TestHelper.*; + + +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("AuthenticationService 클래스") +class AuthenticationServiceTest extends JpaTest { + + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class login_메서드는 { + private AuthenticationService authenticationService = new AuthenticationService(getUserRepository(), getJwtUtil()); + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_유저로그인정보_요청를_받으면 { + private UserLoginData AUTH_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + @BeforeEach + void setUp() { + getUserRepository().deleteAll(); + getUserRepository().save(AUTH_USER); + } + + @DisplayName("인증토큰을 반환한다.") + @Test + void It_returns_token() { + String accessToken = authenticationService.login(AUTH_USER_DATA); + Assertions.assertThat(accessToken).isEqualTo(VALID_TOKEN); + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_로그인정보를_받으면 { + + @BeforeEach + void setUp() { + getUserRepository().deleteAll(); + getUserRepository().save(AUTH_USER); + } + + @DisplayName("해당 정보의 회원이 존재하지 않으면 UserNotFoundException을 반환한다.") + @Test + void It_throws_UserNotFoundException() { + Assertions.assertThatThrownBy(() -> authenticationService.login(IS_NOT_EXISTS_USER_DATA)).isInstanceOf(UserNotFoundException.class); + } + + @DisplayName("비밀번호가 일치하지 않으면 InvalidLoginException을 반환한다.") + @Test + void It_throws_InvalidLoginRequest() { + Assertions.assertThatThrownBy(() -> authenticationService.login(INVALID_PASSWORD_USER_DATA)).isInstanceOf(InvalidLoginException.class); + } + } + + } +} diff --git a/app/src/test/java/com/codesoom/assignment/application/JpaTest.java b/app/src/test/java/com/codesoom/assignment/application/JpaTest.java new file mode 100644 index 000000000..df04c6247 --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/application/JpaTest.java @@ -0,0 +1,23 @@ +package com.codesoom.assignment.application; + +import com.codesoom.assignment.domain.UserRepository; +import com.codesoom.assignment.utils.JwtUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; + +@DataJpaTest +public class JpaTest { + private final String SECRET = "12345678901234567890123456789010"; + private final JwtUtil jwtUtil = new JwtUtil(SECRET); + + @Autowired + public UserRepository userRepository; + + public UserRepository getUserRepository() { + return userRepository; + } + + public JwtUtil getJwtUtil() { + return jwtUtil; + } +} diff --git a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java index a387bea09..a032c1ed2 100644 --- a/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java +++ b/app/src/test/java/com/codesoom/assignment/controllers/ProductControllerTest.java @@ -3,9 +3,15 @@ import com.codesoom.assignment.application.ProductService; import com.codesoom.assignment.domain.Product; import com.codesoom.assignment.dto.ProductData; +import com.codesoom.assignment.errors.InvalidAccessTokenException; import com.codesoom.assignment.errors.ProductNotFoundException; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import com.codesoom.assignment.utils.JwtUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.jsonwebtoken.Claims; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; @@ -14,175 +20,361 @@ import java.util.List; +import static com.codesoom.assignment.utils.TestHelper.*; import static org.hamcrest.Matchers.containsString; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(ProductController.class) +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("ProductController 클래스") class ProductControllerTest { - private static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaDk"; - private static final String INVALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9." + - "eyJ1c2VySWQiOjF9.ZZ3CUl0jxeLGvQ1Js5nG2Ty5qGTlqai5ubDMXZOdaD0"; @Autowired private MockMvc mockMvc; + @Autowired + private ObjectMapper objectMapper; + @MockBean private ProductService productService; - @BeforeEach - void setUp() { - Product product = Product.builder() - .id(1L) - .name("쥐돌이") - .maker("냥이월드") - .price(5000) - .build(); - given(productService.getProducts()).willReturn(List.of(product)); - - given(productService.getProduct(1L)).willReturn(product); - - given(productService.getProduct(1000L)) - .willThrow(new ProductNotFoundException(1000L)); - - given(productService.createProduct(any(ProductData.class))) - .willReturn(product); - - given(productService.updateProduct(eq(1L), any(ProductData.class))) - .will(invocation -> { - Long id = invocation.getArgument(0); - ProductData productData = invocation.getArgument(1); - return Product.builder() - .id(id) - .name(productData.getName()) - .maker(productData.getMaker()) - .price(productData.getPrice()) - .build(); - }); + @MockBean + private JwtUtil jwtUtil; - given(productService.updateProduct(eq(1000L), any(ProductData.class))) - .willThrow(new ProductNotFoundException(1000L)); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class list_메서드는 { - given(productService.deleteProduct(1000L)) - .willThrow(new ProductNotFoundException(1000L)); - } + @BeforeEach + void setUp() { + given(productService.getProducts()).willReturn(List.of(TEST_PRODUCT)); + } - @Test - void list() throws Exception { - mockMvc.perform( - get("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐돌이"))); + @Test + @DisplayName("상품목록을 반환한다") + void It_returns_product_list() throws Exception { + mockMvc.perform(get("/products") + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } } - @Test - void deatilWithExsitedProduct() throws Exception { - mockMvc.perform( - get("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐돌이"))); - } + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class detail_메서드는 { - @Test - void deatilWithNotExsitedProduct() throws Exception { - mockMvc.perform(get("/products/1000")) - .andExpect(status().isNotFound()); - } + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 요청받은_id에_해당하는_상품이_존재하는_경우 { + @BeforeEach + void setUp() { + given(productService.getProduct(1L)).willReturn(TEST_PRODUCT); + } - @Test - void createWithValidAttributes() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐돌이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - ) - .andExpect(status().isCreated()) - .andExpect(content().string(containsString("쥐돌이"))); - - verify(productService).createProduct(any(ProductData.class)); - } + @Test + @DisplayName("해당 id의 상품을 반환한다") + void It_returns_product() throws Exception { + mockMvc.perform(get("/products/1") + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } + } - @Test - void createWithInvalidAttributes() throws Exception { - mockMvc.perform( - post("/products") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"\",\"maker\":\"\"," + - "\"price\":0}") - ) - .andExpect(status().isBadRequest()); - } + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 요청받은_id에_해당하는_상품이_존재하지_않는_경우 { + @BeforeEach + void setUp() { + given(productService.getProduct(1000L)) + .willThrow(new ProductNotFoundException(1000L)); + } - @Test - void updateWithExistedProduct() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - ) - .andExpect(status().isOk()) - .andExpect(content().string(containsString("쥐순이"))); - - verify(productService).updateProduct(eq(1L), any(ProductData.class)); - } + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(get("/products/1000") + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } - @Test - void updateWithNotExistedProduct() throws Exception { - mockMvc.perform( - patch("/products/1000") - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"쥐순이\",\"maker\":\"냥이월드\"," + - "\"price\":5000}") - ) - .andExpect(status().isNotFound()); - - verify(productService).updateProduct(eq(1000L), any(ProductData.class)); } - @Test - void updateWithInvalidAttributes() throws Exception { - mockMvc.perform( - patch("/products/1") - .accept(MediaType.APPLICATION_JSON_UTF8) - .contentType(MediaType.APPLICATION_JSON) - .content("{\"name\":\"\",\"maker\":\"\"," + - "\"price\":0}") - ) - .andExpect(status().isBadRequest()); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class create_메서드는 { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_요청이_정상적인_경우 { + @BeforeEach + void setUp() { + given(productService.createProduct(any(ProductData.class))) + .willReturn(TEST_PRODUCT); + } + + @Test + @DisplayName("상품을 생성하고, 생성된 상품을 반환한다") + void It_creates_product_and_returns_it() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(TEST_PRODUCT_DATA))) + .andExpect(status().isCreated()) + .andExpect(content().string(containsString(TEST_PRODUCT_NAME))); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품요청이_비정상적인_경우 { + + @ParameterizedTest + @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidProductRequests") + @DisplayName("에러메시지를 반환한다") + void It_returns_400_error() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidAccessTokenException()); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(post("/products") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(TEST_PRODUCT_DATA))) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } - @Test - void destroyWithExistedProduct() throws Exception { - mockMvc.perform( - delete("/products/1") - ) - .andExpect(status().isNoContent()); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class update_메서드는 { + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_요청이_정상적인_경우 { + @BeforeEach + void setUp() { + given(productService.updateProduct(eq(1L), any(ProductData.class))) + .will(invocation -> { + Long id = invocation.getArgument(0); + ProductData productData = invocation.getArgument(1); + return Product.builder() + .id(id) + .name(productData.getName()) + .maker(productData.getMaker()) + .price(productData.getPrice()) + .build(); + }); + } + + @Test + @DisplayName("상품을 수정하고, 수정된 상품을 반환한다") + void It_updates_product_and_returns_it() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isOk()) + .andExpect(content().string(containsString(TEST_UPDATE_PRODUCT_NAME))); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품요청이_비정상적인_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).willReturn(null); + } + + @ParameterizedTest + @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidProductRequests") + @DisplayName("에러메시지를 반환한다") + void It_returns_400_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isBadRequest()) + .andDo(print()); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 수정할_상품_정보가_없는경우 { + @BeforeEach + void setUp() { + given(productService.updateProduct(eq(1000L), any(ProductData.class))) + .willThrow(new ProductNotFoundException(1000L)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(patch("/products/1000") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } - verify(productService).deleteProduct(1L); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidAccessTokenException()); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(patch("/products/1") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } - @Test - void destroyWithNotExistedProduct() throws Exception { - mockMvc.perform( - delete("/products/1000") - ) - .andExpect(status().isNotFound()); + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class destroy_메서드는 { + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + given(jwtUtil.decode(VALID_TOKEN)).will(invocation -> { + String accessToken = invocation.getArgument(0); + Claims claims = new JwtUtil("12345678901234567890123456789010").decode(accessToken); + return claims; + }); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 상품_삭제요청이_정상적인_경우 { + @Test + @DisplayName("상품을 삭제한다") + void It_delete_product() throws Exception { + mockMvc.perform(delete("/products/1") + .header("Authorization", "Bearer " + VALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8) + .content(objectMapper.writeValueAsString(UPDATE_PRODUCT_DATA))) + .andExpect(status().isNoContent()); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 삭제할_상품_정보가_없는경우 { + @BeforeEach + void setUp() { + given(productService.deleteProduct(1000L)) + .willThrow(new ProductNotFoundException(1000L)); + } + + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_404_error() throws Exception { + mockMvc.perform(delete("/products/1000") + .header("Authorization", "Bearer " + VALID_TOKEN) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isNotFound()) + .andDo(print()); + } + } + + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_토큰을_전달받은_경우 { + @BeforeEach + void setUp() { + Mockito.reset(jwtUtil); + given(jwtUtil.decode(INVALID_TOKEN)).willThrow(new InvalidAccessTokenException()); + } - verify(productService).deleteProduct(1000L); + @Test + @DisplayName("에러메시지를 반환한다") + void It_returns_401_error() throws Exception { + mockMvc.perform(delete("/products/1") + .header("Authorization", "Bearer " + INVALID_TOKEN) + .accept(MediaType.APPLICATION_JSON_UTF8) + .contentType(MediaType.APPLICATION_JSON_UTF8)) + .andExpect(status().isUnauthorized()) + .andDo(print()); + } + } } } diff --git a/app/src/test/java/com/codesoom/assignment/controllers/SessionControllerTest.java b/app/src/test/java/com/codesoom/assignment/controllers/SessionControllerTest.java new file mode 100644 index 000000000..fc7f80434 --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/controllers/SessionControllerTest.java @@ -0,0 +1,86 @@ +package com.codesoom.assignment.controllers; + +import com.codesoom.assignment.application.AuthenticationService; +import com.codesoom.assignment.dto.UserLoginData; +import com.codesoom.assignment.utils.JwtUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static com.codesoom.assignment.utils.TestHelper.AUTH_USER_LOGIN_DATA; +import static com.codesoom.assignment.utils.TestHelper.VALID_TOKEN; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(SessionController.class) +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("SessionController 클래스") +class SessionControllerTest { + + @Autowired + MockMvc mockMvc; + @Autowired + ObjectMapper objectMapper; + @MockBean + AuthenticationService authenticationService; + @MockBean + JwtUtil jwtUtil; + + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class login_메서드는 { + + @BeforeEach + void setUp() { + given(authenticationService.login(any(UserLoginData.class))) + .willReturn(VALID_TOKEN); + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효한_유저로그인정보_요청를_받으면 { + + @DisplayName("인증토큰을 반환한다.") + @Test + void It_returns_token() throws Exception { + String jsonString = objectMapper.writeValueAsString(AUTH_USER_LOGIN_DATA); + + mockMvc.perform(post("/session") + .contentType(MediaType.APPLICATION_JSON) + .content(jsonString)) + .andExpect(status().isCreated()) + .andExpect(jsonPath("accessToken").value(VALID_TOKEN)); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 유효하지_않은_유저로그인_요청을_받으면 { + + @DisplayName("예외를 반환한다.") + @ParameterizedTest + @MethodSource("com.codesoom.assignment.utils.TestHelper#provideInvalidUserLoginRequests") + void It_returns_exception(UserLoginData loginData) throws Exception { + String jsonString = objectMapper.writeValueAsString(loginData); + + mockMvc.perform(post("/session") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(jsonString)) + .andExpect(status().isBadRequest()); + } + + + } + } +} diff --git a/app/src/test/java/com/codesoom/assignment/interceptor/AuthInterceptorTest.java b/app/src/test/java/com/codesoom/assignment/interceptor/AuthInterceptorTest.java new file mode 100644 index 000000000..969f756b1 --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/interceptor/AuthInterceptorTest.java @@ -0,0 +1,42 @@ +package com.codesoom.assignment.interceptor; + +import com.codesoom.assignment.application.JpaTest; +import com.codesoom.assignment.errors.AccessTokenNotFoundException; +import com.codesoom.assignment.errors.InvalidAccessTokenException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; + +import static com.codesoom.assignment.utils.TestHelper.*; + +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("AuthInterceptor 클래스") +class AuthInterceptorTest extends JpaTest { + AuthInterceptor authInterceptor = new AuthInterceptor(getJwtUtil()); + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class preHandle_메서드는 { + + @DisplayName("인증토큰이 없으면 AccessTokenNotFoundException을 반환한다.") + @Test + void It_returns_AccessTokenNotFoundException() { + Assertions.assertThatThrownBy(() -> authInterceptor.preHandle(INVALID_SERVLET_REQUEST, null, null)) + .isInstanceOf(AccessTokenNotFoundException.class); + } + + @DisplayName("인증토큰이 유효하지 않으면 InvalidAccessTokenException을 반환한다.") + @Test + void It_returns_InvalidAccessTokenException() { + Assertions.assertThatThrownBy(() -> authInterceptor.preHandle(getInvalidTokenServletRequest(), null, null)) + .isInstanceOf(InvalidAccessTokenException.class); + } + + @DisplayName("인증토큰이 유효하면 true를 반환한다.") + @Test + void It_returns_true() throws Exception { + Assertions.assertThat(authInterceptor.preHandle(getValidTokenServletRequest(), null, null)) + .isTrue(); + } + } + +} diff --git a/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java b/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java new file mode 100644 index 000000000..ac260824b --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/utils/JwtUtilTest.java @@ -0,0 +1,40 @@ +package com.codesoom.assignment.utils; + + +import org.junit.jupiter.api.*; + +import static org.assertj.core.api.Assertions.assertThat; + +@SuppressWarnings({"InnerClassMayBeStatic", "NonAsciiCharacters"}) +@DisplayName("JwtUtil 클래스") +class JwtUtilTest { + private final String SECRET = "12345678901234567890123456789010"; + private final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.neCsyNLzy3lQ4o2yliotWT06FwSGZagaHpKdAkjnGGw"; + private final Long USER_ID = 1L; + + JwtUtil jwtUtil = new JwtUtil(SECRET); + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class encode_메서드는 { + @DisplayName("유저 아이디를 받아 토큰을 생성한다.") + @Test + void It_returns_token() { + String token = jwtUtil.encode(USER_ID); + + assertThat(token).isNotNull(); + assertThat(token).isEqualTo(VALID_TOKEN); + } + } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class decode_메서드는 { + @DisplayName("토큰을 파싱하여 클레임을 반환한다.") + @Test + void It_returns_claims() { + assertThat(jwtUtil.decode(VALID_TOKEN)).isNotNull(); + assertThat(jwtUtil.decode(VALID_TOKEN).get("userId", Long.class)).isEqualTo(USER_ID); + } + } +} diff --git a/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java new file mode 100644 index 000000000..87a9d8349 --- /dev/null +++ b/app/src/test/java/com/codesoom/assignment/utils/TestHelper.java @@ -0,0 +1,137 @@ +package com.codesoom.assignment.utils; + +import com.codesoom.assignment.domain.Product; +import com.codesoom.assignment.domain.User; +import com.codesoom.assignment.dto.ProductData; +import com.codesoom.assignment.dto.UserLoginData; +import org.junit.jupiter.params.provider.Arguments; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.stream.Stream; + +public class TestHelper { + + public static final String VALID_TOKEN = "eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjF9.neCsyNLzy3lQ4o2yliotWT06FwSGZagaHpKdAkjnGGw"; + public static final String INVALID_TOKEN = VALID_TOKEN + "INVALID"; + public static final String AUTH_NAME = "AUTH_NAME"; + public static final String AUTH_EMAIL = "auth@foo.com"; + public static final String INVALID_EMAIL = AUTH_EMAIL + "INVALID"; + public static final String AUTH_PASSWORD = "12345678"; + public static final String TEST_PRODUCT_NAME = "쥐돌이"; + public static final String TEST_UPDATE_PRODUCT_NAME = "쥐순이"; + public static final String TEST_PRODUCT_MAKER = "냥이월드"; + public static final int TEST_PRODUCT_PRICE = 5000; + public static final String INVALID_PASSWORD = AUTH_PASSWORD + "INVALID"; + public static final MockHttpServletRequest INVALID_SERVLET_REQUEST = new MockHttpServletRequest(); + private static final String TEST_LONG_PASSWORD = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut dignissim ex vitae congue congue. Nunc fermentum tellus leo. Donec malesuada, dolor non euismod suscipit, quam elit scelerisque ligula, in finibus eros justo eu justo. Duis tempor porta odio, id finibus nibh pellentesque congue. Ut et velit eget nibh tincidunt porta et id risus. Vestibulum suscipit ullamcorper varius. Proin eget arcu quam. Cras id feugiat libero. Integer auctor sem nec tempor pellentesque. Donec tempor molestie ex in viverra. Aliquam nec purus consequat purus ullamcorper tristique eu sodales erat. Nunc vitae accumsan orci. Vestibulum dictum ante non hendrerit convallis. Ut eu interdum nisl.\n" + + "\n" + + "Vestibulum et tellus tortor. Maecenas vulputate urna eu massa mattis, eu vulputate magna pretium. Vestibulum at sapien vitae mi tempus elementum at eget ante. Morbi risus dolor, eleifend eu ante sed, commodo aliquam augue. Pellentesque aliquet, tellus ultrices fermentum bibendum, turpis urna mollis mauris, sagittis posuere dolor mi et enim. Quisque mollis vulputate est vel eleifend. Donec nec sollicitudin massa. Sed mattis posuere metus sed dictum. Pellentesque varius est a arcu vulputate sollicitudin.\n" + + "\n" + + "Cras ac diam vehicula, elementum mauris tempus, accumsan lacus. Sed lectus diam, hendrerit a consequat id, eleifend eget libero. Praesent laoreet tempor magna et imperdiet. Aenean dictum non velit id lacinia. Donec congue ante dui, id rutrum ex accumsan at. Nulla ut massa elementum, posuere nunc sit amet, ornare nisl. Pellentesque in dui ipsum. Vivamus placerat velit sit amet tempus efficitur.\n" + + "\n" + + "Donec auctor lacus sit amet neque luctus, vitae tincidunt tortor lobortis. Fusce aliquam sem ut magna sollicitudin, ac vulputate est placerat. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nullam scelerisque augue elit, at bibendum libero efficitur ac. Sed fringilla purus pretium tortor condimentum imperdiet. Praesent in nibh lacinia, euismod enim eu, bibendum felis. Aliquam quis placerat ipsum. Integer dictum volutpat."; + + public static final UserLoginData IS_NOT_EXISTS_USER_DATA = UserLoginData.builder() + .email(INVALID_EMAIL) + .password(AUTH_PASSWORD).build(); + + public static final UserLoginData INVALID_PASSWORD_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password(INVALID_PASSWORD) + .build(); + + public static final UserLoginData AUTH_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static final User AUTH_USER = User.builder() + .name(AUTH_NAME) + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static final Product TEST_PRODUCT = Product.builder() + .id(1L) + .name(TEST_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static final ProductData TEST_PRODUCT_DATA = ProductData.builder() + .name(TEST_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static Stream provideInvalidProductRequests() { + return Stream.of( + Arguments.of(ProductData.builder().name("").maker("").price(0).build()), + Arguments.of(ProductData.builder().name("").maker(TEST_PRODUCT_MAKER).price(TEST_PRODUCT_PRICE).build()), + Arguments.of(ProductData.builder().name(TEST_PRODUCT_NAME).maker("").price(TEST_PRODUCT_PRICE).build()), + Arguments.of(ProductData.builder().name(TEST_PRODUCT_NAME).maker(TEST_PRODUCT_MAKER).price(null).build()) + ); + } + + public static final ProductData UPDATE_PRODUCT_DATA = ProductData.builder() + .name(TEST_UPDATE_PRODUCT_NAME) + .maker(TEST_PRODUCT_MAKER) + .price(TEST_PRODUCT_PRICE) + .build(); + + public static MockHttpServletRequest getInvalidTokenServletRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + INVALID_TOKEN); + return request; + } + + public static MockHttpServletRequest getValidTokenServletRequest() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.addHeader("Authorization", "Bearer " + VALID_TOKEN); + return request; + } + + public static UserLoginData AUTH_USER_LOGIN_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password(AUTH_PASSWORD) + .build(); + + public static UserLoginData EMAIL_NULL_LOGIN_USER_DATA = UserLoginData.builder() + .password(AUTH_PASSWORD) + .build(); + + public static UserLoginData PASSWORD_NULL_LOGIN_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .build(); + + public static UserLoginData EMPTY_LOGIN_USER_DATA = UserLoginData.builder() + .email("") + .password("") + .build(); + + public static UserLoginData EMAIL_IS_SHORT_LOGIN_USER_DATA = UserLoginData.builder() + .email("aa") + .password(AUTH_PASSWORD) + .build(); + + public static UserLoginData PASSWORD_IS_TOO_SHORT_LOGIN_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password("111") + .build(); + + public static UserLoginData PASSWORD_IS_TOO_LONG_LOGIN_USER_DATA = UserLoginData.builder() + .email(AUTH_EMAIL) + .password(TEST_LONG_PASSWORD) + .build(); + + public static Stream provideInvalidUserLoginRequests() { + return Stream.of( + Arguments.of(EMAIL_NULL_LOGIN_USER_DATA), + Arguments.of(PASSWORD_NULL_LOGIN_USER_DATA), + Arguments.of(EMPTY_LOGIN_USER_DATA), + Arguments.of(EMAIL_IS_SHORT_LOGIN_USER_DATA), + Arguments.of(PASSWORD_IS_TOO_SHORT_LOGIN_USER_DATA), + Arguments.of(PASSWORD_IS_TOO_LONG_LOGIN_USER_DATA) + ); + } +}