/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.runtime.core.message.processing; import static java.util.Optional.empty; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mule.runtime.core.api.Event.setCurrentEvent; import static org.mule.runtime.core.context.notification.ConnectorMessageNotification.MESSAGE_ERROR_RESPONSE; import static org.mule.runtime.core.context.notification.ConnectorMessageNotification.MESSAGE_RESPONSE; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.core.api.Event; import org.mule.runtime.core.api.MuleContext; import org.mule.runtime.core.api.construct.FlowConstruct; import org.mule.runtime.core.api.source.MessageSource; import org.mule.runtime.core.context.notification.NotificationHelper; import org.mule.runtime.core.context.notification.ServerNotificationManager; import org.mule.runtime.core.exception.MessagingException; import org.mule.runtime.core.execution.AsyncResponseFlowProcessingPhase; import org.mule.runtime.core.execution.AsyncResponseFlowProcessingPhaseTemplate; import org.mule.runtime.core.execution.MessageProcessContext; import org.mule.runtime.core.execution.MessageProcessPhase; import org.mule.runtime.core.execution.PhaseResultNotifier; import org.mule.runtime.core.execution.ResponseCompletionCallback; import org.mule.runtime.core.execution.ResponseDispatchException; import org.mule.runtime.core.execution.ValidationPhase; import org.mule.tck.junit4.AbstractMuleTestCase; import org.mule.tck.size.SmallTest; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Answers; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.runners.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) @SmallTest public class AsyncResponseFlowProcessingPhaseTestCase extends AbstractMuleTestCase { private AsyncResponseFlowProcessingPhase phase = new AsyncResponseFlowProcessingPhase() { // We cannot mock this method since its protected @Override protected NotificationHelper getNotificationHelper(ServerNotificationManager serverNotificationManager) { return notificationHelper; }; }; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private AsyncResponseFlowProcessingPhaseTemplate mockTemplate; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private MessageProcessContext mockContext; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private PhaseResultNotifier mockNotifier; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private ResponseDispatchException mockResponseDispatchException; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private MessagingException mockMessagingException; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Event mockMuleEvent; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private MessagingException mockException; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private NotificationHelper notificationHelper; @Before public void before() { phase.setMuleContext(mock(MuleContext.class)); when(mockContext.getTransactionConfig()).thenReturn(empty()); when(mockMuleEvent.getError()).thenReturn(empty()); } @Before public void configureExpectedBehaviour() throws Exception { when(mockTemplate.getEvent()).thenReturn(mockMuleEvent); doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentSuccessfully(); return null; }).when(mockTemplate).sendFailureResponseToClient(any(MessagingException.class), any(ResponseCompletionCallback.class)); doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentSuccessfully(); return null; }).when(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); when(mockTemplate.getEvent()).thenReturn(mockMuleEvent); } @Test public void supportedTemplates() { new PhaseSupportTestHelper<>(AsyncResponseFlowProcessingPhaseTemplate.class).testSupportTemplates(phase); } @Test public void order() { assertThat(phase.compareTo(new ValidationPhase()), is(1)); assertThat(phase.compareTo(Mockito.mock(MessageProcessPhase.class)), is(0)); } @Test public void runPhaseWithMessagingExceptionThrown() throws Exception { when(mockContext.supportsAsynchronousProcessing()).thenReturn(false); doThrow(mockMessagingException).when(mockTemplate).routeEvent(any(Event.class)); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockContext.getFlowConstruct().getExceptionListener()).handleException(any(MessagingException.class), any(Event.class)); verifyOnlySuccessfulWasCalled(); } @Test public void runPhaseWithSuccessfulFlowProcessing() throws Exception { phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); verifyOnlySuccessfulWasCalled(); } @Test public void sendResponseWhenFlowExecutionFailsAndExceptionIsHandled() throws MuleException { when(mockTemplate.routeEvent(any(Event.class))).thenThrow(mockMessagingException); when(mockMessagingException.handled()).thenReturn(true); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); verifyOnlySuccessfulWasCalled(); } @Test public void sendFailureResponseWhenFlowExecutionFailsAndExceptionIsNotHandled() throws MuleException { when(mockTemplate.routeEvent(any(Event.class))).thenThrow(mockMessagingException); when(mockMessagingException.handled()).thenReturn(false); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockTemplate).sendFailureResponseToClient(any(MessagingException.class), any(ResponseCompletionCallback.class)); verifyOnlySuccessfulWasCalled(); } @Test public void callExceptionHandlerWhenSuccessfulExecutionFailsWritingResponse() throws Exception { doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentWithFailure(mockException, mockMuleEvent); return null; }).when(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockContext.getFlowConstruct().getExceptionListener()).handleException(any(MessagingException.class), any(Event.class)); verifyOnlySuccessfulWasCalled(); } @Test public void doNotCallExceptionHandlerWhenFailureExecutionFailsWritingResponse() throws Exception { doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentWithFailure(mockException, ((MessagingException) invocationOnMock.getArguments()[0]).getEvent()); return null; }).when(mockTemplate).sendFailureResponseToClient(any(MessagingException.class), any(ResponseCompletionCallback.class)); when(mockTemplate.routeEvent(any(Event.class))).thenThrow(mockMessagingException); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(mockContext.getFlowConstruct().getExceptionListener()).handleException(any(MessagingException.class), any(Event.class)); verifyOnlyFailureWasCalled(mockException); } @Test public void allowNullEventsOnNotifications() throws Exception { setCurrentEvent(null); when(mockTemplate.getEvent()).thenReturn(null); when(mockTemplate.routeEvent(any(Event.class))).thenReturn(null); phase.runPhase(mockTemplate, mockContext, mockNotifier); setCurrentEvent(mockMuleEvent); phase.runPhase(mockTemplate, mockContext, mockNotifier); } @Test public void responseNotificationFired() throws Exception { doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentWithFailure(mockException, mockMuleEvent); return null; }).when(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(notificationHelper).fireNotification(any(MessageSource.class), any(Event.class), any(FlowConstruct.class), eq(MESSAGE_RESPONSE)); verify(notificationHelper, never()).fireNotification(any(MessageSource.class), any(Event.class), any(FlowConstruct.class), eq(MESSAGE_ERROR_RESPONSE)); } @Test public void errorResponseNotificationFired() throws Exception { doAnswer(invocationOnMock -> { ResponseCompletionCallback callback = (ResponseCompletionCallback) invocationOnMock.getArguments()[1]; callback.responseSentWithFailure(mockException, mockMuleEvent); return null; }).when(mockTemplate).sendResponseToClient(any(Event.class), any(ResponseCompletionCallback.class)); when(mockTemplate.routeEvent(any(Event.class))).thenThrow(mockMessagingException); phase.runPhase(mockTemplate, mockContext, mockNotifier); verify(notificationHelper, never()).fireNotification(any(MessageSource.class), any(Event.class), any(FlowConstruct.class), eq(MESSAGE_RESPONSE)); verify(notificationHelper).fireNotification(any(MessageSource.class), any(Event.class), any(FlowConstruct.class), eq(MESSAGE_ERROR_RESPONSE)); } private void verifyOnlySuccessfulWasCalled() { verify(mockNotifier, never()).phaseFailure(any(Exception.class)); verify(mockNotifier, never()).phaseConsumedMessage(); verify(mockNotifier).phaseSuccessfully(); } private void verifyOnlyFailureWasCalled(Exception e) { verify(mockNotifier).phaseFailure(e); verify(mockNotifier, never()).phaseConsumedMessage(); verify(mockNotifier, never()).phaseSuccessfully(); } }