diff --git a/src/main/java/com/example/cp_main_be/domain/social/follow/domain/repository/FollowRepository.java b/src/main/java/com/example/cp_main_be/domain/social/follow/domain/repository/FollowRepository.java index f74aba35..25c951b2 100644 --- a/src/main/java/com/example/cp_main_be/domain/social/follow/domain/repository/FollowRepository.java +++ b/src/main/java/com/example/cp_main_be/domain/social/follow/domain/repository/FollowRepository.java @@ -5,14 +5,30 @@ import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface FollowRepository extends JpaRepository { Optional findByFollowerAndFollowing(User follower, User following); List findByFollower(User follower); + @Query( + "SELECT f FROM Follow f " + + "JOIN FETCH f.following u " + + "LEFT JOIN FETCH u.avatarList " + + "WHERE f.follower = :follower") + List findByFollowerWithFollowingAndAvatars(@Param("follower") User follower); + List findByFollowing(User following); + @Query( + "SELECT f FROM Follow f " + + "JOIN FETCH f.follower u " + + "LEFT JOIN FETCH u.avatarList " + + "WHERE f.following = :following") + List findByFollowingWithFollowerAndAvatars(@Param("following") User following); + boolean existsByFollowerAndFollowing(User follower, User following); void deleteByFollowerAndFollowing(User follower, User following); diff --git a/src/main/java/com/example/cp_main_be/domain/social/follow/service/FollowService.java b/src/main/java/com/example/cp_main_be/domain/social/follow/service/FollowService.java index fab2060c..e91733e0 100644 --- a/src/main/java/com/example/cp_main_be/domain/social/follow/service/FollowService.java +++ b/src/main/java/com/example/cp_main_be/domain/social/follow/service/FollowService.java @@ -87,7 +87,9 @@ public List getFollowers(Long userId) { .orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND, "사용자를 찾을 수 없습니다.")); List userList = - followRepository.findByFollowing(user).stream().map(Follow::getFollower).toList(); + followRepository.findByFollowingWithFollowerAndAvatars(user).stream() + .map(Follow::getFollower) + .toList(); return userList.stream() .map( @@ -109,8 +111,11 @@ public List getFollowing(Long userId) { userRepository .findById(userId) .orElseThrow(() -> new CustomApiException(ErrorCode.USER_NOT_FOUND, "사용자를 찾을 수 없습니다.")); + List userList = - followRepository.findByFollower(user).stream().map(Follow::getFollowing).toList(); + followRepository.findByFollowerWithFollowingAndAvatars(user).stream() + .map(Follow::getFollowing) + .toList(); return userList.stream() .map( diff --git a/src/test/java/com/example/cp_main_be/domain/social/follow/service/FollowServiceTest.java b/src/test/java/com/example/cp_main_be/domain/social/follow/service/FollowServiceTest.java index f8d3440b..813255ca 100644 --- a/src/test/java/com/example/cp_main_be/domain/social/follow/service/FollowServiceTest.java +++ b/src/test/java/com/example/cp_main_be/domain/social/follow/service/FollowServiceTest.java @@ -1,166 +1,244 @@ -// package com.example.cp_main_be.domain.social.follow.service; -// -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.BDDMockito.given; -// import static org.mockito.Mockito.verify; -// -// import com.example.cp_main_be.domain.member.notification.service.NotificationService; -// import com.example.cp_main_be.domain.member.user.domain.User; -// import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; -// import com.example.cp_main_be.domain.social.follow.domain.Follow; -// import com.example.cp_main_be.domain.social.follow.domain.repository.FollowRepository; -// import java.util.Arrays; -// import java.util.List; -// import java.util.Optional; -// import org.junit.jupiter.api.Assertions; -// import org.junit.jupiter.api.DisplayName; -// import org.junit.jupiter.api.Test; -// import org.junit.jupiter.api.extension.ExtendWith; -// import org.mockito.InjectMocks; -// import org.mockito.Mock; -// import org.mockito.junit.jupiter.MockitoExtension; -// -// @ExtendWith(MockitoExtension.class) -// class FollowServiceTest { -// -// @Mock private FollowRepository followRepository; -// @Mock private UserRepository userRepository; -// -// @InjectMocks private FollowService followService; -// @Mock private NotificationService notificationService; -// -// @DisplayName("팔로우 성공") -// @Test -// void followUser_success() { -// // given -// Long followerId = 1L; -// Long followingId = 2L; -// User follower = User.builder().id(followerId).nickname("follower").build(); -// User following = User.builder().id(followingId).nickname("following").build(); -// -// given(userRepository.findById(followerId)).willReturn(Optional.of(follower)); -// given(userRepository.findById(followingId)).willReturn(Optional.of(following)); -// given(followRepository.existsByFollowerAndFollowing(follower, following)).willReturn(false); -// -// // when -// followService.followUser(followerId, followingId); -// -// // then -// verify(followRepository).save(any(Follow.class)); -// } -// -// @DisplayName("팔로우 실패 - 이미 팔로우한 사용자") -// @Test -// void followUser_fail_alreadyFollowing() { -// // given -// Long followerId = 1L; -// Long followingId = 2L; -// User follower = User.builder().id(followerId).nickname("follower").build(); -// User following = User.builder().id(followingId).nickname("following").build(); -// -// given(userRepository.findById(followerId)).willReturn(Optional.of(follower)); -// given(userRepository.findById(followingId)).willReturn(Optional.of(following)); -// given(followRepository.existsByFollowerAndFollowing(follower, following)).willReturn(true); -// -// // when & then -// Assertions.assertThrows( -// RuntimeException.class, () -> followService.followUser(followerId, followingId)); -// verify(followRepository, org.mockito.Mockito.never()).save(any(Follow.class)); -// } -// -// @DisplayName("언팔로우 성공") -// @Test -// void unfollowUser_success() { -// // given -// Long followerId = 1L; -// Long followingId = 2L; -// User follower = User.builder().id(followerId).nickname("follower").build(); -// User following = User.builder().id(followingId).nickname("following").build(); -// Follow follow = Follow.builder().follower(follower).following(following).build(); -// -// given(userRepository.findById(followerId)).willReturn(Optional.of(follower)); -// given(userRepository.findById(followingId)).willReturn(Optional.of(following)); -// given(followRepository.findByFollowerAndFollowing(follower, following)) -// .willReturn(Optional.of(follow)); -// -// // when -// followService.unfollowUser(followerId, followingId); -// -// // then -// verify(followRepository).delete(follow); -// } -// -// @DisplayName("언팔로우 실패 - 팔로우 관계를 찾을 수 없음") -// @Test -// void unfollowUser_fail_followNotFound() { -// // given -// Long followerId = 1L; -// Long followingId = 2L; -// User follower = User.builder().id(followerId).nickname("follower").build(); -// User following = User.builder().id(followingId).nickname("following").build(); -// -// given(userRepository.findById(followerId)).willReturn(Optional.of(follower)); -// given(userRepository.findById(followingId)).willReturn(Optional.of(following)); -// given(followRepository.findByFollowerAndFollowing(follower, following)) -// .willReturn(Optional.empty()); -// -// // when & then -// Assertions.assertThrows( -// RuntimeException.class, () -> followService.unfollowUser(followerId, followingId)); -// verify(followRepository, org.mockito.Mockito.never()).delete(any(Follow.class)); -// } -// -// @DisplayName("팔로워 목록 조회 성공") -// @Test -// void getFollowers_success() { -// // given -// Long userId = 1L; -// User user = User.builder().id(userId).nickname("user").build(); -// User follower1 = User.builder().id(2L).nickname("follower1").build(); -// User follower2 = User.builder().id(3L).nickname("follower2").build(); -// -// given(userRepository.findById(userId)).willReturn(Optional.of(user)); -// given(followRepository.findByFollowing(user)) -// .willReturn( -// Arrays.asList( -// Follow.builder().follower(follower1).following(user).build(), -// Follow.builder().follower(follower2).following(user).build())); -// -// // when -// List followers = followService.getFollowers(userId); -// -// // then -// Assertions.assertEquals(2, followers.size()); -// Assertions.assertTrue(followers.contains(follower1)); -// Assertions.assertTrue(followers.contains(follower2)); -// verify(userRepository).findById(userId); -// verify(followRepository).findByFollowing(user); -// } -// -// @DisplayName("팔로잉 목록 조회 성공") -// @Test -// void getFollowing_success() { -// // given -// Long userId = 1L; -// User user = User.builder().id(userId).nickname("user").build(); -// User following1 = User.builder().id(2L).nickname("following1").build(); -// User following2 = User.builder().id(3L).nickname("following2").build(); -// -// given(userRepository.findById(userId)).willReturn(Optional.of(user)); -// given(followRepository.findByFollower(user)) -// .willReturn( -// Arrays.asList( -// Follow.builder().follower(user).following(following1).build(), -// Follow.builder().follower(user).following(following2).build())); -// -// // when -// List following = followService.getFollowing(userId); -// -// // then -// Assertions.assertEquals(2, following.size()); -// Assertions.assertTrue(following.contains(following1)); -// Assertions.assertTrue(following.contains(following2)); -// verify(userRepository).findById(userId); -// verify(followRepository).findByFollower(user); -// } -// } +package com.example.cp_main_be.domain.social.follow.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import com.example.cp_main_be.domain.avatar.avatar.domain.Avatar; +import com.example.cp_main_be.domain.member.notification.service.NotificationService; +import com.example.cp_main_be.domain.member.user.domain.Role; +import com.example.cp_main_be.domain.member.user.domain.User; +import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository; +import com.example.cp_main_be.domain.member.userblock.UserBlockRepository; +import com.example.cp_main_be.domain.social.follow.domain.Follow; +import com.example.cp_main_be.domain.social.follow.domain.repository.FollowRepository; +import com.example.cp_main_be.domain.social.follow.dto.FollowResponseDTO; +import com.example.cp_main_be.global.common.CustomApiException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class FollowServiceTest { + + @Mock private FollowRepository followRepository; + @Mock private UserRepository userRepository; + @Mock private NotificationService notificationService; + @Mock private UserBlockRepository userBlockRepository; + + @InjectMocks private FollowService followService; + + private User createUser(Long id, String nickname) { + return User.builder() + .id(id) + .uuid(UUID.randomUUID()) + .nickname(nickname) + .role(Role.USER) + .avatarList(new ArrayList<>()) + .build(); + } + + private User createUserWithAvatar(Long id, String nickname, String avatarImageUrl) { + User user = createUser(id, nickname); + Avatar avatar = + Avatar.builder().id(1L).nickname("avatar").imageUrl(avatarImageUrl).user(user).build(); + user.getAvatarList().add(avatar); + return user; + } + + @Nested + @DisplayName("getFollowers 메서드") + class GetFollowersTest { + + @Test + @DisplayName("팔로워 목록 조회 성공 - 아바타가 있는 경우") + void getFollowers_success_withAvatar() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + User follower1 = createUserWithAvatar(2L, "follower1", "http://image1.com"); + User follower2 = createUserWithAvatar(3L, "follower2", "http://image2.com"); + + List follows = + Arrays.asList( + Follow.builder().follower(follower1).following(user).build(), + Follow.builder().follower(follower2).following(user).build()); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowingWithFollowerAndAvatars(user)).willReturn(follows); + + // when + List result = followService.getFollowers(userId); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getUserId()).isEqualTo(2L); + assertThat(result.get(0).getUsername()).isEqualTo("follower1"); + assertThat(result.get(0).getUserImageUrl()).isEqualTo("http://image1.com"); + assertThat(result.get(1).getUserId()).isEqualTo(3L); + assertThat(result.get(1).getUsername()).isEqualTo("follower2"); + assertThat(result.get(1).getUserImageUrl()).isEqualTo("http://image2.com"); + + verify(userRepository).findById(userId); + verify(followRepository).findByFollowingWithFollowerAndAvatars(user); + } + + @Test + @DisplayName("팔로워 목록 조회 성공 - 아바타가 없는 경우") + void getFollowers_success_withoutAvatar() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + User follower1 = createUser(2L, "follower1"); + + List follows = List.of(Follow.builder().follower(follower1).following(user).build()); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowingWithFollowerAndAvatars(user)).willReturn(follows); + + // when + List result = followService.getFollowers(userId); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getUserId()).isEqualTo(2L); + assertThat(result.get(0).getUsername()).isEqualTo("follower1"); + assertThat(result.get(0).getUserImageUrl()).isNull(); + } + + @Test + @DisplayName("팔로워 목록 조회 성공 - 팔로워가 없는 경우") + void getFollowers_success_noFollowers() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowingWithFollowerAndAvatars(user)).willReturn(List.of()); + + // when + List result = followService.getFollowers(userId); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("팔로워 목록 조회 실패 - 사용자를 찾을 수 없음") + void getFollowers_fail_userNotFound() { + // given + Long userId = 1L; + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> followService.getFollowers(userId)) + .isInstanceOf(CustomApiException.class); + + verify(followRepository, never()).findByFollowingWithFollowerAndAvatars(any()); + } + } + + @Nested + @DisplayName("getFollowing 메서드") + class GetFollowingTest { + + @Test + @DisplayName("팔로잉 목록 조회 성공 - 아바타가 있는 경우") + void getFollowing_success_withAvatar() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + User following1 = createUserWithAvatar(2L, "following1", "http://image1.com"); + User following2 = createUserWithAvatar(3L, "following2", "http://image2.com"); + + List follows = + Arrays.asList( + Follow.builder().follower(user).following(following1).build(), + Follow.builder().follower(user).following(following2).build()); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowerWithFollowingAndAvatars(user)).willReturn(follows); + + // when + List result = followService.getFollowing(userId); + + // then + assertThat(result).hasSize(2); + assertThat(result.get(0).getUserId()).isEqualTo(2L); + assertThat(result.get(0).getUsername()).isEqualTo("following1"); + assertThat(result.get(0).getUserImageUrl()).isEqualTo("http://image1.com"); + assertThat(result.get(1).getUserId()).isEqualTo(3L); + assertThat(result.get(1).getUsername()).isEqualTo("following2"); + assertThat(result.get(1).getUserImageUrl()).isEqualTo("http://image2.com"); + + verify(userRepository).findById(userId); + verify(followRepository).findByFollowerWithFollowingAndAvatars(user); + } + + @Test + @DisplayName("팔로잉 목록 조회 성공 - 아바타가 없는 경우") + void getFollowing_success_withoutAvatar() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + User following1 = createUser(2L, "following1"); + + List follows = List.of(Follow.builder().follower(user).following(following1).build()); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowerWithFollowingAndAvatars(user)).willReturn(follows); + + // when + List result = followService.getFollowing(userId); + + // then + assertThat(result).hasSize(1); + assertThat(result.get(0).getUserId()).isEqualTo(2L); + assertThat(result.get(0).getUsername()).isEqualTo("following1"); + assertThat(result.get(0).getUserImageUrl()).isNull(); + } + + @Test + @DisplayName("팔로잉 목록 조회 성공 - 팔로잉이 없는 경우") + void getFollowing_success_noFollowing() { + // given + Long userId = 1L; + User user = createUser(userId, "user"); + + given(userRepository.findById(userId)).willReturn(Optional.of(user)); + given(followRepository.findByFollowerWithFollowingAndAvatars(user)).willReturn(List.of()); + + // when + List result = followService.getFollowing(userId); + + // then + assertThat(result).isEmpty(); + } + + @Test + @DisplayName("팔로잉 목록 조회 실패 - 사용자를 찾을 수 없음") + void getFollowing_fail_userNotFound() { + // given + Long userId = 1L; + given(userRepository.findById(userId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> followService.getFollowing(userId)) + .isInstanceOf(CustomApiException.class); + + verify(followRepository, never()).findByFollowerWithFollowingAndAvatars(any()); + } + } +}