Skip to content

Commit 890cc05

Browse files
jueunk617namgigun
authored andcommitted
Test: 변경사항에 맞춰 테스트 코드 수정
1 parent 09923c6 commit 890cc05

File tree

5 files changed

+425
-71
lines changed

5 files changed

+425
-71
lines changed

src/main/java/com/back/domain/notification/repository/NotificationRepositoryImpl.java

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.back.domain.notification.entity.NotificationType;
55
import com.back.domain.notification.entity.QNotification;
66
import com.back.domain.notification.entity.QNotificationRead;
7+
import com.back.domain.studyroom.entity.QRoomMember;
78
import com.querydsl.jpa.impl.JPAQueryFactory;
89
import lombok.RequiredArgsConstructor;
910
import org.springframework.data.domain.Page;
@@ -25,24 +26,35 @@ public class NotificationRepositoryImpl implements NotificationRepositoryCustom
2526
@Override
2627
public Page<Notification> findByUserIdOrSystemType(Long userId, Pageable pageable) {
2728
QNotification notification = QNotification.notification;
29+
QRoomMember roomMember = QRoomMember.roomMember;
2830

31+
// 알림 목록 조회
2932
List<Notification> content = queryFactory
3033
.selectFrom(notification)
34+
.leftJoin(roomMember)
35+
.on(notification.room.id.eq(roomMember.room.id)
36+
.and(roomMember.user.id.eq(userId)))
3137
.where(
32-
notification.receiver.id.eq(userId)
33-
.or(notification.type.eq(NotificationType.SYSTEM))
38+
notification.receiver.id.eq(userId) // 개인/커뮤니티 알림
39+
.or(notification.type.eq(NotificationType.SYSTEM)) // 시스템 알림
40+
.or(roomMember.id.isNotNull()) // 룸 알림 (내가 멤버인 경우)
3441
)
3542
.orderBy(notification.createdAt.desc())
3643
.offset(pageable.getOffset())
3744
.limit(pageable.getPageSize())
3845
.fetch();
3946

47+
// 전체 개수 조회
4048
Long total = queryFactory
4149
.select(notification.count())
4250
.from(notification)
51+
.leftJoin(roomMember)
52+
.on(notification.room.id.eq(roomMember.room.id)
53+
.and(roomMember.user.id.eq(userId)))
4354
.where(
4455
notification.receiver.id.eq(userId)
4556
.or(notification.type.eq(NotificationType.SYSTEM))
57+
.or(roomMember.id.isNotNull())
4658
)
4759
.fetchOne();
4860

@@ -58,16 +70,21 @@ public Page<Notification> findByUserIdOrSystemType(Long userId, Pageable pageabl
5870
public long countUnreadByUserId(Long userId) {
5971
QNotification notification = QNotification.notification;
6072
QNotificationRead notificationRead = QNotificationRead.notificationRead;
73+
QRoomMember roomMember = QRoomMember.roomMember;
6174

6275
Long count = queryFactory
6376
.select(notification.count())
6477
.from(notification)
6578
.leftJoin(notificationRead)
6679
.on(notification.id.eq(notificationRead.notification.id)
6780
.and(notificationRead.user.id.eq(userId)))
81+
.leftJoin(roomMember)
82+
.on(notification.room.id.eq(roomMember.room.id)
83+
.and(roomMember.user.id.eq(userId)))
6884
.where(
69-
notification.receiver.id.eq(userId) // ✨ user → receiver
70-
.or(notification.type.eq(NotificationType.SYSTEM)),
85+
notification.receiver.id.eq(userId)
86+
.or(notification.type.eq(NotificationType.SYSTEM))
87+
.or(roomMember.id.isNotNull()),
7188
notificationRead.id.isNull()
7289
)
7390
.fetchOne();
@@ -84,31 +101,42 @@ public long countUnreadByUserId(Long userId) {
84101
public Page<Notification> findUnreadByUserId(Long userId, Pageable pageable) {
85102
QNotification notification = QNotification.notification;
86103
QNotificationRead notificationRead = QNotificationRead.notificationRead;
104+
QRoomMember roomMember = QRoomMember.roomMember;
87105

106+
// 읽지 않은 알림 목록 조회
88107
List<Notification> content = queryFactory
89108
.selectFrom(notification)
90109
.leftJoin(notificationRead)
91110
.on(notification.id.eq(notificationRead.notification.id)
92111
.and(notificationRead.user.id.eq(userId)))
112+
.leftJoin(roomMember)
113+
.on(notification.room.id.eq(roomMember.room.id)
114+
.and(roomMember.user.id.eq(userId)))
93115
.where(
94116
notification.receiver.id.eq(userId)
95-
.or(notification.type.eq(NotificationType.SYSTEM)),
117+
.or(notification.type.eq(NotificationType.SYSTEM))
118+
.or(roomMember.id.isNotNull()),
96119
notificationRead.id.isNull()
97120
)
98121
.orderBy(notification.createdAt.desc())
99122
.offset(pageable.getOffset())
100123
.limit(pageable.getPageSize())
101124
.fetch();
102125

126+
// 전체 개수 조회
103127
Long total = queryFactory
104128
.select(notification.count())
105129
.from(notification)
106130
.leftJoin(notificationRead)
107131
.on(notification.id.eq(notificationRead.notification.id)
108132
.and(notificationRead.user.id.eq(userId)))
133+
.leftJoin(roomMember)
134+
.on(notification.room.id.eq(roomMember.room.id)
135+
.and(roomMember.user.id.eq(userId)))
109136
.where(
110137
notification.receiver.id.eq(userId)
111-
.or(notification.type.eq(NotificationType.SYSTEM)),
138+
.or(notification.type.eq(NotificationType.SYSTEM))
139+
.or(roomMember.id.isNotNull()),
112140
notificationRead.id.isNull()
113141
)
114142
.fetchOne();
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
package com.back.domain.notification.controller;
2+
3+
import com.back.domain.notification.dto.NotificationCreateRequest;
4+
import com.back.domain.notification.entity.Notification;
5+
import com.back.domain.notification.service.NotificationService;
6+
import com.back.domain.studyroom.repository.RoomRepository;
7+
import com.back.domain.user.entity.Role;
8+
import com.back.domain.user.entity.User;
9+
import com.back.domain.user.repository.UserRepository;
10+
import com.back.global.exception.CustomException;
11+
import com.back.global.exception.ErrorCode;
12+
import com.back.global.security.jwt.JwtTokenProvider;
13+
import com.back.global.security.user.CustomUserDetails;
14+
import com.fasterxml.jackson.databind.ObjectMapper;
15+
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.DisplayName;
17+
import org.junit.jupiter.api.Nested;
18+
import org.junit.jupiter.api.Test;
19+
import org.springframework.beans.factory.annotation.Autowired;
20+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
21+
import org.springframework.boot.test.context.SpringBootTest;
22+
import org.springframework.data.domain.Page;
23+
import org.springframework.data.domain.PageImpl;
24+
import org.springframework.data.domain.Pageable;
25+
import org.springframework.http.MediaType;
26+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
27+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
28+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
29+
import org.springframework.test.web.servlet.MockMvc;
30+
import org.springframework.test.web.servlet.ResultActions;
31+
32+
import java.util.List;
33+
import java.util.Optional;
34+
35+
import static org.mockito.ArgumentMatchers.*;
36+
import static org.mockito.BDDMockito.*;
37+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
38+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
39+
40+
@SpringBootTest
41+
@AutoConfigureMockMvc
42+
class NotificationControllerTest {
43+
44+
@Autowired
45+
private MockMvc mockMvc;
46+
47+
@Autowired
48+
private ObjectMapper objectMapper;
49+
50+
@MockitoBean
51+
private JwtTokenProvider jwtTokenProvider;
52+
53+
@MockitoBean
54+
private NotificationService notificationService;
55+
56+
@MockitoBean
57+
private UserRepository userRepository;
58+
59+
@MockitoBean
60+
private RoomRepository roomRepository;
61+
62+
private User receiver;
63+
private User actor;
64+
private Notification notification;
65+
private CustomUserDetails mockUser;
66+
67+
@BeforeEach
68+
void setUp() {
69+
receiver = User.builder()
70+
.id(1L)
71+
72+
.username("수신자")
73+
.password("password123")
74+
.role(Role.USER)
75+
.build();
76+
77+
actor = User.builder()
78+
.id(2L)
79+
80+
.username("발신자")
81+
.password("password123")
82+
.role(Role.USER)
83+
.build();
84+
85+
notification = Notification.createPersonalNotification(
86+
receiver, actor, "테스트 알림", "내용", "/target"
87+
);
88+
89+
// JWT Mock 설정
90+
mockUser = CustomUserDetails.builder()
91+
.userId(1L)
92+
.username("testuser")
93+
.role(Role.USER)
94+
.build();
95+
96+
given(jwtTokenProvider.validateAccessToken("faketoken")).willReturn(true);
97+
given(jwtTokenProvider.getAuthentication("faketoken"))
98+
.willReturn(new UsernamePasswordAuthenticationToken(
99+
mockUser,
100+
null,
101+
List.of(new SimpleGrantedAuthority("ROLE_USER"))
102+
));
103+
}
104+
105+
@Nested
106+
@DisplayName("알림 전송")
107+
class CreateNotificationTest {
108+
109+
@Test
110+
@DisplayName("개인 알림 생성 성공")
111+
void t1() throws Exception {
112+
// given
113+
NotificationCreateRequest request = new NotificationCreateRequest(
114+
"USER", 1L, 2L, "개인 알림", "내용", "/target"
115+
);
116+
117+
given(userRepository.findById(1L)).willReturn(Optional.of(receiver));
118+
given(userRepository.findById(2L)).willReturn(Optional.of(actor));
119+
given(notificationService.createPersonalNotification(any(), any(), any(), any(), any()))
120+
.willReturn(notification);
121+
122+
// when
123+
ResultActions result = mockMvc.perform(post("/api/notifications")
124+
.header("Authorization", "Bearer faketoken")
125+
.contentType(MediaType.APPLICATION_JSON)
126+
.content(objectMapper.writeValueAsString(request)));
127+
128+
// then
129+
result.andExpect(status().isOk())
130+
.andExpect(jsonPath("$.success").value(true));
131+
}
132+
}
133+
134+
@Nested
135+
@DisplayName("알림 목록 조회")
136+
class GetNotificationsTest {
137+
138+
@Test
139+
@DisplayName("알림 목록 조회 성공")
140+
void t1() throws Exception {
141+
// given
142+
Page<Notification> notificationPage = new PageImpl<>(List.of(notification));
143+
144+
given(notificationService.getUserNotifications(anyLong(), any(Pageable.class)))
145+
.willReturn(notificationPage);
146+
given(notificationService.getUnreadCount(anyLong())).willReturn(3L);
147+
given(notificationService.isNotificationRead(anyLong(), anyLong())).willReturn(false);
148+
149+
// when
150+
ResultActions result = mockMvc.perform(get("/api/notifications")
151+
.header("Authorization", "Bearer faketoken")
152+
.param("page", "0")
153+
.param("size", "20"));
154+
155+
// then
156+
result.andExpect(status().isOk())
157+
.andExpect(jsonPath("$.success").value(true));
158+
}
159+
160+
@Test
161+
@DisplayName("읽지 않은 알림만 조회")
162+
void t2() throws Exception {
163+
// given
164+
Page<Notification> notificationPage = new PageImpl<>(List.of(notification));
165+
166+
given(notificationService.getUnreadNotifications(anyLong(), any(Pageable.class)))
167+
.willReturn(notificationPage);
168+
given(notificationService.getUnreadCount(anyLong())).willReturn(1L);
169+
given(notificationService.isNotificationRead(anyLong(), anyLong())).willReturn(false);
170+
171+
// when
172+
ResultActions result = mockMvc.perform(get("/api/notifications")
173+
.header("Authorization", "Bearer faketoken")
174+
.param("unreadOnly", "true"));
175+
176+
// then
177+
result.andExpect(status().isOk());
178+
verify(notificationService).getUnreadNotifications(anyLong(), any(Pageable.class));
179+
}
180+
}
181+
182+
@Nested
183+
@DisplayName("알림 읽음 처리")
184+
class MarkAsReadTest {
185+
186+
@Test
187+
@DisplayName("알림 읽음 처리 성공")
188+
void t1() throws Exception {
189+
// given
190+
given(userRepository.findById(anyLong())).willReturn(Optional.of(receiver));
191+
given(notificationService.getNotification(1L)).willReturn(notification);
192+
given(notificationService.isNotificationRead(1L, receiver.getId())).willReturn(true);
193+
194+
// when
195+
ResultActions result = mockMvc.perform(put("/api/notifications/1/read")
196+
.header("Authorization", "Bearer faketoken"));
197+
198+
// then
199+
result.andExpect(status().isOk())
200+
.andExpect(jsonPath("$.success").value(true));
201+
202+
verify(notificationService).markAsRead(eq(1L), any(User.class));
203+
}
204+
205+
@Test
206+
@DisplayName("존재하지 않는 알림 - 404 에러")
207+
void t2() throws Exception {
208+
// given
209+
given(userRepository.findById(anyLong())).willReturn(Optional.of(receiver));
210+
given(notificationService.getNotification(999L))
211+
.willThrow(new CustomException(ErrorCode.NOTIFICATION_NOT_FOUND));
212+
213+
// when
214+
ResultActions result = mockMvc.perform(put("/api/notifications/999/read")
215+
.header("Authorization", "Bearer faketoken"));
216+
217+
// then
218+
result.andExpect(status().isNotFound());
219+
}
220+
221+
@Test
222+
@DisplayName("이미 읽은 알림 - 400 에러")
223+
void t3() throws Exception {
224+
// given
225+
given(userRepository.findById(anyLong())).willReturn(Optional.of(receiver));
226+
willThrow(new CustomException(ErrorCode.NOTIFICATION_ALREADY_READ))
227+
.given(notificationService).markAsRead(eq(1L), any(User.class));
228+
229+
// when
230+
ResultActions result = mockMvc.perform(put("/api/notifications/1/read")
231+
.header("Authorization", "Bearer faketoken"));
232+
233+
// then
234+
result.andExpect(status().isBadRequest());
235+
}
236+
}
237+
238+
@Nested
239+
@DisplayName("전체 읽음 처리")
240+
class MarkAllAsReadTest {
241+
242+
@Test
243+
@DisplayName("전체 알림 읽음 처리 성공")
244+
void t1() throws Exception {
245+
// given
246+
given(userRepository.findById(anyLong())).willReturn(Optional.of(receiver));
247+
248+
// when
249+
ResultActions result = mockMvc.perform(put("/api/notifications/read-all")
250+
.header("Authorization", "Bearer faketoken"));
251+
252+
// then
253+
result.andExpect(status().isOk())
254+
.andExpect(jsonPath("$.success").value(true));
255+
256+
verify(notificationService).markMultipleAsRead(anyLong(), any(User.class));
257+
}
258+
259+
@Test
260+
@DisplayName("존재하지 않는 사용자 - 404 에러")
261+
void t2() throws Exception {
262+
// given
263+
given(userRepository.findById(anyLong())).willReturn(Optional.empty());
264+
265+
// when
266+
ResultActions result = mockMvc.perform(put("/api/notifications/read-all")
267+
.header("Authorization", "Bearer faketoken"));
268+
269+
// then
270+
result.andExpect(status().isNotFound());
271+
}
272+
}
273+
}

0 commit comments

Comments
 (0)