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