Skip to content

Commit 2e3c3c3

Browse files
authored
[test] NotificationController의 알림 관련 기능 통합 테스트 추가 #218 (#220)
* Revert "chore: initData용 이미지 추가" This reverts commit ef30eef. * . * feat: NotificationController의 알림 관련 기능 통합 테스트 추가
1 parent 2c227e4 commit 2e3c3c3

File tree

1 file changed

+246
-0
lines changed

1 file changed

+246
-0
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
package com.back.domain.notification.controller;
2+
3+
import com.back.domain.notification.dto.NotificationGoResponseDto;
4+
import com.back.domain.notification.dto.NotificationItemDto;
5+
import com.back.domain.notification.dto.NotificationListResponseDto;
6+
import com.back.domain.notification.dto.NotificationSettingDto;
7+
import com.back.domain.notification.enums.NotificationType;
8+
import com.back.domain.notification.service.NotificationService;
9+
import com.back.domain.notification.service.NotificationSettingService;
10+
import com.back.global.aspect.ResponseAspect;
11+
import com.back.global.jwt.JwtUtil;
12+
import com.back.global.rq.Rq;
13+
import com.back.global.security.SecurityUser;
14+
import java.time.LocalDateTime;
15+
import java.util.List;
16+
import java.util.Map;
17+
import org.junit.jupiter.api.AfterEach;
18+
import org.junit.jupiter.api.DisplayName;
19+
import org.junit.jupiter.api.Test;
20+
import org.springframework.beans.factory.annotation.Autowired;
21+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
22+
import org.springframework.boot.autoconfigure.aop.AopAutoConfiguration;
23+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
24+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
25+
import org.springframework.context.annotation.Import;
26+
import org.springframework.http.MediaType;
27+
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
28+
import org.springframework.security.core.authority.SimpleGrantedAuthority;
29+
import org.springframework.security.core.context.SecurityContext;
30+
import org.springframework.security.core.context.SecurityContextHolder;
31+
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
32+
import org.springframework.test.context.bean.override.mockito.MockitoBean;
33+
import org.springframework.test.web.servlet.MockMvc;
34+
import org.springframework.test.web.servlet.MvcResult;
35+
import org.springframework.test.web.servlet.request.RequestPostProcessor;
36+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
37+
38+
import static org.mockito.ArgumentMatchers.eq;
39+
import static org.mockito.BDDMockito.given;
40+
import static org.mockito.Mockito.verify;
41+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
42+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
43+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
44+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
45+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
46+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
47+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
48+
49+
@WebMvcTest(NotificationController.class)
50+
@AutoConfigureMockMvc(addFilters = false)
51+
@Import(ResponseAspect.class)
52+
@ImportAutoConfiguration(AopAutoConfiguration.class)
53+
class NotificationControllerTest {
54+
55+
@Autowired
56+
private MockMvc mockMvc;
57+
58+
@MockitoBean
59+
private NotificationService notificationService;
60+
61+
@MockitoBean
62+
private NotificationSettingService notificationSettingService;
63+
64+
@MockitoBean
65+
private JwtUtil jwtUtil;
66+
67+
@MockitoBean
68+
private Rq rq;
69+
70+
@AfterEach
71+
void clearSecurityContext() {
72+
SecurityContextHolder.clearContext();
73+
}
74+
75+
private SecurityUser createPrincipal(Long userId) {
76+
return new SecurityUser(
77+
userId,
78+
"user" + userId + "@example.com",
79+
"user" + userId,
80+
false,
81+
List.of(new SimpleGrantedAuthority("ROLE_USER")),
82+
Map.of()
83+
);
84+
}
85+
86+
private UsernamePasswordAuthenticationToken authenticated(SecurityUser principal) {
87+
return new UsernamePasswordAuthenticationToken(principal, null, principal.getAuthorities());
88+
}
89+
90+
private RequestPostProcessor withPrincipal(SecurityUser principal) {
91+
return request -> {
92+
UsernamePasswordAuthenticationToken authentication = authenticated(principal);
93+
SecurityContext context = SecurityContextHolder.createEmptyContext();
94+
context.setAuthentication(authentication);
95+
SecurityContextHolder.setContext(context);
96+
request.setUserPrincipal(authentication);
97+
request.getSession(true)
98+
.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);
99+
return request;
100+
};
101+
}
102+
103+
@Test
104+
@DisplayName("Subscribe SSE")
105+
void subscribe_success() throws Exception {
106+
SseEmitter emitter = new SseEmitter(0L);
107+
emitter.complete();
108+
given(notificationService.subscribe()).willReturn(emitter);
109+
110+
MvcResult result = mockMvc.perform(get("/me/subscribe")
111+
.with(withPrincipal(createPrincipal(1L)))
112+
.accept(MediaType.TEXT_EVENT_STREAM))
113+
.andExpect(request().asyncStarted())
114+
.andReturn();
115+
116+
mockMvc.perform(asyncDispatch(result))
117+
.andExpect(status().isOk());
118+
119+
verify(notificationService).subscribe();
120+
}
121+
122+
@Test
123+
@DisplayName("Get notifications without cursor")
124+
void getNotifications_noCursor() throws Exception {
125+
SecurityUser principal = createPrincipal(3L);
126+
127+
NotificationItemDto item = NotificationItemDto.builder()
128+
.id(101L)
129+
.type(NotificationType.COMMENT)
130+
.postId(55L)
131+
.postTitle("새 댓글")
132+
.read(false)
133+
.createdAt(LocalDateTime.of(2025, 1, 2, 12, 0))
134+
.build();
135+
136+
NotificationListResponseDto responseDto = new NotificationListResponseDto(
137+
List.of(item),
138+
false,
139+
null,
140+
null
141+
);
142+
143+
given(notificationService.getNotifications(
144+
principal.getId(),
145+
null,
146+
null,
147+
20
148+
)).willReturn(responseDto);
149+
150+
mockMvc.perform(get("/me/notifications")
151+
.with(withPrincipal(principal))
152+
.accept(MediaType.APPLICATION_JSON))
153+
.andExpect(status().isOk())
154+
.andExpect(jsonPath("$.code").value(200))
155+
.andExpect(jsonPath("$.message").value("success"))
156+
.andExpect(jsonPath("$.data.items[0].id").value(101))
157+
.andExpect(jsonPath("$.data.hasNext").value(false))
158+
.andExpect(jsonPath("$.data.nextCreatedAt").doesNotExist());
159+
160+
verify(notificationService).getNotifications(principal.getId(), null, null, 20);
161+
}
162+
163+
@Test
164+
@DisplayName("Get notifications with cursor")
165+
void getNotifications_withCursor() throws Exception {
166+
SecurityUser principal = createPrincipal(5L);
167+
LocalDateTime cursor = LocalDateTime.of(2025, 3, 10, 9, 30, 15);
168+
169+
NotificationListResponseDto responseDto = new NotificationListResponseDto(
170+
List.of(),
171+
true,
172+
cursor.minusHours(1),
173+
88L
174+
);
175+
176+
given(notificationService.getNotifications(
177+
eq(principal.getId()),
178+
eq(cursor),
179+
eq(77L),
180+
eq(5)
181+
)).willReturn(responseDto);
182+
183+
mockMvc.perform(get("/me/notifications")
184+
.with(withPrincipal(principal))
185+
.param("lastCreatedAt", cursor.toString())
186+
.param("lastId", "77")
187+
.param("limit", "5")
188+
.accept(MediaType.APPLICATION_JSON))
189+
.andExpect(status().isOk())
190+
.andExpect(jsonPath("$.data.hasNext").value(true))
191+
.andExpect(jsonPath("$.data.nextId").value(88));
192+
193+
verify(notificationService).getNotifications(principal.getId(), cursor, 77L, 5);
194+
}
195+
196+
@Test
197+
@DisplayName("Get notification setting")
198+
void getNotificationSetting() throws Exception {
199+
SecurityUser principal = createPrincipal(9L);
200+
NotificationSettingDto dto = new NotificationSettingDto(true);
201+
given(notificationSettingService.getMySetting(principal.getId())).willReturn(dto);
202+
203+
mockMvc.perform(get("/me/notification-setting")
204+
.with(withPrincipal(principal))
205+
.accept(MediaType.APPLICATION_JSON))
206+
.andExpect(status().isOk())
207+
.andExpect(jsonPath("$.data.enabled").value(true));
208+
209+
verify(notificationSettingService).getMySetting(principal.getId());
210+
}
211+
212+
@Test
213+
@DisplayName("Patch notification setting")
214+
void patchNotificationSetting() throws Exception {
215+
SecurityUser principal = createPrincipal(11L);
216+
NotificationSettingDto dto = new NotificationSettingDto(false);
217+
given(notificationSettingService.setMySetting(eq(principal.getId()), eq(false))).willReturn(dto);
218+
219+
mockMvc.perform(patch("/me/notification-setting")
220+
.with(withPrincipal(principal))
221+
.contentType(MediaType.APPLICATION_JSON)
222+
.content("{\"enabled\":false}")
223+
.accept(MediaType.APPLICATION_JSON))
224+
.andExpect(status().isOk())
225+
.andExpect(jsonPath("$.data.enabled").value(false));
226+
227+
verify(notificationSettingService).setMySetting(principal.getId(), false);
228+
}
229+
230+
@Test
231+
@DisplayName("Go post link and mark read")
232+
void goPostLink() throws Exception {
233+
SecurityUser principal = createPrincipal(13L);
234+
NotificationGoResponseDto dto = new NotificationGoResponseDto(321L, "/posts/321");
235+
given(notificationService.markAsReadAndGetPostLink(eq(principal.getId()), eq(999L))).willReturn(dto);
236+
237+
mockMvc.perform(post("/me/notifications/{id}", 999L)
238+
.with(withPrincipal(principal))
239+
.accept(MediaType.APPLICATION_JSON))
240+
.andExpect(status().isOk())
241+
.andExpect(jsonPath("$.data.postId").value(321))
242+
.andExpect(jsonPath("$.data.postApiUrl").value("/posts/321"));
243+
244+
verify(notificationService).markAsReadAndGetPostLink(principal.getId(), 999L);
245+
}
246+
}

0 commit comments

Comments
 (0)