Skip to content

Commit 83bb650

Browse files
committed
refactor 공유 제안 게시판 좋아요 수정
- redis에도 count 저장 - 페이지 조회 시 캐시값을 먼저 조회하여 정합성 증가 - 첫 토글 시 Redis 조회 후 없을 경우 DB 조회하여 캐싱
1 parent 425ddef commit 83bb650

File tree

2 files changed

+170
-13
lines changed

2 files changed

+170
-13
lines changed

src/test/java/io/crops/warmletter/domain/share/scheduler/LikeSchedulerTest.java

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ class LikeSchedulerTest {
3131
private LikeScheduler likeScheduler;
3232

3333
@Test
34-
@DisplayName("Redis 데이터를 DB에 동기화")
34+
@DisplayName("Redis 데이터를 DB에 동기화 - 기존 데이터 ")
3535
void syncLikesToDatabase() {
3636
// given
3737
Map<String, Boolean> likeStatusMap = new HashMap<>();
3838
likeStatusMap.put("post:1:like:memberId:1", true);
3939

40-
SharePostLike existingLike = new SharePostLike(1L, 1L, false);
40+
SharePostLike existingLike = mock(SharePostLike.class);
41+
when(existingLike.isLiked()).thenReturn(false);
4142

4243
when(postLikeRedisManager.getAllLikeStatus()).thenReturn(likeStatusMap);
4344
when(sharePostLikeRepository.findBySharePostIdAndMemberId(1L, 1L))
@@ -47,8 +48,8 @@ void syncLikesToDatabase() {
4748
likeScheduler.syncLikesToDatabase();
4849

4950
// then
51+
verify(existingLike).updateLikeStatus(true);
5052
verify(postLikeRedisManager).clearCache();
51-
assertThat(existingLike.isLiked()).isTrue();
5253
}
5354

5455
@Test
@@ -68,22 +69,22 @@ void syncLikesToDatabase_CreateNew() {
6869
}
6970

7071
@Test
71-
@DisplayName("Redis 데이터를 DB에 동기화 - 기존 데이터 존재시 반대")
72+
@DisplayName("Redis 데이터를 DB에 동기화 - 상태가 같은 경우 업데이트 안함 ")
7273
void syncLikesToDatabase_Already() {
7374
Map<String, Boolean> likeStatusMap = new HashMap<>();
7475
likeStatusMap.put("post:1:like:memberId:1", true);
7576

76-
SharePostLike sharePostLike = new SharePostLike(1L, 1L, true);
77+
SharePostLike existingLike = mock(SharePostLike.class);
78+
when(existingLike.isLiked()).thenReturn(true); // 이미 같은 상태
7779

7880
when(postLikeRedisManager.getAllLikeStatus()).thenReturn(likeStatusMap);
7981
when(sharePostLikeRepository.findBySharePostIdAndMemberId(1L, 1L))
80-
.thenReturn(Optional.of(sharePostLike));
82+
.thenReturn(Optional.of(existingLike));
8183

8284
likeScheduler.syncLikesToDatabase();
8385

86+
verify(existingLike, never()).updateLikeStatus(anyBoolean()); // 상태가 같으므로 업데이트하지 않음
8487
verify(postLikeRedisManager).clearCache();
85-
assertThat(sharePostLike.isLiked()).isFalse();
86-
8788
}
8889

8990
@Test
@@ -120,4 +121,75 @@ void syncLikesToDatabase_EmptyStatusMap() {
120121
verify(sharePostLikeRepository, never()).save(any());
121122
}
122123

124+
@Test
125+
@DisplayName("Redis 데이터를 DB에 동기화 - 처리 중 예외 발생")
126+
void syncLikesToDatabase_ExceptionHandling() {
127+
// given
128+
Map<String, Boolean> likeStatusMap = new HashMap<>();
129+
likeStatusMap.put("post:1:like:memberId:1", true);
130+
likeStatusMap.put("post:2:like:memberId:1", true);
131+
132+
when(postLikeRedisManager.getAllLikeStatus()).thenReturn(likeStatusMap);
133+
when(sharePostLikeRepository.findBySharePostIdAndMemberId(1L, 1L))
134+
.thenThrow(new RuntimeException("Database error"));
135+
when(sharePostLikeRepository.findBySharePostIdAndMemberId(2L, 1L))
136+
.thenReturn(Optional.empty());
137+
138+
// when
139+
likeScheduler.syncLikesToDatabase();
140+
141+
// then
142+
// 첫 번째 항목 처리 중 예외가 발생해도 두 번째 항목은 처리되어야 함
143+
verify(sharePostLikeRepository).save(any(SharePostLike.class));
144+
verify(postLikeRedisManager).clearCache();
145+
}
146+
@Test
147+
@DisplayName("Redis 데이터를 DB에 동기화 - 잘못된 키 형식 처리")
148+
void syncLikesToDatabase_InvalidKeyFormat() {
149+
// given
150+
Map<String, Boolean> likeStatusMap = new HashMap<>();
151+
likeStatusMap.put("post:1:like:memberId:1", true); // 유효한 키
152+
likeStatusMap.put("invalid:format:key", true); // 잘못된 키
153+
154+
when(postLikeRedisManager.getAllLikeStatus()).thenReturn(likeStatusMap);
155+
when(sharePostLikeRepository.findBySharePostIdAndMemberId(1L, 1L))
156+
.thenReturn(Optional.empty());
157+
158+
// when
159+
likeScheduler.syncLikesToDatabase();
160+
161+
// then
162+
// 유효한 키만 처리되어야 함
163+
verify(sharePostLikeRepository, times(1)).findBySharePostIdAndMemberId(anyLong(), anyLong());
164+
verify(sharePostLikeRepository).save(any(SharePostLike.class));
165+
verify(postLikeRedisManager).clearCache();
166+
}
167+
168+
@Test
169+
@DisplayName("Redis 데이터 조회 중 예외 발생")
170+
void syncLikesToDatabase_RedisException() {
171+
// given
172+
when(postLikeRedisManager.getAllLikeStatus()).thenThrow(new RuntimeException("Redis error"));
173+
174+
// when
175+
likeScheduler.syncLikesToDatabase();
176+
177+
// then
178+
// 예외가 발생해도 테스트는 통과해야 함 (예외가 잡혀야 함)
179+
verify(postLikeRedisManager, never()).clearCache();
180+
}
181+
@Test
182+
@DisplayName("Redis getAllLikeStatus가 null 반환")
183+
void syncLikesToDatabase_NullStatusMap() {
184+
// given
185+
when(postLikeRedisManager.getAllLikeStatus()).thenReturn(null);
186+
187+
// when
188+
likeScheduler.syncLikesToDatabase();
189+
190+
// then
191+
verify(postLikeRedisManager).clearCache();
192+
verify(sharePostLikeRepository, never()).findBySharePostIdAndMemberId(any(), any());
193+
}
194+
123195
}

src/test/java/io/crops/warmletter/domain/share/service/SharePostLikeServiceTest.java

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.crops.warmletter.domain.auth.facade.AuthFacade;
44
import io.crops.warmletter.domain.share.cache.PostLikeRedisManager;
55
import io.crops.warmletter.domain.share.dto.response.SharePostLikeResponse;
6+
import io.crops.warmletter.domain.share.entity.SharePostLike;
67
import io.crops.warmletter.domain.share.exception.ShareInvalidInputValue;
78
import io.crops.warmletter.domain.share.repository.SharePostLikeRepository;
89
import org.junit.jupiter.api.DisplayName;
@@ -12,6 +13,8 @@
1213
import org.mockito.Mock;
1314
import org.mockito.junit.jupiter.MockitoExtension;
1415

16+
import java.util.Optional;
17+
1518
import static org.junit.jupiter.api.Assertions.*;
1619
import static org.mockito.Mockito.*;
1720

@@ -31,25 +34,101 @@ class SharePostLikeServiceTest {
3134
private SharePostLikeService sharePostLikeService;
3235

3336
@Test
34-
@DisplayName("좋아요 토글 요청 처리")
35-
void toggleLike() {
37+
@DisplayName("좋아요 토글 요청 처리 - Redis에 존재")
38+
void toggleLike_Redis() {
39+
// given
40+
Long postId = 1L;
41+
Long memberId = 1L;
42+
43+
when(authFacade.getCurrentUserId()).thenReturn(memberId);
44+
when(redisManager.getLikedStatus(postId, memberId)).thenReturn(Optional.of(true));
45+
46+
// when
47+
sharePostLikeService.toggleLike(postId);
48+
49+
// then
50+
verify(authFacade).getCurrentUserId();
51+
verify(redisManager).getLikedStatus(postId, memberId);
52+
verify(redisManager).toggleLike(postId, memberId, false); // true -> false로 토글
53+
verify(sharePostLikeRepository, never()).findBySharePostIdAndMemberId(anyLong(), anyLong());
54+
}
55+
56+
@Test
57+
@DisplayName("좋아요 토글 - Redis에 정보가 없고 DB에 있는 경우")
58+
void toggleLike_DB() {
59+
// given
60+
Long postId = 1L;
61+
Long memberId = 1L;
62+
SharePostLike existingLike = mock(SharePostLike.class);
63+
64+
when(authFacade.getCurrentUserId()).thenReturn(memberId);
65+
when(redisManager.getLikedStatus(postId, memberId)).thenReturn(Optional.empty());
66+
when(sharePostLikeRepository.findBySharePostIdAndMemberId(postId, memberId))
67+
.thenReturn(Optional.of(existingLike));
68+
when(existingLike.isLiked()).thenReturn(true);
69+
70+
// when
71+
sharePostLikeService.toggleLike(postId);
72+
73+
// then
74+
verify(authFacade).getCurrentUserId();
75+
verify(redisManager).getLikedStatus(postId, memberId);
76+
verify(sharePostLikeRepository).findBySharePostIdAndMemberId(postId, memberId);
77+
verify(redisManager).toggleLike(postId, memberId, false); // true -> false로 토글
78+
}
79+
80+
@Test
81+
@DisplayName("좋아요 토글 - Redis에 정보가 없고 DB에도 없는 경우")
82+
void toggleLike_WithoutRedisAndDB() {
3683
// given
3784
Long postId = 1L;
3885
Long memberId = 1L;
3986

4087
when(authFacade.getCurrentUserId()).thenReturn(memberId);
88+
when(redisManager.getLikedStatus(postId, memberId)).thenReturn(Optional.empty());
89+
when(sharePostLikeRepository.findBySharePostIdAndMemberId(postId, memberId))
90+
.thenReturn(Optional.empty());
4191

4292
// when
4393
sharePostLikeService.toggleLike(postId);
4494

4595
// then
4696
verify(authFacade).getCurrentUserId();
47-
verify(redisManager).toggleLike(postId, memberId);
97+
verify(redisManager).getLikedStatus(postId, memberId);
98+
verify(sharePostLikeRepository).findBySharePostIdAndMemberId(postId, memberId);
99+
verify(redisManager).toggleLike(postId, memberId, true); // false -> true로 토글 (기본값은 false)
100+
}
101+
102+
@Test
103+
@DisplayName("좋아요 개수와 상태 - Redis")
104+
void getLikeCountAndStatus_WithRedisStatus() {
105+
// given
106+
Long sharePostId = 1L;
107+
Long memberId = 1L;
108+
SharePostLikeResponse dbResponse = new SharePostLikeResponse(5L, false);
109+
110+
when(authFacade.getCurrentUserId()).thenReturn(memberId);
111+
when(sharePostLikeRepository.getLikeCountAndStatus(sharePostId, memberId))
112+
.thenReturn(dbResponse);
113+
when(redisManager.getLikeCount(sharePostId)).thenReturn(2);
114+
when(redisManager.getLikedStatus(sharePostId, memberId)).thenReturn(Optional.of(true));
115+
116+
// when
117+
SharePostLikeResponse result = sharePostLikeService.getLikeCountAndStatus(sharePostId);
118+
119+
// then
120+
assertNotNull(result);
121+
assertEquals(7L, result.getLikeCount()); // 5(DB) + 2(Redis) = 7
122+
assertTrue(result.isLiked()); // Redis 값 사용
123+
verify(authFacade).getCurrentUserId();
124+
verify(sharePostLikeRepository).getLikeCountAndStatus(sharePostId, memberId);
125+
verify(redisManager).getLikeCount(sharePostId);
126+
verify(redisManager).getLikedStatus(sharePostId, memberId);
48127
}
49128

50129
@Test
51-
@DisplayName("좋아요 개수와 상태 조회 성공")
52-
void getLikeCountAndStatus_Success() {
130+
@DisplayName("좋아요 개수와 상태 - NoRedis ")
131+
void getLikeCountAndStatus_SuccessNoRedis() {
53132
// given
54133
Long sharePostId = 1L;
55134
Long memberId = 1L;
@@ -58,6 +137,8 @@ void getLikeCountAndStatus_Success() {
58137
when(authFacade.getCurrentUserId()).thenReturn(memberId);
59138
when(sharePostLikeRepository.getLikeCountAndStatus(sharePostId, memberId))
60139
.thenReturn(mockResponse);
140+
when(redisManager.getLikeCount(sharePostId)).thenReturn(0);
141+
when(redisManager.getLikedStatus(sharePostId, memberId)).thenReturn(Optional.empty());
61142

62143
// when
63144
SharePostLikeResponse result = sharePostLikeService.getLikeCountAndStatus(sharePostId);
@@ -68,6 +149,8 @@ void getLikeCountAndStatus_Success() {
68149
assertTrue(result.isLiked());
69150
verify(authFacade).getCurrentUserId();
70151
verify(sharePostLikeRepository).getLikeCountAndStatus(sharePostId, memberId);
152+
verify(redisManager).getLikeCount(sharePostId);
153+
verify(redisManager).getLikedStatus(sharePostId, memberId);
71154
}
72155

73156
@Test
@@ -107,5 +190,7 @@ void getLikeCountAndStatus_NullPostId() {
107190

108191
verify(authFacade).getCurrentUserId();
109192
verify(sharePostLikeRepository, never()).getLikeCountAndStatus(any(), any());
193+
verify(redisManager, never()).getLikeCount(any());
194+
verify(redisManager, never()).getLikedStatus(any(), any());
110195
}
111196
}

0 commit comments

Comments
 (0)