From 04174314e3c7db6c1475db653cec69c9dd39eaa2 Mon Sep 17 00:00:00 2001 From: ownue Date: Sun, 2 Aug 2026 09:00:08 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20nullable=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=A1=B0=EA=B1=B4=EC=9C=BC=EB=A1=9C=20=EC=9D=B8=ED=95=9C=20?= =?UTF-8?q?=ED=99=88=20API=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95=20(#1?= =?UTF-8?q?17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../history/service/HistoryService.java | 8 +++++-- .../mr/domain/home/service/HomeService.java | 2 +- .../playing/repository/PlayingRepository.java | 16 +++++++++++++- .../history/service/HistoryServiceTest.java | 22 ++++++++++++++++--- .../domain/home/service/HomeServiceTest.java | 4 ++-- 5 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/mr/domain/history/service/HistoryService.java b/src/main/java/com/mr/domain/history/service/HistoryService.java index 03cc9758..d7bff36a 100644 --- a/src/main/java/com/mr/domain/history/service/HistoryService.java +++ b/src/main/java/com/mr/domain/history/service/HistoryService.java @@ -42,8 +42,12 @@ public HistoryListResponseDTO getHistories(Long userId, int page, int size, Hist validatePaging(page, size); LocalDateTime cutoff = resolveCutoff(period); - Slice slice = playingRepository.findPlayingsByUserAndStatus( - userId, PlayingStatus.COMPLETED, cutoff, PageRequest.of(page, size)); + PageRequest pageRequest = PageRequest.of(page, size); + Slice slice = cutoff == null + ? playingRepository.findPlayingsByUserAndStatus( + userId, PlayingStatus.COMPLETED, pageRequest) + : playingRepository.findPlayingsByUserAndStatusSince( + userId, PlayingStatus.COMPLETED, cutoff, pageRequest); List playings = slice.getContent(); Map latestByPlayingId = fetchLatestCompletedAnalyses(playings); diff --git a/src/main/java/com/mr/domain/home/service/HomeService.java b/src/main/java/com/mr/domain/home/service/HomeService.java index 8c2bfeda..667f297d 100644 --- a/src/main/java/com/mr/domain/home/service/HomeService.java +++ b/src/main/java/com/mr/domain/home/service/HomeService.java @@ -191,7 +191,7 @@ private int sumDurationSince(List playings, LocalDateTime since) { private List buildRecentPlayings(Long userId) { Slice slice = playingRepository.findPlayingsByUserAndStatus( - userId, PlayingStatus.COMPLETED, null, PageRequest.of(0, RECENT_PLAYINGS_LIMIT)); + userId, PlayingStatus.COMPLETED, PageRequest.of(0, RECENT_PLAYINGS_LIMIT)); return slice.getContent().stream() .map(playing -> RecentPlaying.of(playing, RelativeDateFormatter.format(playing.getEndedAt()))) diff --git a/src/main/java/com/mr/domain/playing/repository/PlayingRepository.java b/src/main/java/com/mr/domain/playing/repository/PlayingRepository.java index 1a9c804e..f264c3b0 100644 --- a/src/main/java/com/mr/domain/playing/repository/PlayingRepository.java +++ b/src/main/java/com/mr/domain/playing/repository/PlayingRepository.java @@ -22,10 +22,24 @@ public interface PlayingRepository extends JpaRepository { where p.user.userId = :userId and p.status = :status and p.deletedAt is null - and (:cutoff is null or p.endedAt >= :cutoff) order by p.endedAt desc, p.id desc """) Slice findPlayingsByUserAndStatus( + @Param("userId") Long userId, + @Param("status") PlayingStatus status, + Pageable pageable + ); + + @Query(""" + select p from Playing p + left join fetch p.backingTrack + where p.user.userId = :userId + and p.status = :status + and p.deletedAt is null + and p.endedAt >= :cutoff + order by p.endedAt desc, p.id desc + """) + Slice findPlayingsByUserAndStatusSince( @Param("userId") Long userId, @Param("status") PlayingStatus status, @Param("cutoff") LocalDateTime cutoff, diff --git a/src/test/java/com/mr/domain/history/service/HistoryServiceTest.java b/src/test/java/com/mr/domain/history/service/HistoryServiceTest.java index 50fb377e..28d31e0d 100644 --- a/src/test/java/com/mr/domain/history/service/HistoryServiceTest.java +++ b/src/test/java/com/mr/domain/history/service/HistoryServiceTest.java @@ -8,12 +8,14 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; import com.mr.domain.analysis.entity.Analysis; import com.mr.domain.analysis.entity.enums.AnalysisGrade; import com.mr.domain.analysis.entity.enums.AnalysisStatus; import com.mr.domain.analysis.repository.AnalysisRepository; +import com.mr.domain.history.dto.req.HistoryPeriod; import com.mr.domain.history.dto.res.HistoryDetailResponseDTO; import com.mr.domain.history.dto.res.HistoryListResponseDTO; import com.mr.domain.history.exception.HistoryErrorStatus; @@ -83,7 +85,7 @@ private Analysis completedAnalysis(Long playingId, Integer totalScore) { @Test @DisplayName("getHistories - 결과가 0건이면 빈 목록을 반환하고 Analysis는 조회하지 않는다") void getHistories_empty_returnsEmptyList() { - given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any(), any())) + given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any())) .willReturn(new SliceImpl<>(List.of(), PageRequest.of(0, 10), false)); HistoryListResponseDTO response = historyService.getHistories(1L, 0, 10, null); @@ -97,7 +99,7 @@ void getHistories_empty_returnsEmptyList() { @DisplayName("getHistories - 최신 COMPLETED 분석이 없는 Playing은 latestAnalysisId가 null이다") void getHistories_noCompletedAnalysis_latestAnalysisIdIsNull() { Playing playing = mockPlaying(1L, 1L, PlayingStatus.COMPLETED, LocalDateTime.now()); - given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any(), any())) + given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any())) .willReturn(new SliceImpl<>(List.of(playing), PageRequest.of(0, 10), false)); given(analysisRepository.findByPlayingIdInAndStatusOrderByCreatedAtDescIdDesc( anyList(), eq(AnalysisStatus.COMPLETED))) @@ -115,7 +117,7 @@ void getHistories_noCompletedAnalysis_latestAnalysisIdIsNull() { void getHistories_scoreChange_adjacentComparisonOnly() { Playing playing1 = mockPlaying(1L, 1L, PlayingStatus.COMPLETED, LocalDateTime.now()); Playing playing2 = mockPlaying(2L, 1L, PlayingStatus.COMPLETED, LocalDateTime.now().minusDays(1)); - given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any(), any())) + given(playingRepository.findPlayingsByUserAndStatus(eq(1L), eq(PlayingStatus.COMPLETED), any())) .willReturn(new SliceImpl<>(List.of(playing1, playing2), PageRequest.of(0, 10), false)); Analysis analysis1 = completedAnalysis(1L, 90); @@ -130,6 +132,20 @@ void getHistories_scoreChange_adjacentComparisonOnly() { assertThat(response.items().get(1).scoreChange()).isNull(); } + @Test + @DisplayName("getHistories - 기간 필터가 있으면 cutoff 전용 쿼리를 사용한다") + void getHistories_withPeriod_usesSinceQuery() { + given(playingRepository.findPlayingsByUserAndStatusSince( + eq(1L), eq(PlayingStatus.COMPLETED), any(LocalDateTime.class), eq(PageRequest.of(0, 10)))) + .willReturn(new SliceImpl<>(List.of(), PageRequest.of(0, 10), false)); + + HistoryListResponseDTO response = historyService.getHistories(1L, 0, 10, HistoryPeriod.WEEKLY); + + assertThat(response.items()).isEmpty(); + verify(playingRepository).findPlayingsByUserAndStatusSince( + eq(1L), eq(PlayingStatus.COMPLETED), any(LocalDateTime.class), eq(PageRequest.of(0, 10))); + } + @Test @DisplayName("getHistories - page가 음수면 400") void getHistories_negativePage_throws400() { diff --git a/src/test/java/com/mr/domain/home/service/HomeServiceTest.java b/src/test/java/com/mr/domain/home/service/HomeServiceTest.java index 41cc6525..b5fed0a9 100644 --- a/src/test/java/com/mr/domain/home/service/HomeServiceTest.java +++ b/src/test/java/com/mr/domain/home/service/HomeServiceTest.java @@ -75,7 +75,7 @@ private void stubBaseline(Long userId) { lenient().when(studentRepository.findByUser(user)).thenReturn(Optional.empty()); lenient().when(playingRepository.findDistinctEndedDatesByUserAndStatus(anyLong(), any())).thenReturn(List.of()); lenient().when(playingRepository.findByUserAndStatusSince(anyLong(), any(), any())).thenReturn(List.of()); - lenient().when(playingRepository.findPlayingsByUserAndStatus(anyLong(), any(), any(), any())) + lenient().when(playingRepository.findPlayingsByUserAndStatus(anyLong(), any(), any())) .thenReturn(new SliceImpl<>(List.of())); lenient().when(learningService.getCurrentLearning(anyLong())).thenReturn(null); } @@ -336,7 +336,7 @@ void getHome_recentPlayings_mapsFromRepository() { stubBaseline(1L); Playing playing = mockPlaying(LocalDateTime.now(), 600); - given(playingRepository.findPlayingsByUserAndStatus(1L, PlayingStatus.COMPLETED, null, PageRequest.of(0, 5))) + given(playingRepository.findPlayingsByUserAndStatus(1L, PlayingStatus.COMPLETED, PageRequest.of(0, 5))) .willReturn(new SliceImpl<>(List.of(playing))); HomeResponseDTO response = homeService.getHome(1L);