From 585b16b78a381f4f6e12293739d36546d008774e Mon Sep 17 00:00:00 2001 From: gmuz1c Date: Wed, 23 Oct 2024 10:28:59 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20?= =?UTF-8?q?=EC=8B=9C=20=ED=95=84=EC=9A=94=ED=95=9C=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4.=20(#856)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bang_ggood/auth/service/AuthService.java | 61 +++++++---- .../user/repository/UserRepository.java | 7 ++ .../auth/service/AuthServiceTest.java | 103 ++++++++++++++++-- .../user/repository/UserRepositoryTest.java | 45 +++++++- 4 files changed, 182 insertions(+), 34 deletions(-) diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java index cb47bb416..74cb4c368 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/auth/service/AuthService.java @@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -39,40 +38,60 @@ public class AuthService { @Transactional public Long register(RegisterRequestV1 request) { - try { - return userRepository.save(request.toUserEntity()).getId(); - } catch (DataIntegrityViolationException e) { - throw new BangggoodException(ExceptionCode.USER_EMAIL_ALREADY_USED); - } + User user = processUser(LoginType.LOCAL, request.toUserEntity(), true); + return user.getId(); } @Transactional - public void withdraw(User user) { - userRepository.deleteById(user.getId()); + public AuthTokenResponse oauthLogin(OauthLoginRequest request) { + OauthInfoApiResponse oauthInfo = oauthClient.requestOauthInfo(request); + User user = processUser(LoginType.KAKAO, oauthInfo.toUserEntity(), false); + return createAuthTokenResponse(user); } - @Transactional - public AuthTokenResponse oauthLogin(OauthLoginRequest request) { - OauthInfoApiResponse oauthInfoApiResponse = oauthClient.requestOauthInfo(request); + private User processUser(LoginType loginType, User user, boolean isRegistration) { + return userRepository.findByEmailAndLoginTypeWithDeleted(user.getEmail(), loginType) + .map(savedUser -> handleExistingUser(savedUser, loginType, isRegistration)) + .orElseGet(() -> signUp(user)); + } + + private User handleExistingUser(User user, LoginType loginType, boolean isRegistrationRequest) { + validateRegister(user, isRegistrationRequest); + restoreUser(user, loginType); + return user; + } - User user = userRepository.findByEmailAndLoginType(new Email(oauthInfoApiResponse.kakao_account().email()), - LoginType.KAKAO) - .orElseGet(() -> signUp(oauthInfoApiResponse)); + private void validateRegister(User user, boolean isRegistrationRequest) { + if (!user.isDeleted() && isRegistrationRequest) { + throw new BangggoodException(ExceptionCode.USER_EMAIL_ALREADY_USED); + } + } + private void restoreUser(User user, LoginType loginType) { + if (user.isDeleted()) { + userRepository.resaveByEmailAndLoginType(user.getEmail(), loginType); + } + } + + private AuthTokenResponse createAuthTokenResponse(User user) { String accessToken = jwtTokenProvider.createAccessToken(user); String refreshToken = jwtTokenProvider.createRefreshToken(user); return AuthTokenResponse.of(accessToken, refreshToken); } + + @Transactional + public void withdraw(User user) { + userRepository.deleteById(user.getId()); + } + @Transactional(readOnly = true) public AuthTokenResponse localLogin(LocalLoginRequestV1 request) { User user = userRepository.findByEmailAndLoginType(new Email(request.email()), LoginType.LOCAL) .orElseThrow(() -> new BangggoodException(ExceptionCode.USER_NOT_FOUND)); checkPassword(request, user); - String accessToken = jwtTokenProvider.createAccessToken(user); - String refreshToken = jwtTokenProvider.createRefreshToken(user); - return AuthTokenResponse.of(accessToken, refreshToken); + return createAuthTokenResponse(user); } @@ -82,10 +101,10 @@ private void checkPassword(LocalLoginRequestV1 request, User user) { } } - private User signUp(OauthInfoApiResponse oauthInfoApiResponse) { - User user = userRepository.save(oauthInfoApiResponse.toUserEntity()); - defaultChecklistService.createDefaultChecklistAndQuestions(user); - return user; + private User signUp(User user) { + User savedUser = userRepository.save(user); + defaultChecklistService.createDefaultChecklistAndQuestions(savedUser); + return savedUser; } @Transactional(readOnly = true) diff --git a/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java b/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java index 7b71728ce..ecce99b56 100644 --- a/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java +++ b/backend/bang-ggood/src/main/java/com/bang_ggood/user/repository/UserRepository.java @@ -34,4 +34,11 @@ default User getUserById(Long id) { @Query("UPDATE User u SET u.deleted = true WHERE u.id = :id") void deleteById(@Param("id") Long id); + @Transactional + @Modifying(flushAutomatically = true, clearAutomatically = true) + @Query("UPDATE User u SET u.deleted = false WHERE u.email = :email AND u.loginType = :loginType") + void resaveByEmailAndLoginType(@Param("email") Email email, @Param("loginType") LoginType loginType); + + @Query("SELECT u FROM User u WHERE u.email = :email and u.loginType = :loginType") + Optional findByEmailAndLoginTypeWithDeleted(Email email, LoginType loginType); } diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java index 8b725a38d..be5171979 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/auth/service/AuthServiceTest.java @@ -1,6 +1,7 @@ package com.bang_ggood.auth.service; import com.bang_ggood.IntegrationTestSupport; +import com.bang_ggood.auth.dto.request.LocalLoginRequestV1; import com.bang_ggood.auth.dto.request.OauthLoginRequest; import com.bang_ggood.auth.dto.request.RegisterRequestV1; import com.bang_ggood.auth.dto.response.AuthTokenResponse; @@ -36,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; import static org.mockito.ArgumentMatchers.any; @ExtendWith(MockitoExtension.class) @@ -61,8 +63,10 @@ void localLogin() { AuthTokenResponse response = authService.localLogin(LOCAL_LOGIN_REQUEST); // then - assertThat(response.accessToken()).isNotBlank(); - assertThat(response.refreshToken()).isNotBlank(); + assertAll( + () -> assertThat(response.accessToken()).isNotBlank(), + () -> assertThat(response.refreshToken()).isNotBlank() + ); } @DisplayName("로컬 로그인 실패: 일치하는 유저가 없는 경우") @@ -83,9 +87,9 @@ void localLogin_userInvalidPassword() { .hasMessage(ExceptionCode.USER_INVALID_PASSWORD.getMessage()); } - @DisplayName("회원가입 성공") + @DisplayName("회원가입 성공 : 회원가입 이력 없는 회원인 경우") @Test - void register() { + void register_newUser() { //given RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); @@ -97,6 +101,62 @@ void register() { assertThat(findUser.getId()).isEqualTo(userId); } + @DisplayName("회원가입 성공 : 탈퇴한 회원인 경우") + @Test + void register_deletedUser() { + // given + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); + User existingUser = userRepository.save(request.toUserEntity()); + userRepository.deleteById(existingUser.getId()); + + // when + Long userId = authService.register(request); + + // then + User findUser = userRepository.findById(userId).orElseThrow(); + assertThat(findUser.getId()).isEqualTo(userId); + } + + @DisplayName("회원 가입 성공 : 회원 가입 시 디폴트 체크리스트 질문을 추가") + @Test + void register_default_checklist_question() { + // given + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); + authService.register(request); + + // when + AuthTokenResponse token = authService.localLogin(new LocalLoginRequestV1("bang@gmail.com", "password1234")); + + // then + User user = authService.getAuthUser(token.accessToken()); + CustomChecklistQuestionsResponse customChecklistQuestions = questionManageService.readCustomChecklistQuestions( + user); + + int sum = 0; + for (CategoryQuestionsResponse response : customChecklistQuestions.categories()) { + sum += response.questions().size(); + } + + assertThat(sum).isEqualTo(Question.findDefaultQuestions().size()); + } + + @DisplayName("회원 가입 성공 : 회원 가입시 디폴트 체크리스트를 추가") + @Test + void register_default_checklist() { + // given + RegisterRequestV1 request = new RegisterRequestV1("방방이", "bang@gmail.com", "password1234"); + Long userId = authService.register(request); + + // when + AuthTokenResponse token = authService.localLogin(new LocalLoginRequestV1("bang@gmail.com", "password1234")); + + // then + User user = authService.getAuthUser(token.accessToken()); + ChecklistsPreviewResponse response = checklistManageService.readAllChecklistsPreview(user); + assertThat(response.checklists()).hasSize(1); + } + + @DisplayName("회원가입 성공 : 비밀번호 암호화") @Test void register_encodePassword() { @@ -147,7 +207,7 @@ void withdraw() { assertThat(findUser).isEmpty(); } - @DisplayName("카카오 로그인 성공 : 존재하지 않는 회원이면 데이터베이스에 새로운 유저를 추가하고 토큰 반환") + @DisplayName("카카오 로그인 성공 : 존재하지 않는 회원이면 회원 가입 후 로그인") @Test void oauthLogin_signup() { // given @@ -158,11 +218,13 @@ void oauthLogin_signup() { AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then - assertThat(token.accessToken()).isNotBlank(); - assertThat(token.refreshToken()).isNotBlank(); + assertAll( + () -> assertThat(token.accessToken()).isNotBlank(), + () -> assertThat(token.refreshToken()).isNotBlank() + ); } - @DisplayName("카카오 로그인 성공 : 존재하는 회원이면 데이터베이스에 새로운 유저를 추가하지 않고 토큰을 바로 반환") + @DisplayName("카카오 로그인 성공 : 존재하는 회원이면 로그인") @Test void oauthLogin() { // given @@ -174,8 +236,29 @@ void oauthLogin() { AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); // then - assertThat(token.accessToken()).isNotBlank(); - assertThat(token.refreshToken()).isNotBlank(); + assertAll( + () -> assertThat(token.accessToken()).isNotBlank(), + () -> assertThat(token.refreshToken()).isNotBlank() + ); + } + + @DisplayName("카카오 로그인 성공 : 탈퇴한 회원이면 재가입") + @Test + void oauthLogin_withdrawUser() { + // given + User user = userRepository.save(UserFixture.USER1()); + userRepository.deleteById(user.getId()); + Mockito.when(oauthClient.requestOauthInfo(any(OauthLoginRequest.class))) + .thenReturn(UserFixture.OAUTH_INFO_RESPONSE_USER1()); + + // when + AuthTokenResponse token = authService.oauthLogin(OAUTH_LOGIN_REQUEST); + + // then + assertAll( + () -> assertThat(token.accessToken()).isNotBlank(), + () -> assertThat(token.refreshToken()).isNotBlank() + ); } @DisplayName("카카오 로그인 성공 : 회원 가입시 디폴트 체크리스트 질문을 추가") diff --git a/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java b/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java index 92cdcc8b6..9ae7d06b4 100644 --- a/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java +++ b/backend/bang-ggood/src/test/java/com/bang_ggood/user/repository/UserRepositoryTest.java @@ -8,9 +8,13 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + class UserRepositoryTest extends IntegrationTestSupport { @Autowired @@ -27,7 +31,7 @@ void findByEmail() { Optional findUser = userRepository.findByEmailAndLoginType(user.getEmail(), user.getLoginType()); // then - Assertions.assertThat(findUser).isEmpty(); + assertThat(findUser).isEmpty(); } @DisplayName("유저 타입으로 조회 성공") @@ -40,7 +44,7 @@ void findByType() { List users = userRepository.findUserByUserType(UserType.GUEST); // then - Assertions.assertThat(users).containsExactly(expectedUser); + assertThat(users).containsExactly(expectedUser); } @DisplayName("논리적 삭제 성공") @@ -54,6 +58,41 @@ void deleteById() { // then Optional findUser = userRepository.findById(user.getId()); - Assertions.assertThat(findUser).isEmpty(); + assertThat(findUser).isEmpty(); + } + + @DisplayName("이메일과 로그인 타입으로 재저장 성공") + @Test + void resaveByEmailAndLoginType() { + // given + User user = userRepository.save(UserFixture.USER1()); + userRepository.deleteById(user.getId()); + + // when + userRepository.resaveByEmailAndLoginType(user.getEmail(), user.getLoginType()); + + // then + Optional deletedUser = userRepository.findByEmailAndLoginType(user.getEmail(), user.getLoginType()); + assertAll( + () -> assertThat(deletedUser).isPresent(), + () -> assertThat(deletedUser.get().isDeleted()).isFalse() + ); + } + + @DisplayName("논리적 삭제된 유저를 포함해 이메일과 로그인 타입으로 조회 성공") + @Test + void findByEmailAndLoginTypeWithDeleted() { + // given + User user = userRepository.save(UserFixture.USER1()); + userRepository.deleteById(user.getId()); + + // when + Optional deletedUser = userRepository.findByEmailAndLoginTypeWithDeleted(user.getEmail(), user.getLoginType()); + + // then + assertAll( + () -> assertThat(deletedUser).isPresent(), + () -> assertThat(deletedUser.get().isDeleted()).isTrue() + ); } }