1+ package com .back .global .websocket .controller ;
2+
3+ import com .back .global .websocket .service .WebSocketSessionManager ;
4+ import com .fasterxml .jackson .databind .ObjectMapper ;
5+ import org .junit .jupiter .api .DisplayName ;
6+ import org .junit .jupiter .api .Nested ;
7+ import org .junit .jupiter .api .Test ;
8+ import org .springframework .beans .factory .annotation .Autowired ;
9+ import org .springframework .boot .test .autoconfigure .web .servlet .AutoConfigureMockMvc ;
10+ import org .springframework .boot .test .context .SpringBootTest ;
11+ import org .springframework .http .MediaType ;
12+ import org .springframework .security .test .context .support .WithMockUser ;
13+ import org .springframework .test .context .bean .override .mockito .MockitoBean ;
14+ import org .springframework .test .web .servlet .MockMvc ;
15+ import org .springframework .test .web .servlet .MvcResult ;
16+
17+ import static org .assertj .core .api .Assertions .assertThat ;
18+ import static org .mockito .BDDMockito .given ;
19+ import static org .mockito .Mockito .verify ;
20+ import static org .springframework .test .web .servlet .request .MockMvcRequestBuilders .get ;
21+ import static org .springframework .test .web .servlet .result .MockMvcResultHandlers .print ;
22+ import static org .springframework .test .web .servlet .result .MockMvcResultMatchers .*;
23+
24+ @ WithMockUser
25+ @ SpringBootTest
26+ @ AutoConfigureMockMvc
27+ @ DisplayName ("WebSocket REST API 컨트롤러" )
28+ class WebSocketApiControllerTest {
29+
30+ @ Autowired
31+ private MockMvc mockMvc ;
32+
33+ @ Autowired
34+ private ObjectMapper objectMapper ;
35+
36+ @ MockitoBean
37+ private WebSocketSessionManager sessionManager ;
38+
39+ @ Nested
40+ @ DisplayName ("헬스체크" )
41+ class HealthCheckTest {
42+
43+ @ Test
44+ @ DisplayName ("정상 - 서비스 상태 확인" )
45+ void t1 () throws Exception {
46+ // given
47+ long totalOnlineUsers = 5L ;
48+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (totalOnlineUsers );
49+
50+ // when & then
51+ mockMvc .perform (get ("/api/ws/health" )
52+ .contentType (MediaType .APPLICATION_JSON ))
53+ .andDo (print ())
54+ .andExpect (status ().isOk ())
55+ .andExpect (jsonPath ("$.code" ).value ("SUCCESS_200" ))
56+ .andExpect (jsonPath ("$.message" ).value ("WebSocket 서비스가 정상 동작중입니다." ))
57+ .andExpect (jsonPath ("$.success" ).value (true ))
58+ .andExpect (jsonPath ("$.data.service" ).value ("WebSocket" ))
59+ .andExpect (jsonPath ("$.data.status" ).value ("running" ))
60+ .andExpect (jsonPath ("$.data.timestamp" ).exists ())
61+ .andExpect (jsonPath ("$.data.sessionTTL" ).value ("10분 (Heartbeat 방식)" ))
62+ .andExpect (jsonPath ("$.data.heartbeatInterval" ).value ("5분" ))
63+ .andExpect (jsonPath ("$.data.totalOnlineUsers" ).value (totalOnlineUsers ))
64+ .andExpect (jsonPath ("$.data.endpoints" ).exists ())
65+ .andExpect (jsonPath ("$.data.endpoints.websocket" ).value ("/ws" ))
66+ .andExpect (jsonPath ("$.data.endpoints.heartbeat" ).value ("/app/heartbeat" ))
67+ .andExpect (jsonPath ("$.data.endpoints.activity" ).value ("/app/activity" ));
68+
69+ verify (sessionManager ).getTotalOnlineUserCount ();
70+ }
71+
72+ @ Test
73+ @ DisplayName ("정상 - 온라인 유저 0명" )
74+ void t2 () throws Exception {
75+ // given
76+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (0L );
77+
78+ // when & then
79+ mockMvc .perform (get ("/api/ws/health" )
80+ .contentType (MediaType .APPLICATION_JSON ))
81+ .andDo (print ())
82+ .andExpect (status ().isOk ())
83+ .andExpect (jsonPath ("$.data.totalOnlineUsers" ).value (0 ));
84+
85+ verify (sessionManager ).getTotalOnlineUserCount ();
86+ }
87+
88+ @ Test
89+ @ DisplayName ("정상 - 응답 구조 검증" )
90+ void t3 () throws Exception {
91+ // given
92+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (10L );
93+
94+ // when & then
95+ MvcResult result = mockMvc .perform (get ("/api/ws/health" )
96+ .contentType (MediaType .APPLICATION_JSON ))
97+ .andDo (print ())
98+ .andExpect (status ().isOk ())
99+ .andReturn ();
100+
101+ String content = result .getResponse ().getContentAsString ();
102+
103+ assertThat (content ).contains ("service" );
104+ assertThat (content ).contains ("status" );
105+ assertThat (content ).contains ("timestamp" );
106+ assertThat (content ).contains ("sessionTTL" );
107+ assertThat (content ).contains ("heartbeatInterval" );
108+ assertThat (content ).contains ("totalOnlineUsers" );
109+ assertThat (content ).contains ("endpoints" );
110+ }
111+
112+ @ Test
113+ @ DisplayName ("정상 - 엔드포인트 정보 포함" )
114+ void t4 () throws Exception {
115+ // given
116+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (3L );
117+
118+ // when & then
119+ mockMvc .perform (get ("/api/ws/health" ))
120+ .andExpect (status ().isOk ())
121+ .andExpect (jsonPath ("$.data.endpoints.websocket" ).exists ())
122+ .andExpect (jsonPath ("$.data.endpoints.heartbeat" ).exists ())
123+ .andExpect (jsonPath ("$.data.endpoints.activity" ).exists ())
124+ .andExpect (jsonPath ("$.data.endpoints.joinRoom" ).exists ())
125+ .andExpect (jsonPath ("$.data.endpoints.leaveRoom" ).exists ());
126+ }
127+
128+ @ Test
129+ @ DisplayName ("정상 - Content-Type 없이도 동작" )
130+ void t5 () throws Exception {
131+ // given
132+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (1L );
133+
134+ // when & then
135+ mockMvc .perform (get ("/api/ws/health" ))
136+ .andExpect (status ().isOk ())
137+ .andExpect (jsonPath ("$.success" ).value (true ));
138+ }
139+ }
140+
141+ @ Nested
142+ @ DisplayName ("연결 정보 조회" )
143+ class ConnectionInfoTest {
144+
145+ @ Test
146+ @ DisplayName ("정상 - 연결 정보 조회" )
147+ void t6 () throws Exception {
148+ // when & then
149+ mockMvc .perform (get ("/api/ws/info" )
150+ .contentType (MediaType .APPLICATION_JSON ))
151+ .andDo (print ())
152+ .andExpect (status ().isOk ())
153+ .andExpect (jsonPath ("$.code" ).value ("SUCCESS_200" ))
154+ .andExpect (jsonPath ("$.message" ).value ("WebSocket 연결 정보" ))
155+ .andExpect (jsonPath ("$.success" ).value (true ))
156+ .andExpect (jsonPath ("$.data.websocketUrl" ).value ("/ws" ))
157+ .andExpect (jsonPath ("$.data.sockjsSupport" ).value (true ))
158+ .andExpect (jsonPath ("$.data.stompVersion" ).value ("1.2" ))
159+ .andExpect (jsonPath ("$.data.heartbeatInterval" ).value ("5분" ))
160+ .andExpect (jsonPath ("$.data.sessionTTL" ).value ("10분" ));
161+ }
162+
163+ @ Test
164+ @ DisplayName ("정상 - 구독 토픽 정보 포함" )
165+ void t7 () throws Exception {
166+ // when & then
167+ mockMvc .perform (get ("/api/ws/info" )
168+ .contentType (MediaType .APPLICATION_JSON ))
169+ .andDo (print ())
170+ .andExpect (status ().isOk ())
171+ .andExpect (jsonPath ("$.data.subscribeTopics" ).exists ())
172+ .andExpect (jsonPath ("$.data.subscribeTopics.roomChat" ).value ("/topic/rooms/{roomId}/chat" ))
173+ .andExpect (jsonPath ("$.data.subscribeTopics.privateMessage" ).value ("/user/queue/messages" ))
174+ .andExpect (jsonPath ("$.data.subscribeTopics.notifications" ).value ("/user/queue/notifications" ));
175+ }
176+
177+ @ Test
178+ @ DisplayName ("정상 - 전송 목적지 정보 포함" )
179+ void t8 () throws Exception {
180+ // when & then
181+ mockMvc .perform (get ("/api/ws/info" )
182+ .contentType (MediaType .APPLICATION_JSON ))
183+ .andDo (print ())
184+ .andExpect (status ().isOk ())
185+ .andExpect (jsonPath ("$.data.sendDestinations" ).exists ())
186+ .andExpect (jsonPath ("$.data.sendDestinations.heartbeat" ).value ("/app/heartbeat" ))
187+ .andExpect (jsonPath ("$.data.sendDestinations.activity" ).value ("/app/activity" ))
188+ .andExpect (jsonPath ("$.data.sendDestinations.roomChat" ).value ("/app/rooms/{roomId}/chat" ));
189+ }
190+
191+ @ Test
192+ @ DisplayName ("정상 - 응답 구조 검증" )
193+ void t9 () throws Exception {
194+ // when & then
195+ MvcResult result = mockMvc .perform (get ("/api/ws/info" )
196+ .contentType (MediaType .APPLICATION_JSON ))
197+ .andDo (print ())
198+ .andExpect (status ().isOk ())
199+ .andReturn ();
200+
201+ String content = result .getResponse ().getContentAsString ();
202+
203+ assertThat (content ).contains ("websocketUrl" );
204+ assertThat (content ).contains ("sockjsSupport" );
205+ assertThat (content ).contains ("stompVersion" );
206+ assertThat (content ).contains ("heartbeatInterval" );
207+ assertThat (content ).contains ("sessionTTL" );
208+ assertThat (content ).contains ("subscribeTopics" );
209+ assertThat (content ).contains ("sendDestinations" );
210+ }
211+
212+ @ Test
213+ @ DisplayName ("정상 - SockJS 지원 확인" )
214+ void t10 () throws Exception {
215+ // when & then
216+ mockMvc .perform (get ("/api/ws/info" ))
217+ .andExpect (status ().isOk ())
218+ .andExpect (jsonPath ("$.data.sockjsSupport" ).value (true ))
219+ .andExpect (jsonPath ("$.data.stompVersion" ).value ("1.2" ));
220+ }
221+
222+ @ Test
223+ @ DisplayName ("정상 - Content-Type 없이도 동작" )
224+ void t11 () throws Exception {
225+ // when & then
226+ mockMvc .perform (get ("/api/ws/info" ))
227+ .andExpect (status ().isOk ())
228+ .andExpect (jsonPath ("$.success" ).value (true ));
229+ }
230+ }
231+
232+ @ Nested
233+ @ DisplayName ("API 엔드포인트" )
234+ class EndpointTest {
235+
236+ @ Test
237+ @ DisplayName ("정상 - 모든 엔드포인트 JSON 응답" )
238+ void t12 () throws Exception {
239+ // given
240+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (0L );
241+
242+ // when & then
243+ mockMvc .perform (get ("/api/ws/health" ))
244+ .andExpect (status ().isOk ())
245+ .andExpect (content ().contentTypeCompatibleWith (MediaType .APPLICATION_JSON ));
246+
247+ mockMvc .perform (get ("/api/ws/info" ))
248+ .andExpect (status ().isOk ())
249+ .andExpect (content ().contentTypeCompatibleWith (MediaType .APPLICATION_JSON ));
250+ }
251+
252+ @ Test
253+ @ DisplayName ("정상 - RsData 구조 일관성" )
254+ void t13 () throws Exception {
255+ // given
256+ given (sessionManager .getTotalOnlineUserCount ()).willReturn (0L );
257+
258+ // when & then - health
259+ mockMvc .perform (get ("/api/ws/health" ))
260+ .andExpect (status ().isOk ())
261+ .andExpect (jsonPath ("$.code" ).exists ())
262+ .andExpect (jsonPath ("$.message" ).exists ())
263+ .andExpect (jsonPath ("$.success" ).exists ())
264+ .andExpect (jsonPath ("$.data" ).exists ());
265+
266+ // when & then - info
267+ mockMvc .perform (get ("/api/ws/info" ))
268+ .andExpect (status ().isOk ())
269+ .andExpect (jsonPath ("$.code" ).exists ())
270+ .andExpect (jsonPath ("$.message" ).exists ())
271+ .andExpect (jsonPath ("$.success" ).exists ())
272+ .andExpect (jsonPath ("$.data" ).exists ());
273+ }
274+ }
275+ }
0 commit comments