From 9e58131167727e86dbb0aed6c556794a7962fe0f Mon Sep 17 00:00:00 2001 From: Klaus Date: Fri, 19 Jun 2026 01:56:28 +0900 Subject: [PATCH] =?UTF-8?q?test(user-creator-chat):=20WebSocket=20handshak?= =?UTF-8?q?e=20slice=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...orChatWebSocketHandshakeIntegrationTest.kt | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt diff --git a/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt b/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt new file mode 100644 index 00000000..17ef1b82 --- /dev/null +++ b/src/test/kotlin/kr/co/vividnext/sodalive/v2/usercreatorchat/websocket/UserCreatorChatWebSocketHandshakeIntegrationTest.kt @@ -0,0 +1,132 @@ +package kr.co.vividnext.sodalive.v2.usercreatorchat.websocket + +import com.fasterxml.jackson.databind.ObjectMapper +import kr.co.vividnext.sodalive.jwt.TokenProvider +import kr.co.vividnext.sodalive.member.Member +import kr.co.vividnext.sodalive.member.MemberAdapter +import kr.co.vividnext.sodalive.v2.usercreatorchat.service.UserCreatorChatService +import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.Assertions.assertSame +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.mockito.Mockito +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.context.ApplicationContext +import org.springframework.http.HttpHeaders +import org.springframework.http.server.ServerHttpResponse +import org.springframework.http.server.ServletServerHttpRequest +import org.springframework.mock.web.MockHttpServletRequest +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping +import org.springframework.web.socket.WebSocketHandler +import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler + +@SpringBootTest( + classes = [ + UserCreatorChatWebSocketConfig::class, + UserCreatorChatWebSocketHandler::class, + UserCreatorChatWebSocketAuthInterceptor::class + ] +) +class UserCreatorChatWebSocketHandshakeIntegrationTest @Autowired constructor( + private val applicationContext: ApplicationContext +) { + @MockBean + private lateinit var tokenProvider: TokenProvider + + @MockBean + private lateinit var service: UserCreatorChatService + + @MockBean + private lateinit var presenceService: UserCreatorChatPresenceService + + @MockBean + private lateinit var sessionRegistry: UserCreatorChatWebSocketSessionRegistry + + @MockBean + private lateinit var objectMapper: ObjectMapper + + private val response = Mockito.mock(ServerHttpResponse::class.java) + private val wsHandler = Mockito.mock(WebSocketHandler::class.java) + + @Test + @DisplayName("유효한 Bearer token이 있으면 등록된 WebSocket auth interceptor가 handshake를 허용한다") + fun shouldAcceptHandshakeWithValidBearerToken() { + val member = Member(email = "ws-handshake@test.com", password = "pw", nickname = "ws-handshake") + .apply { id = 10L } + val authentication = UsernamePasswordAuthenticationToken(MemberAdapter(member), "valid-token") + Mockito.`when`(tokenProvider.validateToken("valid-token")).thenReturn(true) + Mockito.`when`(tokenProvider.getAuthentication("valid-token")).thenReturn(authentication) + val attributes = mutableMapOf() + + val result = authInterceptor().beforeHandshake( + requestWithAuthorization("Bearer valid-token"), + response, + wsHandler, + attributes + ) + + assertTrue(result) + assertSame(authentication, attributes[UserCreatorChatWebSocketAuthInterceptor.AUTHENTICATION_ATTRIBUTE]) + } + + @Test + @DisplayName("Authorization header가 없으면 등록된 WebSocket auth interceptor가 handshake를 거부한다") + fun shouldRejectHandshakeWithoutAuthorizationHeader() { + val attributes = mutableMapOf() + + val result = authInterceptor().beforeHandshake( + requestWithAuthorization(null), + response, + wsHandler, + attributes + ) + + assertFalse(result) + assertTrue(attributes.isEmpty()) + } + + @Test + @DisplayName("유효하지 않은 token이면 등록된 WebSocket auth interceptor가 handshake를 거부한다") + fun shouldRejectHandshakeWithInvalidBearerToken() { + Mockito.`when`(tokenProvider.validateToken("invalid-token")).thenReturn(false) + val attributes = mutableMapOf() + + val result = authInterceptor().beforeHandshake( + requestWithAuthorization("Bearer invalid-token"), + response, + wsHandler, + attributes + ) + + assertFalse(result) + assertTrue(attributes.isEmpty()) + } + + private fun authInterceptor(): UserCreatorChatWebSocketAuthInterceptor { + val handler = registeredWebSocketHandler() + return handler.handshakeInterceptors.filterIsInstance().single() + } + + private fun registeredWebSocketHandler(): WebSocketHttpRequestHandler { + val handlerMappings = applicationContext.getBeansOfType(SimpleUrlHandlerMapping::class.java).values + val urlMap = handlerMappings.flatMap { mapping -> mapping.urlMap.entries } + val handler = urlMap.firstNotNullOfOrNull { (path, handler) -> + if (path == UserCreatorChatWebSocketConfig.ENDPOINT) handler as? WebSocketHttpRequestHandler else null + } + assertNotNull(handler, "Expected /ws/v2/user-creator-chat to be registered") + return handler!! + } + + private fun requestWithAuthorization(authorization: String?): ServletServerHttpRequest { + val request = MockHttpServletRequest() + if (authorization != null) { + request.addHeader(HttpHeaders.AUTHORIZATION, authorization) + } + return ServletServerHttpRequest(request) + } +}