diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationUserService.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationUserService.java new file mode 100644 index 000000000..69486e4bd --- /dev/null +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/application/AuthenticationUserService.java @@ -0,0 +1,18 @@ +package com.ddang.ddang.authentication.application; + +import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuthenticationUserService { + + private final JpaUserRepository userRepository; + + public boolean isWithdrawal(final Long userId) { + return userRepository.existsByIdAndDeletedIsTrue(userId); + } +} diff --git a/backend/ddang/src/main/java/com/ddang/ddang/authentication/configuration/AuthenticationInterceptor.java b/backend/ddang/src/main/java/com/ddang/ddang/authentication/configuration/AuthenticationInterceptor.java index 62e7f583b..e750506a9 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/authentication/configuration/AuthenticationInterceptor.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/authentication/configuration/AuthenticationInterceptor.java @@ -1,5 +1,6 @@ package com.ddang.ddang.authentication.configuration; +import com.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.infrastructure.jwt.PrivateClaims; import com.ddang.ddang.authentication.domain.TokenDecoder; @@ -19,6 +20,7 @@ public class AuthenticationInterceptor implements HandlerInterceptor { private final BlackListTokenService blackListTokenService; + private final AuthenticationUserService authenticationUserService; private final TokenDecoder tokenDecoder; private final AuthenticationStore store; @@ -42,6 +44,10 @@ public boolean preHandle( new InvalidTokenException("유효한 토큰이 아닙니다.") ); + if (authenticationUserService.isWithdrawal(privateClaims.userId())) { + throw new InvalidTokenException("유효한 토큰이 아닙니다."); + } + store.set(new AuthenticationUserInfo(privateClaims.userId())); return true; } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/application/UserService.java b/backend/ddang/src/main/java/com/ddang/ddang/user/application/UserService.java index 8c230464d..d2a89764d 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/application/UserService.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/application/UserService.java @@ -21,4 +21,12 @@ public ReadUserDto readById(final Long userId) { return ReadUserDto.from(user); } + + @Transactional + public void deleteById(final Long userId) { + final User user = userRepository.findByIdAndDeletedIsFalse(userId) + .orElseThrow(() -> new UserNotFoundException("사용자 정보를 사용할 수 없습니다.")); + + user.withdrawal(); + } } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java index 41c76a1ad..107602dd4 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/domain/User.java @@ -22,6 +22,8 @@ @Table(name = "users") public class User extends BaseTimeEntity { + private static final boolean DELETED_STATUS = true; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -36,6 +38,9 @@ public class User extends BaseTimeEntity { @Column(unique = true) private String oauthId; + @Column(name = "is_deleted") + private boolean deleted = false; + @Builder private User( final String name, @@ -48,4 +53,8 @@ private User( this.reliability = reliability; this.oauthId = oauthId; } + + public void withdrawal() { + this.deleted = DELETED_STATUS; + } } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java b/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java index 2121abb48..44bda95bb 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepository.java @@ -8,4 +8,8 @@ public interface JpaUserRepository extends JpaRepository { Optional findByOauthId(final String oauthId); + + Optional findByIdAndDeletedIsFalse(final Long id); + + boolean existsByIdAndDeletedIsTrue(final Long id); } diff --git a/backend/ddang/src/main/java/com/ddang/ddang/user/presentation/UserController.java b/backend/ddang/src/main/java/com/ddang/ddang/user/presentation/UserController.java index 4333a5d3b..431e1690d 100644 --- a/backend/ddang/src/main/java/com/ddang/ddang/user/presentation/UserController.java +++ b/backend/ddang/src/main/java/com/ddang/ddang/user/presentation/UserController.java @@ -7,6 +7,7 @@ import com.ddang.ddang.user.presentation.dto.ReadUserResponse; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -25,4 +26,12 @@ public ResponseEntity readById(@AuthenticateUser final Authent return ResponseEntity.ok(response); } + + @DeleteMapping("/withdrawal") + public ResponseEntity delete(@AuthenticateUser final AuthenticationUserInfo userInfo) { + userService.deleteById(userInfo.userId()); + + return ResponseEntity.noContent() + .build(); + } } diff --git a/backend/ddang/src/main/resources/db/migration/V9__alter_user_tables.sql b/backend/ddang/src/main/resources/db/migration/V9__alter_user_tables.sql new file mode 100644 index 000000000..37857e802 --- /dev/null +++ b/backend/ddang/src/main/resources/db/migration/V9__alter_user_tables.sql @@ -0,0 +1,3 @@ +alter table users add is_deleted bit; + +UPDATE users SET is_deleted = 0 where auctioneer_count is null; diff --git a/backend/ddang/src/test/java/com/ddang/ddang/auction/presentation/AuctionControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/auction/presentation/AuctionControllerTest.java index aa114e00c..5b7dbdd2a 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/auction/presentation/AuctionControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/auction/presentation/AuctionControllerTest.java @@ -38,6 +38,7 @@ import com.ddang.ddang.auction.application.dto.ReadRegionsDto; import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; import com.ddang.ddang.auction.presentation.dto.request.CreateAuctionRequest; +import com.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor; import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver; @@ -99,6 +100,9 @@ class AuctionControllerTest { @MockBean BlackListTokenService blackListTokenService; + @MockBean + AuthenticationUserService authenticationUserService; + @Autowired AuctionController auctionController; @@ -119,6 +123,7 @@ void setUp(@Autowired RestDocumentationContextProvider provider) { final AuthenticationStore store = new AuthenticationStore(); final AuthenticationInterceptor interceptor = new AuthenticationInterceptor( blackListTokenService, + authenticationUserService, mockTokenDecoder, store ); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationUserServiceTest.java b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationUserServiceTest.java new file mode 100644 index 000000000..e5a7108ed --- /dev/null +++ b/backend/ddang/src/test/java/com/ddang/ddang/authentication/application/AuthenticationUserServiceTest.java @@ -0,0 +1,62 @@ +package com.ddang.ddang.authentication.application; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.ddang.ddang.configuration.IsolateDatabase; +import com.ddang.ddang.user.domain.User; +import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +@IsolateDatabase +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class AuthenticationUserServiceTest { + + @Autowired + JpaUserRepository userRepository; + + @Autowired + AuthenticationUserService authenticationUserService; + + @Test + void 회원탈퇴한_회원의_id를_전달하면_참을_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + user.withdrawal(); + userRepository.save(user); + + // when + final boolean actual = authenticationUserService.isWithdrawal(user.getId()); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 회원탈퇴하지_않거나_회원가입하지_않은_회원의_id를_전달하면_거짓을_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + userRepository.save(user); + + // when + final boolean actual = authenticationUserService.isWithdrawal(user.getId()); + + // then + assertThat(actual).isFalse(); + } +} diff --git a/backend/ddang/src/test/java/com/ddang/ddang/bid/presentation/BidControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/bid/presentation/BidControllerTest.java index d37a094cc..9c4d06d9e 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/bid/presentation/BidControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/bid/presentation/BidControllerTest.java @@ -1,6 +1,26 @@ package com.ddang.ddang.bid.presentation; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; +import com.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor; import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver; @@ -19,6 +39,9 @@ import com.ddang.ddang.exception.GlobalExceptionHandler; import com.ddang.ddang.user.application.exception.UserNotFoundException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -42,29 +65,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @WebMvcTest(controllers = {BidController.class}, excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebMvcConfigurer.class), @@ -83,6 +83,9 @@ class BidControllerTest { @MockBean BlackListTokenService blackListTokenService; + @MockBean + AuthenticationUserService authenticationUserService; + @Autowired BidController bidController; @@ -103,6 +106,7 @@ void setUp(@Autowired RestDocumentationContextProvider provider) { final AuthenticationStore store = new AuthenticationStore(); final AuthenticationInterceptor interceptor = new AuthenticationInterceptor( blackListTokenService, + authenticationUserService, mockTokenDecoder, store ); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/chat/presentation/ChatRoomControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/chat/presentation/ChatRoomControllerTest.java index bbf94d515..37a82c7cf 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/chat/presentation/ChatRoomControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/chat/presentation/ChatRoomControllerTest.java @@ -1,10 +1,26 @@ package com.ddang.ddang.chat.presentation; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; import com.ddang.ddang.auction.domain.Auction; import com.ddang.ddang.auction.domain.BidUnit; import com.ddang.ddang.auction.domain.Price; import com.ddang.ddang.auction.domain.exception.WinnerNotFoundException; +import com.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor; import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver; @@ -38,6 +54,10 @@ import com.ddang.ddang.user.application.exception.UserNotFoundException; import com.ddang.ddang.user.domain.User; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -53,26 +73,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -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.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @WebMvcTest(controllers = {ChatRoomController.class}, excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebMvcConfigurer.class), @@ -92,6 +92,9 @@ class ChatRoomControllerTest { @MockBean MessageService messageService; + @MockBean + AuthenticationUserService authenticationUserService; + @Autowired ChatRoomController chatRoomController; @@ -108,7 +111,8 @@ void setUp() { final AuthenticationStore store = new AuthenticationStore(); final AuthenticationInterceptor interceptor = new AuthenticationInterceptor( - blackListTokenService, + blackListTokenService, + authenticationUserService, mockTokenDecoder, store ); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/report/presentation/ReportControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/report/presentation/ReportControllerTest.java index b96a03059..5a4e619e7 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/report/presentation/ReportControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/report/presentation/ReportControllerTest.java @@ -1,6 +1,20 @@ package com.ddang.ddang.report.presentation; +import static org.hamcrest.Matchers.is; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.mock; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +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.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import com.ddang.ddang.auction.application.exception.AuctionNotFoundException; +import com.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor; import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver; @@ -30,6 +44,9 @@ import com.ddang.ddang.report.presentation.dto.request.CreateChatRoomReportRequest; import com.ddang.ddang.user.application.exception.UserNotFoundException; import com.fasterxml.jackson.databind.ObjectMapper; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -49,23 +66,6 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; -import static org.mockito.Mockito.mock; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @WebMvcTest(controllers = {ReportController.class}, excludeFilters = { @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = WebMvcConfigurer.class), @@ -87,6 +87,9 @@ class ReportControllerTest { @MockBean BlackListTokenService blackListTokenService; + @MockBean + AuthenticationUserService authenticationUserService; + @Autowired ReportController reportController; @@ -104,6 +107,7 @@ void setUp() { final AuthenticationStore store = new AuthenticationStore(); final AuthenticationInterceptor interceptor = new AuthenticationInterceptor( blackListTokenService, + authenticationUserService, mockTokenDecoder, store ); diff --git a/backend/ddang/src/test/java/com/ddang/ddang/user/application/UserServiceTest.java b/backend/ddang/src/test/java/com/ddang/ddang/user/application/UserServiceTest.java index 3c5527c84..f70b19b0b 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/user/application/UserServiceTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/user/application/UserServiceTest.java @@ -1,18 +1,19 @@ package com.ddang.ddang.user.application; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import com.ddang.ddang.configuration.IsolateDatabase; import com.ddang.ddang.user.application.dto.ReadUserDto; import com.ddang.ddang.user.application.exception.UserNotFoundException; import com.ddang.ddang.user.domain.User; import com.ddang.ddang.user.infrastructure.persistence.JpaUserRepository; +import java.util.Optional; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - @IsolateDatabase @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -57,4 +58,58 @@ class UserServiceTest { .isInstanceOf(UserNotFoundException.class) .hasMessage("사용자 정보를 사용할 수 없습니다."); } + + @Test + void 회원_탈퇴한다() { + // given + final User user = User.builder() + .name("사용자") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + userRepository.save(user); + + // when + userService.deleteById(user.getId()); + + // then + final Optional actual = userRepository.findById(user.getId()); + + SoftAssertions.assertSoftly(softAssertions -> { + softAssertions.assertThat(actual).isPresent(); + softAssertions.assertThat(actual.get().isDeleted()).isTrue(); + }); + } + + @Test + void 회원_탈퇴할때_이미_탈퇴한_회원이면_예외가_발생한다() { + // given + final User user = User.builder() + .name("사용자") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + user.withdrawal(); + userRepository.save(user); + + // when & then + assertThatThrownBy(() -> userService.deleteById(user.getId())) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("사용자 정보를 사용할 수 없습니다."); + } + + @Test + void 회원_탈퇴할때_존재하지_않는_사용자_정보_조회시_예외를_반환한다() { + // given + final Long invalidUserId = -999L; + + // when & then + assertThatThrownBy(() -> userService.deleteById(invalidUserId)) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("사용자 정보를 사용할 수 없습니다."); + } } diff --git a/backend/ddang/src/test/java/com/ddang/ddang/user/domain/UserTest.java b/backend/ddang/src/test/java/com/ddang/ddang/user/domain/UserTest.java new file mode 100644 index 000000000..6fac9f57e --- /dev/null +++ b/backend/ddang/src/test/java/com/ddang/ddang/user/domain/UserTest.java @@ -0,0 +1,26 @@ +package com.ddang.ddang.user.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayNameGeneration; +import org.junit.jupiter.api.DisplayNameGenerator; +import org.junit.jupiter.api.Test; + +@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) +@SuppressWarnings("NonAsciiCharacters") +class UserTest { + + @Test + void 회원_탈퇴한다() { + // given + final User user = User.builder() + .name("kakao12345") + .build(); + + // when + user.withdrawal(); + + // then + assertThat(user.isDeleted()).isTrue(); + } +} diff --git a/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java b/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java index 124307ae7..8b7e2a650 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/user/infrastructure/persistence/JpaUserRepositoryTest.java @@ -30,7 +30,7 @@ class JpaUserRepositoryTest { JpaUserRepository userRepository; @Test - void 존재하는_oauthId를_전달하면_해당_회원을_Optional로_반환한다() { + void 존재하는_oauthId를_전달하면_해당_회원을_Optional로_감싸_반환한다() { // given final User user = User.builder() .name("회원") @@ -52,7 +52,7 @@ class JpaUserRepositoryTest { } @Test - void 존재하지_않는_oauthId를_전달하면_해당_회원을_Optional로_반환한다() { + void 존재하지_않는_oauthId를_전달하면_해당_회원을_빈_Optional로_반환한다() { // given final String invalidOauthId = "invalidOauthId"; @@ -62,4 +62,106 @@ class JpaUserRepositoryTest { // then assertThat(actual).isEmpty(); } + + @Test + void 회원가입과_탈퇴하지_않은_회원_id를_전달하면_해당_회원을_Optional로_감싸_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + userRepository.save(user); + + em.flush(); + em.clear(); + + // when + final Optional actual = userRepository.findByIdAndDeletedIsFalse(user.getId()); + + // then + assertThat(actual).isPresent(); + } + + @Test + void 회원탈퇴한_회원의_id를_전달하면_빈_Optional을_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + user.withdrawal(); + userRepository.save(user); + + em.flush(); + em.clear(); + + // when + final Optional actual = userRepository.findByIdAndDeletedIsFalse(user.getId()); + + // then + assertThat(actual).isEmpty(); + } + + @Test + void 없는_id를_전달하면_빈_Optional을_반환한다() { + // given + final Long invalidUserId = -999L; + + // when + final Optional actual = userRepository.findByIdAndDeletedIsFalse(invalidUserId); + + // then + assertThat(actual).isEmpty(); + } + + @Test + void 회원탈퇴한_회원의_id를_전달하면_참을_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + user.withdrawal(); + userRepository.save(user); + + em.flush(); + em.clear(); + + // when + final boolean actual = userRepository.existsByIdAndDeletedIsTrue(user.getId()); + + // then + assertThat(actual).isTrue(); + } + + @Test + void 회원탈퇴하지_않거나_회원가입하지_않은_회원의_id를_전달하면_거짓을_반환한다() { + // given + final User user = User.builder() + .name("회원") + .profileImage("profile.png") + .reliability(4.7d) + .oauthId("12345") + .build(); + + userRepository.save(user); + + em.flush(); + em.clear(); + + // when + final boolean actual = userRepository.existsByIdAndDeletedIsTrue(user.getId()); + + // then + assertThat(actual).isFalse(); + } } diff --git a/backend/ddang/src/test/java/com/ddang/ddang/user/presentation/UserControllerTest.java b/backend/ddang/src/test/java/com/ddang/ddang/user/presentation/UserControllerTest.java index c7c3cb901..4cab74a64 100644 --- a/backend/ddang/src/test/java/com/ddang/ddang/user/presentation/UserControllerTest.java +++ b/backend/ddang/src/test/java/com/ddang/ddang/user/presentation/UserControllerTest.java @@ -5,12 +5,15 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.mock; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; 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.ddang.ddang.authentication.application.AuthenticationUserService; import com.ddang.ddang.authentication.application.BlackListTokenService; import com.ddang.ddang.authentication.configuration.AuthenticationInterceptor; import com.ddang.ddang.authentication.configuration.AuthenticationPrincipalArgumentResolver; @@ -59,6 +62,9 @@ class UserControllerTest { @MockBean BlackListTokenService blackListTokenService; + @MockBean + AuthenticationUserService authenticationUserService; + @Autowired UserController userController; @@ -76,6 +82,7 @@ void setUp() { final AuthenticationStore store = new AuthenticationStore(); final AuthenticationInterceptor interceptor = new AuthenticationInterceptor( blackListTokenService, + authenticationUserService, mockTokenDecoder, store ); @@ -128,4 +135,20 @@ void setUp() { jsonPath("$.message", is(userNotFoundException.getMessage())) ); } + + @Test + void 회원_탈퇴한다() throws Exception { + // given + final PrivateClaims privateClaims = new PrivateClaims(1L); + + given(mockTokenDecoder.decode(eq(TokenType.ACCESS), anyString())).willReturn(Optional.of(privateClaims)); + willDoNothing().given(userService).deleteById(anyLong()); + + // when & then + mockMvc.perform(delete("/users/withdrawal") + .header(HttpHeaders.AUTHORIZATION, "Bearer accessToken") + ).andExpectAll( + status().isNoContent() + ); + } }