From 0c26555665584efc0772e913bbd60b41a3040e09 Mon Sep 17 00:00:00 2001 From: Kim DongHyo <60608509+kdhyo@users.noreply.github.com> Date: Thu, 26 May 2022 10:51:38 +0900 Subject: [PATCH] =?UTF-8?q?#3=20=ED=9A=8C=EC=9B=90=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EB=B0=8F=20=ED=8C=A8=EC=8A=A4=EC=9B=8C=EB=93=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EA=B5=AC=ED=98=84=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: user delete 메소드 구현 및 테스트 객체 MethodSource 형식으로 분리 * feat: 회원탈퇴 구현 * refactor: PasswordMismatchException 패키지 위치 변경 * feat: oldpassword, newpassword properties 추가 * test: 패스워드 변경 메소드 테스트 코드 작성 * feat: 패스워드 변경 메소드 구현 * feat: 패스워드 변경 서비스 구현 * feat: 패스워드 변경 컨트롤러 구현 * refactor: controller log 추가 * feat: UserPasswordModifyRequest -> UserModifyPasswordRequest 네이밍 변경 * refactor: param name 변경 * refactor: 패스워드 변경 시 삭제된 이메일 제외하고 불러오는 형식으로 변경 * refactor: 메소드 네이밍 수정 * feat: User ResponseDTO 작성 * refactor: controller delete -> deleteUser 메소드 명 변경 * refactor: modifyPassword @Transactional 적용 * refactor: dto 객체 명 DTO 붙여서 구분 * refactor: register method Optional 반환 -> Exception 처리 Optional 반환과 Exception 반환이 혼용되어 사용되고 있어, Exception 으로 통일 * refactor: NotFoundEmailException 구현 * refactor: User delete method Optional 반환 -> Exception 처리 Optional 반환과 Exception 반환이 혼용되어 사용되고 있어, Exception 으로 통일 * refactor: modifyPassword method Optional 반환 -> Exception 처리 Optional 반환과 Exception 반환이 혼용되어 사용되고 있어, Exception 으로 통일 * refactor: comparePassword -> checkPassword 메서드 네이밍 변경 * refactor: modifyPassword -> changePassword 네이밍 변경 * refactor: provide 메소드 선언 위치 하단으로 변경 및 users Map 추가 정상적인 users 를 재사용하기 위해 분리하여 선언 * refactor: provideDeleteUsers 공통 users 사용하는 방식으로 수정 * refactor: changePassword 공통 users 사용하는 방식으로 수정 * refactor: UserModifyPasswordResponse -> UserChangePasswordResponse * refactor: dto 클래스 네이밍 변경으로 인한 인수, 변수, 메소드 네이밍 수정 --- .../exception/AlreadyDeletedException.java | 9 + .../exception/NotFoundEmailException.java | 11 + .../exception/PasswordMismatchException.java | 11 + .../user/application/UserController.java | 30 ++- .../application/request/SignUpRequest.java | 6 +- .../request/UserDeleteRequest.java | 23 ++ .../request/UserModifyPasswordRequest.java | 30 +++ .../application/response/SignUpResponse.java | 20 ++ .../response/UserChangePasswordResponse.java | 20 ++ .../response/UserDeleteResponse.java | 20 ++ .../ticketing/server/user/domain/User.java | 42 +++- .../domain/repository/UserRepository.java | 2 + .../server/user/service/UserServiceImpl.java | 38 +++- .../user/service/dto/ChangePasswordDTO.java | 47 ++++ .../user/service/dto/DeleteUserDTO.java | 40 ++++ .../user/service/dto/PasswordMatches.java | 7 + .../dto/{SignUp.java => SignUpDTO.java} | 4 +- .../user/service/interfaces/UserService.java | 11 +- .../main/resources/i18n/messages.properties | 2 + .../resources/i18n/messages_en.properties | 2 + .../resources/i18n/messages_ko.properties | 2 + .../server/user/domain/UserTest.java | 202 +++++++++++++++--- .../user/service/UserServiceImplTest.java | 77 ++++++- .../user/service/dto/DeleteUserDtoTest.java | 37 ++++ .../{SignUpTest.java => SignUpDtoTest.java} | 4 +- 25 files changed, 631 insertions(+), 66 deletions(-) create mode 100644 server/src/main/java/com/ticketing/server/global/exception/AlreadyDeletedException.java create mode 100644 server/src/main/java/com/ticketing/server/global/exception/NotFoundEmailException.java create mode 100644 server/src/main/java/com/ticketing/server/global/exception/PasswordMismatchException.java create mode 100644 server/src/main/java/com/ticketing/server/user/application/request/UserDeleteRequest.java create mode 100644 server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java create mode 100644 server/src/main/java/com/ticketing/server/user/application/response/SignUpResponse.java create mode 100644 server/src/main/java/com/ticketing/server/user/application/response/UserChangePasswordResponse.java create mode 100644 server/src/main/java/com/ticketing/server/user/application/response/UserDeleteResponse.java create mode 100644 server/src/main/java/com/ticketing/server/user/service/dto/ChangePasswordDTO.java create mode 100644 server/src/main/java/com/ticketing/server/user/service/dto/DeleteUserDTO.java create mode 100644 server/src/main/java/com/ticketing/server/user/service/dto/PasswordMatches.java rename server/src/main/java/com/ticketing/server/user/service/dto/{SignUp.java => SignUpDTO.java} (91%) create mode 100644 server/src/test/java/com/ticketing/server/user/service/dto/DeleteUserDtoTest.java rename server/src/test/java/com/ticketing/server/user/service/dto/{SignUpTest.java => SignUpDtoTest.java} (78%) diff --git a/server/src/main/java/com/ticketing/server/global/exception/AlreadyDeletedException.java b/server/src/main/java/com/ticketing/server/global/exception/AlreadyDeletedException.java new file mode 100644 index 0000000..7c0e004 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/exception/AlreadyDeletedException.java @@ -0,0 +1,9 @@ +package com.ticketing.server.global.exception; + +public class AlreadyDeletedException extends RuntimeException { + + public AlreadyDeletedException(String message) { + super(message); + } + +} diff --git a/server/src/main/java/com/ticketing/server/global/exception/NotFoundEmailException.java b/server/src/main/java/com/ticketing/server/global/exception/NotFoundEmailException.java new file mode 100644 index 0000000..d04c4e8 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/exception/NotFoundEmailException.java @@ -0,0 +1,11 @@ +package com.ticketing.server.global.exception; + +public class NotFoundEmailException extends IllegalArgumentException { + + private static final String MESSAGE = "존재하지 않는 이메일 입니다."; + + public NotFoundEmailException() { + super(MESSAGE); + } + +} diff --git a/server/src/main/java/com/ticketing/server/global/exception/PasswordMismatchException.java b/server/src/main/java/com/ticketing/server/global/exception/PasswordMismatchException.java new file mode 100644 index 0000000..d2a2a46 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/global/exception/PasswordMismatchException.java @@ -0,0 +1,11 @@ +package com.ticketing.server.global.exception; + +public class PasswordMismatchException extends RuntimeException { + + private static final String MESSAGE = "패스워드가 일치하지 않습니다"; + + public PasswordMismatchException() { + super(MESSAGE); + } + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/UserController.java b/server/src/main/java/com/ticketing/server/user/application/UserController.java index 2987deb..cc153f9 100644 --- a/server/src/main/java/com/ticketing/server/user/application/UserController.java +++ b/server/src/main/java/com/ticketing/server/user/application/UserController.java @@ -1,14 +1,21 @@ package com.ticketing.server.user.application; import com.ticketing.server.user.application.request.SignUpRequest; +import com.ticketing.server.user.application.request.UserDeleteRequest; +import com.ticketing.server.user.application.request.UserModifyPasswordRequest; +import com.ticketing.server.user.application.response.SignUpResponse; +import com.ticketing.server.user.application.response.UserDeleteResponse; +import com.ticketing.server.user.application.response.UserChangePasswordResponse; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.service.UserServiceImpl; -import java.util.Optional; import javax.validation.Valid; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -17,20 +24,33 @@ import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor @RequestMapping("/user") +@Slf4j public class UserController { private final UserServiceImpl userService; private final PasswordEncoder passwordEncoder; @PostMapping - public ResponseEntity register(@RequestBody @Valid SignUpRequest signUpRequest) { - Optional user = userService.register(signUpRequest.toSignUp(passwordEncoder)); + public ResponseEntity register(@RequestBody @Valid SignUpRequest request) { + User user = userService.register(request.toSignUpDto(passwordEncoder)); + return ResponseEntity.status(HttpStatus.CREATED).body(SignUpResponse.of(user)); + } - if (user.isEmpty()) { + @DeleteMapping + public ResponseEntity deleteUser(@RequestBody @Valid UserDeleteRequest request) { + User user = userService.delete(request.toDeleteUserDto(passwordEncoder)); + return ResponseEntity.status(HttpStatus.OK).body(UserDeleteResponse.of(user)); + } + + @PatchMapping("/password") + public ResponseEntity changePassword(@RequestBody @Valid UserModifyPasswordRequest request) { + if (request.oldEqualNew()) { + log.error("기존 패스워드와 동일한 패스워드로 변경할 수 없습니다."); return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); } - return ResponseEntity.status(HttpStatus.CREATED).build(); + User user = userService.changePassword(request.toChangePasswordDto(passwordEncoder)); + return ResponseEntity.status(HttpStatus.OK).body(UserChangePasswordResponse.of(user)); } } diff --git a/server/src/main/java/com/ticketing/server/user/application/request/SignUpRequest.java b/server/src/main/java/com/ticketing/server/user/application/request/SignUpRequest.java index e53dcd2..45b0a7b 100644 --- a/server/src/main/java/com/ticketing/server/user/application/request/SignUpRequest.java +++ b/server/src/main/java/com/ticketing/server/user/application/request/SignUpRequest.java @@ -1,7 +1,7 @@ package com.ticketing.server.user.application.request; import com.ticketing.server.global.validator.constraints.Phone; -import com.ticketing.server.user.service.dto.SignUp; +import com.ticketing.server.user.service.dto.SignUpDTO; import javax.validation.constraints.Email; import javax.validation.constraints.NotEmpty; import lombok.Getter; @@ -24,8 +24,8 @@ public class SignUpRequest { @Phone private String phone; - public SignUp toSignUp(PasswordEncoder passwordEncoder) { - return new SignUp(name, email, getEncodePassword(passwordEncoder), phone); + public SignUpDTO toSignUpDto(PasswordEncoder passwordEncoder) { + return new SignUpDTO(name, email, getEncodePassword(passwordEncoder), phone); } private String getEncodePassword(PasswordEncoder passwordEncoder) { diff --git a/server/src/main/java/com/ticketing/server/user/application/request/UserDeleteRequest.java b/server/src/main/java/com/ticketing/server/user/application/request/UserDeleteRequest.java new file mode 100644 index 0000000..3cb6487 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/request/UserDeleteRequest.java @@ -0,0 +1,23 @@ +package com.ticketing.server.user.application.request; + +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import lombok.Getter; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Getter +public class UserDeleteRequest { + + @NotEmpty(message = "{validation.not.empty.email}") + @Email(message = "{validation.email}") + private String email; + + @NotEmpty(message = "{validation.not.empty.password}") + private String password; + + public DeleteUserDTO toDeleteUserDto(PasswordEncoder passwordEncoder) { + return new DeleteUserDTO(email, password, passwordEncoder); + } + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java b/server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java new file mode 100644 index 0000000..fa6fe11 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/request/UserModifyPasswordRequest.java @@ -0,0 +1,30 @@ +package com.ticketing.server.user.application.request; + +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import lombok.Getter; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Getter +public class UserModifyPasswordRequest { + + @NotEmpty(message = "{validation.not.empty.email}") + @Email(message = "{validation.email}") + private String email; + + @NotEmpty(message = "{validation.not.empty.oldpassword}") + private String oldPassword; + + @NotEmpty(message = "{validation.not.empty.newpassword}") + private String newPassword; + + public ChangePasswordDTO toChangePasswordDto(PasswordEncoder passwordEncoder) { + return new ChangePasswordDTO(email, oldPassword, newPassword, passwordEncoder); + } + + public boolean oldEqualNew() { + return oldPassword.equals(newPassword); + } + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/response/SignUpResponse.java b/server/src/main/java/com/ticketing/server/user/application/response/SignUpResponse.java new file mode 100644 index 0000000..d5ad086 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/response/SignUpResponse.java @@ -0,0 +1,20 @@ +package com.ticketing.server.user.application.response; + +import com.ticketing.server.user.domain.User; + +public class SignUpResponse { + + public static SignUpResponse of(User user) { + return new SignUpResponse(user.getName(), user.getEmail()); + } + + public SignUpResponse(String name, String email) { + this.name = name; + this.email = email; + } + + private String name; + + private String email; + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/response/UserChangePasswordResponse.java b/server/src/main/java/com/ticketing/server/user/application/response/UserChangePasswordResponse.java new file mode 100644 index 0000000..b3d2fa3 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/response/UserChangePasswordResponse.java @@ -0,0 +1,20 @@ +package com.ticketing.server.user.application.response; + +import com.ticketing.server.user.domain.User; + +public class UserChangePasswordResponse { + + public static SignUpResponse of(User user) { + return new SignUpResponse(user.getName(), user.getEmail()); + } + + public UserChangePasswordResponse(String name, String email) { + this.name = name; + this.email = email; + } + + private String name; + + private String email; + +} diff --git a/server/src/main/java/com/ticketing/server/user/application/response/UserDeleteResponse.java b/server/src/main/java/com/ticketing/server/user/application/response/UserDeleteResponse.java new file mode 100644 index 0000000..0b82f23 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/application/response/UserDeleteResponse.java @@ -0,0 +1,20 @@ +package com.ticketing.server.user.application.response; + +import com.ticketing.server.user.domain.User; + +public class UserDeleteResponse { + + public static SignUpResponse of(User user) { + return new SignUpResponse(user.getName(), user.getEmail()); + } + + public UserDeleteResponse(String name, String email) { + this.name = name; + this.email = email; + } + + private String name; + + private String email; + +} diff --git a/server/src/main/java/com/ticketing/server/user/domain/User.java b/server/src/main/java/com/ticketing/server/user/domain/User.java index 8fd17c0..2a6df63 100644 --- a/server/src/main/java/com/ticketing/server/user/domain/User.java +++ b/server/src/main/java/com/ticketing/server/user/domain/User.java @@ -1,7 +1,12 @@ package com.ticketing.server.user.domain; import com.ticketing.server.global.dto.repository.AbstractEntity; +import com.ticketing.server.global.exception.AlreadyDeletedException; +import com.ticketing.server.global.exception.PasswordMismatchException; import com.ticketing.server.global.validator.constraints.Phone; +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import com.ticketing.server.user.service.dto.PasswordMatches; import java.time.LocalDateTime; import javax.persistence.Column; import javax.persistence.Entity; @@ -18,6 +23,14 @@ import lombok.NoArgsConstructor; @NoArgsConstructor public class User extends AbstractEntity { + public User(String name, String email, String password, UserGrade grade, String phone) { + this.name = name; + this.email = email; + this.password = password; + this.grade = grade; + this.phone = phone; + } + @Column(name = "name") @NotEmpty(message = "{validation.not.empty.name}") private String name; @@ -45,12 +58,29 @@ public class User extends AbstractEntity { private LocalDateTime deletedAt; - public User(String name, String email, String password, UserGrade grade, String phone) { - this.name = name; - this.email = email; - this.password = password; - this.grade = grade; - this.phone = phone; + public User delete(DeleteUserDTO deleteUser) { + if (isDeleted) { + throw new AlreadyDeletedException("이미 탈퇴된 회원 입니다."); + } + + checkPassword(deleteUser); + + isDeleted = true; + deletedAt = LocalDateTime.now(); + return this; + } + + public User changePassword(ChangePasswordDTO changePassword) { + checkPassword(changePassword); + + this.password = changePassword.getEncodePassword(); + return this; + } + + private void checkPassword(PasswordMatches passwordMatches) { + if (!passwordMatches.passwordMatches(password)) { + throw new PasswordMismatchException(); + } } } diff --git a/server/src/main/java/com/ticketing/server/user/domain/repository/UserRepository.java b/server/src/main/java/com/ticketing/server/user/domain/repository/UserRepository.java index 917570a..adec774 100644 --- a/server/src/main/java/com/ticketing/server/user/domain/repository/UserRepository.java +++ b/server/src/main/java/com/ticketing/server/user/domain/repository/UserRepository.java @@ -10,4 +10,6 @@ public interface UserRepository extends JpaRepository { Optional findByEmail(String email); + Optional findByEmailAndIsDeletedFalse(String email); + } diff --git a/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java b/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java index 86fe3cc..7650fc2 100644 --- a/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java +++ b/server/src/main/java/com/ticketing/server/user/service/UserServiceImpl.java @@ -1,8 +1,11 @@ package com.ticketing.server.user.service; +import com.ticketing.server.global.exception.NotFoundEmailException; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.domain.repository.UserRepository; -import com.ticketing.server.user.service.dto.SignUp; +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import com.ticketing.server.user.service.dto.SignUpDTO; import com.ticketing.server.user.service.interfaces.UserService; import java.util.Optional; import javax.validation.Valid; @@ -23,15 +26,40 @@ public class UserServiceImpl implements UserService { @Override @Transactional - public Optional register(@Valid SignUp signUpDto) { + public User register(@Valid SignUpDTO signUpDto) { Optional user = userRepository.findByEmail(signUpDto.getEmail()); if (user.isPresent()) { log.error("이미 존재하는 이메일이기 때문에 신규 회원가입을 진행할 수 없습니다. :: {}", signUpDto); - return Optional.empty(); + throw new IllegalArgumentException("이미 존재하는 이메일이기 때문에 신규 회원가입을 진행할 수 없습니다."); } - User newUser = userRepository.save(signUpDto.toUser()); - return Optional.of(newUser); + return userRepository.save(signUpDto.toUser()); + } + + @Override + @Transactional + public User delete(@Valid DeleteUserDTO deleteUserDto) { + Optional optionalUser = userRepository.findByEmail(deleteUserDto.getEmail()); + if (optionalUser.isEmpty()) { + log.error("존재하지 않는 이메일 입니다. :: {}", deleteUserDto); + throw new NotFoundEmailException(); + } + + User user = optionalUser.get(); + return user.delete(deleteUserDto); + } + + @Override + @Transactional + public User changePassword(@Valid ChangePasswordDTO changePasswordDto) { + Optional optionalUser = userRepository.findByEmailAndIsDeletedFalse(changePasswordDto.getEmail()); + if (optionalUser.isEmpty()) { + log.error("존재하지 않는 이메일 입니다. :: {}", changePasswordDto); + throw new NotFoundEmailException(); + } + + User user = optionalUser.get(); + return user.changePassword(changePasswordDto); } } diff --git a/server/src/main/java/com/ticketing/server/user/service/dto/ChangePasswordDTO.java b/server/src/main/java/com/ticketing/server/user/service/dto/ChangePasswordDTO.java new file mode 100644 index 0000000..a0ae34a --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/service/dto/ChangePasswordDTO.java @@ -0,0 +1,47 @@ +package com.ticketing.server.user.service.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import org.springframework.security.crypto.password.PasswordEncoder; + +public class ChangePasswordDTO implements PasswordMatches { + + public ChangePasswordDTO(String email, String oldPassword, String newPassword, PasswordEncoder passwordEncoder) { + this.email = email; + this.oldPassword = oldPassword; + this.newPassword = newPassword; + this.passwordEncoder = passwordEncoder; + } + + @NotEmpty(message = "{validation.not.empty.email}") + @Email(message = "{validation.email}") + private String email; + + @NotEmpty(message = "{validation.not.empty.oldpassword}") + private String oldPassword; + + @NotEmpty(message = "{validation.not.empty.newpassword}") + private String newPassword; + + private PasswordEncoder passwordEncoder; + + public String getEmail() { + return email; + } + + @Override + public boolean passwordMatches(String password) { + return passwordEncoder.matches(oldPassword, password); + } + + public String getEncodePassword() { + return passwordEncoder.encode(newPassword); + } + + @Override + public String toString() { + return "ChangePassword{" + + "email='" + email + '\'' + + '}'; + } +} diff --git a/server/src/main/java/com/ticketing/server/user/service/dto/DeleteUserDTO.java b/server/src/main/java/com/ticketing/server/user/service/dto/DeleteUserDTO.java new file mode 100644 index 0000000..ed1c576 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/service/dto/DeleteUserDTO.java @@ -0,0 +1,40 @@ +package com.ticketing.server.user.service.dto; + +import javax.validation.constraints.Email; +import javax.validation.constraints.NotEmpty; +import org.springframework.security.crypto.password.PasswordEncoder; + +public class DeleteUserDTO implements PasswordMatches { + + public DeleteUserDTO(String email, String inputPassword, PasswordEncoder passwordEncoder) { + this.email = email; + this.inputPassword = inputPassword; + this.passwordEncoder = passwordEncoder; + } + + @NotEmpty(message = "{validation.not.empty.email}") + @Email(message = "{validation.email}") + private String email; + + @NotEmpty(message = "{validation.not.empty.password}") + private String inputPassword; + + private PasswordEncoder passwordEncoder; + + @Override + public boolean passwordMatches(String password) { + return passwordEncoder.matches(this.inputPassword, password); + } + + public String getEmail() { + return email; + } + + @Override + public String toString() { + return "DeleteUser{" + + "email='" + email + '\'' + + '}'; + } + +} diff --git a/server/src/main/java/com/ticketing/server/user/service/dto/PasswordMatches.java b/server/src/main/java/com/ticketing/server/user/service/dto/PasswordMatches.java new file mode 100644 index 0000000..7ef8538 --- /dev/null +++ b/server/src/main/java/com/ticketing/server/user/service/dto/PasswordMatches.java @@ -0,0 +1,7 @@ +package com.ticketing.server.user.service.dto; + +@FunctionalInterface +public interface PasswordMatches { + + boolean passwordMatches(String password); +} diff --git a/server/src/main/java/com/ticketing/server/user/service/dto/SignUp.java b/server/src/main/java/com/ticketing/server/user/service/dto/SignUpDTO.java similarity index 91% rename from server/src/main/java/com/ticketing/server/user/service/dto/SignUp.java rename to server/src/main/java/com/ticketing/server/user/service/dto/SignUpDTO.java index a6549ce..2dfc484 100644 --- a/server/src/main/java/com/ticketing/server/user/service/dto/SignUp.java +++ b/server/src/main/java/com/ticketing/server/user/service/dto/SignUpDTO.java @@ -8,7 +8,7 @@ import javax.validation.constraints.NotEmpty; import lombok.Getter; @Getter -public class SignUp { +public class SignUpDTO { @NotEmpty(message = "{validation.not.empty.name}") private String name; @@ -24,7 +24,7 @@ public class SignUp { @Phone private String phone; - public SignUp(String name, String email, String password, String phone) { + public SignUpDTO(String name, String email, String password, String phone) { this.name = name; this.email = email; this.password = password; diff --git a/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java b/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java index 2fab6c8..76a0232 100644 --- a/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java +++ b/server/src/main/java/com/ticketing/server/user/service/interfaces/UserService.java @@ -1,12 +1,17 @@ package com.ticketing.server.user.service.interfaces; import com.ticketing.server.user.domain.User; -import com.ticketing.server.user.service.dto.SignUp; -import java.util.Optional; +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import com.ticketing.server.user.service.dto.SignUpDTO; import javax.validation.Valid; public interface UserService { - Optional register(@Valid SignUp signUpDto); + User register(@Valid SignUpDTO signUpDto); + + User delete(@Valid DeleteUserDTO deleteUserDto); + + User changePassword(@Valid ChangePasswordDTO changePasswordDto); } diff --git a/server/src/main/resources/i18n/messages.properties b/server/src/main/resources/i18n/messages.properties index a8c66b8..2d2f70c 100644 --- a/server/src/main/resources/i18n/messages.properties +++ b/server/src/main/resources/i18n/messages.properties @@ -1,6 +1,8 @@ validation.not.empty.name="\uC774\uB984\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.email="\uC774\uBA54\uC77C\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.password="\uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." +validation.not.empty.oldpassword="\uD604\uC7AC \uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." +validation.not.empty.newpassword="\uBCC0\uACBD\uD560 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.grade="\uC0AC\uC6A9\uC790 \uB4F1\uAE09\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.phone="\uD734\uB300\uBC88\uD638\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.email="\uC774\uBA54\uC77C\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." diff --git a/server/src/main/resources/i18n/messages_en.properties b/server/src/main/resources/i18n/messages_en.properties index 36cdd98..65240e4 100644 --- a/server/src/main/resources/i18n/messages_en.properties +++ b/server/src/main/resources/i18n/messages_en.properties @@ -1,6 +1,8 @@ validation.not.empty.name="name is required." validation.not.empty.email="email is required." validation.not.empty.password="password is required." +validation.not.empty.oldpassword="Old Password is required." +validation.not.empty.newpassword="New Password is required." validation.not.empty.grade="user grade is required." validation.not.empty.phone="phone is required." validation.email="email is not valid." diff --git a/server/src/main/resources/i18n/messages_ko.properties b/server/src/main/resources/i18n/messages_ko.properties index a8c66b8..2d2f70c 100644 --- a/server/src/main/resources/i18n/messages_ko.properties +++ b/server/src/main/resources/i18n/messages_ko.properties @@ -1,6 +1,8 @@ validation.not.empty.name="\uC774\uB984\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.email="\uC774\uBA54\uC77C\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.password="\uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." +validation.not.empty.oldpassword="\uD604\uC7AC \uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." +validation.not.empty.newpassword="\uBCC0\uACBD\uD560 \uD328\uC2A4\uC6CC\uB4DC\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.grade="\uC0AC\uC6A9\uC790 \uB4F1\uAE09\uC740 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.not.empty.phone="\uD734\uB300\uBC88\uD638\uB294 \uD544\uC218 \uC785\uB2C8\uB2E4." validation.email="\uC774\uBA54\uC77C\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." diff --git a/server/src/test/java/com/ticketing/server/user/domain/UserTest.java b/server/src/test/java/com/ticketing/server/user/domain/UserTest.java index 63d929e..c8c7fa3 100644 --- a/server/src/test/java/com/ticketing/server/user/domain/UserTest.java +++ b/server/src/test/java/com/ticketing/server/user/domain/UserTest.java @@ -1,8 +1,18 @@ package com.ticketing.server.user.domain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; +import com.ticketing.server.global.exception.AlreadyDeletedException; +import com.ticketing.server.global.exception.PasswordMismatchException; +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import com.ticketing.server.user.service.dto.DeleteUserDtoTest; +import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; @@ -11,26 +21,98 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.junit.jupiter.params.provider.ValueSource; +import org.junit.jupiter.params.provider.MethodSource; class UserTest { - private static Validator validator; + private Validator validator; + private Map users; @BeforeEach void init() { ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); validator = factory.getValidator(); + users = provideCorrectUsers().collect(Collectors.toMap(User::getEmail, user -> user)); + } + @ParameterizedTest + @MethodSource("provideDifferentPasswordDeleteUsers") + @DisplayName("입력된 패스워드가 다를 경우") + void passwordMismatchException(DeleteUserDTO deleteUser) { + // given + User user = users.get(deleteUser.getEmail()); + + // when + // then + assertThatThrownBy(() -> user.delete(deleteUser)) + .isInstanceOf(PasswordMismatchException.class); + } + + @ParameterizedTest + @MethodSource("provideDeleteUsers") + @DisplayName("이미 회원탈퇴 되어 있는 경우") + void alreadyDeletedException(DeleteUserDTO deleteUserDto) { + // given + User user = users.get(deleteUserDto.getEmail()); + + // when + user.delete(deleteUserDto); + + // then + assertThatThrownBy(() -> user.delete(deleteUserDto)) + .isInstanceOf(AlreadyDeletedException.class); + } + + @ParameterizedTest + @MethodSource("provideDeleteUsers") + @DisplayName("회원탈퇴 성공") + void deleteSuccess(DeleteUserDTO deleteUserDto) { + // given + User user = users.get(deleteUserDto.getEmail()); + + // when + User deletedUser = user.delete(deleteUserDto); + + // then + assertAll( + () -> assertThat(deletedUser.getDeletedAt()).isNotNull() + , () -> assertThat(deletedUser.isDeleted()).isTrue() + ); } @Test - @DisplayName("유저 검증 성공") - void validateSuccess() { + @DisplayName("입력받은 패스워드와 불일치로 변경 실패") + void changePasswordFail() { // given - User user = new User("유저1", "email@gmail.com", "testPassword01", UserGrade.GUEST, "010-1234-5678"); + ChangePasswordDTO changePasswordDto = new ChangePasswordDTO("ticketing1@gmail.com", "1234567", "ticketing1234", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER); + User user = users.get(changePasswordDto.getEmail()); + // when + // then + assertThatThrownBy(() -> user.changePassword(changePasswordDto)) + .isInstanceOf(PasswordMismatchException.class); + } + + @Test + @DisplayName("패스워드 변경 성공") + void changePasswordSuccess() { + // given + ChangePasswordDTO changePasswordDto = new ChangePasswordDTO("ticketing1@gmail.com", "123456", "ticketing1234", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER); + User user = users.get(changePasswordDto.getEmail()); + String oldPassword = user.getPassword(); + + // when + User modifiedUser = user.changePassword(changePasswordDto); + + // then + assertThat(modifiedUser.getPassword()).isNotEqualTo(oldPassword); + } + + @ParameterizedTest + @MethodSource("provideCorrectUsers") + @DisplayName("유저 검증 성공") + void validateSuccess(User user) { + // given // when Set> constraintViolations = validator.validate(user); @@ -39,12 +121,10 @@ class UserTest { } @ParameterizedTest - @NullAndEmptySource + @MethodSource("provideNullOrEmptyOfName") @DisplayName("name null 혹은 빈값 검증") - void nameNullOrEmpty(String name) { + void nameNullOrEmpty(User user) { // given - User user = new User(name, "email@gmail.com", "testPassword01", UserGrade.GUEST, "010-1234-5678"); - // when Set> constraintViolations = validator.validate(user); @@ -53,12 +133,10 @@ class UserTest { } @ParameterizedTest - @NullAndEmptySource + @MethodSource("provideNullOrEmptyOfEmail") @DisplayName("email null or empty 검증") - void emailNullOrEmpty(String email) { + void emailNullOrEmpty(User user) { // given - User user = new User("유저1", email, "testPassword01", UserGrade.GUEST, "010-1234-5678"); - // when Set> constraintViolations = validator.validate(user); @@ -67,12 +145,10 @@ class UserTest { } @ParameterizedTest - @ValueSource(strings = {"email", "@hello.com", "12Bye#domain.com"}) + @MethodSource("provideValidationFailedOfEmail") @DisplayName("email 실패 검증") - void emailValid(String email) { + void emailValid(User user) { // given - User user = new User("유저1", email, "testPassword01", UserGrade.GUEST, "010-1234-5678"); - // when Set> constraintViolations = validator.validate(user); @@ -81,12 +157,10 @@ class UserTest { } @ParameterizedTest - @NullAndEmptySource + @MethodSource("provideNullOrEmptyOfPassword") @DisplayName("password null 혹은 빈값 검증") - void passwordNullOrEmpty(String password) { + void passwordNullOrEmpty(User user) { // given - User user = new User("유저1", "email@gmail.com", password, UserGrade.GUEST, "010-1234-5678"); - // when Set> constraintViolations = validator.validate(user); @@ -108,12 +182,10 @@ class UserTest { } @ParameterizedTest + @MethodSource("provideNullOrEmptyOfPhone") @DisplayName("phone null or empty 검증") - @NullAndEmptySource - void phoneNullOrEmpty(String phone) { + void phoneNullOrEmpty(User user) { // given - User user = new User("유저1", "email@gmail.com", "testPassword01", UserGrade.GUEST, phone); - // when Set> constraintViolations = validator.validate(user); @@ -122,12 +194,10 @@ class UserTest { } @ParameterizedTest + @MethodSource("provideValidationFailedOfPhone") @DisplayName("phone 실패 검증") - @ValueSource(strings = {"010-123-1234", "02-0444-4044", "033-7953", "033-0455-504"}) - void phoneValid(String phone) { + void phoneValid(User user) { // given - User user = new User("유저1", "email@gmail.com", "testPassword01", UserGrade.GUEST, phone); - // when Set> constraintViolations = validator.validate(user); @@ -135,4 +205,76 @@ class UserTest { assertThat(constraintViolations).hasSize(1); } + public static Stream provideCorrectUsers() { + return Stream.of( + new User("유저1", "ticketing1@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678") + , new User("유저2", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "010-2234-5678") + , new User("유저3", "ticketing3@gmail.com", "ticketing", UserGrade.STAFF, "010-3234-5678") + , new User("유저4", "ticketing4@gmail.com", "ticketing123456", UserGrade.STAFF, "010-4234-5678") + ); + } + + public static Stream provideNullOrEmptyOfName() { + return Stream.of( + new User(null, "ticketing1@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678") + , new User("", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "010-2234-5678") + ); + } + + public static Stream provideNullOrEmptyOfEmail() { + return Stream.of( + new User("유저1", null, "123456", UserGrade.GUEST, "010-1234-5678") + , new User("유저2", "", "qwe123", UserGrade.GUEST, "010-2234-5678") + ); + } + + public static Stream provideValidationFailedOfEmail() { + return Stream.of( + new User("유저1", "email", "123456", UserGrade.GUEST, "010-1234-5678") + , new User("유저2", "@gmail.com", "qwe123", UserGrade.GUEST, "010-2234-5678") + , new User("유저3", "12Bye#domain.com", "ticketing", UserGrade.STAFF, "010-3234-5678") + ); + } + + public static Stream provideNullOrEmptyOfPassword() { + return Stream.of( + new User("유저1", "ticketing1@gmail.com", null, UserGrade.GUEST, "010-1234-5678") + , new User("유저2", "ticketing2@gmail.com", "", UserGrade.GUEST, "010-2234-5678") + ); + } + + public static Stream provideNullOrEmptyOfPhone() { + return Stream.of( + new User("유저1", "ticketing1@gmail.com", "123456", UserGrade.GUEST, null) + , new User("유저2", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "") + ); + } + + public static Stream provideValidationFailedOfPhone() { + return Stream.of( + new User("유저1", "ticketing1@gmail.com", "123456", UserGrade.GUEST, "010-123-1234") + , new User("유저2", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "02-0444-4044") + , new User("유저3", "ticketing3@gmail.com", "ticketing", UserGrade.STAFF, "033-7953") + , new User("유저4", "ticketing4@gmail.com", "ticketing123456", UserGrade.STAFF, "033-0455-504") + ); + } + + public static Stream provideDifferentPasswordDeleteUsers() { + return Stream.of( + new DeleteUserDTO("ticketing1@gmail.com", "1234561", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing2@gmail.com", "qwe1231", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing3@gmail.com", "ticketing1", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing4@gmail.com", "ticketing1234561", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + ); + } + + public static Stream provideDeleteUsers() { + return Stream.of( + new DeleteUserDTO("ticketing1@gmail.com", "123456", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing2@gmail.com", "qwe123", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing3@gmail.com", "ticketing", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + , new DeleteUserDTO("ticketing4@gmail.com", "ticketing123456", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER) + ); + } + } diff --git a/server/src/test/java/com/ticketing/server/user/service/UserServiceImplTest.java b/server/src/test/java/com/ticketing/server/user/service/UserServiceImplTest.java index e7b17d8..5088a08 100644 --- a/server/src/test/java/com/ticketing/server/user/service/UserServiceImplTest.java +++ b/server/src/test/java/com/ticketing/server/user/service/UserServiceImplTest.java @@ -1,13 +1,18 @@ package com.ticketing.server.user.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import com.ticketing.server.global.exception.NotFoundEmailException; import com.ticketing.server.user.domain.User; import com.ticketing.server.user.domain.UserGrade; import com.ticketing.server.user.domain.repository.UserRepository; -import com.ticketing.server.user.service.dto.SignUp; +import com.ticketing.server.user.service.dto.ChangePasswordDTO; +import com.ticketing.server.user.service.dto.DeleteUserDTO; +import com.ticketing.server.user.service.dto.DeleteUserDtoTest; +import com.ticketing.server.user.service.dto.SignUpDTO; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -21,7 +26,9 @@ import org.mockito.junit.jupiter.MockitoExtension; class UserServiceImplTest { User user; - SignUp signUp; + SignUpDTO signUpDto; + DeleteUserDTO deleteUserDto; + ChangePasswordDTO changePasswordDto; @Mock UserRepository userRepository; @@ -31,9 +38,10 @@ class UserServiceImplTest { @BeforeEach void init() { - - signUp = new SignUp("유저", "ticketing@gmail.com", "123456", "010-1234-5678"); + signUpDto = new SignUpDTO("유저", "ticketing@gmail.com", "123456", "010-1234-5678"); user = new User("유저", "ticketing@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678"); + deleteUserDto = new DeleteUserDTO("ticketing@gmail.com", "123456", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER); + changePasswordDto = new ChangePasswordDTO("ticketing@gmail.com", "123456", "ticketing1234", DeleteUserDtoTest.CUSTOM_PASSWORD_ENCODER); } @Test @@ -43,24 +51,73 @@ class UserServiceImplTest { when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.of(user)); // when - Optional user = userService.register(signUp); - // then - assertThat(user).isEmpty(); + assertThatThrownBy(() -> userService.register(signUpDto)) + .isInstanceOf(IllegalArgumentException.class); } @Test @DisplayName("회원가입 성공했을 경우") - void UserServiceImplTest() { + void registerSuccess() { // given when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.empty()); when(userRepository.save(any())).thenReturn(user); // when - Optional user = userService.register(signUp); + User user = userService.register(signUpDto); // then - assertThat(user).isPresent(); + assertThat(user).isNotNull(); + } + + @Test + @DisplayName("회원탈퇴 시 이메일이 존재하지 않을 경우") + void deleteFail() { + // given + when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userService.delete(deleteUserDto)) + .isInstanceOf(NotFoundEmailException.class); + } + + @Test + @DisplayName("회원탈퇴 성공했을 경우") + void deleteSuccess() { + // given + when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.of(user)); + + // when + User user = userService.delete(deleteUserDto); + + // then + assertThat(user).isNotNull(); + } + + @Test + @DisplayName("패스워드 변경 시 이메일이 존재하지 않을 경우") + void changePasswordFail() { + // given + when(userRepository.findByEmailAndIsDeletedFalse("ticketing@gmail.com")).thenReturn(Optional.empty()); + + // when + // then + assertThatThrownBy(() -> userService.changePassword(changePasswordDto)) + .isInstanceOf(NotFoundEmailException.class); + } + + @Test + @DisplayName("패스워드 변경 성공했을 경우") + void changePasswordSuccess() { + // given + when(userRepository.findByEmailAndIsDeletedFalse("ticketing@gmail.com")).thenReturn(Optional.of(user)); + + // when + User user = userService.changePassword(changePasswordDto); + + // then + assertThat(user).isNotNull(); } } diff --git a/server/src/test/java/com/ticketing/server/user/service/dto/DeleteUserDtoTest.java b/server/src/test/java/com/ticketing/server/user/service/dto/DeleteUserDtoTest.java new file mode 100644 index 0000000..2ca6d83 --- /dev/null +++ b/server/src/test/java/com/ticketing/server/user/service/dto/DeleteUserDtoTest.java @@ -0,0 +1,37 @@ +package com.ticketing.server.user.service.dto; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.security.crypto.password.PasswordEncoder; + +public class DeleteUserDtoTest { + + public static PasswordEncoder CUSTOM_PASSWORD_ENCODER = new CustomPasswordEncoder(); + + public static class CustomPasswordEncoder implements PasswordEncoder { + + @Override + public String encode(CharSequence rawPassword) { + return null; + } + + @Override + public boolean matches(CharSequence rawPassword, String encodedPassword) { + return rawPassword.toString().equals(encodedPassword); + } + } + + @Test + @DisplayName("CustomPasswordEncoder matches 테스트") + void customPasswordEncoderMatches() { + // given + DeleteUserDTO deleteUserDto = new DeleteUserDTO("ticketing@gmail.com", "123456", CUSTOM_PASSWORD_ENCODER); + + // when + // then + assertThat(deleteUserDto.passwordMatches("123456")).isTrue(); + } + +} diff --git a/server/src/test/java/com/ticketing/server/user/service/dto/SignUpTest.java b/server/src/test/java/com/ticketing/server/user/service/dto/SignUpDtoTest.java similarity index 78% rename from server/src/test/java/com/ticketing/server/user/service/dto/SignUpTest.java rename to server/src/test/java/com/ticketing/server/user/service/dto/SignUpDtoTest.java index 5dec314..2b57fa8 100644 --- a/server/src/test/java/com/ticketing/server/user/service/dto/SignUpTest.java +++ b/server/src/test/java/com/ticketing/server/user/service/dto/SignUpDtoTest.java @@ -6,13 +6,13 @@ import com.ticketing.server.user.domain.User; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class SignUpTest { +class SignUpDtoTest { @Test @DisplayName("toUser 메소드로 User 객체 생성") void toUser() { // given - SignUp signUp = new SignUp("유저1", "ticketing@gmail.com", "123456", "010-1234-5678"); + SignUpDTO signUp = new SignUpDTO("유저1", "ticketing@gmail.com", "123456", "010-1234-5678"); // when User user = signUp.toUser();