diff --git a/src/main/java/com/swyp/picke/domain/user/service/MypageService.java b/src/main/java/com/swyp/picke/domain/user/service/MypageService.java index 3ab60bd..3347e33 100644 --- a/src/main/java/com/swyp/picke/domain/user/service/MypageService.java +++ b/src/main/java/com/swyp/picke/domain/user/service/MypageService.java @@ -158,8 +158,7 @@ public BattleRecordListResponse getBattleRecords(Integer offset, Integer size, V List items = votes.stream() .map(BattleVote -> { Battle battle = BattleVote.getBattle(); - BattleOption selectedOption = BattleVote.getPostVoteOption() != null - ? BattleVote.getPostVoteOption() : BattleVote.getPreVoteOption(); + BattleOption selectedOption = BattleVote.getPostVoteOption(); VoteSide side = BattleOptionDisplay.voteSide(selectedOption); String category = categoryMap.get(battle.getId()); diff --git a/src/main/java/com/swyp/picke/domain/vote/repository/BattleVoteRepository.java b/src/main/java/com/swyp/picke/domain/vote/repository/BattleVoteRepository.java index f3c5e50..9e19caf 100644 --- a/src/main/java/com/swyp/picke/domain/vote/repository/BattleVoteRepository.java +++ b/src/main/java/com/swyp/picke/domain/vote/repository/BattleVoteRepository.java @@ -34,31 +34,36 @@ public interface BattleVoteRepository extends JpaRepository { Optional findTopByBattleOrderByUpdatedAtDesc(Battle battle); - @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption " + - "WHERE v.user.id = :userId ORDER BY v.createdAt DESC") - List findByUserIdOrderByCreatedAtDesc(@Param("userId") Long userId, Pageable pageable); - - @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption " + - "WHERE v.user.id = :userId AND v.preVoteOption.displayOrder = :displayOrder ORDER BY v.createdAt DESC") - List findByUserIdAndPreVoteOptionDisplayOrderOrderByCreatedAtDesc( + // 내 배틀 기록: 사후투표까지 완료한 기록만 노출 + @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption JOIN FETCH v.postVoteOption " + + "WHERE v.user.id = :userId AND v.postVoteOption IS NOT NULL ORDER BY v.createdAt DESC") + List findByUserIdAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(@Param("userId") Long userId, Pageable pageable); + + @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption JOIN FETCH v.postVoteOption " + + "WHERE v.user.id = :userId AND v.preVoteOption.displayOrder = :displayOrder AND v.postVoteOption IS NOT NULL ORDER BY v.createdAt DESC") + List findByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNullOrderByCreatedAtDesc( @Param("userId") Long userId, @Param("displayOrder") Integer displayOrder, Pageable pageable); - @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption " + - "WHERE v.user.id = :userId AND v.preVoteOption.displayOrder <> :displayOrder ORDER BY v.createdAt DESC") - List findByUserIdAndPreVoteOptionDisplayOrderNotOrderByCreatedAtDesc( + @Query("SELECT v FROM BattleVote v JOIN FETCH v.battle JOIN FETCH v.preVoteOption JOIN FETCH v.postVoteOption " + + "WHERE v.user.id = :userId AND v.preVoteOption.displayOrder <> :displayOrder AND v.postVoteOption IS NOT NULL ORDER BY v.createdAt DESC") + List findByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNullOrderByCreatedAtDesc( @Param("userId") Long userId, @Param("displayOrder") Integer displayOrder, Pageable pageable); + @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId AND v.postVoteOption IS NOT NULL") + long countByUserIdAndPostVoteOptionIsNotNull(@Param("userId") Long userId); + + @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId AND v.preVoteOption.displayOrder = :displayOrder AND v.postVoteOption IS NOT NULL") + long countByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNull(@Param("userId") Long userId, @Param("displayOrder") Integer displayOrder); + + @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId AND v.preVoteOption.displayOrder <> :displayOrder AND v.postVoteOption IS NOT NULL") + long countByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNull(@Param("userId") Long userId, @Param("displayOrder") Integer displayOrder); + + // countTotalParticipation 등 "사전투표만 해도 참여로 집계"하는 통계용 — postVoteOption 필터 없음 long countByUserId(Long userId); // 오늘의 배틀 무료 참여(일 1회) 제한 체크용: 유저가 오늘 날짜인 배틀에 이미 참여했는지 확인 boolean existsByUserIdAndBattle_TargetDate(Long userId, LocalDate targetDate); - @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId AND v.preVoteOption.displayOrder = :displayOrder") - long countByUserIdAndPreVoteOptionDisplayOrder(@Param("userId") Long userId, @Param("displayOrder") Integer displayOrder); - - @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId AND v.preVoteOption.displayOrder <> :displayOrder") - long countByUserIdAndPreVoteOptionDisplayOrderNot(@Param("userId") Long userId, @Param("displayOrder") Integer displayOrder); - @Query("SELECT COUNT(v) FROM BattleVote v WHERE v.user.id = :userId " + "AND v.postVoteOption IS NOT NULL " + "AND v.preVoteOption <> v.postVoteOption") diff --git a/src/main/java/com/swyp/picke/domain/vote/service/VoteQueryService.java b/src/main/java/com/swyp/picke/domain/vote/service/VoteQueryService.java index 24b5a31..210027a 100644 --- a/src/main/java/com/swyp/picke/domain/vote/service/VoteQueryService.java +++ b/src/main/java/com/swyp/picke/domain/vote/service/VoteQueryService.java @@ -21,22 +21,22 @@ public class VoteQueryService { public List findUserVotes(Long userId, int offset, int size, VoteSide voteSide) { PageRequest pageable = PageRequest.of(offset / size, size); if (voteSide == VoteSide.PRO) { - return battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderOrderByCreatedAtDesc(userId, 1, pageable); + return battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(userId, 1, pageable); } if (voteSide == VoteSide.CON) { - return battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderNotOrderByCreatedAtDesc(userId, 1, pageable); + return battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(userId, 1, pageable); } - return battleVoteRepository.findByUserIdOrderByCreatedAtDesc(userId, pageable); + return battleVoteRepository.findByUserIdAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(userId, pageable); } public long countUserVotes(Long userId, VoteSide voteSide) { if (voteSide == VoteSide.PRO) { - return battleVoteRepository.countByUserIdAndPreVoteOptionDisplayOrder(userId, 1); + return battleVoteRepository.countByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNull(userId, 1); } if (voteSide == VoteSide.CON) { - return battleVoteRepository.countByUserIdAndPreVoteOptionDisplayOrderNot(userId, 1); + return battleVoteRepository.countByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNull(userId, 1); } - return battleVoteRepository.countByUserId(userId); + return battleVoteRepository.countByUserIdAndPostVoteOptionIsNotNull(userId); } public long countTotalParticipation(Long userId) { diff --git a/src/test/java/com/swyp/picke/domain/user/service/MypageServiceTest.java b/src/test/java/com/swyp/picke/domain/user/service/MypageServiceTest.java index 1956966..5ac2999 100644 --- a/src/test/java/com/swyp/picke/domain/user/service/MypageServiceTest.java +++ b/src/test/java/com/swyp/picke/domain/user/service/MypageServiceTest.java @@ -166,6 +166,7 @@ void getBattleRecords_returns_paginated_records() { .user(user) .battle(battle) .preVoteOption(optionA) + .postVoteOption(optionA) .build(); ReflectionTestUtils.setField(vote, "id", generateId()); ReflectionTestUtils.setField(vote, "createdAt", LocalDateTime.now()); @@ -192,6 +193,7 @@ void getBattleRecords_returns_no_next_when_last_page() { .user(user) .battle(battle) .preVoteOption(optionA) + .postVoteOption(optionA) .build(); ReflectionTestUtils.setField(vote, "id", generateId()); ReflectionTestUtils.setField(vote, "createdAt", LocalDateTime.now()); diff --git a/src/test/java/com/swyp/picke/domain/vote/service/VoteQueryServiceTest.java b/src/test/java/com/swyp/picke/domain/vote/service/VoteQueryServiceTest.java new file mode 100644 index 0000000..d34caf1 --- /dev/null +++ b/src/test/java/com/swyp/picke/domain/vote/service/VoteQueryServiceTest.java @@ -0,0 +1,83 @@ +package com.swyp.picke.domain.vote.service; + +import com.swyp.picke.domain.user.enums.VoteSide; +import com.swyp.picke.domain.vote.repository.BattleVoteRepository; +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; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class VoteQueryServiceTest { + + @Mock + private BattleVoteRepository battleVoteRepository; + + @InjectMocks + private VoteQueryService voteQueryService; + + @Test + @DisplayName("voteSide 없이 조회하면 사후투표를 완료한 기록만 조회한다") + void findUserVotes_without_voteSide_uses_post_vote_completed_query() { + when(battleVoteRepository.findByUserIdAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), any())) + .thenReturn(List.of()); + + voteQueryService.findUserVotes(1L, 0, 20, null); + + verify(battleVoteRepository).findByUserIdAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), any()); + } + + @Test + @DisplayName("voteSide=PRO로 조회하면 사후투표를 완료한 PRO 기록만 조회한다") + void findUserVotes_with_pro_uses_post_vote_completed_query() { + when(battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), eq(1), any())) + .thenReturn(List.of()); + + voteQueryService.findUserVotes(1L, 0, 20, VoteSide.PRO); + + verify(battleVoteRepository).findByUserIdAndPreVoteOptionDisplayOrderAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), eq(1), any()); + } + + @Test + @DisplayName("voteSide=CON으로 조회하면 사후투표를 완료한 CON 기록만 조회한다") + void findUserVotes_with_con_uses_post_vote_completed_query() { + when(battleVoteRepository.findByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), eq(1), any())) + .thenReturn(List.of()); + + voteQueryService.findUserVotes(1L, 0, 20, VoteSide.CON); + + verify(battleVoteRepository).findByUserIdAndPreVoteOptionDisplayOrderNotAndPostVoteOptionIsNotNullOrderByCreatedAtDesc(eq(1L), eq(1), any()); + } + + @Test + @DisplayName("voteSide 없이 카운트하면 사후투표를 완료한 기록만 센다") + void countUserVotes_without_voteSide_uses_post_vote_completed_query() { + when(battleVoteRepository.countByUserIdAndPostVoteOptionIsNotNull(1L)).thenReturn(3L); + + long count = voteQueryService.countUserVotes(1L, null); + + assertThat(count).isEqualTo(3L); + verify(battleVoteRepository).countByUserIdAndPostVoteOptionIsNotNull(1L); + } + + @Test + @DisplayName("총 참여 횟수(countTotalParticipation)는 사전투표만 한 기록도 포함한다") + void countTotalParticipation_includes_pre_vote_only_records() { + when(battleVoteRepository.countByUserId(1L)).thenReturn(5L); + + long count = voteQueryService.countTotalParticipation(1L); + + assertThat(count).isEqualTo(5L); + verify(battleVoteRepository).countByUserId(1L); + } +}