From 8c3f5d8e658015ace3dd0058341f411374da38d8 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Thu, 3 Aug 2023 10:27:48 +0900 Subject: [PATCH 01/20] =?UTF-8?q?[refactor]=20:=20UserVO=EC=9D=84=20UserDt?= =?UTF-8?q?o=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 12 ++++---- .../com/swger/tddstudy/user/domain/User.java | 7 +++-- .../user/domain/{UserVO.java => UserDto.java} | 5 ++-- .../swger/tddstudy/user/domain/UserType.java | 1 - .../user/repository/UserRepository.java | 1 - .../tddstudy/user/request/JoinRequest.java | 7 ++--- .../tddstudy/user/request/LoginRequest.java | 6 ++-- .../restController/UserRestController.java | 5 ++-- .../tddstudy/user/service/UserService.java | 30 +++++++++---------- .../com/swger/tddstudy/user/UserTest.java | 18 +++++------ .../user/controller/UserControllerTest.java | 15 +++++----- 11 files changed, 51 insertions(+), 56 deletions(-) rename src/main/java/com/swger/tddstudy/user/domain/{UserVO.java => UserDto.java} (85%) diff --git a/src/main/java/com/swger/tddstudy/user/controller/UserController.java b/src/main/java/com/swger/tddstudy/user/controller/UserController.java index 6fae1d9..cfc93d1 100644 --- a/src/main/java/com/swger/tddstudy/user/controller/UserController.java +++ b/src/main/java/com/swger/tddstudy/user/controller/UserController.java @@ -1,12 +1,15 @@ package com.swger.tddstudy.user.controller; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpSession; @@ -23,7 +26,7 @@ public String joinForm() { } @GetMapping("/login-form") - public String loginForm() throws Exception { + public String loginForm() { return "userPages/login"; } @@ -35,7 +38,7 @@ public String join(@ModelAttribute JoinRequest joinRequest) { @PostMapping("/login") public String login(@ModelAttribute LoginRequest loginRequest, HttpSession session) { - UserVO loginResult = userService.login(loginRequest); + UserDto loginResult = userService.login(loginRequest); if (loginResult != null) { session.setAttribute("username", loginResult.getUsername()); session.setAttribute("id", loginResult.getId()); @@ -44,5 +47,4 @@ public String login(@ModelAttribute LoginRequest loginRequest, HttpSession sessi return "userPages/login"; } } - } diff --git a/src/main/java/com/swger/tddstudy/user/domain/User.java b/src/main/java/com/swger/tddstudy/user/domain/User.java index 3861cef..206b629 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/User.java +++ b/src/main/java/com/swger/tddstudy/user/domain/User.java @@ -13,6 +13,7 @@ @Entity @Table(name = "Users") public class User extends BaseEntity { + @Id @Column(name = "id", nullable = false) @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -36,9 +37,9 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private UserType type; - public UserVO toUserVO() { - UserVO userVO = UserVO.builder().id(this.id).username(this.username).password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()).type(this.type.name()).build(); - return userVO; + public UserDto toUserDto() { + UserDto userDto = UserDto.builder().id(this.id).username(this.username).password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()).type(this.type.name()).build(); + return userDto; } public void levelUp() { diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserVO.java b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java similarity index 85% rename from src/main/java/com/swger/tddstudy/user/domain/UserVO.java rename to src/main/java/com/swger/tddstudy/user/domain/UserDto.java index 1ef2533..6507ee9 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserVO.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java @@ -8,7 +8,8 @@ @Getter @Setter @Builder -public class UserVO { +public class UserDto { + private Long id; private String username; private String password; @@ -16,7 +17,7 @@ public class UserVO { private String userLevel; private String type; - public UserVO(String username, String password, String nickname, String userLevel, String type) { + public UserDto(String username, String password, String nickname, String userLevel, String type) { this.username = username; this.password = password; this.nickname = nickname; diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserType.java b/src/main/java/com/swger/tddstudy/user/domain/UserType.java index 73912c7..bef358c 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserType.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserType.java @@ -8,5 +8,4 @@ public enum UserType { USER("일반회원"), ADMIN("관리자"); private final String text; - } diff --git a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java index b938c8f..a467a2e 100644 --- a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java +++ b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java @@ -2,7 +2,6 @@ import com.swger.tddstudy.user.domain.User; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; import java.util.Optional; diff --git a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java index 2689943..e92a6a9 100644 --- a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java @@ -1,6 +1,6 @@ package com.swger.tddstudy.user.request; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import lombok.*; import javax.validation.constraints.NotBlank; @@ -25,8 +25,7 @@ public class JoinRequest { @Size(min = 2, max = 10, message = "닉네임을 2자 이상 10자 이하로 입력하세요. ") private String nickname; - public UserVO toUserVO() { - return UserVO.builder().username(this.username).password(this.password).nickname(this.nickname).build(); + public UserDto toUserDto() { + return UserDto.builder().username(this.username).password(this.password).nickname(this.nickname).build(); } - } diff --git a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java index d777504..de4ebf0 100644 --- a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java @@ -1,6 +1,6 @@ package com.swger.tddstudy.user.request; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import lombok.*; import javax.validation.constraints.NotBlank; @@ -21,7 +21,7 @@ public class LoginRequest { @Size(min = 8, max = 15, message = "비밀번호를 8자 이상 10자 이하로 입력하세요.") private String password; - public UserVO toUserVO() { - return UserVO.builder().username(this.username).password(this.password).build(); + public UserDto toUserDto() { + return UserDto.builder().username(this.username).password(this.password).build(); } } diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java b/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java index 652ddc9..083e594 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java +++ b/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java @@ -1,6 +1,6 @@ package com.swger.tddstudy.user.restController; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; @@ -43,12 +43,11 @@ public ResponseEntity joinAdmin(@Valid @RequestBody JoinRequest joinRequest) @PostMapping("/login") public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest, HttpSession session) { Map message = new HashMap<>(); - UserVO loginResult = userService.login(loginRequest); + UserDto loginResult = userService.login(loginRequest); session.setAttribute("username", loginResult.getUsername()); session.setAttribute("id", loginResult.getId()); message.put("status", 200); message.put("data", loginResult); return ResponseEntity.status(HttpStatus.OK).body(message); } - } diff --git a/src/main/java/com/swger/tddstudy/user/service/UserService.java b/src/main/java/com/swger/tddstudy/user/service/UserService.java index ea3ef86..461ba60 100644 --- a/src/main/java/com/swger/tddstudy/user/service/UserService.java +++ b/src/main/java/com/swger/tddstudy/user/service/UserService.java @@ -1,46 +1,44 @@ package com.swger.tddstudy.user.service; import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.domain.UserLevel; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.exception.LoginFailureException; import com.swger.tddstudy.user.repository.UserRepository; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; -import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import javax.transaction.Transactional; import java.util.Optional; -import java.util.regex.Pattern; @Service @RequiredArgsConstructor @Transactional public class UserService { + private final UserRepository userRepository; - public UserVO save(JoinRequest joinRequest) { - UserVO userVO = joinRequest.toUserVO(); - userVO.setUserLevel("BRONZE"); - userVO.setType("USER"); - return userRepository.save(userVO.toEntity()).toUserVO(); + public UserDto save(JoinRequest joinRequest) { + UserDto userDto = joinRequest.toUserDto(); + userDto.setUserLevel("BRONZE"); + userDto.setType("USER"); + return userRepository.save(userDto.toEntity()).toUserDto(); } - public UserVO saveAdmin(JoinRequest joinRequest) { - UserVO userVO = joinRequest.toUserVO(); - userVO.setUserLevel("BRONZE"); - userVO.setType("ADMIN"); - return userRepository.save(userVO.toEntity()).toUserVO(); + public UserDto saveAdmin(JoinRequest joinRequest) { + UserDto userDto = joinRequest.toUserDto(); + userDto.setUserLevel("BRONZE"); + userDto.setType("ADMIN"); + return userRepository.save(userDto.toEntity()).toUserDto(); } - public UserVO login(LoginRequest loginRequest) { + public UserDto login(LoginRequest loginRequest) { Optional optionalUserEntity = userRepository.findByUsername(loginRequest.getUsername()); if (optionalUserEntity.isPresent()) { User loginEntity = optionalUserEntity.get(); if (loginEntity.getPassword().equals(loginRequest.getPassword())) { - return loginEntity.toUserVO(); + return loginEntity.toUserDto(); } else { throw new LoginFailureException("비밀번호가 일치하지 않습니다."); } diff --git a/src/test/java/com/swger/tddstudy/user/UserTest.java b/src/test/java/com/swger/tddstudy/user/UserTest.java index 09ba648..a9690f4 100644 --- a/src/test/java/com/swger/tddstudy/user/UserTest.java +++ b/src/test/java/com/swger/tddstudy/user/UserTest.java @@ -1,13 +1,12 @@ package com.swger.tddstudy.user; import com.swger.tddstudy.user.domain.User; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.domain.UserLevel; import com.swger.tddstudy.user.domain.UserType; -import com.swger.tddstudy.user.domain.UserVO; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -25,8 +24,8 @@ public class UserTest { @Autowired private UserService userService; - public UserVO newUser() { - return new UserVO("abc", "abcd1234!", "nickname", "BRONZE", "USER"); + public UserDto newUser() { + return new UserDto("abc", "abcd1234!", "nickname", "BRONZE", "USER"); } @Test @@ -34,8 +33,8 @@ public UserVO newUser() { @DisplayName("회원가입 테스트 (USER)") public void userJoinTest1() { JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO savedUserVO = userService.save(joinRequest); - assertThat(savedUserVO).extracting("username", "password", "nickname", "userLevel", "type") + UserDto savedUserDto = userService.save(joinRequest); + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type") .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); } @@ -44,8 +43,8 @@ public void userJoinTest1() { @DisplayName("회원가입 테스트 (ADMIN)") public void userJoinTest2() { JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO savedUserVO = userService.saveAdmin(joinRequest); - assertThat(savedUserVO).extracting("username", "password", "nickname", "userLevel", "type") + UserDto savedUserDto = userService.saveAdmin(joinRequest); + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type") .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); } @@ -57,7 +56,7 @@ public void userloginTest1() { userService.save(joinRequest); // 로그인 객체 생성 후 로그인 LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); - UserVO loginResult = userService.login(loginRequest); + UserDto loginResult = userService.login(loginRequest); // 로그인 결과가 UserVO면 성공 assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", "type") .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); @@ -89,5 +88,4 @@ public void levelUpTest3() { user.levelUp(); assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); } - } diff --git a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java b/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java index 7fe809e..8a61234 100644 --- a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java +++ b/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java @@ -1,7 +1,7 @@ package com.swger.tddstudy.user.controller; import com.fasterxml.jackson.databind.ObjectMapper; -import com.swger.tddstudy.user.domain.UserVO; +import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.restController.UserRestController; @@ -14,7 +14,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -36,10 +35,10 @@ public class UserControllerTest { void joinTest1() throws Exception { JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserVO UserVO = new UserVO(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); + UserDto UserDto = new UserDto(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); given(userService.save(joinRequest)).willReturn( - UserVO + UserDto ); String json = new ObjectMapper().writeValueAsString(joinRequest); @@ -63,10 +62,10 @@ void joinTest1() throws Exception { @DisplayName("회원가입 테스트 (양식 틀림)") void joinTest2() throws Exception { JoinRequest joinRequest = new JoinRequest("abc", "abc123", "nickname"); - UserVO UserVO = new UserVO(0L, "abc", "abc123", "nickname", "BRONZE", "USER"); + UserDto UserDto = new UserDto(0L, "abc", "abc123", "nickname", "BRONZE", "USER"); given(userService.save(joinRequest)).willReturn( - UserVO + UserDto ); String json = new ObjectMapper().writeValueAsString(joinRequest); @@ -83,12 +82,12 @@ void joinTest2() throws Exception { @Test @DisplayName("로그인 테스트") void loginTest1() throws Exception { - UserVO userVO = new UserVO(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); + UserDto userDto = new UserDto(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); given(userService.login(loginRequest)).willReturn( - userVO + userDto ); String json = new ObjectMapper().writeValueAsString(loginRequest); From 00d3067c1950598147760ac897cd289438c389ec Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Thu, 3 Aug 2023 22:54:11 +0900 Subject: [PATCH 02/20] =?UTF-8?q?[refactor]=20:=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EC=9E=90=EB=B0=94=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=9D=84=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 +++++----- .../user/controller/UserController.java | 3 +-- .../com/swger/tddstudy/user/domain/User.java | 22 +++++++++++++++---- .../swger/tddstudy/user/domain/UserDto.java | 14 ++++++++---- .../swger/tddstudy/user/domain/UserLevel.java | 4 +++- .../user/exception/LoginFailureException.java | 3 ++- .../user/repository/UserRepository.java | 4 ++-- .../tddstudy/user/request/JoinRequest.java | 10 +++++---- .../tddstudy/user/request/LoginRequest.java | 7 +++--- .../restController/UserRestController.java | 12 +++++----- .../UserRestControllerAdvice.java | 19 ++++++++-------- 11 files changed, 67 insertions(+), 43 deletions(-) diff --git a/build.gradle b/build.gradle index d72096c..84ec5d8 100644 --- a/build.gradle +++ b/build.gradle @@ -25,15 +25,15 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' - compileOnly 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'com.fasterxml.jackson.core:jackson-core:2.11.4' + implementation 'com.fasterxml.jackson.core:jackson-annotations:2.11.4' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.11.4' + implementation 'com.fasterxml.jackson.module:jackson-module-kotlin:2.11.4' runtimeOnly 'com.h2database:h2' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' - implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' - implementation "com.fasterxml.jackson.core:jackson-core:2.11.4" - implementation "com.fasterxml.jackson.core:jackson-annotations:2.11.4" - implementation "com.fasterxml.jackson.core:jackson-databind:2.11.4" - implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.11.4" } tasks.named('test') { diff --git a/src/main/java/com/swger/tddstudy/user/controller/UserController.java b/src/main/java/com/swger/tddstudy/user/controller/UserController.java index cfc93d1..5c09f39 100644 --- a/src/main/java/com/swger/tddstudy/user/controller/UserController.java +++ b/src/main/java/com/swger/tddstudy/user/controller/UserController.java @@ -4,6 +4,7 @@ import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; +import javax.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @@ -11,8 +12,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import javax.servlet.http.HttpSession; - @Controller @RequiredArgsConstructor @RequestMapping("user") diff --git a/src/main/java/com/swger/tddstudy/user/domain/User.java b/src/main/java/com/swger/tddstudy/user/domain/User.java index 206b629..b707cbc 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/User.java +++ b/src/main/java/com/swger/tddstudy/user/domain/User.java @@ -1,9 +1,21 @@ package com.swger.tddstudy.user.domain; import com.swger.tddstudy.util.BaseEntity; -import lombok.*; - -import javax.persistence.*; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; @Getter @Setter @@ -38,7 +50,9 @@ public class User extends BaseEntity { private UserType type; public UserDto toUserDto() { - UserDto userDto = UserDto.builder().id(this.id).username(this.username).password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()).type(this.type.name()).build(); + UserDto userDto = UserDto.builder().id(this.id).username(this.username) + .password(this.password).nickname(this.nickname).userLevel(this.userLevel.name()) + .type(this.type.name()).build(); return userDto; } diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserDto.java b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java index 6507ee9..f449b96 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserDto.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserDto.java @@ -1,6 +1,11 @@ package com.swger.tddstudy.user.domain; -import lombok.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; @Data @NoArgsConstructor @@ -17,7 +22,8 @@ public class UserDto { private String userLevel; private String type; - public UserDto(String username, String password, String nickname, String userLevel, String type) { + public UserDto(String username, String password, String nickname, String userLevel, + String type) { this.username = username; this.password = password; this.nickname = nickname; @@ -27,7 +33,7 @@ public UserDto(String username, String password, String nickname, String userLev public User toEntity() { return User.builder().username(this.username) - .password(this.password).nickname(this.nickname) - .userLevel(UserLevel.valueOf(this.userLevel)).type(UserType.valueOf(this.type)).build(); + .password(this.password).nickname(this.nickname) + .userLevel(UserLevel.valueOf(this.userLevel)).type(UserType.valueOf(this.type)).build(); } } diff --git a/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java b/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java index c83833b..be17687 100644 --- a/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java +++ b/src/main/java/com/swger/tddstudy/user/domain/UserLevel.java @@ -12,7 +12,9 @@ public enum UserLevel { public UserLevel levelUp() { UserLevel[] levels = values(); - if (this.level >= levels.length - 1) return this; + if (this.level >= levels.length - 1) { + return this; + } return levels[this.level + 1]; } } diff --git a/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java b/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java index d4ba025..e78b858 100644 --- a/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java +++ b/src/main/java/com/swger/tddstudy/user/exception/LoginFailureException.java @@ -1,6 +1,7 @@ package com.swger.tddstudy.user.exception; -public class LoginFailureException extends RuntimeException{ +public class LoginFailureException extends RuntimeException { + public LoginFailureException(String message) { super(message); } diff --git a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java index a467a2e..aa03d6b 100644 --- a/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java +++ b/src/main/java/com/swger/tddstudy/user/repository/UserRepository.java @@ -1,10 +1,10 @@ package com.swger.tddstudy.user.repository; import com.swger.tddstudy.user.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; - import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository { + Optional findByUsername(String username); } diff --git a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java index e92a6a9..42f1605 100644 --- a/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/JoinRequest.java @@ -1,16 +1,17 @@ package com.swger.tddstudy.user.request; import com.swger.tddstudy.user.domain.UserDto; -import lombok.*; - import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder @NoArgsConstructor @AllArgsConstructor -@EqualsAndHashCode public class JoinRequest { @NotBlank(message = "이름을 입력하세요.") @@ -26,6 +27,7 @@ public class JoinRequest { private String nickname; public UserDto toUserDto() { - return UserDto.builder().username(this.username).password(this.password).nickname(this.nickname).build(); + return UserDto.builder().username(this.username).password(this.password) + .nickname(this.nickname).build(); } } diff --git a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java index de4ebf0..148ae2b 100644 --- a/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java +++ b/src/main/java/com/swger/tddstudy/user/request/LoginRequest.java @@ -1,16 +1,17 @@ package com.swger.tddstudy.user.request; import com.swger.tddstudy.user.domain.UserDto; -import lombok.*; - import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; @Getter @Builder @NoArgsConstructor @AllArgsConstructor -@EqualsAndHashCode public class LoginRequest { @NotBlank(message = "이름을 입력하세요.") diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java b/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java index 083e594..3d22883 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java +++ b/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java @@ -4,6 +4,10 @@ import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.service.UserService; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -12,11 +16,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpSession; -import javax.validation.Valid; -import java.util.HashMap; -import java.util.Map; - @RestController @RequiredArgsConstructor @RequestMapping("api/user") @@ -41,7 +40,8 @@ public ResponseEntity joinAdmin(@Valid @RequestBody JoinRequest joinRequest) } @PostMapping("/login") - public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest, HttpSession session) { + public ResponseEntity login(@Valid @RequestBody LoginRequest loginRequest, + HttpSession session) { Map message = new HashMap<>(); UserDto loginResult = userService.login(loginRequest); session.setAttribute("username", loginResult.getUsername()); diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java b/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java index 7cba0dd..5bbce6a 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java +++ b/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java @@ -1,6 +1,8 @@ package com.swger.tddstudy.user.restController; import com.swger.tddstudy.user.exception.LoginFailureException; +import java.util.HashMap; +import java.util.Map; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -8,9 +10,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import java.util.HashMap; -import java.util.Map; - @RestControllerAdvice public class UserRestControllerAdvice { @@ -19,8 +18,8 @@ public ResponseEntity bindException(BindException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } @ExceptionHandler @@ -28,16 +27,16 @@ public ResponseEntity illegalArgException(IllegalArgumentException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } @ExceptionHandler - public ResponseEntity LoginFailureException(LoginFailureException e) { + public ResponseEntity loginFailureException(LoginFailureException e) { Map errorMessage = new HashMap<>(); errorMessage.put("error", e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST) - .contentType(MediaType.APPLICATION_JSON) - .body(errorMessage); + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); } } From 2115ebf7994c1fb1c2a3d2a6d4264047378f3c4d Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Thu, 3 Aug 2023 22:58:40 +0900 Subject: [PATCH 03/20] =?UTF-8?q?[test]=20:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=84=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserTest 클래스를 삭제하고 UserServiceTest를 생성하여 단위테스트를 진행 - mock 객체를 통한 독립적인 테스트 코드 구현 - 예외처리 확인 테스트 코드 추가 - inner 클래스를 활용한 테스트 세분화 --- .../com/swger/tddstudy/user/UserTest.java | 91 ------- .../user/controller/UserControllerTest.java | 170 +++++++++---- .../user/service/UserServiceTest.java | 230 ++++++++++++++++++ 3 files changed, 349 insertions(+), 142 deletions(-) delete mode 100644 src/test/java/com/swger/tddstudy/user/UserTest.java create mode 100644 src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java diff --git a/src/test/java/com/swger/tddstudy/user/UserTest.java b/src/test/java/com/swger/tddstudy/user/UserTest.java deleted file mode 100644 index a9690f4..0000000 --- a/src/test/java/com/swger/tddstudy/user/UserTest.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.swger.tddstudy.user; - -import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.domain.UserDto; -import com.swger.tddstudy.user.domain.UserLevel; -import com.swger.tddstudy.user.domain.UserType; -import com.swger.tddstudy.user.request.JoinRequest; -import com.swger.tddstudy.user.request.LoginRequest; -import com.swger.tddstudy.user.service.UserService; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.annotation.Rollback; - -import javax.transaction.Transactional; - -import static org.assertj.core.api.Assertions.assertThat; - -@SpringBootTest -@Transactional -public class UserTest { - - @Autowired - private UserService userService; - - public UserDto newUser() { - return new UserDto("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("회원가입 테스트 (USER)") - public void userJoinTest1() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserDto savedUserDto = userService.save(joinRequest); - assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("회원가입 테스트 (ADMIN)") - public void userJoinTest2() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserDto savedUserDto = userService.saveAdmin(joinRequest); - assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); - } - - @Test - @Rollback(value = true) - @DisplayName("로그인 테스트") - public void userloginTest1() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - userService.save(joinRequest); - // 로그인 객체 생성 후 로그인 - LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); - UserDto loginResult = userService.login(loginRequest); - // 로그인 결과가 UserVO면 성공 - assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", "type") - .containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (BRONZE -> SILVER)") - public void levelUpTest1() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.BRONZE).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.SILVER); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (SILVER -> GOLD)") - public void levelUpTest2() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.SILVER).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); - } - - @Test - @Rollback(value = true) - @DisplayName("레벨업 테스트 (GOLD -> GOLD)") - public void levelUpTest3() { - User user = User.builder().id(0L).username("abc").password("abcd1234!").nickname("abcd").userLevel(UserLevel.GOLD).type(UserType.USER).build(); - user.levelUp(); - assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); - } -} diff --git a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java b/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java index 8a61234..ac1afda 100644 --- a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java +++ b/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java @@ -1,12 +1,24 @@ package com.swger.tddstudy.user.controller; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.fasterxml.jackson.databind.ObjectMapper; import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.exception.LoginFailureException; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; import com.swger.tddstudy.user.restController.UserRestController; import com.swger.tddstudy.user.service.UserService; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -14,13 +26,6 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @WebMvcTest(UserRestController.class) public class UserControllerTest { @@ -30,22 +35,48 @@ public class UserControllerTest { @MockBean UserService userService; - @Test - @DisplayName("회원가입 테스트 (양식 맞음)") - void joinTest1() throws Exception { + @Nested + @DisplayName("회원가입 컨트롤러 : ") + class JoinControllerTest { + + private JoinRequest newJoinRequest() { + + return JoinRequest.builder().username("abc").password("abcd1234!").nickname("nickname") + .build(); + } + + private JoinRequest newWrongJoinRequest() { - JoinRequest joinRequest = new JoinRequest("abc", "abcd1234!", "nickname"); - UserDto UserDto = new UserDto(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); + return JoinRequest.builder().username("abc").password("abc123").nickname("nickname") + .build(); + } - given(userService.save(joinRequest)).willReturn( - UserDto - ); + private UserDto newUserDto() { - String json = new ObjectMapper().writeValueAsString(joinRequest); + return UserDto.builder().id(0L).username("abc").password("abcd1234!") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); + } - mockMvc.perform(post("/api/user/join") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) + private UserDto newWrongUserDto() { + + return UserDto.builder().id(0L).username("abc").password("abc123") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); + } + + @Test + @DisplayName("양식에 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(userService.save(any(JoinRequest.class))).willReturn( + newUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newJoinRequest()); + + // when, then + mockMvc.perform(post("/api/user/join") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.id").exists()) .andExpect(jsonPath("$.data.username").exists()) @@ -54,47 +85,65 @@ void joinTest1() throws Exception { .andExpect(jsonPath("$.data.userLevel").exists()) .andExpect(jsonPath("$.data.type").exists()) .andDo(print()); - - verify(userService).save(joinRequest); + verify(userService, times(1)).save(any(JoinRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(userService.save(any(JoinRequest.class))).willReturn( + newWrongUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newWrongJoinRequest()); + + // when, then + mockMvc.perform(post("/api/user/join") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()) + .andDo(print()); + verify(userService, never()).save(newJoinRequest()); + } } - @Test - @DisplayName("회원가입 테스트 (양식 틀림)") - void joinTest2() throws Exception { - JoinRequest joinRequest = new JoinRequest("abc", "abc123", "nickname"); - UserDto UserDto = new UserDto(0L, "abc", "abc123", "nickname", "BRONZE", "USER"); + @Nested + @DisplayName("로그인 컨트롤러 테스트 : ") + class LoginControllerTest { - given(userService.save(joinRequest)).willReturn( - UserDto - ); + private LoginRequest newLoginRequest() { - String json = new ObjectMapper().writeValueAsString(joinRequest); + return LoginRequest.builder().username("abc").password("abcd1234!").build(); + } - mockMvc.perform(post("/api/user/join") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) - .andExpect(status().isBadRequest()) - .andExpect(jsonPath("$.error").exists()) - .andDo(print()); - } + private LoginRequest newWrongLoginRequest() { + + return LoginRequest.builder().username("abc").password("abc123").build(); + } + private UserDto newUserDto() { - @Test - @DisplayName("로그인 테스트") - void loginTest1() throws Exception { - UserDto userDto = new UserDto(0L, "abc", "abcd1234!", "nickname", "BRONZE", "USER"); + return UserDto.builder().id(0L).username("abc").password("abcd1234!") + .nickname("nickname").userLevel("BRONZE").type("USER").build(); - LoginRequest loginRequest = LoginRequest.builder().username("abc").password("abcd1234!").build(); + } - given(userService.login(loginRequest)).willReturn( - userDto - ); + @Test + @DisplayName("양식에 맞을 경우 성공") + void Test1() throws Exception { - String json = new ObjectMapper().writeValueAsString(loginRequest); + // given + given(userService.login(any(LoginRequest.class))).willReturn( + newUserDto() + ); + String json = new ObjectMapper().writeValueAsString(newLoginRequest()); - mockMvc.perform(post("/api/user/login") - .content(json) - .contentType(MediaType.APPLICATION_JSON)) + // when, then + mockMvc.perform(post("/api/user/login") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.data.id").exists()) .andExpect(jsonPath("$.data.username").exists()) @@ -103,7 +152,26 @@ void loginTest1() throws Exception { .andExpect(jsonPath("$.data.userLevel").exists()) .andExpect(jsonPath("$.data.type").exists()) .andDo(print()); - - verify(userService).login(loginRequest); + verify(userService, times(1)).login(any(LoginRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(userService.login(any(LoginRequest.class))).willThrow( + LoginFailureException.class); + String json = new ObjectMapper().writeValueAsString(newWrongLoginRequest()); + + // when, then + mockMvc.perform(post("/api/user/login") + .content(json) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()) + .andDo(print()); + verify(userService, never()).login(any(LoginRequest.class)); + } } } diff --git a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java new file mode 100644 index 0000000..99f1077 --- /dev/null +++ b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java @@ -0,0 +1,230 @@ +package com.swger.tddstudy.user.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +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.swger.tddstudy.user.domain.User; +import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.domain.UserLevel; +import com.swger.tddstudy.user.domain.UserType; +import com.swger.tddstudy.user.exception.LoginFailureException; +import com.swger.tddstudy.user.repository.UserRepository; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.request.LoginRequest; +import java.util.Optional; +import javax.transaction.Transactional; +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; + +@Transactional +@ExtendWith(MockitoExtension.class) +public class UserServiceTest { + + @InjectMocks + private UserService userService; + + @Mock + private UserRepository userRepository; + + @Nested + @DisplayName("회원가입 서비스 : ") + class JoinServiceTest { + + private JoinRequest newJoinRequest() { + + return JoinRequest.builder().username("abc").password("abcd1234!").nickname("nickname") + .build(); + } + + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newAdmin() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.ADMIN).build(); + } + + @Test + @DisplayName("일반회원 회원가입 성공") + void Test1() { + + // given + given(userRepository.save(any(User.class))).willReturn(newUser()); + + // when + UserDto savedUserDto = userService.save(newJoinRequest()); + + // then + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); + + verify(userRepository, times(1)).save(any(User.class)); + } + + @Test + @DisplayName("관리자 회원가입 성공") + void Test2() { + + // given + given(userRepository.save(any(User.class))).willReturn(newAdmin()); + + // when + UserDto savedUserDto = userService.saveAdmin(newJoinRequest()); + + // then + assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); + + verify(userRepository, times(1)).save(any(User.class)); + } + } + + @Nested + @DisplayName("로그인 서비스 : ") + class LoginServiceTest { + + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private LoginRequest newLoginRequest() { + + return LoginRequest.builder().username("abc").password("abcd1234!").build(); + } + + private LoginRequest newWrongLoginRequest() { + + return LoginRequest.builder().username("abc").password("abcd5678!").build(); + } + + @Test + @DisplayName("이름에 해당하는 회원이 있고, 비밀번호까지 일치하는 경우 성공") + void Test1() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findByUsername(any(String.class))).willReturn(optionalUser); + + // when + UserDto loginResult = userService.login(newLoginRequest()); + + // then + assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", + "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); + + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + + @Test + @DisplayName("이름이 일치하는 회원이 없을 경우 실패") + void Test2() { + + // given + given(userRepository.findByUsername(any(String.class))).willReturn(Optional.empty()); + + // when, then + Throwable exception = assertThrows(LoginFailureException.class, () -> { + userService.login(newLoginRequest()); + }); + assertEquals("일치하는 회원정보가 없습니다.", exception.getMessage()); + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + + @Test + @DisplayName("비밀번호가 일치하지 않는 경우 실패") + void Test3() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findByUsername(any(String.class))).willReturn(optionalUser); + + // when, then + Throwable exception = assertThrows(LoginFailureException.class, () -> { + userService.login(newWrongLoginRequest()); + }); + assertEquals("비밀번호가 일치하지 않습니다.", exception.getMessage()); + verify(userRepository, times(1)).findByUsername(any(String.class)); + } + } + + @Nested + @DisplayName("레벨업 서비스 : ") + class LevelUpServiceTest { + + private User newUserBronze() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newUserSilver() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.SILVER).type(UserType.USER).build(); + } + + private User newUserGold() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.GOLD).type(UserType.USER).build(); + } + + @Test + @DisplayName("BRONZE -> SILVER") + void Test1() { + + // given + User user = newUserBronze(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.SILVER); + } + + @Test + @DisplayName("SILVER -> GOLD") + void Test2() { + + // given + User user = newUserSilver(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); + } + + @Test + @DisplayName("GOLD -> GOLD") + void Test3() { + + // given + User user = newUserGold(); + + // when + user.levelUp(); + + // then + assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); + } + } +} \ No newline at end of file From 6a289e74c28d6f39d3190ba8fed489a33d00efd2 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:34:38 +0900 Subject: [PATCH 04/20] =?UTF-8?q?[chore]=20:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85,=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - restController -> restcontroller - UserControllerTest -> UserRestControllerTest --- .../UserRestController.java | 2 +- .../UserRestControllerAdvice.java | 2 +- .../{UserControllerTest.java => UserRestControllerTest.java} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/swger/tddstudy/user/{restController => restcontroller}/UserRestController.java (97%) rename src/main/java/com/swger/tddstudy/user/{restController => restcontroller}/UserRestControllerAdvice.java (97%) rename src/test/java/com/swger/tddstudy/user/controller/{UserControllerTest.java => UserRestControllerTest.java} (98%) diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java similarity index 97% rename from src/main/java/com/swger/tddstudy/user/restController/UserRestController.java rename to src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java index 3d22883..8b6a466 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestController.java +++ b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestController.java @@ -1,4 +1,4 @@ -package com.swger.tddstudy.user.restController; +package com.swger.tddstudy.user.restcontroller; import com.swger.tddstudy.user.domain.UserDto; import com.swger.tddstudy.user.request.JoinRequest; diff --git a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java similarity index 97% rename from src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java rename to src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java index 5bbce6a..6d1b8b7 100644 --- a/src/main/java/com/swger/tddstudy/user/restController/UserRestControllerAdvice.java +++ b/src/main/java/com/swger/tddstudy/user/restcontroller/UserRestControllerAdvice.java @@ -1,4 +1,4 @@ -package com.swger.tddstudy.user.restController; +package com.swger.tddstudy.user.restcontroller; import com.swger.tddstudy.user.exception.LoginFailureException; import java.util.HashMap; diff --git a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java b/src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java similarity index 98% rename from src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java rename to src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java index ac1afda..8264d2a 100644 --- a/src/test/java/com/swger/tddstudy/user/controller/UserControllerTest.java +++ b/src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java @@ -15,7 +15,7 @@ import com.swger.tddstudy.user.exception.LoginFailureException; import com.swger.tddstudy.user.request.JoinRequest; import com.swger.tddstudy.user.request.LoginRequest; -import com.swger.tddstudy.user.restController.UserRestController; +import com.swger.tddstudy.user.restcontroller.UserRestController; import com.swger.tddstudy.user.service.UserService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -27,7 +27,7 @@ import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(UserRestController.class) -public class UserControllerTest { +public class UserRestControllerTest { @Autowired private MockMvc mockMvc; From f1258dfba86c9836033e056c24767b43502ee708 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 15:59:40 +0900 Subject: [PATCH 05/20] =?UTF-8?q?[chore]=20:=20=ED=8C=A8=ED=82=A4=EC=A7=80?= =?UTF-8?q?=EB=AA=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{controller => restcontroller}/UserRestControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/java/com/swger/tddstudy/user/{controller => restcontroller}/UserRestControllerTest.java (99%) diff --git a/src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java b/src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java similarity index 99% rename from src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java rename to src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java index 8264d2a..5be27ba 100644 --- a/src/test/java/com/swger/tddstudy/user/controller/UserRestControllerTest.java +++ b/src/test/java/com/swger/tddstudy/user/restcontroller/UserRestControllerTest.java @@ -1,4 +1,4 @@ -package com.swger.tddstudy.user.controller; +package com.swger.tddstudy.user.restcontroller; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; From dadbdcf67ec9da8bcade85ef109838104b23e5ec Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:02:11 +0900 Subject: [PATCH 06/20] =?UTF-8?q?[test]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductServiceTest.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java diff --git a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java new file mode 100644 index 0000000..07e0a6e --- /dev/null +++ b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java @@ -0,0 +1,107 @@ +package com.swger.tddstudy.product.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +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.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.SellingStatus; +import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.repository.ProductRepository; +import com.swger.tddstudy.user.domain.User; +import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.domain.UserLevel; +import com.swger.tddstudy.user.domain.UserType; +import com.swger.tddstudy.user.exception.LoginFailureException; +import com.swger.tddstudy.user.repository.UserRepository; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.service.UserService; +import java.util.Optional; +import javax.transaction.Transactional; +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; + +@Transactional +@ExtendWith(MockitoExtension.class) +public class ProductServiceTest { + + @InjectMocks + private ProductService productService; + + @Mock + private ProductRepository productRepository; + + @Nested + @DisplayName("상품 판매 상태 변경 : ") + class JoinServiceTest { + + private Product newSoldOutProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(0) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + @Test + @DisplayName("재고가 소진 되어 제품 판매 중지") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + String message = productService.sellingStatusUpdate(0L); + + // then + assertThat(message).isEqualTo("마지막 재고가 소진 되어 상품 판매가 중지 됩니다."); + + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("재고가 소진 되지 않아 판매 지속") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + String message = productService.sellingStatusUpdate(0L); + + // then + assertThat(message).isEqualTo("판매가 계속 됩니다."); + + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("") + void Test3() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.sellingStatusUpdate(0L); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } +} From 4ab050622c78575fea16357c20a3b5561db4d006 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:06:04 +0900 Subject: [PATCH 07/20] =?UTF-8?q?[feat]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8,=20=EC=84=9C=EB=B9=84=EC=8A=A4=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 상품 Dto 생성 - 상품 서비스 생성 - 상품 판매 상태 변경 시 발생할 수 있는 익셉션 생성 - 상품 jpa 레포지토리 생성 --- .../tddstudy/product/domain/Product.java | 25 +++++++++++ .../tddstudy/product/domain/ProductDto.java | 42 +++++++++++++++++++ .../exception/ProductNotFoundException.java | 8 ++++ .../product/repository/ProductRepository.java | 11 +++++ .../product/service/ProductService.java | 37 ++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 src/main/java/com/swger/tddstudy/product/domain/ProductDto.java create mode 100644 src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java create mode 100644 src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java create mode 100644 src/main/java/com/swger/tddstudy/product/service/ProductService.java diff --git a/src/main/java/com/swger/tddstudy/product/domain/Product.java b/src/main/java/com/swger/tddstudy/product/domain/Product.java index e71ebd9..b7b463f 100644 --- a/src/main/java/com/swger/tddstudy/product/domain/Product.java +++ b/src/main/java/com/swger/tddstudy/product/domain/Product.java @@ -1,28 +1,53 @@ package com.swger.tddstudy.product.domain; import com.swger.tddstudy.util.BaseEntity; +import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; import javax.persistence.Id; +import javax.persistence.Table; import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; @Getter +@Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PACKAGE) +@Builder @Entity +@Table(name = "Products") public class Product extends BaseEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @NonNull + @Column(name = "name", nullable = false, length = 30) private String name; + @NonNull + @Column(name = "price", nullable = false) private int price; + @NonNull + @Column(name = "amount", nullable = false) private int amount; @Enumerated(EnumType.STRING) private SellingStatus sellingStatus; + + public ProductDto toProductDto() { + return ProductDto.builder().id(this.id).name(this.name).price(this.price) + .amount(this.amount).sellingStatus(this.sellingStatus.name()).build(); + } + } diff --git a/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java new file mode 100644 index 0000000..ebc211b --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java @@ -0,0 +1,42 @@ +package com.swger.tddstudy.product.domain; + +import javax.persistence.Column; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.NonNull; +import lombok.Setter; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +public class ProductDto { + + private Long id; + private String name; + private int price; + private int amount; + private String sellingStatus; + + public ProductDto(String name, int price, int amount, String sellingStatus) { + this.name = name; + this.price = price; + this.amount = amount; + this.sellingStatus = sellingStatus; + } + + public Product toProduct() { + return Product.builder().name(this.name).price(this.price).amount(this.amount) + .sellingStatus(SellingStatus.valueOf(this.sellingStatus)).build(); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java b/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java new file mode 100644 index 0000000..03fcebc --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/ProductNotFoundException.java @@ -0,0 +1,8 @@ +package com.swger.tddstudy.product.exception; + +public class ProductNotFoundException extends RuntimeException { + + public ProductNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java b/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java new file mode 100644 index 0000000..3360ab8 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/repository/ProductRepository.java @@ -0,0 +1,11 @@ +package com.swger.tddstudy.product.repository; + +import com.swger.tddstudy.product.domain.Product; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ProductRepository extends JpaRepository { + + @Override + Optional findById(Long id); +} diff --git a/src/main/java/com/swger/tddstudy/product/service/ProductService.java b/src/main/java/com/swger/tddstudy/product/service/ProductService.java new file mode 100644 index 0000000..75a7f23 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/service/ProductService.java @@ -0,0 +1,37 @@ +package com.swger.tddstudy.product.service; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.ProductDto; +import com.swger.tddstudy.product.domain.SellingStatus; +import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.repository.ProductRepository; +import com.swger.tddstudy.user.domain.User; +import com.swger.tddstudy.user.exception.LoginFailureException; +import java.util.Optional; +import javax.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Transactional +public class ProductService { + + private final ProductRepository productRepository; + + // 상품 재고가 다 떨어졌다면, 상품의 판매 상태를 STOP_SELLING으로 변경 + public String sellingStatusUpdate(Long id) { + Optional optionalProduct = productRepository.findById(id); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() == 0) { + product.setSellingStatus(SellingStatus.STOP_SELLING); + return "마지막 재고가 소진 되어 상품 판매가 중지 됩니다."; + } else { + return "판매가 계속 됩니다."; + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } +} From 6a6a3c023434930d7920f26df8c5e9f60dabdbf8 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:10:45 +0900 Subject: [PATCH 08/20] =?UTF-8?q?[test]=20:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=9D=B4=EB=84=88=20=ED=81=B4=EB=9E=98=EC=8A=A4=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/swger/tddstudy/product/service/ProductServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java index 07e0a6e..125093f 100644 --- a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java +++ b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java @@ -42,7 +42,7 @@ public class ProductServiceTest { @Nested @DisplayName("상품 판매 상태 변경 : ") - class JoinServiceTest { + class ProductSellingStatusUpdateTest { private Product newSoldOutProduct() { return Product.builder().id(0L).name("product").price(340000).amount(0) From 1a7efc7436a3f7f82485b26f8d73c7e80f018049 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 17:07:08 +0900 Subject: [PATCH 09/20] =?UTF-8?q?[test]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=9E=AC=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductServiceTest.java | 102 ++++++++++++++++-- 1 file changed, 93 insertions(+), 9 deletions(-) diff --git a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java index 125093f..7a1ba3c 100644 --- a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java +++ b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java @@ -9,17 +9,12 @@ import static org.mockito.Mockito.verify; import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.ProductDto; import com.swger.tddstudy.product.domain.SellingStatus; import com.swger.tddstudy.product.exception.ProductNotFoundException; import com.swger.tddstudy.product.repository.ProductRepository; -import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.domain.UserDto; -import com.swger.tddstudy.user.domain.UserLevel; -import com.swger.tddstudy.user.domain.UserType; -import com.swger.tddstudy.user.exception.LoginFailureException; -import com.swger.tddstudy.user.repository.UserRepository; -import com.swger.tddstudy.user.request.JoinRequest; -import com.swger.tddstudy.user.service.UserService; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; import java.util.Optional; import javax.transaction.Transactional; import org.junit.jupiter.api.DisplayName; @@ -89,7 +84,7 @@ void Test2() { } @Test - @DisplayName("") + @DisplayName("일치하는 상품이 없는 경우 실패") void Test3() { // given @@ -104,4 +99,93 @@ void Test3() { verify(productRepository, times(1)).findById(any(Long.class)); } } + + @Nested + @DisplayName("새상품 등록 : ") + class ProductAddTest { + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private ProductDto newProductDto() { + return ProductDto.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus("SELLING").build(); + } + + private ProductAddRequest newProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(10).build(); + } + + @Test + @DisplayName("새상품 추가 성공") + void Test1() { + + // given + given(productRepository.save(any(Product.class))).willReturn(newProduct()); + + // when + ProductDto addedProductDto = productService.productAdd(newProductAddRequest()); + + // then + assertThat(addedProductDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + + verify(productRepository, times(1)).save(any(Product.class)); + } + } + + @Nested + @DisplayName("상품 재고 추가 : ") + class ProductStockUpTest { + + private Product newSoldOutProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(0) + .sellingStatus(SellingStatus.STOP_SELLING).build(); + } + + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + @Test + @DisplayName("재고가 0에서 추가된 경우 판매 상태도 변경") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일치하는 상품이 없는 경우 실패") + void Test2() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.productStockUp(newProductStockUpRequest()); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } } From a7bf914e17f66fb86c910ae0622a7ca47a3cd243 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 17:25:22 +0900 Subject: [PATCH 10/20] =?UTF-8?q?[feat]=20:=20=EC=83=88=EC=83=81=ED=92=88?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EC=9E=AC=EA=B3=A0=20=EC=B6=94=EA=B0=80=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새상품 등록 및 상품 재고 추가 요청 도메인 생성 - 새상품 등록 및 상품 재고 추가 기능 구현 --- .../tddstudy/product/domain/ProductDto.java | 2 +- .../product/request/ProductAddRequest.java | 34 +++++++++++++++++++ .../request/ProductStockUpRequest.java | 23 +++++++++++++ .../product/service/ProductService.java | 29 ++++++++++++++-- 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java create mode 100644 src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java diff --git a/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java index ebc211b..92fa5d6 100644 --- a/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java +++ b/src/main/java/com/swger/tddstudy/product/domain/ProductDto.java @@ -35,7 +35,7 @@ public ProductDto(String name, int price, int amount, String sellingStatus) { this.sellingStatus = sellingStatus; } - public Product toProduct() { + public Product toEntity() { return Product.builder().name(this.name).price(this.price).amount(this.amount) .sellingStatus(SellingStatus.valueOf(this.sellingStatus)).build(); } diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java new file mode 100644 index 0000000..af99925 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java @@ -0,0 +1,34 @@ +package com.swger.tddstudy.product.request; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.domain.ProductDto; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductAddRequest { + + @NotBlank(message = "상품명을 입력하세요.") + @Size(min = 2, max = 10, message = "상품명을 2자 이상 30자 이하로 입력하세요.") + private String name; + + @NotBlank(message = "상품 가격을 입력하세요.") + @Min(value = 0, message = "상품 가격은 0 이상으로 입력하세요.") + private int price; + + @NotBlank(message = "상품 수량을 입력하세요.") + @Min(value = 0, message = "상품 수량은 0 이상으로 입력하세요.") + private int amount; + + public ProductDto toProductDto() { + return ProductDto.builder().name(this.name).price(this.price).amount(this.amount).build(); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java new file mode 100644 index 0000000..1fc1c36 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java @@ -0,0 +1,23 @@ +package com.swger.tddstudy.product.request; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ProductStockUpRequest { + + @NotBlank(message = "상품 ID를 입력하세요.") + private Long id; + + @NotBlank(message = "상품 수량을 입력하세요.") + @Min(value = 0, message = "상품 수량은 0 이상으로 입력하세요.") + private int amount; + +} diff --git a/src/main/java/com/swger/tddstudy/product/service/ProductService.java b/src/main/java/com/swger/tddstudy/product/service/ProductService.java index 75a7f23..61534c8 100644 --- a/src/main/java/com/swger/tddstudy/product/service/ProductService.java +++ b/src/main/java/com/swger/tddstudy/product/service/ProductService.java @@ -5,8 +5,8 @@ import com.swger.tddstudy.product.domain.SellingStatus; import com.swger.tddstudy.product.exception.ProductNotFoundException; import com.swger.tddstudy.product.repository.ProductRepository; -import com.swger.tddstudy.user.domain.User; -import com.swger.tddstudy.user.exception.LoginFailureException; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; import java.util.Optional; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -19,6 +19,31 @@ public class ProductService { private final ProductRepository productRepository; + public ProductDto productAdd(ProductAddRequest productAddRequest) { + ProductDto productDto = productAddRequest.toProductDto(); + productDto.setSellingStatus("SELLING"); + return productRepository.save(productDto.toEntity()).toProductDto(); + } + + public ProductDto productStockUp(ProductStockUpRequest productStockUpRequest) { + Optional optionalProduct = productRepository.findById( + productStockUpRequest.getId()); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() == 0) { + product.setAmount(productStockUpRequest.getAmount()); + product.setSellingStatus(SellingStatus.SELLING); + return product.toProductDto(); + } else { + int result = product.getAmount() + productStockUpRequest.getAmount(); + product.setAmount(result); + return product.toProductDto(); + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } + // 상품 재고가 다 떨어졌다면, 상품의 판매 상태를 STOP_SELLING으로 변경 public String sellingStatusUpdate(Long id) { Optional optionalProduct = productRepository.findById(id); From f5ed5b1c76b1781364a9a885c33c4d2274b97623 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:23:19 +0900 Subject: [PATCH 11/20] =?UTF-8?q?[test]=20:=20=EB=B9=88=20=ED=96=89=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/swger/tddstudy/user/service/UserServiceTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java index 99f1077..1d25b1f 100644 --- a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java +++ b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java @@ -71,7 +71,6 @@ void Test1() { // then assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - verify(userRepository, times(1)).save(any(User.class)); } @@ -88,7 +87,6 @@ void Test2() { // then assertThat(savedUserDto).extracting("username", "password", "nickname", "userLevel", "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "ADMIN"); - verify(userRepository, times(1)).save(any(User.class)); } } @@ -127,7 +125,6 @@ void Test1() { // then assertThat(loginResult).extracting("username", "password", "nickname", "userLevel", "type").containsExactly("abc", "abcd1234!", "nickname", "BRONZE", "USER"); - verify(userRepository, times(1)).findByUsername(any(String.class)); } From 0f35f07b91446400ac46ca46ac01f18fb7744a50 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:24:23 +0900 Subject: [PATCH 12/20] =?UTF-8?q?[feat]=20:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tddstudy/user/service/UserService.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/swger/tddstudy/user/service/UserService.java b/src/main/java/com/swger/tddstudy/user/service/UserService.java index 461ba60..9992e06 100644 --- a/src/main/java/com/swger/tddstudy/user/service/UserService.java +++ b/src/main/java/com/swger/tddstudy/user/service/UserService.java @@ -2,6 +2,7 @@ import com.swger.tddstudy.user.domain.User; import com.swger.tddstudy.user.domain.UserDto; +import com.swger.tddstudy.user.domain.UserType; import com.swger.tddstudy.user.exception.LoginFailureException; import com.swger.tddstudy.user.repository.UserRepository; import com.swger.tddstudy.user.request.JoinRequest; @@ -34,7 +35,8 @@ public UserDto saveAdmin(JoinRequest joinRequest) { } public UserDto login(LoginRequest loginRequest) { - Optional optionalUserEntity = userRepository.findByUsername(loginRequest.getUsername()); + Optional optionalUserEntity = userRepository.findByUsername( + loginRequest.getUsername()); if (optionalUserEntity.isPresent()) { User loginEntity = optionalUserEntity.get(); if (loginEntity.getPassword().equals(loginRequest.getPassword())) { @@ -46,4 +48,17 @@ public UserDto login(LoginRequest loginRequest) { throw new LoginFailureException("일치하는 회원정보가 없습니다."); } } + + public boolean isAdmin(Long id) { + Optional optionalUserEntity = userRepository.findById(id); + if (optionalUserEntity.isPresent()) { + if (optionalUserEntity.get().getType() == UserType.ADMIN) { + return true; + } else { + return false; + } + } else { + throw new LoginFailureException("일치하는 회원정보가 없습니다."); + } + } } From ebc9c0304a1de31d6290706a01414290d5d79568 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:30:54 +0900 Subject: [PATCH 13/20] =?UTF-8?q?[test]=20:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20=EC=97=AC=EB=B6=80=20=ED=99=95=EC=9D=B8=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserServiceTest.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java index 1d25b1f..fef9a88 100644 --- a/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java +++ b/src/test/java/com/swger/tddstudy/user/service/UserServiceTest.java @@ -224,4 +224,52 @@ void Test3() { assertThat(user.getUserLevel()).isEqualTo(UserLevel.GOLD); } } + + @Nested + @DisplayName("관리자 확인 : ") + class IsAdminTest { + private User newUser() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.USER).build(); + } + + private User newAdmin() { + + return User.builder().id(0L).username("abc").password("abcd1234!").nickname("nickname") + .userLevel(UserLevel.BRONZE).type(UserType.ADMIN).build(); + } + + @Test + @DisplayName("일반 회원은 false 반환") + void Test1() { + + // given + Optional optionalUser = Optional.ofNullable(newUser()); + given(userRepository.findById(any(Long.class))).willReturn(optionalUser); + + // when + Boolean isAdmin = userService.isAdmin(0L); + + // then + assertThat(isAdmin).isFalse(); + verify(userRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("관리자는 true 반환") + void Test2() { + + // given + Optional optionalUser = Optional.ofNullable(newAdmin()); + given(userRepository.findById(any(Long.class))).willReturn(optionalUser); + + // when + Boolean isAdmin = userService.isAdmin(0L); + + // then + assertThat(isAdmin).isTrue(); + verify(userRepository, times(1)).findById(any(Long.class)); + } + } } \ No newline at end of file From d64056c38b5cb759e4bf98f4c38df2e705d805bb Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:34:29 +0900 Subject: [PATCH 14/20] =?UTF-8?q?[test]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=ED=8C=90=EB=A7=A4=20=EC=8B=9C=20=EC=9E=AC=EA=B3=A0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=8C=90=EB=A7=A4=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../product/service/ProductServiceTest.java | 132 +++++++++++++++--- 1 file changed, 114 insertions(+), 18 deletions(-) diff --git a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java index 7a1ba3c..2b6ca19 100644 --- a/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java +++ b/src/test/java/com/swger/tddstudy/product/service/ProductServiceTest.java @@ -12,6 +12,7 @@ import com.swger.tddstudy.product.domain.ProductDto; import com.swger.tddstudy.product.domain.SellingStatus; import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; import com.swger.tddstudy.product.repository.ProductRepository; import com.swger.tddstudy.product.request.ProductAddRequest; import com.swger.tddstudy.product.request.ProductStockUpRequest; @@ -58,11 +59,11 @@ void Test1() { given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); // when - String message = productService.sellingStatusUpdate(0L); + ProductDto productDto = productService.sellingStatusUpdate(0L); // then - assertThat(message).isEqualTo("마지막 재고가 소진 되어 상품 판매가 중지 됩니다."); - + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 0, "STOP_SELLING"); verify(productRepository, times(1)).findById(any(Long.class)); } @@ -75,11 +76,11 @@ void Test2() { given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); // when - String message = productService.sellingStatusUpdate(0L); + ProductDto productDto = productService.sellingStatusUpdate(0L); // then - assertThat(message).isEqualTo("판매가 계속 됩니다."); - + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); verify(productRepository, times(1)).findById(any(Long.class)); } @@ -109,11 +110,6 @@ private Product newProduct() { .sellingStatus(SellingStatus.SELLING).build(); } - private ProductDto newProductDto() { - return ProductDto.builder().id(0L).name("product").price(340000).amount(10) - .sellingStatus("SELLING").build(); - } - private ProductAddRequest newProductAddRequest() { return ProductAddRequest.builder().name("product").price(340000).amount(10).build(); } @@ -131,7 +127,6 @@ void Test1() { // then assertThat(addedProductDto).extracting("id", "name", "price", "amount", "sellingStatus") .containsExactly(0L, "product", 340000, 10, "SELLING"); - verify(productRepository, times(1)).save(any(Product.class)); } } @@ -140,41 +135,142 @@ void Test1() { @DisplayName("상품 재고 추가 : ") class ProductStockUpTest { + private Product newProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus(SellingStatus.SELLING).build(); + } + private Product newSoldOutProduct() { return Product.builder().id(0L).name("product").price(340000).amount(0) .sellingStatus(SellingStatus.STOP_SELLING).build(); } + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + @Test + @DisplayName("재고 추가 성공") + void Test1() { + + // given + Optional optionalProduct = Optional.ofNullable(newProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 20, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("재고가 0에서 추가된 경우 판매 상태도 변경") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 10, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + + @Test + @DisplayName("일치하는 상품이 없는 경우 실패") + void Test3() { + + // given + Optional optionalProduct = Optional.empty(); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductNotFoundException.class, () -> { + productService.productStockUp(newProductStockUpRequest()); + }); + assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); + verify(productRepository, times(1)).findById(any(Long.class)); + } + } + + @Nested + @DisplayName("상품 판매 시 수량 체크 : ") + class ProductSaleTest { + private Product newProduct() { return Product.builder().id(0L).name("product").price(340000).amount(10) .sellingStatus(SellingStatus.SELLING).build(); } + private Product newLowCntProduct() { + return Product.builder().id(0L).name("product").price(340000).amount(2) + .sellingStatus(SellingStatus.STOP_SELLING).build(); + } + private ProductStockUpRequest newProductStockUpRequest() { return ProductStockUpRequest.builder().id(0L).amount(10).build(); } @Test - @DisplayName("재고가 0에서 추가된 경우 판매 상태도 변경") + @DisplayName("판매 수량 만큼 수량 감소") void Test1() { // given - Optional optionalProduct = Optional.ofNullable(newSoldOutProduct()); + Optional optionalProduct = Optional.ofNullable(newProduct()); given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); // when - ProductDto productDto = productService.productStockUp(newProductStockUpRequest()); + ProductDto productDto = productService.sale(0L, 3); // then assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") - .containsExactly(0L, "product", 340000, 10, "SELLING"); + .containsExactly(0L, "product", 340000, 7, "SELLING"); + verify(productRepository, times(1)).findById(any(Long.class)); + } + @Test + @DisplayName("판매 후 수량이 0이 될 경우 판매 상태 변경") + void Test2() { + + // given + Optional optionalProduct = Optional.ofNullable(newLowCntProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when + ProductDto productDto = productService.sale(0L, 2); + + // then + assertThat(productDto).extracting("id", "name", "price", "amount", "sellingStatus") + .containsExactly(0L, "product", 340000, 0, "STOP_SELLING"); + verify(productRepository, times(2)).findById(any(Long.class)); + } + + @Test + @DisplayName("수량이 부족한 경우 실패") + void Test3() { + + // given + Optional optionalProduct = Optional.ofNullable(newLowCntProduct()); + given(productRepository.findById(any(Long.class))).willReturn(optionalProduct); + + // when, then + Throwable exception = assertThrows(ProductSoldOutExcpetion.class, () -> { + productService.sale(0L, 3); + }); + assertEquals("재고가 부족합니다.", exception.getMessage()); verify(productRepository, times(1)).findById(any(Long.class)); } @Test @DisplayName("일치하는 상품이 없는 경우 실패") - void Test2() { + void Test4() { // given Optional optionalProduct = Optional.empty(); @@ -182,7 +278,7 @@ void Test2() { // when, then Throwable exception = assertThrows(ProductNotFoundException.class, () -> { - productService.productStockUp(newProductStockUpRequest()); + productService.sale(0L, 3); }); assertEquals("일치하는 상품이 없습니다.", exception.getMessage()); verify(productRepository, times(1)).findById(any(Long.class)); From 92172868f74a0ccc12856f3877ed2237efc474d4 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 21:36:39 +0900 Subject: [PATCH 15/20] =?UTF-8?q?[feat]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=ED=8C=90=EB=A7=A4=20=EC=8B=9C=20=EC=9E=AC=EA=B3=A0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=8C=90=EB=A7=A4=20=EC=83=81=ED=83=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/ProductSoldOutExcpetion.java | 8 ++++++ .../product/service/ProductService.java | 27 ++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java diff --git a/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java b/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java new file mode 100644 index 0000000..2c90f9d --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/ProductSoldOutExcpetion.java @@ -0,0 +1,8 @@ +package com.swger.tddstudy.product.exception; + +public class ProductSoldOutExcpetion extends RuntimeException{ + + public ProductSoldOutExcpetion(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/service/ProductService.java b/src/main/java/com/swger/tddstudy/product/service/ProductService.java index 61534c8..d080545 100644 --- a/src/main/java/com/swger/tddstudy/product/service/ProductService.java +++ b/src/main/java/com/swger/tddstudy/product/service/ProductService.java @@ -4,10 +4,12 @@ import com.swger.tddstudy.product.domain.ProductDto; import com.swger.tddstudy.product.domain.SellingStatus; import com.swger.tddstudy.product.exception.ProductNotFoundException; +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; import com.swger.tddstudy.product.repository.ProductRepository; import com.swger.tddstudy.product.request.ProductAddRequest; import com.swger.tddstudy.product.request.ProductStockUpRequest; import java.util.Optional; +import javax.swing.plaf.basic.BasicTreeUI.TreeHomeAction; import javax.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -44,16 +46,35 @@ public ProductDto productStockUp(ProductStockUpRequest productStockUpRequest) { } } + public ProductDto sale(Long id, int count) { + Optional optionalProduct = productRepository.findById(id); + if (optionalProduct.isPresent()) { + Product product = optionalProduct.get(); + if (product.getAmount() - count < 0) { + throw new ProductSoldOutExcpetion("재고가 부족합니다."); + } else if(product.getAmount() - count ==0) { + product.setAmount(0); + return sellingStatusUpdate(product.getId()); + } else{ + int result = product.getAmount() - count; + product.setAmount(result); + return product.toProductDto(); + } + } else { + throw new ProductNotFoundException("일치하는 상품이 없습니다."); + } + } + // 상품 재고가 다 떨어졌다면, 상품의 판매 상태를 STOP_SELLING으로 변경 - public String sellingStatusUpdate(Long id) { + public ProductDto sellingStatusUpdate(Long id) { Optional optionalProduct = productRepository.findById(id); if (optionalProduct.isPresent()) { Product product = optionalProduct.get(); if (product.getAmount() == 0) { product.setSellingStatus(SellingStatus.STOP_SELLING); - return "마지막 재고가 소진 되어 상품 판매가 중지 됩니다."; + return product.toProductDto(); } else { - return "판매가 계속 됩니다."; + return product.toProductDto(); } } else { throw new ProductNotFoundException("일치하는 상품이 없습니다."); From d1f437e5326044208a18aaba8d574f4dacf371df Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:03:51 +0900 Subject: [PATCH 16/20] =?UTF-8?q?[fix]=20:=20=EC=A0=9C=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=9E=AC=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=9A=94=EC=B2=AD=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EC=96=91=EC=8B=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tddstudy/product/request/ProductAddRequest.java | 10 ++++++---- .../product/request/ProductStockUpRequest.java | 7 ++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java index af99925..a162b98 100644 --- a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java +++ b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java @@ -4,11 +4,13 @@ import com.swger.tddstudy.product.domain.ProductDto; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.NonNull; @Getter @Builder @@ -16,16 +18,16 @@ @AllArgsConstructor public class ProductAddRequest { - @NotBlank(message = "상품명을 입력하세요.") + @NotNull(message = "상품명을 입력하세요.") @Size(min = 2, max = 10, message = "상품명을 2자 이상 30자 이하로 입력하세요.") private String name; - @NotBlank(message = "상품 가격을 입력하세요.") + @NotNull(message = "상품 가격을 입력하세요.") @Min(value = 0, message = "상품 가격은 0 이상으로 입력하세요.") private int price; - @NotBlank(message = "상품 수량을 입력하세요.") - @Min(value = 0, message = "상품 수량은 0 이상으로 입력하세요.") + @NotNull(message = "상품 수량을 입력하세요.") + @Min(value = 1, message = "상품 수량은 1 이상으로 입력하세요.") private int amount; public ProductDto toProductDto() { diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java index 1fc1c36..21c581d 100644 --- a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java +++ b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java @@ -2,6 +2,7 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -13,11 +14,11 @@ @AllArgsConstructor public class ProductStockUpRequest { - @NotBlank(message = "상품 ID를 입력하세요.") + @NotNull(message = "상품 ID를 입력하세요.") private Long id; - @NotBlank(message = "상품 수량을 입력하세요.") - @Min(value = 0, message = "상품 수량은 0 이상으로 입력하세요.") + @NotNull(message = "상품 수량을 입력하세요.") + @Min(value = 1, message = "상품 수량은 1 이상으로 입력하세요.") private int amount; } From 6be7847862d182bab47aaa299f2a97c84581ab42 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:11:28 +0900 Subject: [PATCH 17/20] =?UTF-8?q?[test]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=9E=AC=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ProductRestControllerTest.java | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java diff --git a/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java b/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java new file mode 100644 index 0000000..edf07e4 --- /dev/null +++ b/src/test/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerTest.java @@ -0,0 +1,205 @@ +package com.swger.tddstudy.product.restcontroller; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.swger.tddstudy.product.domain.ProductDto; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import com.swger.tddstudy.product.service.ProductService; +import com.swger.tddstudy.user.service.UserService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +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.mock.web.MockHttpSession; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(ProductRestController.class) +public class ProductRestControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + ProductService productService; + + @MockBean + UserService userService; + + @Nested + @DisplayName("상품 등록 컨트롤러 : ") + class ProductAddControllerTest { + + private ProductDto newProductDto() { + return ProductDto.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus("SELLING").build(); + } + + private ProductAddRequest newProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(10).build(); + } + + private ProductAddRequest newWrongProductAddRequest() { + return ProductAddRequest.builder().name("product").price(340000).amount(0).build(); + } + + @Test + @DisplayName("양식이 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.price").exists()) + .andExpect(jsonPath("$.data.amount").exists()) + .andExpect(jsonPath("$.data.sellingStatus").exists()).andDo(print()); + verify(productService, times(1)).productAdd(any(ProductAddRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newWrongProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productAdd(any(ProductAddRequest.class)); + } + + @Test + @DisplayName("양식에 맞으나 관리자가 아니면 실패") + void Test3() throws Exception { + + // given + given(productService.productAdd(any(ProductAddRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(false); + String json = new ObjectMapper().writeValueAsString(newProductAddRequest()); + + // when, then + mockMvc.perform(post("/api/product/add").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productAdd(any(ProductAddRequest.class)); + } + } + + @Nested + @DisplayName("상품 재고 추가 컨트롤러 : ") + class ProductStockUpControllerTest { + + private ProductDto newProductDto() { + return ProductDto.builder().id(0L).name("product").price(340000).amount(10) + .sellingStatus("SELLING").build(); + } + + private ProductStockUpRequest newProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(10).build(); + } + + private ProductStockUpRequest newWrongProductStockUpRequest() { + return ProductStockUpRequest.builder().id(0L).amount(0).build(); + } + + @Test + @DisplayName("양식이 맞을 경우 성공") + void Test1() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").exists()) + .andExpect(jsonPath("$.data.name").exists()) + .andExpect(jsonPath("$.data.price").exists()) + .andExpect(jsonPath("$.data.amount").exists()) + .andExpect(jsonPath("$.data.sellingStatus").exists()).andDo(print()); + verify(productService, times(1)).productStockUp(any(ProductStockUpRequest.class)); + } + + @Test + @DisplayName("양식에 맞지 않을 경우 실패") + void Test2() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(true); + String json = new ObjectMapper().writeValueAsString(newWrongProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productStockUp(any(ProductStockUpRequest.class)); + } + + @Test + @DisplayName("양식에 맞으나 관리자가 아니면 실패") + void Test3() throws Exception { + + // given + given(productService.productStockUp(any(ProductStockUpRequest.class))).willReturn( + newProductDto()); + MockHttpSession session = new MockHttpSession(); + session.setAttribute("id", 0L); + session.setAttribute("username", "abc"); + given(userService.isAdmin(any(Long.class))).willReturn(false); + String json = new ObjectMapper().writeValueAsString(newProductStockUpRequest()); + + // when, then + mockMvc.perform(post("/api/product/stock-up").session(session).content(json) + .contentType(MediaType.APPLICATION_JSON)).andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.error").exists()).andDo(print()); + verify(productService, never()).productStockUp(any(ProductStockUpRequest.class)); + } + } +} + From 9efb5d2ee0338146e04ed83bf08a45f26f5e6d31 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:14:34 +0900 Subject: [PATCH 18/20] =?UTF-8?q?[feat]=20:=20=EC=83=81=ED=92=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=EB=B0=8F=20=EC=9E=AC=EA=B3=A0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20api=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관리자가 아닐 경우 발생시킬 익셉션 생성 - 익셉션 발생 시 적절한 응답을 보내도록 컨트롤러 어드바이스 생성 - 상품 등록 및 재고 추가 컨트롤러(REST) 구현 --- .../exception/UnauthorizedException.java | 10 +++ .../restcontroller/ProductRestController.java | 64 +++++++++++++++++++ .../ProductRestControllerAdvice.java | 34 ++++++++++ 3 files changed, 108 insertions(+) create mode 100644 src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java create mode 100644 src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java create mode 100644 src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java diff --git a/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java b/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java new file mode 100644 index 0000000..8d0cf54 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/exception/UnauthorizedException.java @@ -0,0 +1,10 @@ +package com.swger.tddstudy.product.exception; + +import org.springframework.web.client.HttpClientErrorException.Unauthorized; + +public class UnauthorizedException extends RuntimeException { + + public UnauthorizedException(String message) { + super(message); + } +} diff --git a/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java new file mode 100644 index 0000000..94ebdbe --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestController.java @@ -0,0 +1,64 @@ +package com.swger.tddstudy.product.restcontroller; + +import com.swger.tddstudy.product.domain.Product; +import com.swger.tddstudy.product.exception.UnauthorizedException; +import com.swger.tddstudy.product.request.ProductAddRequest; +import com.swger.tddstudy.product.request.ProductStockUpRequest; +import com.swger.tddstudy.product.service.ProductService; +import com.swger.tddstudy.user.request.JoinRequest; +import com.swger.tddstudy.user.service.UserService; +import java.util.HashMap; +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("api/product") +public class ProductRestController { + + private final ProductService productService; + private final UserService userService; + + @PostMapping("/add") + public ResponseEntity join(@Valid @RequestBody ProductAddRequest productAddRequest, + HttpServletRequest request) { + Map message = new HashMap<>(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new UnauthorizedException("권한이 없습니다."); + } + if (userService.isAdmin((Long) session.getAttribute("id"))) { + message.put("status", 200); + message.put("data", productService.productAdd(productAddRequest)); + return ResponseEntity.status(HttpStatus.OK).body(message); + } else { + throw new UnauthorizedException("관리자만 상품을 등록할 수 있습니다."); + } + } + + @PostMapping("/stock-up") + public ResponseEntity join(@Valid @RequestBody ProductStockUpRequest productStockUpRequest, + HttpServletRequest request) { + Map message = new HashMap<>(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new UnauthorizedException("권한이 없습니다."); + } + if (userService.isAdmin((Long) session.getAttribute("id"))) { + message.put("status", 200); + message.put("data", productService.productStockUp(productStockUpRequest)); + return ResponseEntity.status(HttpStatus.OK).body(message); + } else { + throw new UnauthorizedException("관리자만 상품 재고를 추가할 수 있습니다."); + } + } +} diff --git a/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java new file mode 100644 index 0000000..b958773 --- /dev/null +++ b/src/main/java/com/swger/tddstudy/product/restcontroller/ProductRestControllerAdvice.java @@ -0,0 +1,34 @@ +package com.swger.tddstudy.product.restcontroller; + +import com.swger.tddstudy.product.exception.ProductSoldOutExcpetion; +import com.swger.tddstudy.product.exception.UnauthorizedException; +import java.util.HashMap; +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpClientErrorException.Unauthorized; + +@RestControllerAdvice +public class ProductRestControllerAdvice { + + @ExceptionHandler + public ResponseEntity unauthorizedException(UnauthorizedException e) { + Map errorMessage = new HashMap<>(); + errorMessage.put("error", e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); + } + + @ExceptionHandler + public ResponseEntity productSoldOutException(ProductSoldOutExcpetion e) { + Map errorMessage = new HashMap<>(); + errorMessage.put("error", e.getMessage()); + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .contentType(MediaType.APPLICATION_JSON) + .body(errorMessage); + } +} From 9d094ae63abda823e6fddfa0ee6d43c0dfcad2fe Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Fri, 4 Aug 2023 22:45:18 +0900 Subject: [PATCH 19/20] =?UTF-8?q?[refactor]=20:=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=9E=84=ED=8F=AC?= =?UTF-8?q?=ED=8A=B8=EB=AC=B8=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/swger/tddstudy/product/request/ProductAddRequest.java | 3 --- .../swger/tddstudy/product/request/ProductStockUpRequest.java | 1 - 2 files changed, 4 deletions(-) diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java index a162b98..19c2ba2 100644 --- a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java +++ b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java @@ -1,16 +1,13 @@ package com.swger.tddstudy.product.request; -import com.swger.tddstudy.product.domain.Product; import com.swger.tddstudy.product.domain.ProductDto; import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.NonNull; @Getter @Builder diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java index 21c581d..f78b065 100644 --- a/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java +++ b/src/main/java/com/swger/tddstudy/product/request/ProductStockUpRequest.java @@ -1,7 +1,6 @@ package com.swger.tddstudy.product.request; import javax.validation.constraints.Min; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Builder; From fef74b1842afcdb9c007f09855bc4e9e6808cc01 Mon Sep 17 00:00:00 2001 From: JeongUijeong <94631526+JeongUijeong@users.noreply.github.com> Date: Mon, 7 Aug 2023 16:41:47 +0900 Subject: [PATCH 20/20] =?UTF-8?q?[fix]=20:=20=EC=83=81=ED=92=88=EB=AA=85?= =?UTF-8?q?=20validation=20@NotBlank=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/swger/tddstudy/product/request/ProductAddRequest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java index 19c2ba2..5f3915c 100644 --- a/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java +++ b/src/main/java/com/swger/tddstudy/product/request/ProductAddRequest.java @@ -2,6 +2,7 @@ import com.swger.tddstudy.product.domain.ProductDto; import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import lombok.AllArgsConstructor; @@ -15,7 +16,7 @@ @AllArgsConstructor public class ProductAddRequest { - @NotNull(message = "상품명을 입력하세요.") + @NotBlank(message = "상품명을 입력하세요.") @Size(min = 2, max = 10, message = "상품명을 2자 이상 30자 이하로 입력하세요.") private String name;