Skip to content

Commit 9e209e1

Browse files
committed
Refactor: WebRTCSignalingController 예외 응답 경로 및 형식 통일
1 parent 16dca5d commit 9e209e1

File tree

2 files changed

+48
-135
lines changed

2 files changed

+48
-135
lines changed

src/main/java/com/back/global/websocket/webrtc/controller/WebRTCSignalingController.java

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.springframework.messaging.handler.annotation.MessageExceptionHandler;
1818
import org.springframework.messaging.handler.annotation.MessageMapping;
1919
import org.springframework.messaging.handler.annotation.Payload;
20-
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
2120
import org.springframework.messaging.simp.SimpMessagingTemplate;
2221
import org.springframework.stereotype.Controller;
2322
import org.springframework.validation.annotation.Validated;
@@ -36,13 +35,10 @@ public class WebRTCSignalingController {
3635

3736
// WebRTC Offer 메시지 처리
3837
@MessageMapping("/webrtc/offer")
39-
public void handleOffer(@Validated @Payload WebRTCOfferRequest request,
40-
SimpMessageHeaderAccessor headerAccessor,
41-
Principal principal) {
38+
public void handleOffer(@Validated @Payload WebRTCOfferRequest request, Principal principal) {
4239
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
4340
if (userDetails == null) {
44-
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
45-
return;
41+
throw new CustomException(ErrorCode.UNAUTHORIZED);
4642
}
4743

4844
Long fromUserId = userDetails.getUserId();
@@ -67,13 +63,10 @@ public void handleOffer(@Validated @Payload WebRTCOfferRequest request,
6763

6864
// WebRTC Answer 메시지 처리
6965
@MessageMapping("/webrtc/answer")
70-
public void handleAnswer(@Validated @Payload WebRTCAnswerRequest request,
71-
SimpMessageHeaderAccessor headerAccessor,
72-
Principal principal) {
66+
public void handleAnswer(@Validated @Payload WebRTCAnswerRequest request, Principal principal) {
7367
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
7468
if (userDetails == null) {
75-
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
76-
return;
69+
throw new CustomException(ErrorCode.UNAUTHORIZED);
7770
}
7871

7972
Long fromUserId = userDetails.getUserId();
@@ -98,13 +91,10 @@ public void handleAnswer(@Validated @Payload WebRTCAnswerRequest request,
9891

9992
// ICE Candidate 메시지 처리
10093
@MessageMapping("/webrtc/ice-candidate")
101-
public void handleIceCandidate(@Validated @Payload WebRTCIceCandidateRequest request,
102-
SimpMessageHeaderAccessor headerAccessor,
103-
Principal principal) {
94+
public void handleIceCandidate(@Validated @Payload WebRTCIceCandidateRequest request, Principal principal) {
10495
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
10596
if (userDetails == null) {
106-
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
107-
return;
97+
throw new CustomException(ErrorCode.UNAUTHORIZED);
10898
}
10999

110100
Long fromUserId = userDetails.getUserId();
@@ -128,13 +118,10 @@ public void handleIceCandidate(@Validated @Payload WebRTCIceCandidateRequest req
128118

129119
// 미디어 상태 토글 처리 (방 전체에 상태 공유)
130120
@MessageMapping("/webrtc/media/toggle")
131-
public void handleMediaToggle(@Validated @Payload WebRTCMediaToggleRequest request,
132-
SimpMessageHeaderAccessor headerAccessor,
133-
Principal principal) {
121+
public void handleMediaToggle(@Validated @Payload WebRTCMediaToggleRequest request, Principal principal) {
134122
CustomUserDetails userDetails = WebSocketAuthHelper.extractUserDetails(principal);
135123
if (userDetails == null) {
136-
errorHelper.sendUnauthorizedError(headerAccessor.getSessionId());
137-
return;
124+
throw new CustomException(ErrorCode.UNAUTHORIZED);
138125
}
139126

140127
Long userId = userDetails.getUserId();
@@ -162,16 +149,30 @@ public void handleCustomException(CustomException e, Principal principal) {
162149
WebRTCErrorResponse errorResponse = WebRTCErrorResponse.from(e);
163150

164151
messagingTemplate.convertAndSendToUser(
165-
principal.getName(), // 에러를 발생시킨 사람의 username
152+
principal.getName(), // 에러를 발생시킨 사람의 username
166153
"/queue/webrtc",
167154
errorResponse
168155
);
169156
}
170157

171158
// 예상치 못한 모든 Exception 처리
172159
@MessageExceptionHandler(Exception.class)
173-
public void handleGeneralException(Exception e, SimpMessageHeaderAccessor headerAccessor) {
174-
log.error("WebRTC 시그널링 처리 중 예상치 못한 오류 발생", e);
175-
errorHelper.sendGenericErrorToUser(headerAccessor.getSessionId(), e, "시그널링 처리 중 오류가 발생했습니다.");
160+
public void handleGeneralException(Exception e, Principal principal) { // headerAccessor -> Principal
161+
if (principal == null) {
162+
log.error("WebRTC 처리 중 인증 정보 없는 사용자의 예외 발생", e);
163+
return;
164+
}
165+
166+
log.error("WebRTC 시그널링 처리 중 예상치 못한 오류 발생 (to {})", principal.getName(), e);
167+
168+
// CustomException으로 감싸서 일관된 형식의 에러 DTO를 생성
169+
CustomException customException = new CustomException(ErrorCode.WS_INTERNAL_ERROR);
170+
WebRTCErrorResponse errorResponse = WebRTCErrorResponse.from(customException);
171+
172+
messagingTemplate.convertAndSendToUser(
173+
principal.getName(),
174+
"/queue/webrtc",
175+
errorResponse
176+
);
176177
}
177178
}

src/test/java/com/back/global/websocket/webrtc/controller/WebRTCSignalingControllerTest.java

Lines changed: 22 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
import com.back.global.websocket.webrtc.dto.media.WebRTCMediaType;
1111
import com.back.global.websocket.webrtc.dto.signal.*;
1212
import com.back.global.websocket.webrtc.service.WebRTCSignalValidator;
13-
import com.back.global.websocket.util.WebSocketErrorHelper;
1413
import org.junit.jupiter.api.BeforeEach;
1514
import org.junit.jupiter.api.DisplayName;
1615
import org.junit.jupiter.api.Nested;
@@ -19,7 +18,6 @@
1918
import org.mockito.InjectMocks;
2019
import org.mockito.Mock;
2120
import org.mockito.junit.jupiter.MockitoExtension;
22-
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
2321
import org.springframework.messaging.simp.SimpMessagingTemplate;
2422
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
2523
import org.springframework.security.core.Authentication;
@@ -37,28 +35,20 @@ class WebRTCSignalingControllerTest {
3735

3836
@Mock
3937
private SimpMessagingTemplate messagingTemplate;
40-
41-
@Mock
42-
private WebSocketErrorHelper errorHelper;
43-
4438
@Mock
4539
private WebRTCSignalValidator validator;
46-
4740
@Mock
4841
private WebSocketSessionManager sessionManager;
4942

5043
@InjectMocks
5144
private WebRTCSignalingController controller;
5245

53-
private SimpMessageHeaderAccessor headerAccessor;
5446
private Authentication authentication;
5547
private Long roomId;
5648
private Long fromUserId;
5749
private String fromUsername;
5850
private Long targetUserId;
5951
private String targetUsername;
60-
private String fromSessionId;
61-
private String targetSessionId;
6252

6353
@BeforeEach
6454
void setUp() {
@@ -67,16 +57,11 @@ void setUp() {
6757
fromUsername = "userA";
6858
targetUserId = 20L;
6959
targetUsername = "userB";
70-
fromSessionId = "from-session-id";
71-
targetSessionId = "target-session-id";
7260

7361
CustomUserDetails userDetails = mock(CustomUserDetails.class);
7462
lenient().when(userDetails.getUserId()).thenReturn(fromUserId);
7563
lenient().when(userDetails.getUsername()).thenReturn(fromUsername);
7664
authentication = new UsernamePasswordAuthenticationToken(userDetails, null, null);
77-
78-
headerAccessor = mock(SimpMessageHeaderAccessor.class);
79-
lenient().when(headerAccessor.getSessionId()).thenReturn(fromSessionId);
8065
}
8166

8267
@Nested
@@ -87,180 +72,107 @@ class HandleOfferTest {
8772
@DisplayName("성공 - Offer 메시지를 특정 사용자에게 전송")
8873
void t1() {
8974
// given
90-
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, "sdp", WebRTCMediaType.AUDIO);
91-
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, targetSessionId, LocalDateTime.now(), LocalDateTime.now(), null);
75+
SdpData sdpData = new SdpData("offer", "sdp-content");
76+
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, sdpData, WebRTCMediaType.AUDIO);
77+
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, "session-id", LocalDateTime.now(), LocalDateTime.now(), null);
9278
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(targetSession);
9379

9480
// when
95-
controller.handleOffer(request, headerAccessor, authentication);
81+
controller.handleOffer(request, authentication);
9682

9783
// then
9884
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
9985
verify(messagingTemplate).convertAndSendToUser(eq(targetUsername), eq("/queue/webrtc"), any(WebRTCSignalResponse.class));
100-
verifyNoInteractions(errorHelper);
10186
}
10287

10388
@Test
10489
@DisplayName("실패 - 대상 사용자가 오프라인일 때 예외를 던짐")
10590
void t2() {
10691
// given
107-
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, "sdp", WebRTCMediaType.AUDIO);
92+
SdpData sdpData = new SdpData("offer", "sdp-content");
93+
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, sdpData, WebRTCMediaType.AUDIO);
10894
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(null);
10995

11096
// when & then
11197
CustomException exception = assertThrows(CustomException.class, () ->
112-
controller.handleOffer(request, headerAccessor, authentication)
98+
controller.handleOffer(request, authentication)
11399
);
114-
115100
assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.WS_TARGET_OFFLINE);
116-
117-
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
118-
verifyNoInteractions(errorHelper);
119101
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
120102
}
121103

122104
@Test
123-
@DisplayName("실패 - 인증 정보 없음")
105+
@DisplayName("실패 - 인증 정보가 없을 때 Unauthorized 예외를 던짐")
124106
void t3() {
125107
// given
126-
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, "sdp", WebRTCMediaType.AUDIO);
127-
128-
// when
129-
controller.handleOffer(request, headerAccessor, null);
130-
131-
// then
132-
verify(errorHelper).sendUnauthorizedError(fromSessionId);
133-
verify(validator, never()).validateSignal(any(), any(), any());
134-
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
135-
}
136-
137-
@Test
138-
@DisplayName("실패 - 검증 오류 시 예외를 던짐")
139-
void t4() {
140-
// given
141-
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, "sdp", WebRTCMediaType.VIDEO);
142-
doThrow(new CustomException(ErrorCode.BAD_REQUEST)).when(validator).validateSignal(roomId, fromUserId, targetUserId);
108+
SdpData sdpData = new SdpData("offer", "sdp-content");
109+
WebRTCOfferRequest request = new WebRTCOfferRequest(roomId, targetUserId, sdpData, WebRTCMediaType.AUDIO);
143110

144111
// when & then
145-
assertThrows(CustomException.class, () -> controller.handleOffer(request, headerAccessor, authentication));
146-
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
147-
verifyNoInteractions(errorHelper);
148-
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
112+
CustomException exception = assertThrows(CustomException.class, () ->
113+
controller.handleOffer(request, null) // principal = null
114+
);
115+
assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.UNAUTHORIZED);
116+
verify(validator, never()).validateSignal(any(), any(), any());
149117
}
150118
}
151119

152120
@Nested
153121
@DisplayName("Answer 처리")
154122
class HandleAnswerTest {
155-
156123
@Test
157124
@DisplayName("성공 - Answer 메시지를 특정 사용자에게 전송")
158125
void t1() {
159126
// given
160-
WebRTCAnswerRequest request = new WebRTCAnswerRequest(roomId, targetUserId, "sdp", WebRTCMediaType.AUDIO);
161-
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, targetSessionId, LocalDateTime.now(), LocalDateTime.now(), null);
127+
SdpData sdpData = new SdpData("answer", "sdp-content");
128+
WebRTCAnswerRequest request = new WebRTCAnswerRequest(roomId, targetUserId, sdpData, WebRTCMediaType.AUDIO);
129+
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, "session-id", LocalDateTime.now(), LocalDateTime.now(), null);
162130
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(targetSession);
163131

164132
// when
165-
controller.handleAnswer(request, headerAccessor, authentication);
133+
controller.handleAnswer(request, authentication);
166134

167135
// then
168136
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
169137
verify(messagingTemplate).convertAndSendToUser(eq(targetUsername), eq("/queue/webrtc"), any(WebRTCSignalResponse.class));
170-
verifyNoInteractions(errorHelper);
171-
}
172-
173-
@Test
174-
@DisplayName("실패 - 대상 사용자가 오프라인일 때 예외를 던짐")
175-
void t2() {
176-
// given
177-
WebRTCAnswerRequest request = new WebRTCAnswerRequest(roomId, targetUserId, "sdp", WebRTCMediaType.AUDIO);
178-
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(null);
179-
180-
// when & then
181-
CustomException exception = assertThrows(CustomException.class, () ->
182-
controller.handleAnswer(request, headerAccessor, authentication)
183-
);
184-
185-
assertThat(exception.getErrorCode()).isEqualTo(ErrorCode.WS_TARGET_OFFLINE);
186-
187-
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
188-
verifyNoInteractions(errorHelper);
189-
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
190138
}
191139
}
192140

193141
@Nested
194142
@DisplayName("ICE Candidate 처리")
195143
class HandleIceCandidateTest {
196-
197144
@Test
198145
@DisplayName("성공 - ICE Candidate를 특정 사용자에게 전송")
199146
void t1() {
200147
// given
201148
WebRTCIceCandidateRequest request = new WebRTCIceCandidateRequest(roomId, targetUserId, "candidate", "audio", 0);
202-
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, targetSessionId, LocalDateTime.now(), LocalDateTime.now(), null);
149+
WebSocketSessionInfo targetSession = new WebSocketSessionInfo(targetUserId, targetUsername, "session-id", LocalDateTime.now(), LocalDateTime.now(), null);
203150
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(targetSession);
204151

205152
// when
206-
controller.handleIceCandidate(request, headerAccessor, authentication);
153+
controller.handleIceCandidate(request, authentication);
207154

208155
// then
209156
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
210157
verify(messagingTemplate).convertAndSendToUser(eq(targetUsername), eq("/queue/webrtc"), any(WebRTCSignalResponse.class));
211-
verifyNoInteractions(errorHelper);
212-
}
213-
214-
@Test
215-
@DisplayName("성공 - 대상 사용자가 오프라인이어도 조용히 무시")
216-
void t2() {
217-
// given
218-
WebRTCIceCandidateRequest request = new WebRTCIceCandidateRequest(roomId, targetUserId, "candidate", "audio", 0);
219-
when(sessionManager.getSessionInfo(targetUserId)).thenReturn(null);
220-
221-
// when
222-
controller.handleIceCandidate(request, headerAccessor, authentication);
223-
224-
// then
225-
verify(validator).validateSignal(roomId, fromUserId, targetUserId);
226-
verifyNoInteractions(errorHelper);
227-
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
228158
}
229159
}
230160

231161
@Nested
232162
@DisplayName("미디어 상태 토글")
233163
class HandleMediaToggleTest {
234-
235164
@Test
236165
@DisplayName("성공 - 미디어 상태를 방 전체에 브로드캐스트")
237166
void t1() {
238167
// given
239168
WebRTCMediaToggleRequest request = new WebRTCMediaToggleRequest(roomId, WebRTCMediaType.AUDIO, true);
240-
doNothing().when(validator).validateMediaStateChange(roomId, fromUserId);
241169

242170
// when
243-
controller.handleMediaToggle(request, headerAccessor, authentication);
171+
controller.handleMediaToggle(request, authentication);
244172

245173
// then
246174
verify(validator).validateMediaStateChange(roomId, fromUserId);
247175
verify(messagingTemplate).convertAndSend(eq("/topic/room/" + roomId + "/media-status"), any(WebRTCMediaStateResponse.class));
248-
verify(messagingTemplate, never()).convertAndSendToUser(anyString(), anyString(), any());
249-
verifyNoInteractions(errorHelper);
250-
}
251-
252-
@Test
253-
@DisplayName("실패 - 검증 오류 시 예외를 던짐")
254-
void t2() {
255-
// given
256-
WebRTCMediaToggleRequest request = new WebRTCMediaToggleRequest(roomId, WebRTCMediaType.SCREEN, true);
257-
doThrow(new RuntimeException("검증 실패")).when(validator).validateMediaStateChange(roomId, fromUserId);
258-
259-
// when & then
260-
assertThrows(RuntimeException.class, () -> controller.handleMediaToggle(request, headerAccessor, authentication));
261-
verify(validator).validateMediaStateChange(roomId, fromUserId);
262-
verifyNoInteractions(errorHelper);
263-
verify(messagingTemplate, never()).convertAndSend(anyString(), any(WebRTCMediaStateResponse.class));
264176
}
265177
}
266178
}

0 commit comments

Comments
 (0)