feat: 회원탈퇴 구현

This commit is contained in:
dongHyo
2022-05-22 20:45:39 +09:00
parent 1767d71baf
commit 940c6b327c
11 changed files with 246 additions and 24 deletions

View File

@@ -0,0 +1,9 @@
package com.ticketing.server.global.exception;
public class AlreadyDeletedException extends RuntimeException {
public AlreadyDeletedException(String message) {
super(message);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,11 @@
package com.ticketing.server.user.domain;
public class PasswordMismatchException extends RuntimeException {
private static final String MESSAGE = "패스워드가 일치하지 않습니다";
public PasswordMismatchException() {
super(MESSAGE);
}
}

View File

@@ -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;
}
}

View File

@@ -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));
}
}

View File

@@ -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 + '\'' +
'}';
}
}

View File

@@ -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);
}

View File

@@ -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()
);
}

View File

@@ -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();
}
}

View File

@@ -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();
}
}