From 45f533c7cc763e1ff3f4daaa1b4dd545a4dede65 Mon Sep 17 00:00:00 2001 From: "Jaemin.Park" Date: Wed, 24 Jan 2024 02:44:21 +0900 Subject: [PATCH 1/2] Fix wrong converting soft deleted user's activation logic --- .../com/whatever/raisedragon/domain/user/UserService.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt b/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt index 97e90fb..dd7e546 100644 --- a/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt +++ b/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt @@ -45,6 +45,9 @@ class UserService( fun softDelete(id: Long) { val userEntity = loadById(id).fromDto() userEntity.disable() + fun convertBySoftDeleteToEntity(id: Long) { + val userEntity = userRepository.findById(id).orElseThrow(notFoundExceptionSupplier) + userEntity.able() } @Transactional @@ -52,10 +55,4 @@ class UserService( val userEntity = loadById(id).fromDto() userRepository.delete(userEntity) } - - @Transactional - fun convertBySoftDeleteToEntity(id: Long) { - val userEntity = loadById(id).fromDto() - userEntity.able() - } } \ No newline at end of file From 5272fe685a81f938c4b17f9f52438adfbeeb09d9 Mon Sep 17 00:00:00 2001 From: "Jaemin.Park" Date: Wed, 24 Jan 2024 02:44:34 +0900 Subject: [PATCH 2/2] Write test codes of UserService --- .../auth/AuthApplicationService.kt | 12 +- .../goal/GoalApplicationService.kt | 16 +- .../GoalGifticonApplicationService.kt | 2 +- .../user/UserApplicationService.kt | 2 +- .../controller/test/TestController.kt | 2 +- .../raisedragon/domain/user/UserService.kt | 31 +- .../domain/user/UserServiceTest.kt | 321 ++++++++++++++++++ 7 files changed, 356 insertions(+), 30 deletions(-) create mode 100644 raisedragon-core/src/test/kotlin/com/whatever/raisedragon/domain/user/UserServiceTest.kt diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/auth/AuthApplicationService.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/auth/AuthApplicationService.kt index e04a9f0..fc1234f 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/auth/AuthApplicationService.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/auth/AuthApplicationService.kt @@ -29,15 +29,13 @@ class AuthApplicationService( @Transactional fun kakaoLogin(request: LoginServiceRequest): LoginResponse { val kakaoId = authService.verifyKaKao(request.accessToken) - val user = userService.loadByOAuthPayload(kakaoId) + val user = userService.findByOAuthPayload(kakaoId) if (user == null) { val newUser = userService.create( - User( - oauthTokenPayload = kakaoId, - fcmTokenPayload = null, - nickname = Nickname.generateRandomNickname() - ) + oauthTokenPayload = kakaoId, + fcmTokenPayload = null, + nickname = Nickname.generateRandomNickname() ) return buildLoginResponseByNewUser(newUser) } @@ -90,7 +88,7 @@ class AuthApplicationService( ) val userId = refreshTokenVo.userId - val user = userService.loadById(userId) + val user = userService.findById(userId) val jwtToken = jwtAgent.reissueToken(refreshToken, user) refreshTokenVo.payload = jwtToken.refreshToken diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goal/GoalApplicationService.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goal/GoalApplicationService.kt index af1981f..4bfc4e7 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goal/GoalApplicationService.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goal/GoalApplicationService.kt @@ -51,19 +51,19 @@ class GoalApplicationService( gifticonId = gifticon.id ) } - val hostUser = userService.loadById(request.userId) + val hostUser = userService.findById(request.userId) return GoalResponse.of(goal, hostUser.nickname.value) } fun retrieveGoal(goalId: Long): GoalResponse { val goal = goalService.findById(goalId) - val hostUser = userService.loadById(goal.userId) + val hostUser = userService.findById(goal.userId) return GoalResponse.of(goal, hostUser.nickname.value) } fun retrieveGoalDetail(goalId: Long, userId: Long): GoalWithBettingResponse { val goal = goalService.findById(goalId) - val hostUser = userService.loadById(goal.userId) + val hostUser = userService.findById(goal.userId) val betting = bettingService.loadUserAndGoal(userId, goalId) val goalProofs = goalProofService.findAllByGoalIdAndUserId(goalId, userId) val winnerNickname = winnerService.findWinnerNicknameByGoalId(goalId)?.value @@ -94,7 +94,7 @@ class GoalApplicationService( goalId: Long ): GoalRetrieveParticipantResponse { val goal = goalService.findById(goalId) - val hostUser = userService.loadById(goal.userId) + val hostUser = userService.findById(goal.userId) val bettingList = bettingService.findAllByGoalId(goalId) val hostDto = GoalBettingHostResponse( @@ -106,7 +106,7 @@ class GoalApplicationService( val participants = bettingList.map { GoalBettingParticipantResponse( userId = it.userId, - nickname = userService.loadById(it.userId).nickname.value, + nickname = userService.findById(it.userId).nickname.value, bettingId = it.id, bettingPredictionType = it.bettingPredictionType, bettingResult = it.bettingResult, @@ -124,7 +124,7 @@ class GoalApplicationService( goalId: Long ): GoalRetrieveParticipantResponse { val goal = goalService.findById(goalId) - val goalHostUser = userService.loadById(goal.userId) + val goalHostUser = userService.findById(goal.userId) val bettingList = bettingService.findAllByGoalId(goalId) val hostDto = GoalBettingHostResponse( @@ -136,7 +136,7 @@ class GoalApplicationService( val participants = bettingList.map { GoalBettingParticipantResponse( userId = it.userId, - nickname = userService.loadById(it.userId).nickname.value, + nickname = userService.findById(it.userId).nickname.value, bettingId = it.id, bettingPredictionType = it.bettingPredictionType, bettingResult = it.bettingResult, @@ -185,7 +185,7 @@ class GoalApplicationService( content = request.content ) - val hostUser = userService.loadById(request.userId) + val hostUser = userService.findById(request.userId) return GoalResponse.of(modifiedGoal, hostUser.nickname.value) } diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goalgifticon/GoalGifticonApplicationService.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goalgifticon/GoalGifticonApplicationService.kt index 981f631..18fa8a8 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goalgifticon/GoalGifticonApplicationService.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/goalgifticon/GoalGifticonApplicationService.kt @@ -86,7 +86,7 @@ class GoalGifticonApplicationService( @Transactional fun updateGifticonURLByGoalId(request: GoalGifticonUpdateServiceRequest): GoalGifticonResponse { - val userEntity = userService.loadById(request.userId).fromDto() + val userEntity = userService.findById(request.userId).fromDto() val goal = goalService.findById(request.goalId).fromDto(userEntity).toDto() val goalGifticon = goalGifticonService.findByGoalId(goal.id) ?: throw BaseException.of(ExceptionCode.E404_NOT_FOUND, "다짐에 등록된 기프티콘을 찾을 수 없습니다.") diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/user/UserApplicationService.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/user/UserApplicationService.kt index 8531e11..f231a22 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/user/UserApplicationService.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/applicationservice/user/UserApplicationService.kt @@ -27,7 +27,7 @@ class UserApplicationService( ) { fun retrieve(id: Long): UserRetrieveResponse { - val user = userService.loadById(id) + val user = userService.findById(id) return UserRetrieveResponse( userId = user.id!!, nickname = user.nickname, diff --git a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/test/TestController.kt b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/test/TestController.kt index adab6cc..cee6464 100644 --- a/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/test/TestController.kt +++ b/raisedragon-api/src/main/kotlin/com/whatever/raisedragon/controller/test/TestController.kt @@ -20,7 +20,7 @@ class TestController( @Operation(description = "테스트 토큰 발급") @GetMapping("/token/{userId}") fun getToken(@PathVariable userId: Long): Response { - return Response.success(jwtAgent.provide(userService.loadById(userId)).accessToken) + return Response.success(jwtAgent.provide(userService.findById(userId)).accessToken) } @Operation(description = "단일 다짐에 대한 결과 확정") diff --git a/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt b/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt index dd7e546..55e7006 100644 --- a/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt +++ b/raisedragon-core/src/main/kotlin/com/whatever/raisedragon/domain/user/UserService.kt @@ -13,11 +13,22 @@ class UserService( private val userRepository: UserRepository, ) { - fun loadByOAuthPayload(payload: String): User? { - return userRepository.findByOauthTokenPayload(payload)?.toDto() + @Transactional + fun create( + oauthTokenPayload: String?, + fcmTokenPayload: String?, + nickname: Nickname, + ): User { + return userRepository.save( + UserEntity( + oauthTokenPayload = oauthTokenPayload, + fcmTokenPayload = fcmTokenPayload, + nickname = nickname + ) + ).toDto() } - fun loadById(id: Long): User { + fun findById(id: Long): User { return userRepository.findById(id).orElseThrow(notFoundExceptionSupplier).toDto() } @@ -25,13 +36,12 @@ class UserService( return userRepository.findAllById(ids).map { it.toDto() } } - fun isNicknameDuplicated(nickname: String): Boolean { - return userRepository.existsByNickname(Nickname(nickname)) + fun findByOAuthPayload(payload: String): User? { + return userRepository.findByOauthTokenPayload(payload)?.toDto() } - @Transactional - fun create(user: User): User { - return userRepository.save(user.fromDto()).toDto() + fun isNicknameDuplicated(nickname: String): Boolean { + return userRepository.existsByNickname(Nickname(nickname)) } @Transactional @@ -42,9 +52,6 @@ class UserService( } @Transactional - fun softDelete(id: Long) { - val userEntity = loadById(id).fromDto() - userEntity.disable() fun convertBySoftDeleteToEntity(id: Long) { val userEntity = userRepository.findById(id).orElseThrow(notFoundExceptionSupplier) userEntity.able() @@ -52,7 +59,7 @@ class UserService( @Transactional fun hardDeleteById(id: Long) { - val userEntity = loadById(id).fromDto() + val userEntity = userRepository.findById(id).orElseThrow(notFoundExceptionSupplier) userRepository.delete(userEntity) } } \ No newline at end of file diff --git a/raisedragon-core/src/test/kotlin/com/whatever/raisedragon/domain/user/UserServiceTest.kt b/raisedragon-core/src/test/kotlin/com/whatever/raisedragon/domain/user/UserServiceTest.kt new file mode 100644 index 0000000..06f46af --- /dev/null +++ b/raisedragon-core/src/test/kotlin/com/whatever/raisedragon/domain/user/UserServiceTest.kt @@ -0,0 +1,321 @@ +package com.whatever.raisedragon.domain.user + +import com.whatever.raisedragon.IntegrationTestSupport +import com.whatever.raisedragon.utils.anyNotFoundException +import jakarta.transaction.Transactional +import org.assertj.core.api.Assertions.* +import org.assertj.core.api.ObjectAssert +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.repository.findByIdOrNull +import java.time.LocalDateTime + +@Transactional +class UserServiceTest : IntegrationTestSupport { + + @Autowired + private lateinit var userService: UserService + + @Autowired + private lateinit var userRepository: UserRepository + + @DisplayName("기본적인 User를 생성한다.") + @Test + fun create1() { + // given + val oauthTokenPayload = "Sample oauth token payload" + val fcmTokenPayload = "Sample fcm token payload" + val nickname = Nickname("User") + + // when + val foundUser = userService.create(oauthTokenPayload, fcmTokenPayload, nickname) + + // then + assertThat(foundUser) + .isInstanceOfUser() + .extracting("oauthTokenPayload", "fcmTokenPayload", "nickname") + .contains(oauthTokenPayload, fcmTokenPayload, nickname) + } + + @DisplayName("User를 생성할 때 oauthTokenPayload와 fcmTokenPayload를 null일 수 있다.") + @Test + fun create2() { + // given + val oauthTokenPayload = null + val fcmTokenPayload = null + val nickname = Nickname("User") + + // when + val foundUser = userService.create(oauthTokenPayload, fcmTokenPayload, nickname) + + // then + assertThat(foundUser) + .isInstanceOfUser() + .extracting("oauthTokenPayload", "fcmTokenPayload", "nickname") + .contains(oauthTokenPayload, fcmTokenPayload, nickname) + } + + @DisplayName("id를 입력받아 User를 조회한다.") + @Test + fun findById1() { + // given + val oauthTokenPayload = "Sample oauth token payload" + val fcmTokenPayload = "Sample fcm token payload" + val nickname = Nickname("User") + val userEntity = UserEntity( + oauthTokenPayload = oauthTokenPayload, + fcmTokenPayload = fcmTokenPayload, + nickname = nickname + ) + userRepository.save(userEntity) + + // when + val foundUser = userService.findById(userEntity.id) + + // then + assertThat(foundUser) + .isInstanceOfUser() + .extracting("oauthTokenPayload", "fcmTokenPayload", "nickname") + .contains(oauthTokenPayload, fcmTokenPayload, nickname) + } + + @DisplayName("User를 조회할 때 id에 해당하는 User를 조회할 수 없는경우 예외가 발생한다.") + @Test + fun findById2() { + // given + val oauthTokenPayload = "Sample oauth token payload" + val fcmTokenPayload = "Sample fcm token payload" + val nickname = Nickname("User") + val userEntity = UserEntity( + oauthTokenPayload = oauthTokenPayload, + fcmTokenPayload = fcmTokenPayload, + nickname = nickname + ) + userRepository.save(userEntity) + + // when // then + assertThatThrownBy { userService.findById(-1L) } + .anyNotFoundException() + } + + @DisplayName("id set을 입력받아 set 안에 해당하는 id를 갖는 모든 User를 조회한다.") + @Test + fun findAllByIdInIds1() { + // given + val userEntity1 = UserEntity(nickname = Nickname("User1")) + val userEntity2 = UserEntity(nickname = Nickname("User2")) + val userEntity3 = UserEntity(nickname = Nickname("User3")) + val userEntity4 = UserEntity(nickname = Nickname("User4")) + userRepository.saveAll(listOf(userEntity1, userEntity2, userEntity3, userEntity4)) + + val selectedUserEntityList = listOf(userEntity1,userEntity2,userEntity4) + val selectedUserIds = selectedUserEntityList.map { user -> user.id }.toSet() + val selectedUserNickname = selectedUserEntityList.map { user -> user.nickname }.toTypedArray() + + // when + val foundUserList = userService.findAllByIdInIds(selectedUserIds) + + // then + assertThat(foundUserList) + .hasSize(3) + .allMatch { user -> selectedUserIds.contains(user.id) } + .extracting("nickname") + .containsExactlyInAnyOrder(*selectedUserNickname) + } + + @DisplayName("id set을 입력받아 set 안에 해당하는 id를 갖는 모든 User를 조회하는 중 해당하는 User가 없는 경우 Empty-list를 반환한다.") + @Test + fun findAllByIdInIds2() { + // given + val userEntity1 = UserEntity(nickname = Nickname("User1")) + val userEntity2 = UserEntity(nickname = Nickname("User2")) + val userEntity3 = UserEntity(nickname = Nickname("User3")) + val userEntity4 = UserEntity(nickname = Nickname("User4")) + userRepository.saveAll(listOf(userEntity1, userEntity2, userEntity3, userEntity4)) + + // when + val foundUserList = userService.findAllByIdInIds(setOf(-1,-2,-3)) + + // then + assertThat(foundUserList) + .isEmpty() + } + + @DisplayName("oAuthPayload를 갖는 user를 조회한다.") + @Test + fun findByOAuthPayload1() { + // given + val oauthTokenPayload = "Sample oauth token payload" + val fcmTokenPayload = "Sample fcm token payload" + val nickname = Nickname("User") + val userEntity = UserEntity( + oauthTokenPayload = oauthTokenPayload, + fcmTokenPayload = fcmTokenPayload, + nickname = nickname + ) + userRepository.save(userEntity) + + // when + val foundUser = userService.findByOAuthPayload(oauthTokenPayload) + + // then + assertThat(foundUser) + .isInstanceOfUser() + .extracting("oauthTokenPayload", "fcmTokenPayload", "nickname") + .contains(oauthTokenPayload, fcmTokenPayload, nickname) + } + + @DisplayName("oAuthPayload를 갖는 user를 조회할 때 해당하는 User가 없는 경우 예외가 발생한다.") + @Test + fun findByOAuthPayload2() { + // given + val oauthTokenPayload = "Sample oauth token payload" + val fcmTokenPayload = "Sample fcm token payload" + val nickname = Nickname("User") + val userEntity = UserEntity( + oauthTokenPayload = oauthTokenPayload, + fcmTokenPayload = fcmTokenPayload, + nickname = nickname + ) + userRepository.save(userEntity) + + // when + val foundUser = userService.findByOAuthPayload("another oauthTokenPayload") + + // then + assertThat(foundUser) + .isNull() + } + + @DisplayName("nickname을 입력받아 해당 닉네임을 사용하고 있는 User가 있으면 true를 반환한다.") + @Test + fun isNicknameDuplicated1() { + // given + val userEntity1 = UserEntity(nickname = Nickname("User1")) + val userEntity2 = UserEntity(nickname = Nickname("User2")) + val userEntity3 = UserEntity(nickname = Nickname("User3")) + val userEntity4 = UserEntity(nickname = Nickname("User4")) + userRepository.saveAll(listOf(userEntity1, userEntity2, userEntity3, userEntity4)) + + val willFindNickname = userEntity1.nickname.value + + // when + val result = userService.isNicknameDuplicated(willFindNickname) + + // then + assertThat(result).isTrue() + } + + @DisplayName("nickname을 입력받아 해당 닉네임을 사용하고 있는 User가 없으면 false를 반환한다.") + @Test + fun isNicknameDuplicated2() { + // given + val userEntity1 = UserEntity(nickname = Nickname("User1")) + val userEntity2 = UserEntity(nickname = Nickname("User2")) + val userEntity3 = UserEntity(nickname = Nickname("User3")) + val userEntity4 = UserEntity(nickname = Nickname("User4")) + userRepository.saveAll(listOf(userEntity1, userEntity2, userEntity3, userEntity4)) + + val willFindNickname = "User5" + + // when + val result = userService.isNicknameDuplicated(willFindNickname) + + // then + assertThat(result).isFalse() + } + + @DisplayName("id와 nickname을 입력받아 요청한 id의 User의 nickname을 변경한다.") + @Test + fun updateNickname1() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + + val willChangeNickname = "User2" + + // when + val foundUser = userService.updateNickname(userEntity.id, willChangeNickname) + + // then + assertThat(foundUser) + .isInstanceOfUser() + .extracting("nickname") + .isEqualTo(Nickname(willChangeNickname)) + } + + @DisplayName("id와 nickname을 받아 User의 Nickname을 변경하는 중 id에 해당하는 User가 없는 경우 예외가 발생한다.") + @Test + fun updateNickname2() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + + val willChangeNickname = "User2" + + // when // then + assertThatThrownBy { userService.updateNickname(-1L, willChangeNickname) } + .anyNotFoundException() + } + + @DisplayName("soft-delete된 User를 다시 활성화한다.") + @Test + fun convertBySoftDeleteToEntity1() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + val now = LocalDateTime.now() + userEntity.deletedAt = now + assertThat(userRepository.findById(userEntity.id).get().deletedAt).isEqualTo(now) + + // when + userService.convertBySoftDeleteToEntity(userEntity.id) + + // then + assertThat(userRepository.findById(userEntity.id).get().deletedAt).isNull() + } + + @DisplayName("soft-delete된 User를 다시 활성화할 때 id에 해당하는 User가 없는 경우 예외가 발생한다.") + @Test + fun convertBySoftDeleteToEntity2() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + val now = LocalDateTime.now() + userEntity.deletedAt = now + assertThat(userRepository.findById(userEntity.id).get().deletedAt).isEqualTo(now) + + // when // then + assertThatThrownBy { userService.convertBySoftDeleteToEntity(-1L) } + .anyNotFoundException() + } + + @DisplayName("id에 해당하는 User를 hard-delete한다.") + @Test + fun hardDeleteById1() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + + // when + userService.hardDeleteById(userEntity.id) + + // then + assertThat(userRepository.findByIdOrNull(userEntity.id)).isNull() + } + + @DisplayName("id에 해당하는 User를 hard-delete할 때 id에 해당하는 User가 없는 경우 예외가 발생한다.") + @Test + fun hardDeleteById2() { + // given + val userEntity = UserEntity(nickname = Nickname("User1")) + userRepository.save(userEntity) + + // when // then + assertThatThrownBy { userService.hardDeleteById(-1L) } + .anyNotFoundException() + } + + private fun ObjectAssert<*>.isInstanceOfUser() = isInstanceOf(User::class.java) +} \ No newline at end of file