diff --git a/src/main/java/com/devpick/domain/user/dto/PublicUserProfileResponse.java b/src/main/java/com/devpick/domain/user/dto/PublicUserProfileResponse.java index 193752c1..69ce6bd6 100644 --- a/src/main/java/com/devpick/domain/user/dto/PublicUserProfileResponse.java +++ b/src/main/java/com/devpick/domain/user/dto/PublicUserProfileResponse.java @@ -9,8 +9,10 @@ import java.time.Instant; import java.time.ZoneOffset; +import java.util.LinkedHashMap; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; public record PublicUserProfileResponse( UUID userId, @@ -50,11 +52,16 @@ public static PublicUserProfileResponse of( p.getCreatedAt() != null ? p.getCreatedAt().toInstant(ZoneOffset.UTC) : null)) .toList(), answers.stream() - .map(a -> new RecentAnswer( - a.getId(), - a.getPost().getId(), - a.getPost().getTitle(), - a.getCreatedAt() != null ? a.getCreatedAt().toInstant(ZoneOffset.UTC) : null)) + .collect(Collectors.toMap( + a -> a.getPost().getId(), + a -> new RecentAnswer( + a.getId(), + a.getPost().getId(), + a.getPost().getTitle(), + a.getCreatedAt() != null ? a.getCreatedAt().toInstant(ZoneOffset.UTC) : null), + (existing, replacement) -> existing, // 가장 최신 답변 유지 (쿼리가 createdAt DESC 순) + LinkedHashMap::new)) + .values().stream() .toList() ); } diff --git a/src/test/java/com/devpick/domain/user/service/UserServiceTest.java b/src/test/java/com/devpick/domain/user/service/UserServiceTest.java index 7e21f3d3..6817bfc5 100644 --- a/src/test/java/com/devpick/domain/user/service/UserServiceTest.java +++ b/src/test/java/com/devpick/domain/user/service/UserServiceTest.java @@ -145,6 +145,41 @@ void getPublicProfile_withCreatedAt_convertsToInstant() { assertThat(response.recentAnswers().get(0).createdAt()).isNotNull(); } + @Test + @DisplayName("getPublicProfile — 동일 게시글에 답변 여러 개일 때 recentAnswers에 게시글 1개만 포함") + void getPublicProfile_multipleAnswersOnSamePost_deduplicatesRecentAnswers() { + Post post = Post.builder().user(user).title("Spring 질문").content("내용").level(Level.JUNIOR).build(); + + Answer answer1 = Mockito.mock(Answer.class); + Answer answer2 = Mockito.mock(Answer.class); + UUID postId = UUID.randomUUID(); + UUID answer1Id = UUID.randomUUID(); + UUID answer2Id = UUID.randomUUID(); + + Post mockPost = Mockito.mock(Post.class); + Mockito.when(mockPost.getId()).thenReturn(postId); + Mockito.when(mockPost.getTitle()).thenReturn("Spring 질문"); + + Mockito.when(answer1.getId()).thenReturn(answer1Id); + Mockito.when(answer1.getPost()).thenReturn(mockPost); + Mockito.when(answer1.getCreatedAt()).thenReturn(null); + + Mockito.when(answer2.getId()).thenReturn(answer2Id); + Mockito.when(answer2.getPost()).thenReturn(mockPost); + Mockito.when(answer2.getCreatedAt()).thenReturn(null); + + given(userRepository.findByIdAndIsActiveTrue(userId)).willReturn(Optional.of(user)); + given(userBadgeRepository.findByUser_IdOrderByAcquiredAtDesc(userId)).willReturn(List.of()); + given(postRepository.findByUser_IdOrderByCreatedAtDesc(userId)).willReturn(List.of(post)); + given(answerRepository.findByUserIdWithPost(userId)).willReturn(List.of(answer1, answer2)); + + PublicUserProfileResponse response = userService.getPublicProfile(userId); + + assertThat(response.recentAnswers()).hasSize(1); + assertThat(response.recentAnswers().get(0).postId()).isEqualTo(postId); + assertThat(response.recentAnswers().get(0).answerId()).isEqualTo(answer1Id); // 첫 번째(최신) 답변 유지 + } + @Test @DisplayName("getPublicProfile — 비활성 사용자 USER_NOT_FOUND 예외") void getPublicProfile_inactiveUser_throwsUserNotFound() {