From 8166a1981ccbf3b7268d2aba99c9c232a5df6797 Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 15 Jul 2025 14:22:43 +0900 Subject: [PATCH 1/4] =?UTF-8?q?Feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flipnote/user/controller/UserController.java | 9 +++++++++ .../java/project/flipnote/user/entity/User.java | 5 +++++ .../flipnote/user/repository/UserRepository.java | 2 ++ .../project/flipnote/user/service/UserService.java | 13 +++++++++++++ 4 files changed, 29 insertions(+) diff --git a/src/main/java/project/flipnote/user/controller/UserController.java b/src/main/java/project/flipnote/user/controller/UserController.java index a98e67ee..4a62aef8 100644 --- a/src/main/java/project/flipnote/user/controller/UserController.java +++ b/src/main/java/project/flipnote/user/controller/UserController.java @@ -2,6 +2,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +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; @@ -9,6 +11,7 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import project.flipnote.common.security.dto.UserAuth; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; import project.flipnote.user.service.UserService; @@ -25,4 +28,10 @@ public ResponseEntity register(@Valid @RequestBody UserReg UserRegisterResponse res = userService.register(req); return ResponseEntity.status(HttpStatus.CREATED).body(res); } + + @DeleteMapping + public ResponseEntity unregister(@AuthenticationPrincipal UserAuth userAuth) { + userService.unregister(userAuth.userId()); + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/project/flipnote/user/entity/User.java b/src/main/java/project/flipnote/user/entity/User.java index 24e71942..0256974c 100644 --- a/src/main/java/project/flipnote/user/entity/User.java +++ b/src/main/java/project/flipnote/user/entity/User.java @@ -76,4 +76,9 @@ public User( this.role = UserRole.USER; } + @Override + public void softDelete() { + super.softDelete(); + this.status = UserStatus.INACTIVE; + } } diff --git a/src/main/java/project/flipnote/user/repository/UserRepository.java b/src/main/java/project/flipnote/user/repository/UserRepository.java index 05761956..007c48cb 100644 --- a/src/main/java/project/flipnote/user/repository/UserRepository.java +++ b/src/main/java/project/flipnote/user/repository/UserRepository.java @@ -14,4 +14,6 @@ public interface UserRepository extends JpaRepository { boolean existsByPhone(String phone); Optional findByEmailAndStatus(String email, UserStatus status); + + Optional findByIdAndStatus(Long userId, UserStatus status); } diff --git a/src/main/java/project/flipnote/user/service/UserService.java b/src/main/java/project/flipnote/user/service/UserService.java index 05c00e0c..85484f8a 100644 --- a/src/main/java/project/flipnote/user/service/UserService.java +++ b/src/main/java/project/flipnote/user/service/UserService.java @@ -10,6 +10,7 @@ import project.flipnote.auth.service.AuthService; import project.flipnote.common.exception.BizException; import project.flipnote.user.entity.User; +import project.flipnote.user.entity.UserStatus; import project.flipnote.user.exception.UserErrorCode; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; @@ -50,6 +51,18 @@ public UserRegisterResponse register(UserRegisterRequest req) { return UserRegisterResponse.from(savedUser.getId()); } + @Transactional + public void unregister(Long userId) { + User user = findActiveUserById(userId); + + user.softDelete(); + } + + private User findActiveUserById(Long userId) { + return userRepository.findByIdAndStatus(userId, UserStatus.ACTIVE) + .orElseThrow(() -> new BizException(UserErrorCode.USER_NOT_FOUND)); + } + private void validateEmailDuplicate(String email) { if (userRepository.existsByEmail(email)) { throw new BizException(UserErrorCode.DUPLICATE_EMAIL); From 9ac7f6687c8dfc16894d01a4dbf491515e1e42e0 Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 15 Jul 2025 14:29:03 +0900 Subject: [PATCH 2/4] =?UTF-8?q?Test:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/entity/SoftDeletableEntity.java | 6 ++--- .../user/service/UserServiceTest.java | 27 +++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/main/java/project/flipnote/common/entity/SoftDeletableEntity.java b/src/main/java/project/flipnote/common/entity/SoftDeletableEntity.java index 9c295b69..9963b4df 100644 --- a/src/main/java/project/flipnote/common/entity/SoftDeletableEntity.java +++ b/src/main/java/project/flipnote/common/entity/SoftDeletableEntity.java @@ -9,13 +9,13 @@ @MappedSuperclass public abstract class SoftDeletableEntity extends BaseEntity { - private LocalDateTime deletedDate; + private LocalDateTime deletedAt; public void softDelete() { - this.deletedDate = LocalDateTime.now(); + this.deletedAt = LocalDateTime.now(); } public boolean isDeleted() { - return deletedDate != null; + return deletedAt != null; } } diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index 67b8617a..7bc8cc84 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.*; +import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -19,6 +21,7 @@ import project.flipnote.auth.service.AuthService; import project.flipnote.common.exception.BizException; import project.flipnote.user.entity.User; +import project.flipnote.user.entity.UserStatus; import project.flipnote.user.exception.UserErrorCode; import project.flipnote.user.model.UserRegisterRequest; import project.flipnote.user.model.UserRegisterResponse; @@ -151,4 +154,28 @@ void fail_unverifiedEmail() { } } + @DisplayName("회원 탈퇴 테스트") + @Nested + class Unregister { + + @DisplayName("성공") + @Test + void success() { + given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.of(user)); + + userService.unregister(user.getId()); + + assertThat(user.getStatus()).isEqualTo(UserStatus.INACTIVE); + assertThat(user.getDeletedAt()).isNotNull(); + } + + @DisplayName("회원 id가 존재하지 않는 경우 예외 발생") + @Test + void fail_userNotFound() { + given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.empty()); + + BizException exception = assertThrows(BizException.class, () -> userService.unregister(user.getId())); + assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.USER_NOT_FOUND); + } + } } From 1ce51b760d883517dad7437959672a8b130dadad Mon Sep 17 00:00:00 2001 From: dungbik Date: Tue, 15 Jul 2025 14:31:58 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Test:=20=ED=9A=8C=EC=9B=90=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=97=90=EC=84=9C=20Use?= =?UTF-8?q?rFixture=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=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 --- .../user/service/UserServiceTest.java | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index 7bc8cc84..3adb59ab 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -20,6 +20,7 @@ import project.flipnote.auth.exception.AuthErrorCode; import project.flipnote.auth.service.AuthService; import project.flipnote.common.exception.BizException; +import project.flipnote.fixture.UserFixture; import project.flipnote.user.entity.User; import project.flipnote.user.entity.UserStatus; import project.flipnote.user.exception.UserErrorCode; @@ -43,23 +44,6 @@ class UserServiceTest { @Mock private PasswordEncoder passwordEncoder; - private User user; - - @BeforeEach - void init() { - user = User.builder() - .email("test@test.com") - .password("testPass") - .name("테스트") - .nickname("테스트") - .smsAgree(false) - .phone("010-1234-5678") - .profileImageUrl(null) - .build(); - - ReflectionTestUtils.setField(user, "id", 1L); - } - @DisplayName("회원가입 테스트") @Nested class Register { @@ -67,6 +51,7 @@ class Register { @DisplayName("성공") @Test void success() { + User user = UserFixture.createActiveUser(); UserRegisterRequest req = new UserRegisterRequest( "test@test.com", "testPass", "테스트", "테스트", false, "010-1234-5678", "" ); @@ -87,6 +72,7 @@ void success() { @DisplayName("휴대전화 번호가 null일 때 성공") @Test void success_ifPhoneIsNull() { + User user = UserFixture.createActiveUser(); UserRegisterRequest req = new UserRegisterRequest( "test@test.com", "testPass", "테스트", "테스트", false, null, null ); @@ -161,6 +147,8 @@ class Unregister { @DisplayName("성공") @Test void success() { + User user = UserFixture.createActiveUser(); + given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.of(user)); userService.unregister(user.getId()); @@ -174,7 +162,7 @@ void success() { void fail_userNotFound() { given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.empty()); - BizException exception = assertThrows(BizException.class, () -> userService.unregister(user.getId())); + BizException exception = assertThrows(BizException.class, () -> userService.unregister(1L)); assertThat(exception.getErrorCode()).isEqualTo(UserErrorCode.USER_NOT_FOUND); } } From e5ca6a3df4f0fea8b88dfdfcdc9a24c506e1ce09 Mon Sep 17 00:00:00 2001 From: dungbik Date: Wed, 16 Jul 2025 13:16:53 +0900 Subject: [PATCH 4/4] =?UTF-8?q?Test:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=84=B1=EA=B3=B5=20=EC=BC=80=EC=9D=B4=EC=8A=A4=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 --- .../java/project/flipnote/user/service/UserServiceTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/project/flipnote/user/service/UserServiceTest.java b/src/test/java/project/flipnote/user/service/UserServiceTest.java index 3adb59ab..6b5cb480 100644 --- a/src/test/java/project/flipnote/user/service/UserServiceTest.java +++ b/src/test/java/project/flipnote/user/service/UserServiceTest.java @@ -147,7 +147,7 @@ class Unregister { @DisplayName("성공") @Test void success() { - User user = UserFixture.createActiveUser(); + User user = spy(UserFixture.createActiveUser()); given(userRepository.findByIdAndStatus(anyLong(), any(UserStatus.class))).willReturn(Optional.of(user)); @@ -155,6 +155,8 @@ void success() { assertThat(user.getStatus()).isEqualTo(UserStatus.INACTIVE); assertThat(user.getDeletedAt()).isNotNull(); + + verify(user, times(1)).softDelete(); } @DisplayName("회원 id가 존재하지 않는 경우 예외 발생")