diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/like/dto/LikeServiceDto.java b/backend/memetory/src/main/java/com/example/memetory/domain/like/dto/LikeServiceDto.java index 0d7edfed..df38afe5 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/like/dto/LikeServiceDto.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/like/dto/LikeServiceDto.java @@ -1,8 +1,5 @@ package com.example.memetory.domain.like.dto; -import com.example.memetory.domain.like.entity.Like; -import com.example.memetory.domain.member.entity.Member; -import com.example.memetory.domain.memes.entity.Memes; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -11,20 +8,13 @@ @Builder @AllArgsConstructor public class LikeServiceDto { - private Long memesId; - private String email; + private Long memesId; + private String email; - public static LikeServiceDto fromEmailAndMemesId(String email, Long memesId) { - return LikeServiceDto.builder() - .email(email) - .memesId(memesId) - .build(); - } - - public Like toEntityFromMemberAndMemes(Member member, Memes memes) { - return Like.builder() - .member(member) - .memes(memes) - .build(); - } + public static LikeServiceDto fromEmailAndMemesId(String email, Long memesId) { + return LikeServiceDto.builder() + .email(email) + .memesId(memesId) + .build(); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/like/entity/Like.java b/backend/memetory/src/main/java/com/example/memetory/domain/like/entity/Like.java index 2ac515f0..b5b0c499 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/like/entity/Like.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/like/entity/Like.java @@ -52,4 +52,11 @@ public Like(Member member, Memes memes) { this.member = member; this.memes = memes; } + + public static Like fromMemberAndMemes(Member member, Memes memes) { + return Like.builder() + .member(member) + .memes(memes) + .build(); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/like/service/LikeService.java b/backend/memetory/src/main/java/com/example/memetory/domain/like/service/LikeService.java index 3960e3cb..0c2b9291 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/like/service/LikeService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/like/service/LikeService.java @@ -13,6 +13,7 @@ import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.memes.entity.Memes; import com.example.memetory.domain.memes.service.MemesService; +import com.example.memetory.domain.memes.service.RankingService; import lombok.RequiredArgsConstructor; @@ -22,15 +23,16 @@ public class LikeService { private final MemberService memberService; private final MemesService memesService; private final LikeRepository likeRepository; + private final RankingService rankingService; @Transactional public void registerLike(LikeServiceDto likeServiceDto) { Member member = memberService.findMemberFromEmail(likeServiceDto.getEmail()); Memes memes = memesService.findMemesFromMemesId(likeServiceDto.getMemesId()); - Like newLike = likeServiceDto.toEntityFromMemberAndMemes(member, memes); + Like newLike = Like.fromMemberAndMemes(member, memes); saveLike(newLike); - memes.addLikeCount(); + increaseMemesLikeCount(memes); } private void saveLike(Like like) { @@ -41,6 +43,11 @@ private void saveLike(Like like) { } } + private void increaseMemesLikeCount(Memes memes) { + memes.addLikeCount(); + rankingService.increaseTodayMemesLikeCountFromMemesId(memes.getId()); + } + @Transactional public void cancelLike(LikeServiceDto likeServiceDto) { Member member = memberService.findMemberFromEmail(likeServiceDto.getEmail()); @@ -48,6 +55,11 @@ public void cancelLike(LikeServiceDto likeServiceDto) { Like like = likeRepository.findLikeByMemberAndMemes(member, memes).orElseThrow(NotFoundLikeException::new); likeRepository.delete(like); + decreaseMemesLikeCount(memes); + } + + private void decreaseMemesLikeCount(Memes memes) { memes.cancelLikeCount(); + rankingService.decreaseTodayMemesLikeCountFromMemesId(memes.getId()); } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java b/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java index 227b81b1..e449aa6c 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/meme/service/MemeService.java @@ -60,7 +60,7 @@ public MemeResponse findMemberMemeResponse(MemeServiceDto memeServiceDto) { return MemeResponse.of(meme); } - public static void certifyMemeMember(Member m1, Member m2) { + public void certifyMemeMember(Member m1, Member m2) { if (!m1.equals(m2)) { throw new AccessDeniedMemeException(); } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/MemesRankDto.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/MemesRankDto.java new file mode 100644 index 00000000..9d08fdc7 --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/MemesRankDto.java @@ -0,0 +1,22 @@ +package com.example.memetory.domain.memes.dto; + +import org.springframework.data.redis.core.ZSetOperations; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Builder +public class MemesRankDto { + private Long memesId; + private Long score; + + public static MemesRankDto of(ZSetOperations.TypedTuple zSet) { + return MemesRankDto.builder() + .memesId(zSet.getValue()) + .score(zSet.getScore().longValue()) + .build(); + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/response/MemesInfoResponse.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/response/MemesInfoResponse.java index 29142722..fe208cee 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/response/MemesInfoResponse.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/dto/response/MemesInfoResponse.java @@ -55,4 +55,15 @@ public static MemesInfoResponse of(Memes memes) { .createdAt(memes.getCreatedAt()) .build(); } + + public static MemesInfoResponse fromMemesAndLikeCount(Memes memes, Long likeCount) { + return MemesInfoResponse.builder() + .memesId(memes.getId()) + .memberNickname(memes.getMember().getNickname()) + .title(memes.getTitle()) + .commentCount(memes.getCommentCount()) + .likeCount(likeCount) + .createdAt(memes.getCreatedAt()) + .build(); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java index 92244d36..1dc480c2 100644 --- a/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/MemesService.java @@ -1,7 +1,5 @@ package com.example.memetory.domain.memes.service; -import static java.time.LocalDateTime.*; - import java.util.List; import org.springframework.data.domain.Pageable; @@ -13,6 +11,7 @@ import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.meme.entity.Meme; import com.example.memetory.domain.meme.service.MemeService; +import com.example.memetory.domain.memes.dto.MemesRankDto; import com.example.memetory.domain.memes.dto.MemesServiceDto; import com.example.memetory.domain.memes.dto.response.MemesInfoResponse; import com.example.memetory.domain.memes.dto.response.MemesInfoSliceResponse; @@ -30,6 +29,7 @@ public class MemesService { private final MemberService memberService; private final MemeService memeService; private final MemesRepository memesRepository; + private final RankingService rankingService; @Transactional public MemesResponse registerMemes(MemesServiceDto memesServiceDto) { @@ -97,12 +97,26 @@ public List findTopMemesByLike() { @Transactional(readOnly = true) public List findTopMemesByLikeForMonth() { - return memesRepository.findTopMemesOrderByLikeCountForPeriod(now().minusMonths(1)); + List memesRankDtoList = rankingService.findTopTenMemesLikeCountForMonth(); + + return convertMemesRankDtoListIntoMemesInfoResponseList(memesRankDtoList); } @Transactional(readOnly = true) public List findTopMemesByLikeForWeek() { - return memesRepository.findTopMemesOrderByLikeCountForPeriod(now().minusWeeks(1)); + List memesRankDtoList = rankingService.findTopTenMemesLikeCountForWeek(); + + return convertMemesRankDtoListIntoMemesInfoResponseList(memesRankDtoList); } + private List convertMemesRankDtoListIntoMemesInfoResponseList( + List memesRankDtoList) { + + return memesRankDtoList.stream().map(this::convertMemesRankDtoInooMemesInfoResponse).toList(); + } + + private MemesInfoResponse convertMemesRankDtoInooMemesInfoResponse(MemesRankDto memesRank) { + Memes memes = findMemesFromMemesId(memesRank.getMemesId()); + return MemesInfoResponse.fromMemesAndLikeCount(memes, memesRank.getScore()); + } } diff --git a/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/RankingService.java b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/RankingService.java new file mode 100644 index 00000000..bda34cfd --- /dev/null +++ b/backend/memetory/src/main/java/com/example/memetory/domain/memes/service/RankingService.java @@ -0,0 +1,127 @@ +package com.example.memetory.domain.memes.service; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.memetory.domain.memes.dto.MemesRankDto; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Service +public class RankingService { + private final String PREFIX = "LIKE_RANKING_DATE::"; + private final String POSTFIX_WEEK = "::WEEK"; + private final String POSTFIX_MONTH = "::MONTH"; + private final Long TOP_TEN = 9L; + + private final ZSetOperations rankingZSet; + + @Transactional + public void increaseTodayMemesLikeCountFromMemesId(Long memesId) { + String key = PREFIX + LocalDate.now(); + + increaseMemesLikeCountForToday(key, memesId); + increaseMemesLikeCountForLastWeek(key, memesId); + increaseMemesLikeCountForLastMonth(key, memesId); + } + + private void increaseMemesLikeCountForToday(String key, Long memesId) { + rankingZSet.incrementScore(key, memesId, 1); + } + + private void increaseMemesLikeCountForLastWeek(String key, Long memesId) { + key += POSTFIX_WEEK; + + unionMemesIfKeyNotExists(key, 7); + rankingZSet.incrementScore(key, memesId, 1); + } + + private void increaseMemesLikeCountForLastMonth(String key, Long memesId) { + key += POSTFIX_MONTH; + + unionMemesIfKeyNotExists(key, 30); + rankingZSet.incrementScore(key, memesId, 1); + } + + private void unionMemesIfKeyNotExists(String key, int day) { + if (isNotExistedKey(key)) { + unionMemesFromKeyAndDay(key, day); + } + } + + private boolean isNotExistedKey(String key) { + Set check = rankingZSet.range(key, 0, 1); + + return check.isEmpty(); + } + + private void unionMemesFromKeyAndDay(String key, int day) { + List keyList = new ArrayList<>(); + LocalDate today = LocalDate.now(); + + for (int i = 1; i < day; i++) { + LocalDate date = today.minusDays(i); + keyList.add(PREFIX + date); + } + + rankingZSet.unionAndStore(key, keyList, key); + } + + @Transactional + public void decreaseTodayMemesLikeCountFromMemesId(Long memesId) { + String key = PREFIX + LocalDate.now(); + + decreaseMemesLikeCountForToday(key, memesId); + decreaseMemesLikeCountForLastWeek(key, memesId); + decreaseMemesLikeCountForLastMonth(key, memesId); + } + + private void decreaseMemesLikeCountForToday(String key, Long memesId) { + rankingZSet.incrementScore(key, memesId, -1); + } + + private void decreaseMemesLikeCountForLastWeek(String key, Long memesId) { + key += POSTFIX_WEEK; + + unionMemesIfKeyNotExists(key, 7); + rankingZSet.incrementScore(key, memesId, -1); + } + + private void decreaseMemesLikeCountForLastMonth(String key, Long memesId) { + key += POSTFIX_MONTH; + + unionMemesIfKeyNotExists(key, 30); + rankingZSet.incrementScore(key, memesId, -1); + } + + @Transactional(readOnly = true) + public List findTopTenMemesLikeCountForWeek() { + String key = PREFIX + LocalDate.now() + POSTFIX_WEEK; + + unionMemesIfKeyNotExists(key, 7); + + Set> rankTuple = rankingZSet.reverseRangeWithScores(key, 0, TOP_TEN); + List result = rankTuple.stream().map(MemesRankDto::of).toList(); + + return result; + } + + @Transactional(readOnly = true) + public List findTopTenMemesLikeCountForMonth() { + String key = PREFIX + LocalDate.now() + POSTFIX_MONTH; + + unionMemesIfKeyNotExists(key, 30); + + Set> rankTuple = rankingZSet.reverseRangeWithScores(key, 0, TOP_TEN); + List result = rankTuple.stream().map(MemesRankDto::of).toList(); + + return result; + } +} diff --git a/backend/memetory/src/main/java/com/example/memetory/global/config/RedisConfig.java b/backend/memetory/src/main/java/com/example/memetory/global/config/RedisConfig.java index 09aef7f6..31fbb0a8 100644 --- a/backend/memetory/src/main/java/com/example/memetory/global/config/RedisConfig.java +++ b/backend/memetory/src/main/java/com/example/memetory/global/config/RedisConfig.java @@ -1,12 +1,24 @@ package com.example.memetory.global.config; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ZSetOperations; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.orm.jpa.JpaTransactionManager; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration +@EnableRedisRepositories +@EnableTransactionManagement public class RedisConfig { @Value("${spring.redis.host}") private String host; @@ -16,6 +28,29 @@ public class RedisConfig { @Bean public RedisConnectionFactory redisConnectionFactory() { - return new LettuceConnectionFactory(host, port); + return new LettuceConnectionFactory(new RedisStandaloneConfiguration(host, port)); + } + + @Bean(name = "rankingRedisTemplate") + public RedisTemplate rankingRedisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer(Long.class)); + redisTemplate.setEnableTransactionSupport(true); + + redisTemplate.setConnectionFactory(redisConnectionFactory()); + + return redisTemplate; + } + + @Bean(name = "rankingZSetOperations") + public ZSetOperations rankingZSetOperations( + @Qualifier("rankingRedisTemplate") RedisTemplate redisTemplate) { + return redisTemplate.opsForZSet(); + } + + @Bean + public PlatformTransactionManager transactionManager() { + return new JpaTransactionManager(); } } diff --git a/backend/memetory/src/main/java/com/example/memetory/global/util/PasswordUtil.java b/backend/memetory/src/main/java/com/example/memetory/global/util/PasswordUtil.java index 46a3b420..eae9daab 100644 --- a/backend/memetory/src/main/java/com/example/memetory/global/util/PasswordUtil.java +++ b/backend/memetory/src/main/java/com/example/memetory/global/util/PasswordUtil.java @@ -4,27 +4,25 @@ public class PasswordUtil { - public static String generateRandomPassword() { - int index = 0; - char[] charSet = new char[] { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' - }; //배열안의 문자 숫자는 원하는대로 + public static String generateRandomPassword() { + int index = 0; + char[] charSet = new char[] { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' + }; - StringBuffer password = new StringBuffer(); - Random random = new Random(); + StringBuffer password = new StringBuffer(); + Random random = new Random(); - for (int i = 0; i < 8 ; i++) { - double rd = random.nextDouble(); - index = (int) (charSet.length * rd); + for (int i = 0; i < 8; i++) { + double rd = random.nextDouble(); + index = (int)(charSet.length * rd); - password.append(charSet[index]); - } - System.out.println(password); - return password.toString(); - //StringBuffer를 String으로 변환해서 return 하려면 toString()을 사용하면 된다. - } + password.append(charSet[index]); + } + return password.toString(); + } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/like/service/LikeServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/like/service/LikeServiceTest.java index 4c3615f0..9598a269 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/like/service/LikeServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/like/service/LikeServiceTest.java @@ -26,7 +26,9 @@ import com.example.memetory.domain.member.entity.Member; import com.example.memetory.domain.member.service.MemberService; import com.example.memetory.domain.memes.entity.Memes; +import com.example.memetory.domain.memes.repository.MemesRepository; import com.example.memetory.domain.memes.service.MemesService; +import com.example.memetory.domain.memes.service.RankingService; @DisplayName("Like 서비스 테스트의 ") @ExtendWith(MockitoExtension.class) @@ -37,6 +39,8 @@ public class LikeServiceTest { private MemesService memesService; @Mock private LikeRepository likeRepository; + @Mock + private RankingService rankingService; @InjectMocks private LikeService likeService; @@ -63,6 +67,7 @@ void Given_likeServiceDto_When_registerLike_Execute_likeRepository_save() { // then assertThat(memes.getLikeCount()).isEqualTo(2L); + verify(rankingService).increaseTodayMemesLikeCountFromMemesId(any()); verify(likeRepository).save(any(Like.class)); } @@ -95,5 +100,6 @@ void Given_LikeServiceDto_when_cancelLike_Execute_likeRepository_delete() { // then assertThat(memes.getLikeCount()).isZero(); verify(likeRepository).delete(like); + verify(rankingService).decreaseTodayMemesLikeCountFromMemesId(any()); } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java index 057ebfff..dc65a254 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/meme/service/MemeServiceTest.java @@ -28,6 +28,7 @@ import com.example.memetory.domain.meme.dto.MemeResponse; import com.example.memetory.domain.meme.dto.MemeServiceDto; import com.example.memetory.domain.meme.entity.Meme; +import com.example.memetory.domain.meme.exception.AccessDeniedMemeException; import com.example.memetory.domain.meme.exception.NotFoundMemeException; import com.example.memetory.domain.meme.repository.MemeRepository; @@ -135,4 +136,15 @@ private List createMemeResponseList(int pageSize) { return memeResponseList; } + + @Test + @DisplayName("밈 멤버와 다른 멤버로 인한 AccessDeniedMemeException 반환") + void Given_differentMember_When_certifyMemeMember_Throw_AccessDeniedMemeException() { + // given + Member differentMember = OTHER_MEMBER(); + + // then + assertThrows(AccessDeniedMemeException.class, () -> memeService.certifyMemeMember(member, differentMember)); + } + } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java index acc5856a..d6fe1370 100644 --- a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/MemesServiceTest.java @@ -7,6 +7,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.*; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; @@ -22,7 +24,9 @@ import com.example.memetory.domain.meme.entity.Meme; import com.example.memetory.domain.meme.exception.AccessDeniedMemeException; import com.example.memetory.domain.meme.service.MemeService; +import com.example.memetory.domain.memes.dto.MemesRankDto; import com.example.memetory.domain.memes.dto.MemesServiceDto; +import com.example.memetory.domain.memes.dto.response.MemesInfoResponse; import com.example.memetory.domain.memes.dto.response.MemesResponse; import com.example.memetory.domain.memes.entity.Memes; import com.example.memetory.domain.memes.exception.AccessDinedMemesException; @@ -40,6 +44,8 @@ public class MemesServiceTest { private MemesRepository memesRepository; @Mock private MemeService memeService; + @Mock + private RankingService rankingService; private MemesServiceDto memesServiceDto; private Member member; @@ -80,6 +86,9 @@ void Given_unAuthorizedMember_When_registerMemes_Throw_NotAccessMemeException() given(memeService.findMemeFromId(memesServiceDto.getMemeId())).willReturn(meme); given(memberService.findMemberFromEmail(any(String.class))).willReturn(unAuthorizedMember); + doThrow(new AccessDeniedMemeException()).when(memeService) + .certifyMemeMember(any(Member.class), any(Member.class)); + // then assertThrows(AccessDeniedMemeException.class, () -> memesService.registerMemes(memesServiceDto)); } @@ -135,4 +144,29 @@ void Given_notExistMemesId_When_findMemesResponse_Throw_MemesResponse() { // then assertThrows(NotFoundMemesException.class, () -> memesService.findMemesResponse(memesServiceDto)); } + + @Test + @DisplayName("주간 탑 10 조회로 인한 List 반환") + void When_findTopMemesByLikeForWeek_Then_List_MemesInfoResponse() { + // given + List memesRankDtoList = generateMemesRankDtoList(); + given(rankingService.findTopTenMemesLikeCountForWeek()).willReturn(memesRankDtoList); + given(memesRepository.findByMemesId(any())).willReturn(Optional.ofNullable(MEMES(member, meme))); + + // when + List result = memesService.findTopMemesByLikeForWeek(); + + // then + assertThat(result).hasSize(10); + assertThat(result.get(0)).isInstanceOf(MemesInfoResponse.class); + } + + private List generateMemesRankDtoList() { + List expectedResult = new ArrayList<>(); + + for (long i = 1; i <= 10; i++) { + expectedResult.add(new MemesRankDto(i, i)); + } + return expectedResult; + } } diff --git a/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/RankingServiceTest.java b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/RankingServiceTest.java new file mode 100644 index 00000000..e7558a85 --- /dev/null +++ b/backend/memetory/src/test/java/com/example/memetory/domain/memes/service/RankingServiceTest.java @@ -0,0 +1,131 @@ +package com.example.memetory.domain.memes.service; + +import static org.assertj.core.api.Assertions.*; + +import java.time.LocalDate; +import java.util.List; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.ZSetOperations; + +import com.example.memetory.domain.memes.dto.MemesRankDto; + +@DisplayName("Ranking 서비스 테스트의 ") +@SpringBootTest +public class RankingServiceTest { + private final String PREFIX = "LIKE_RANKING_DATE::"; + private final String POSTFIX_WEEK = "::WEEK"; + private final String POSTFIX_MONTH = "::MONTH"; + private final Long MEMES_ID = -1L; + + private String key; + + @Autowired + private RankingService rankingService; + @Autowired + private ZSetOperations rankingZSet; + @Autowired + private RedisConnectionFactory redisConnectionFactory; + + @BeforeEach + public void setUp() { + redisConnectionFactory.getConnection().flushAll(); + key = PREFIX + LocalDate.now(); + } + + @AfterEach + void clearRedis() { + redisConnectionFactory.getConnection().flushAll(); + } + + @Test + @DisplayName("존재하지 않는 memes와 memesId를 통한 score 증가 성공") + void Given_memesId_When_increaseTodayMemesLikeCountFromMemesId_Then_Memes_Score() { + // given + Double expectedScore = 1.0; + + // when + rankingService.increaseTodayMemesLikeCountFromMemesId(MEMES_ID); + + // then + assertThat(rankingZSet.score(key, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_WEEK, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_MONTH, MEMES_ID)).isEqualTo(expectedScore); + } + + @Test + @DisplayName("존재하는 memes와 memesId를 통한 score 증가 성공") + void Given_existMemesAndMemesId_When_increaseTodayMemesLikeCountFromMemesId_Then_Memes_Score() { + // given + Double expectedScore = 2.0; + + // when + rankingService.increaseTodayMemesLikeCountFromMemesId(MEMES_ID); + rankingService.increaseTodayMemesLikeCountFromMemesId(MEMES_ID); + + // then + assertThat(rankingZSet.score(key, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_WEEK, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_MONTH, MEMES_ID)).isEqualTo(expectedScore); + } + + @Test + @DisplayName("존재하는 memes와 memesId를 통한 score 감소") + void Given_existMemesAndMemesId_When_decreaseTodayMemesLikeCountFromMemesId_Then_Memes_Score() { + // given + Double expectedScore = -1.0; + + // when + rankingService.decreaseTodayMemesLikeCountFromMemesId(MEMES_ID); + + // then + assertThat(rankingZSet.score(key, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_WEEK, MEMES_ID)).isEqualTo(expectedScore); + assertThat(rankingZSet.score(key + POSTFIX_MONTH, MEMES_ID)).isEqualTo(expectedScore); + } + + @Test + @DisplayName("존재하지 않는 주간 key를 통한 MemesIdList 반환") + void Given_NotExistKey_When_findTopTenMemesLikeCountForWeek_Then_MemesIdList() { + // given + for (int i = 0; i < 7; i++) { + LocalDate date = LocalDate.now().minusDays(i); + String dateKey = PREFIX + date; + rankingZSet.add(dateKey, (long)i, i); + } + + // when + List result = rankingService.findTopTenMemesLikeCountForWeek(); + + //then + assertThat(result.size()).isEqualTo(6); + assertThat(result.get(0).getMemesId()).isEqualTo(6); + } + + @Test + @DisplayName("존재하는 주간 key를 통한 MemesIdList 반환") + void Given_ExistKey_When_findTopTenMemesLikeCountForWeek_Then_MemesIdList() { + // given + key += POSTFIX_WEEK; + rankingZSet.add(key, 7L, 7); + + for (int i = 0; i < 7; i++) { + LocalDate date = LocalDate.now().minusDays(i); + String dateKey = PREFIX + date; + rankingZSet.add(dateKey, (long)i, i); + } + + // when + List result = rankingService.findTopTenMemesLikeCountForWeek(); + + //then + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(0).getMemesId()).isEqualTo(7L); + } +}