feat: 회원탈퇴 구현
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
package com.ticketing.server.global.exception;
|
||||
|
||||
public class AlreadyDeletedException extends RuntimeException {
|
||||
|
||||
public AlreadyDeletedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
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.domain.User;
|
||||
import com.ticketing.server.user.service.UserServiceImpl;
|
||||
import java.util.Optional;
|
||||
@@ -9,6 +10,7 @@ import lombok.RequiredArgsConstructor;
|
||||
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.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -33,4 +35,15 @@ public class UserController {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public ResponseEntity<Object> delete(@RequestBody @Valid UserDeleteRequest userDeleteRequest) {
|
||||
Optional<User> user = userService.delete(userDeleteRequest.toDeleteUser(passwordEncoder));
|
||||
|
||||
if (user.isEmpty()) {
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
|
||||
}
|
||||
|
||||
return ResponseEntity.status(HttpStatus.OK).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.ticketing.server.user.application.request;
|
||||
|
||||
import com.ticketing.server.user.service.dto.DeleteUser;
|
||||
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 DeleteUser toDeleteUser(PasswordEncoder passwordEncoder) {
|
||||
return new DeleteUser(email, password, passwordEncoder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.ticketing.server.user.domain;
|
||||
|
||||
public class PasswordMismatchException extends RuntimeException {
|
||||
|
||||
private static final String MESSAGE = "패스워드가 일치하지 않습니다";
|
||||
|
||||
public PasswordMismatchException() {
|
||||
super(MESSAGE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
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.validator.constraints.Phone;
|
||||
import com.ticketing.server.user.service.dto.DeleteUser;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Optional;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
@@ -54,14 +55,18 @@ public class User extends AbstractEntity {
|
||||
|
||||
private LocalDateTime deletedAt;
|
||||
|
||||
public Optional<User> delete() {
|
||||
public User delete(DeleteUser deleteUser) {
|
||||
if (isDeleted) {
|
||||
return Optional.empty();
|
||||
throw new AlreadyDeletedException("이미 탈퇴된 회원 입니다.");
|
||||
}
|
||||
|
||||
if (!deleteUser.passwordEquals(password)) {
|
||||
throw new PasswordMismatchException();
|
||||
}
|
||||
|
||||
isDeleted = true;
|
||||
deletedAt = LocalDateTime.now();
|
||||
return Optional.of(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ticketing.server.user.service;
|
||||
|
||||
import com.ticketing.server.user.domain.User;
|
||||
import com.ticketing.server.user.domain.repository.UserRepository;
|
||||
import com.ticketing.server.user.service.dto.DeleteUser;
|
||||
import com.ticketing.server.user.service.dto.SignUp;
|
||||
import com.ticketing.server.user.service.interfaces.UserService;
|
||||
import java.util.Optional;
|
||||
@@ -34,4 +35,17 @@ public class UserServiceImpl implements UserService {
|
||||
return Optional.of(newUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Optional<User> delete(@Valid DeleteUser deleteUser) {
|
||||
Optional<User> optionalUser = userRepository.findByEmail(deleteUser.getEmail());
|
||||
if (optionalUser.isEmpty()) {
|
||||
log.error("존재하지 않는 이메일 입니다. :: {}", deleteUser);
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
User user = optionalUser.get();
|
||||
return Optional.of(user.delete(deleteUser));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
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 DeleteUser {
|
||||
|
||||
public DeleteUser(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;
|
||||
|
||||
public boolean passwordEquals(String password) {
|
||||
return passwordEncoder.matches(this.inputPassword, password);
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DeleteUser{" +
|
||||
"email='" + email + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ticketing.server.user.service.interfaces;
|
||||
|
||||
import com.ticketing.server.user.domain.User;
|
||||
import com.ticketing.server.user.service.dto.DeleteUser;
|
||||
import com.ticketing.server.user.service.dto.SignUp;
|
||||
import java.util.Optional;
|
||||
import javax.validation.Valid;
|
||||
@@ -9,4 +10,6 @@ public interface UserService {
|
||||
|
||||
Optional<User> register(@Valid SignUp signUpDto);
|
||||
|
||||
Optional<User> delete(@Valid DeleteUser deleteUser);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
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 java.util.Optional;
|
||||
import com.ticketing.server.global.exception.AlreadyDeletedException;
|
||||
import com.ticketing.server.user.service.dto.DeleteUser;
|
||||
import com.ticketing.server.user.service.dto.DeleteUserTest;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
import javax.validation.ConstraintViolation;
|
||||
@@ -14,6 +17,7 @@ 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.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
class UserTest {
|
||||
@@ -81,33 +85,68 @@ class UserTest {
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideCorrectUsers")
|
||||
@DisplayName("이미 회원탈퇴 되어 있는 경우")
|
||||
void deleteFail(User user) {
|
||||
// given
|
||||
user.delete();
|
||||
public static Stream<Arguments> provideDifferentPasswordDeleteUsers() {
|
||||
return Stream.of(
|
||||
Arguments.of(new User("유저1", "ticketing1@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678")
|
||||
, new DeleteUser("ticketing1@gmail.com", "1234561", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저2", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "010-2234-5678")
|
||||
, new DeleteUser("ticketing2@gmail.com", "qwe1231", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저3", "ticketing3@gmail.com", "ticketing", UserGrade.STAFF, "010-3234-5678")
|
||||
, new DeleteUser("ticketing3@gmail.com", "ticketing1", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저4", "ticketing4@gmail.com", "ticketing123456", UserGrade.STAFF, "010-4234-5678")
|
||||
, new DeleteUser("ticketing4@gmail.com", "ticketing1234561", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
);
|
||||
}
|
||||
|
||||
// when
|
||||
Optional<User> deletedUser = user.delete();
|
||||
|
||||
// then
|
||||
assertThat(deletedUser).isEmpty();
|
||||
public static Stream<Arguments> provideDeleteUsers() {
|
||||
return Stream.of(
|
||||
Arguments.of(new User("유저1", "ticketing1@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678")
|
||||
, new DeleteUser("ticketing1@gmail.com", "123456", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저2", "ticketing2@gmail.com", "qwe123", UserGrade.GUEST, "010-2234-5678")
|
||||
, new DeleteUser("ticketing2@gmail.com", "qwe123", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저3", "ticketing3@gmail.com", "ticketing", UserGrade.STAFF, "010-3234-5678")
|
||||
, new DeleteUser("ticketing3@gmail.com", "ticketing", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
, Arguments.of(new User("유저4", "ticketing4@gmail.com", "ticketing123456", UserGrade.STAFF, "010-4234-5678")
|
||||
, new DeleteUser("ticketing4@gmail.com", "ticketing123456", DeleteUserTest.CUSTOM_PASSWORD_ENCODER))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideCorrectUsers")
|
||||
@DisplayName("회원탈퇴 성공")
|
||||
void deleteSuccess(User user) {
|
||||
@MethodSource("provideDifferentPasswordDeleteUsers")
|
||||
@DisplayName("입력된 패스워드가 다를 경우")
|
||||
void passwordMismatchException(User user, DeleteUser deleteUser) {
|
||||
// given
|
||||
// when
|
||||
Optional<User> deletedUser = user.delete();
|
||||
// then
|
||||
assertThatThrownBy(() -> user.delete(deleteUser))
|
||||
.isInstanceOf(PasswordMismatchException.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideDeleteUsers")
|
||||
@DisplayName("이미 회원탈퇴 되어 있는 경우")
|
||||
void alreadyDeletedException(User user, DeleteUser deleteUser) {
|
||||
// given
|
||||
// when
|
||||
user.delete(deleteUser);
|
||||
|
||||
// then
|
||||
assertThatThrownBy(() -> user.delete(deleteUser))
|
||||
.isInstanceOf(AlreadyDeletedException.class);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideDeleteUsers")
|
||||
@DisplayName("회원탈퇴 성공")
|
||||
void deleteSuccess(User user, DeleteUser deleteUser) {
|
||||
// given
|
||||
// when
|
||||
User deletedUser = user.delete(deleteUser);
|
||||
|
||||
// then
|
||||
assertThat(deletedUser).isPresent();
|
||||
assertAll(
|
||||
() -> assertThat(deletedUser.get().getDeletedAt()).isNotNull()
|
||||
, () -> assertThat(deletedUser.get().isDeleted()).isTrue()
|
||||
() -> assertThat(deletedUser.getDeletedAt()).isNotNull()
|
||||
, () -> assertThat(deletedUser.isDeleted()).isTrue()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ import static org.mockito.Mockito.when;
|
||||
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.DeleteUser;
|
||||
import com.ticketing.server.user.service.dto.DeleteUserTest;
|
||||
import com.ticketing.server.user.service.dto.SignUp;
|
||||
import java.util.Optional;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -22,6 +24,7 @@ class UserServiceImplTest {
|
||||
|
||||
User user;
|
||||
SignUp signUp;
|
||||
DeleteUser deleteUser;
|
||||
|
||||
@Mock
|
||||
UserRepository userRepository;
|
||||
@@ -31,9 +34,9 @@ class UserServiceImplTest {
|
||||
|
||||
@BeforeEach
|
||||
void init() {
|
||||
|
||||
signUp = new SignUp("유저", "ticketing@gmail.com", "123456", "010-1234-5678");
|
||||
user = new User("유저", "ticketing@gmail.com", "123456", UserGrade.GUEST, "010-1234-5678");
|
||||
deleteUser = new DeleteUser("ticketing@gmail.com", "123456", DeleteUserTest.CUSTOM_PASSWORD_ENCODER);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -63,4 +66,30 @@ class UserServiceImplTest {
|
||||
assertThat(user).isPresent();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("회원탈퇴 시 이메일이 존재하지 않을 경우")
|
||||
void deleteFail() {
|
||||
// given
|
||||
when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.empty());
|
||||
|
||||
// when
|
||||
Optional<User> user = userService.delete(deleteUser);
|
||||
|
||||
// then
|
||||
assertThat(user).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("회원탈퇴 성공했을 경우")
|
||||
void deleteSuccess() {
|
||||
// given
|
||||
when(userRepository.findByEmail("ticketing@gmail.com")).thenReturn(Optional.of(user));
|
||||
|
||||
// when
|
||||
Optional<User> user = userService.delete(deleteUser);
|
||||
|
||||
// then
|
||||
assertThat(user).isPresent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 DeleteUserTest {
|
||||
|
||||
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
|
||||
DeleteUser user = new DeleteUser("ticketing@gmail.com", "123456", CUSTOM_PASSWORD_ENCODER);
|
||||
|
||||
// when
|
||||
// then
|
||||
assertThat(user.passwordEquals("123456")).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user