diff --git a/build.gradle b/build.gradle index a24fcd40..06191f38 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,9 @@ dependencies { runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' + // Websocket + implementation 'org.springframework.boot:spring-boot-starter-websocket' + // JWT implementation 'io.jsonwebtoken:jjwt-api:0.11.5' implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/src/main/java/com/debatetimer/config/CorsConfig.java b/src/main/java/com/debatetimer/config/CorsConfig.java index 216c15da..12511729 100644 --- a/src/main/java/com/debatetimer/config/CorsConfig.java +++ b/src/main/java/com/debatetimer/config/CorsConfig.java @@ -1,8 +1,7 @@ package com.debatetimer.config; -import com.debatetimer.exception.custom.DTInitializationException; -import com.debatetimer.exception.errorcode.InitializationErrorCode; -import org.springframework.beans.factory.annotation.Value; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; @@ -10,30 +9,16 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration +@RequiredArgsConstructor +@EnableConfigurationProperties(CorsProperties.class) public class CorsConfig implements WebMvcConfigurer { - private final String[] corsOrigin; - - public CorsConfig(@Value("${cors.origin}") String[] corsOrigin) { - validate(corsOrigin); - this.corsOrigin = corsOrigin; - } - - private void validate(String[] corsOrigin) { - if (corsOrigin == null || corsOrigin.length == 0) { - throw new DTInitializationException(InitializationErrorCode.CORS_ORIGIN_EMPTY); - } - for (String origin : corsOrigin) { - if (origin == null || origin.isBlank()) { - throw new DTInitializationException(InitializationErrorCode.CORS_ORIGIN_STRING_BLANK); - } - } - } + private final CorsProperties corsProperties; @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOriginPatterns(corsOrigin) + .allowedOriginPatterns(corsProperties.getCorsOrigin()) .allowedMethods( HttpMethod.GET.name(), HttpMethod.POST.name(), diff --git a/src/main/java/com/debatetimer/config/CorsProperties.java b/src/main/java/com/debatetimer/config/CorsProperties.java new file mode 100644 index 00000000..8103553b --- /dev/null +++ b/src/main/java/com/debatetimer/config/CorsProperties.java @@ -0,0 +1,31 @@ +package com.debatetimer.config; + +import com.debatetimer.exception.custom.DTInitializationException; +import com.debatetimer.exception.errorcode.InitializationErrorCode; +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; + + +@Getter +@ConfigurationProperties(prefix = "cors.origin") +public class CorsProperties { + + private final String[] corsOrigin; + + //TODO 머지될 때 dev, prod secret 갱신 필요 + public CorsProperties(String[] corsOrigin) { + validate(corsOrigin); + this.corsOrigin = corsOrigin; + } + + private void validate(String[] corsOrigin) { + if (corsOrigin == null || corsOrigin.length == 0) { + throw new DTInitializationException(InitializationErrorCode.CORS_ORIGIN_EMPTY); + } + for (String origin : corsOrigin) { + if (origin == null || origin.isBlank()) { + throw new DTInitializationException(InitializationErrorCode.CORS_ORIGIN_STRING_BLANK); + } + } + } +} diff --git a/src/main/java/com/debatetimer/config/sharing/WebSocketAuthMemberResolver.java b/src/main/java/com/debatetimer/config/sharing/WebSocketAuthMemberResolver.java new file mode 100644 index 00000000..de16ac9f --- /dev/null +++ b/src/main/java/com/debatetimer/config/sharing/WebSocketAuthMemberResolver.java @@ -0,0 +1,41 @@ +package com.debatetimer.config.sharing; + +import com.debatetimer.controller.auth.AuthMember; +import com.debatetimer.controller.tool.jwt.AuthManager; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import com.debatetimer.service.auth.AuthService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.http.HttpHeaders; +import org.springframework.messaging.Message; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class WebSocketAuthMemberResolver implements HandlerMethodArgumentResolver { + + private final AuthManager authManager; + private final AuthService authService; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(AuthMember.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, Message message) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message); + String token = accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION); + + if (token == null) { + throw new DTClientErrorException(ClientErrorCode.UNAUTHORIZED_MEMBER); + } + + String email = authManager.resolveAccessToken(token); + return authService.getMember(email); + } +} + diff --git a/src/main/java/com/debatetimer/config/sharing/WebSocketConfig.java b/src/main/java/com/debatetimer/config/sharing/WebSocketConfig.java new file mode 100644 index 00000000..b2ee31a6 --- /dev/null +++ b/src/main/java/com/debatetimer/config/sharing/WebSocketConfig.java @@ -0,0 +1,38 @@ +package com.debatetimer.config.sharing; + +import com.debatetimer.config.CorsProperties; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; +import org.springframework.messaging.simp.config.MessageBrokerRegistry; +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; +import org.springframework.web.socket.config.annotation.StompEndpointRegistry; +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; + +@Configuration +@RequiredArgsConstructor +@EnableWebSocketMessageBroker +public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { + + private final CorsProperties corsProperties; + private final WebSocketAuthMemberResolver webSocketAuthMemberResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(webSocketAuthMemberResolver); + } + + @Override + public void configureMessageBroker(MessageBrokerRegistry registry) { + registry.enableSimpleBroker("/room", "/chairman"); + registry.setApplicationDestinationPrefixes("/app"); + } + + @Override + public void registerStompEndpoints(StompEndpointRegistry registry) { + registry.addEndpoint("/ws") + .setAllowedOriginPatterns(corsProperties.getCorsOrigin()) + .withSockJS(); + } +} diff --git a/src/main/java/com/debatetimer/controller/sharing/SharingController.java b/src/main/java/com/debatetimer/controller/sharing/SharingController.java new file mode 100644 index 00000000..edacf8e6 --- /dev/null +++ b/src/main/java/com/debatetimer/controller/sharing/SharingController.java @@ -0,0 +1,25 @@ +package com.debatetimer.controller.sharing; + +import com.debatetimer.controller.auth.AuthMember; +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.sharing.request.SharingRequest; +import com.debatetimer.dto.sharing.response.SharingResponse; +import org.springframework.messaging.handler.annotation.DestinationVariable; +import org.springframework.messaging.handler.annotation.MessageMapping; +import org.springframework.messaging.handler.annotation.Payload; +import org.springframework.messaging.handler.annotation.SendTo; +import org.springframework.stereotype.Controller; + +@Controller +public class SharingController { + + @MessageMapping("/event/{roomId}") + @SendTo("/room/{roomId}") + public SharingResponse share( + @AuthMember Member member, + @DestinationVariable(value = "roomId") long roomId, + @Payload SharingRequest request + ) { + return new SharingResponse(request.time()); + } +} diff --git a/src/main/java/com/debatetimer/dto/sharing/request/ChairmanSharingRequest.java b/src/main/java/com/debatetimer/dto/sharing/request/ChairmanSharingRequest.java new file mode 100644 index 00000000..ecc5134f --- /dev/null +++ b/src/main/java/com/debatetimer/dto/sharing/request/ChairmanSharingRequest.java @@ -0,0 +1,7 @@ +package com.debatetimer.dto.sharing.request; + +public record ChairmanSharingRequest( + long roomId +) { + +} diff --git a/src/main/java/com/debatetimer/dto/sharing/request/SharingRequest.java b/src/main/java/com/debatetimer/dto/sharing/request/SharingRequest.java new file mode 100644 index 00000000..b6063711 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/sharing/request/SharingRequest.java @@ -0,0 +1,9 @@ +package com.debatetimer.dto.sharing.request; + +import java.time.LocalDateTime; + +public record SharingRequest( + LocalDateTime time +) { + +} diff --git a/src/main/java/com/debatetimer/dto/sharing/response/SharingResponse.java b/src/main/java/com/debatetimer/dto/sharing/response/SharingResponse.java new file mode 100644 index 00000000..704384d1 --- /dev/null +++ b/src/main/java/com/debatetimer/dto/sharing/response/SharingResponse.java @@ -0,0 +1,9 @@ +package com.debatetimer.dto.sharing.response; + +import java.time.LocalDateTime; + +public record SharingResponse( + LocalDateTime time +) { + +} diff --git a/src/main/java/com/debatetimer/event/sharing/RoomSubscribeListener.java b/src/main/java/com/debatetimer/event/sharing/RoomSubscribeListener.java new file mode 100644 index 00000000..f2e4b47a --- /dev/null +++ b/src/main/java/com/debatetimer/event/sharing/RoomSubscribeListener.java @@ -0,0 +1,47 @@ +package com.debatetimer.event.sharing; + +import com.debatetimer.dto.sharing.request.ChairmanSharingRequest; +import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.errorcode.ClientErrorCode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.event.EventListener; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.messaging.simp.stomp.StompHeaderAccessor; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.messaging.SessionSubscribeEvent; + +@Slf4j +@Component +@RequiredArgsConstructor +public class RoomSubscribeListener { + + private static final String AUDIENCE_SUBSCRIBE_PREFIX = "/room/"; + private static final String CHAIRMAN_CHANNEL_PREFIX = "/chairman/"; + + private final SimpMessagingTemplate messagingTemplate; + + @EventListener + public void handleSubscribeEvent(SessionSubscribeEvent event) { + StompHeaderAccessor accessor = StompHeaderAccessor.wrap(event.getMessage()); + String destination = accessor.getDestination(); + if (destination == null) { + return; + } + + if (destination.startsWith(AUDIENCE_SUBSCRIBE_PREFIX)) { + long roomId = parseRoomId(destination); + messagingTemplate.convertAndSend(CHAIRMAN_CHANNEL_PREFIX + roomId, new ChairmanSharingRequest(roomId)); + } + } + + private long parseRoomId(String destination) { + try { + String parsedRoomId = destination.substring(AUDIENCE_SUBSCRIBE_PREFIX.length()); + return Long.parseLong(parsedRoomId); + } catch (NumberFormatException exception) { + throw new DTClientErrorException(ClientErrorCode.INVALID_ROOM_ID); + } + } +} + diff --git a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java index 695cbb0c..5bf7d05b 100644 --- a/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java +++ b/src/main/java/com/debatetimer/exception/errorcode/ClientErrorCode.java @@ -53,6 +53,8 @@ public enum ClientErrorCode implements ResponseErrorCode { ALREADY_DONE_POLL(HttpStatus.BAD_REQUEST, "이미 완료된 투표 입니다"), ALREADY_VOTED_PARTICIPANT(HttpStatus.BAD_REQUEST, "이미 참여한 투표자 입니다"), + INVALID_ROOM_ID(HttpStatus.BAD_REQUEST, "잘못된 roomId 값입니다"), + TABLE_NOT_FOUND(HttpStatus.NOT_FOUND, "토론 테이블을 찾을 수 없습니다."), NOT_TABLE_OWNER(HttpStatus.UNAUTHORIZED, "테이블을 소유한 회원이 아닙니다."), POLL_NOT_FOUND(HttpStatus.NOT_FOUND, "투표를 찾을 수 없습니다."), diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index b60259e8..46ebc8ed 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -21,7 +21,9 @@ spring: baseline-version: 1 cors: - origin: ${secret.cors.origin} + origin: + cors-origin: + - ${secret.cors.origin} oauth: client_id: ${secret.oauth.client_id} diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 208ac91b..cc4de59d 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -20,7 +20,9 @@ spring: baseline-version: 1 cors: - origin: ${secret.cors.origin} + origin: + cors-origin: + - ${secret.cors.origin} oauth: client_id: ${secret.oauth.client_id} diff --git a/src/test/java/com/debatetimer/BaseStompTest.java b/src/test/java/com/debatetimer/BaseStompTest.java new file mode 100644 index 00000000..4f0410c0 --- /dev/null +++ b/src/test/java/com/debatetimer/BaseStompTest.java @@ -0,0 +1,76 @@ +package com.debatetimer; + +import com.debatetimer.fixture.HeaderGenerator; +import com.debatetimer.fixture.entity.MemberGenerator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.messaging.converter.MappingJackson2MessageConverter; +import org.springframework.messaging.converter.MessageConverter; +import org.springframework.messaging.simp.stomp.StompSession; +import org.springframework.messaging.simp.stomp.StompSessionHandlerAdapter; +import org.springframework.web.socket.client.standard.StandardWebSocketClient; +import org.springframework.web.socket.messaging.WebSocketStompClient; +import org.springframework.web.socket.sockjs.client.SockJsClient; +import org.springframework.web.socket.sockjs.client.Transport; +import org.springframework.web.socket.sockjs.client.WebSocketTransport; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public abstract class BaseStompTest { + + private static final String SOCKET_ENDPOINT = "/ws"; + + protected StompSession stompSession; + + @LocalServerPort + private int port; + + private final String url; + + private final WebSocketStompClient websocketClient; + + @Autowired + protected MemberGenerator memberGenerator; + + @Autowired + protected HeaderGenerator headerGenerator; + + public BaseStompTest() { + List transports = List.of(new WebSocketTransport(new StandardWebSocketClient())); + this.websocketClient = new WebSocketStompClient(new SockJsClient(transports)); + this.websocketClient.setMessageConverter(buildMessageConverter()); + this.url = "ws://localhost:"; + } + + private MessageConverter buildMessageConverter() { + MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter(); + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.findAndRegisterModules(); + converter.setObjectMapper(objectMapper); + return converter; + } + + @BeforeEach + public void connect() throws ExecutionException, InterruptedException, TimeoutException { + this.stompSession = this.websocketClient + .connectAsync(url + port + SOCKET_ENDPOINT, new StompSessionHandlerAdapter() { + }) + .get(3, TimeUnit.SECONDS); + } + + @AfterEach + public void disconnect() { + if (this.stompSession.isConnected()) { + this.stompSession.disconnect(); + } + } +} diff --git a/src/test/java/com/debatetimer/MessageFrameHandler.java b/src/test/java/com/debatetimer/MessageFrameHandler.java new file mode 100644 index 00000000..d0e60346 --- /dev/null +++ b/src/test/java/com/debatetimer/MessageFrameHandler.java @@ -0,0 +1,31 @@ +package com.debatetimer; + +import java.lang.reflect.Type; +import java.util.concurrent.CompletableFuture; +import org.springframework.messaging.simp.stomp.StompFrameHandler; +import org.springframework.messaging.simp.stomp.StompHeaders; + +public class MessageFrameHandler implements StompFrameHandler { + + private final CompletableFuture completableFuture = new CompletableFuture<>(); + private final Class tClass; + + public MessageFrameHandler(Class tClass) { + this.tClass = tClass; + } + + @Override + public void handleFrame(StompHeaders headers, Object payload) { + if (completableFuture.complete((T) payload)) { + } + } + + @Override + public Type getPayloadType(StompHeaders headers) { + return this.tClass; + } + + public CompletableFuture getCompletableFuture() { + return completableFuture; + } +} diff --git a/src/test/java/com/debatetimer/config/CorsConfigTest.java b/src/test/java/com/debatetimer/config/CorsPropertiesTest.java similarity index 84% rename from src/test/java/com/debatetimer/config/CorsConfigTest.java rename to src/test/java/com/debatetimer/config/CorsPropertiesTest.java index 0011729a..b4889839 100644 --- a/src/test/java/com/debatetimer/config/CorsConfigTest.java +++ b/src/test/java/com/debatetimer/config/CorsPropertiesTest.java @@ -9,21 +9,21 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; -class CorsConfigTest { +class CorsPropertiesTest { @Nested class Validate { @Test void 허용된_도메인이_null_일_경우_예외를_발생시칸다() { - assertThatThrownBy(() -> new CorsConfig(null)) + assertThatThrownBy(() -> new CorsProperties(null)) .isInstanceOf(DTInitializationException.class) .hasMessage(InitializationErrorCode.CORS_ORIGIN_EMPTY.getMessage()); } @Test void 허용된_도메인이_빈_배열일_경우_예외를_발생시칸다() { - assertThatThrownBy(() -> new CorsConfig(new String[0])) + assertThatThrownBy(() -> new CorsProperties(new String[0])) .isInstanceOf(DTInitializationException.class) .hasMessage(InitializationErrorCode.CORS_ORIGIN_EMPTY.getMessage()); } @@ -31,10 +31,9 @@ class Validate { @ParameterizedTest @NullAndEmptySource void 허용된_도메인_중에_빈_값이_있을_경우_예외를_발생시킨다(String empty) { - assertThatThrownBy(() -> new CorsConfig(new String[]{empty})) + assertThatThrownBy(() -> new CorsProperties(new String[]{empty})) .isInstanceOf(DTInitializationException.class) .hasMessage(InitializationErrorCode.CORS_ORIGIN_STRING_BLANK.getMessage()); - } } } diff --git a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java index 6c9c041f..4e4cace9 100644 --- a/src/test/java/com/debatetimer/controller/GlobalControllerTest.java +++ b/src/test/java/com/debatetimer/controller/GlobalControllerTest.java @@ -8,7 +8,7 @@ public class GlobalControllerTest extends BaseControllerTest { - @Value("${cors.origin}") + @Value("${cors.origin.cors-origin[0]}") private String corsOrigin; @Nested diff --git a/src/test/java/com/debatetimer/controller/sharing/SharingControllerTest.java b/src/test/java/com/debatetimer/controller/sharing/SharingControllerTest.java new file mode 100644 index 00000000..c1362bc5 --- /dev/null +++ b/src/test/java/com/debatetimer/controller/sharing/SharingControllerTest.java @@ -0,0 +1,39 @@ +package com.debatetimer.controller.sharing; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.debatetimer.BaseStompTest; +import com.debatetimer.MessageFrameHandler; +import com.debatetimer.domain.member.Member; +import com.debatetimer.dto.sharing.request.SharingRequest; +import com.debatetimer.dto.sharing.response.SharingResponse; +import java.time.LocalDateTime; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.messaging.simp.stomp.StompHeaders; + +class SharingControllerTest extends BaseStompTest { + + @Nested + class Share { + + @Test + void 사회자가_발생시킨_이벤트를_청중이_공유받는다() throws ExecutionException, InterruptedException, TimeoutException { + long roomId = 1L; + LocalDateTime time = LocalDateTime.now(); + MessageFrameHandler handler = new MessageFrameHandler<>(SharingResponse.class); + Member member = memberGenerator.generate("example@email.com"); + StompHeaders headers = headerGenerator.generateAccessTokenHeader("/app/event/" + roomId, member); + stompSession.subscribe("/room/" + roomId, handler); //청중의 구독 + + stompSession.send(headers, new SharingRequest(time)); //사회자의 이벤트 발생 + + SharingResponse response = handler.getCompletableFuture() + .get(3L, TimeUnit.SECONDS); + assertThat(response.time()).isEqualTo(time); + } + } +} diff --git a/src/test/java/com/debatetimer/event/sharing/RoomSubscribeListenerTest.java b/src/test/java/com/debatetimer/event/sharing/RoomSubscribeListenerTest.java new file mode 100644 index 00000000..b8e2bba7 --- /dev/null +++ b/src/test/java/com/debatetimer/event/sharing/RoomSubscribeListenerTest.java @@ -0,0 +1,33 @@ +package com.debatetimer.event.sharing; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.debatetimer.BaseStompTest; +import com.debatetimer.MessageFrameHandler; +import com.debatetimer.dto.sharing.request.ChairmanSharingRequest; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class RoomSubscribeListenerTest extends BaseStompTest { + + @Nested + class SubscribeListener { + + @Test + void 새로운_청중이_공유되면_사회자에게_정보공유_트리거를_발송한다() throws ExecutionException, InterruptedException, TimeoutException { + long roomId = 1L; + MessageFrameHandler handler = new MessageFrameHandler<>( + ChairmanSharingRequest.class); + stompSession.subscribe("/chairman/" + roomId, handler); + + stompSession.subscribe("/room/" + roomId, handler); + + ChairmanSharingRequest sharingRequest = handler.getCompletableFuture() + .get(3L, TimeUnit.SECONDS); + assertThat(sharingRequest).isNotNull(); + } + } +} diff --git a/src/test/java/com/debatetimer/fixture/HeaderGenerator.java b/src/test/java/com/debatetimer/fixture/HeaderGenerator.java index a8a6b8bb..70362980 100644 --- a/src/test/java/com/debatetimer/fixture/HeaderGenerator.java +++ b/src/test/java/com/debatetimer/fixture/HeaderGenerator.java @@ -6,6 +6,7 @@ import io.restassured.http.Header; import io.restassured.http.Headers; import org.springframework.http.HttpHeaders; +import org.springframework.messaging.simp.stomp.StompHeaders; import org.springframework.stereotype.Component; @Component @@ -21,4 +22,12 @@ public Headers generateAccessTokenHeader(Member member) { String accessToken = jwtTokenProvider.createAccessToken(new MemberInfo(member)); return new Headers(new Header(HttpHeaders.AUTHORIZATION, accessToken)); } + + public StompHeaders generateAccessTokenHeader(String destination, Member member) { + String accessToken = jwtTokenProvider.createAccessToken(new MemberInfo(member)); + StompHeaders stompHeaders = new StompHeaders(); + stompHeaders.setDestination(destination); + stompHeaders.add(HttpHeaders.AUTHORIZATION, accessToken); + return stompHeaders; + } } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index b844ae6c..fa923caa 100644 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -3,7 +3,9 @@ spring: active: test cors: - origin: http://test.debate-timer.com + origin: + cors-origin: + - http://test.debate-timer.com oauth: client_id: oauth_client_id