/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.messaging.simp.annotation.support; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; import java.security.Principal; import com.fasterxml.jackson.annotation.JsonView; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.springframework.core.MethodParameter; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.MessageHeaders; import org.springframework.messaging.converter.MappingJackson2MessageConverter; import org.springframework.messaging.converter.StringMessageConverter; import org.springframework.messaging.core.MessageSendingOperations; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.messaging.simp.SimpMessageHeaderAccessor; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.messaging.simp.annotation.SubscribeMapping; import org.springframework.messaging.support.MessageBuilder; import org.springframework.messaging.support.MessageHeaderAccessor; import org.springframework.util.MimeType; import static org.junit.Assert.*; import static org.mockito.BDDMockito.*; /** * Test fixture for {@link SubscriptionMethodReturnValueHandler}. * * @author Rossen Stoyanchev * @author Sebastien Deleuze */ public class SubscriptionMethodReturnValueHandlerTests { public static final MimeType MIME_TYPE = new MimeType("text", "plain", StandardCharsets.UTF_8); private static final String PAYLOAD = "payload"; private SubscriptionMethodReturnValueHandler handler; private SubscriptionMethodReturnValueHandler jsonHandler; @Mock private MessageChannel messageChannel; @Captor private ArgumentCaptor<Message<?>> messageCaptor; private MethodParameter subscribeEventReturnType; private MethodParameter subscribeEventSendToReturnType; private MethodParameter messageMappingReturnType; private MethodParameter subscribeEventJsonViewReturnType; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); SimpMessagingTemplate messagingTemplate = new SimpMessagingTemplate(this.messageChannel); messagingTemplate.setMessageConverter(new StringMessageConverter()); this.handler = new SubscriptionMethodReturnValueHandler(messagingTemplate); SimpMessagingTemplate jsonMessagingTemplate = new SimpMessagingTemplate(this.messageChannel); jsonMessagingTemplate.setMessageConverter(new MappingJackson2MessageConverter()); this.jsonHandler = new SubscriptionMethodReturnValueHandler(jsonMessagingTemplate); Method method = this.getClass().getDeclaredMethod("getData"); this.subscribeEventReturnType = new MethodParameter(method, -1); method = this.getClass().getDeclaredMethod("getDataAndSendTo"); this.subscribeEventSendToReturnType = new MethodParameter(method, -1); method = this.getClass().getDeclaredMethod("handle"); this.messageMappingReturnType = new MethodParameter(method, -1); method = this.getClass().getDeclaredMethod("getJsonView"); this.subscribeEventJsonViewReturnType = new MethodParameter(method, -1); } @Test public void supportsReturnType() throws Exception { assertTrue(this.handler.supportsReturnType(this.subscribeEventReturnType)); assertFalse(this.handler.supportsReturnType(this.subscribeEventSendToReturnType)); assertFalse(this.handler.supportsReturnType(this.messageMappingReturnType)); } @Test public void testMessageSentToChannel() throws Exception { given(this.messageChannel.send(any(Message.class))).willReturn(true); String sessionId = "sess1"; String subscriptionId = "subs1"; String destination = "/dest"; Message<?> inputMessage = createInputMessage(sessionId, subscriptionId, destination, null); this.handler.handleReturnValue(PAYLOAD, this.subscribeEventReturnType, inputMessage); verify(this.messageChannel).send(this.messageCaptor.capture()); assertNotNull(this.messageCaptor.getValue()); Message<?> message = this.messageCaptor.getValue(); SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.wrap(message); assertNull("SimpMessageHeaderAccessor should have disabled id", headerAccessor.getId()); assertNull("SimpMessageHeaderAccessor should have disabled timestamp", headerAccessor.getTimestamp()); assertEquals(sessionId, headerAccessor.getSessionId()); assertEquals(subscriptionId, headerAccessor.getSubscriptionId()); assertEquals(destination, headerAccessor.getDestination()); assertEquals(MIME_TYPE, headerAccessor.getContentType()); assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER)); } @Test @SuppressWarnings({ "unchecked", "rawtypes" }) public void testHeadersPassedToMessagingTemplate() throws Exception { String sessionId = "sess1"; String subscriptionId = "subs1"; String destination = "/dest"; Message<?> inputMessage = createInputMessage(sessionId, subscriptionId, destination, null); MessageSendingOperations messagingTemplate = Mockito.mock(MessageSendingOperations.class); SubscriptionMethodReturnValueHandler handler = new SubscriptionMethodReturnValueHandler(messagingTemplate); handler.handleReturnValue(PAYLOAD, this.subscribeEventReturnType, inputMessage); ArgumentCaptor<MessageHeaders> captor = ArgumentCaptor.forClass(MessageHeaders.class); verify(messagingTemplate).convertAndSend(eq("/dest"), eq(PAYLOAD), captor.capture()); SimpMessageHeaderAccessor headerAccessor = MessageHeaderAccessor.getAccessor(captor.getValue(), SimpMessageHeaderAccessor.class); assertNotNull(headerAccessor); assertTrue(headerAccessor.isMutable()); assertEquals(sessionId, headerAccessor.getSessionId()); assertEquals(subscriptionId, headerAccessor.getSubscriptionId()); assertEquals(this.subscribeEventReturnType, headerAccessor.getHeader(SimpMessagingTemplate.CONVERSION_HINT_HEADER)); } @Test public void testJsonView() throws Exception { given(this.messageChannel.send(any(Message.class))).willReturn(true); String sessionId = "sess1"; String subscriptionId = "subs1"; String destination = "/dest"; Message<?> inputMessage = createInputMessage(sessionId, subscriptionId, destination, null); this.jsonHandler.handleReturnValue(getJsonView(), this.subscribeEventJsonViewReturnType, inputMessage); verify(this.messageChannel).send(this.messageCaptor.capture()); Message<?> message = this.messageCaptor.getValue(); assertNotNull(message); assertEquals("{\"withView1\":\"with\"}", new String((byte[]) message.getPayload(), StandardCharsets.UTF_8)); } private Message<?> createInputMessage(String sessId, String subsId, String dest, Principal principal) { SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(); headers.setSessionId(sessId); headers.setSubscriptionId(subsId); headers.setDestination(dest); headers.setUser(principal); return MessageBuilder.withPayload(new byte[0]).copyHeaders(headers.toMap()).build(); } @SubscribeMapping("/data") // not needed for the tests but here for completeness private String getData() { return PAYLOAD; } @SubscribeMapping("/data") // not needed for the tests but here for completeness @SendTo("/sendToDest") private String getDataAndSendTo() { return PAYLOAD; } @MessageMapping("/handle") // not needed for the tests but here for completeness public String handle() { return PAYLOAD; } @SubscribeMapping("/jsonview") // not needed for the tests but here for completeness @JsonView(MyJacksonView1.class) public JacksonViewBean getJsonView() { JacksonViewBean payload = new JacksonViewBean(); payload.setWithView1("with"); payload.setWithView2("with"); payload.setWithoutView("without"); return payload; } private interface MyJacksonView1 {}; private interface MyJacksonView2 {}; private static class JacksonViewBean { @JsonView(MyJacksonView1.class) private String withView1; @JsonView(MyJacksonView2.class) private String withView2; private String withoutView; public String getWithView1() { return withView1; } public void setWithView1(String withView1) { this.withView1 = withView1; } public String getWithView2() { return withView2; } public void setWithView2(String withView2) { this.withView2 = withView2; } public String getWithoutView() { return withoutView; } public void setWithoutView(String withoutView) { this.withoutView = withoutView; } } }