/* * 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.module.extension.internal.runtime; import static java.util.Arrays.asList; import static java.util.Optional.empty; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.sameInstance; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import static org.mockito.Answers.RETURNS_DEEP_STUBS; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyVararg; import static org.mockito.Matchers.same; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.mule.runtime.core.api.lifecycle.LifecycleUtils.initialiseIfNeeded; import static org.mule.test.module.extension.internal.util.ExtensionsTestUtils.mockExceptionEnricher; import static reactor.core.publisher.Mono.error; import static reactor.core.publisher.Mono.from; import static reactor.core.publisher.Mono.just; import org.mule.runtime.api.connection.ConnectionException; import org.mule.runtime.api.connection.ConnectionProvider; import org.mule.runtime.api.exception.MuleException; import org.mule.runtime.api.meta.model.ExtensionModel; import org.mule.runtime.api.meta.model.XmlDslModel; import org.mule.runtime.api.meta.model.config.ConfigurationModel; import org.mule.runtime.api.meta.model.operation.OperationModel; import org.mule.runtime.core.internal.connection.ConnectionManagerAdapter; import org.mule.runtime.core.internal.connection.DefaultConnectionManager; import org.mule.runtime.core.internal.connection.ReconnectableConnectionProviderWrapper; import org.mule.runtime.core.retry.policies.SimpleRetryPolicyTemplate; import org.mule.runtime.extension.api.runtime.ConfigurationInstance; import org.mule.runtime.extension.api.runtime.Interceptable; import org.mule.runtime.extension.api.runtime.exception.ExceptionHandler; import org.mule.runtime.extension.api.runtime.operation.Interceptor; import org.mule.runtime.extension.api.runtime.operation.OperationExecutor; import org.mule.runtime.module.extension.internal.runtime.config.MutableConfigurationStats; import org.mule.runtime.module.extension.internal.runtime.operation.DefaultExecutionMediator; import org.mule.runtime.module.extension.internal.runtime.operation.ExecutionMediator; import org.mule.tck.junit4.AbstractMuleContextTestCase; import org.mule.tck.size.SmallTest; import org.mule.test.heisenberg.extension.exception.HeisenbergException; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.verification.VerificationMode; import reactor.core.Exceptions; import reactor.core.publisher.Mono; @SmallTest @RunWith(MockitoJUnitRunner.class) public class DefaultExecutionMediatorTestCase extends AbstractMuleContextTestCase { public static final int RETRY_COUNT = 10; private static final String DUMMY_NAME = "dummyName"; private static final String ERROR = "Error"; private final Object result = new Object(); @Rule public ExpectedException expectedException = ExpectedException.none(); @Mock(answer = RETURNS_DEEP_STUBS) private ExecutionContextAdapter operationContext; @Mock(extraInterfaces = Interceptable.class) private ConfigurationInstance configurationInstance; @Mock private MutableConfigurationStats configurationStats; @Mock(extraInterfaces = Interceptable.class) private OperationExecutor operationExecutor; @Mock private OperationExecutor operationExceptionExecutor; @Mock private Interceptor configurationInterceptor1; @Mock private Interceptor configurationInterceptor2; @Mock private Interceptor operationInterceptor1; @Mock private Interceptor operationInterceptor2; @Mock private ExceptionHandler exceptionEnricher; @Mock private ConfigurationModel configurationModel; @Mock private ExtensionModel extensionModel; @Mock private OperationModel operationModel; @Mock private ConnectionManagerAdapter connectionManagerAdapter; private ConnectionException connectionException = new ConnectionException("Connection failure"); private Exception exception = new Exception(); private InOrder inOrder; private List<Interceptor> orderedInterceptors; private ExecutionMediator mediator; @Before public void before() throws Exception { when(configurationInstance.getStatistics()).thenReturn(configurationStats); when(configurationInstance.getName()).thenReturn(DUMMY_NAME); when(configurationInstance.getModel()).thenReturn(configurationModel); when(extensionModel.getName()).thenReturn(DUMMY_NAME); mockExceptionEnricher(extensionModel, null); mockExceptionEnricher(operationModel, null); when(operationExecutor.execute(operationContext)).thenReturn(just(result)); when(operationExceptionExecutor.execute(operationContext)).thenReturn(error(exception)); when(operationContext.getConfiguration()).thenReturn(Optional.of(configurationInstance)); when(operationContext.getExtensionModel().getName()).thenReturn(DUMMY_NAME); when(operationContext.getTransactionConfig()).thenReturn(empty()); when(extensionModel.getXmlDslModel()).thenReturn(XmlDslModel.builder().setPrefix("test-extension").build()); mediator = new DefaultExecutionMediator(extensionModel, operationModel, new DefaultConnectionManager(muleContext), muleContext.getErrorTypeRepository()); final ReconnectableConnectionProviderWrapper<Object> connectionProviderWrapper = new ReconnectableConnectionProviderWrapper<>(null, false, new SimpleRetryPolicyTemplate(10, RETRY_COUNT)); initialiseIfNeeded(connectionProviderWrapper, true, muleContext); Optional<ConnectionProvider> connectionProvider = Optional.of(connectionProviderWrapper); when(configurationInstance.getConnectionProvider()).thenReturn(connectionProvider); when(exceptionEnricher.enrichException(exception)).thenReturn(new HeisenbergException(ERROR)); setInterceptors((Interceptable) configurationInstance, configurationInterceptor1, configurationInterceptor2); setInterceptors((Interceptable) operationExecutor, operationInterceptor1, operationInterceptor2); defineOrder(configurationInterceptor1, configurationInterceptor2, operationInterceptor1, operationInterceptor2); } @Test public void interceptorsInvokedOnSuccess() throws Throwable { Object result = execute().block(); assertBefore(); assertOnSuccess(times(1)); assertOnError(never()); assertAfter(result); assertResult(result); } @Test public void interceptorsInvokedOnError() throws Throwable { stubException(); assertException(e -> { assertThat(e, is(instanceOf(ConnectionException.class))); try { assertBefore(); } catch (Exception e2) { throw new RuntimeException(e2); } assertOnSuccess(never()); assertOnError(times(1)); assertAfter(null); }); } @Test public void decoratedException() throws Throwable { stubException(); final Exception decoratedException = mock(Exception.class); when(configurationInterceptor2.onError(same(operationContext), same(connectionException))) .thenReturn(decoratedException); assertException(e -> { assertThat(e, is(sameInstance(decoratedException))); assertAfter(null); }); } @Test public void exceptionOnBefore() throws Throwable { stubExceptionOnBeforeInterceptor(); assertException(e -> { try { assertThat(e, is(sameInstance(exception))); assertBefore(); assertOnError(never()); verify(operationExecutor, never()).execute(operationContext); } catch (Exception e2) { throw new RuntimeException(e2); } }); } @Test public void configurationStatsOnSuccessfulOperation() throws Throwable { execute().block(); assertStatistics(); } @Test public void configurationStatsOnFailedOperation() throws Throwable { stubException(); assertException(e -> assertStatistics()); } @Test public void configurationStatsOnFailedBeforeInterceptor() throws Throwable { stubExceptionOnBeforeInterceptor(); assertException(e -> assertStatistics()); } @Test public void enrichThrownException() throws Throwable { expectedException.expectCause(instanceOf(HeisenbergException.class)); expectedException.expectMessage(ERROR); mockExceptionEnricher(operationModel, () -> exceptionEnricher); when(operationExecutor.execute(any())).thenReturn(Mono.error(new Exception())); Mono.from(new DefaultExecutionMediator(extensionModel, operationModel, new DefaultConnectionManager(muleContext), muleContext.getErrorTypeRepository()) .execute(operationExceptionExecutor, operationContext)) .block(); } @Test public void retry() throws Throwable { stubException(); Interceptor interceptor = mock(Interceptor.class); setInterceptors((Interceptable) configurationInstance, interceptor); setInterceptors((Interceptable) operationExecutor); defineOrder(interceptor); assertException(exception -> { assertThat(exception, instanceOf(ConnectionException.class)); try { verify(interceptor, times(RETRY_COUNT + 1)).before(operationContext); } catch (Exception e) { throw new RuntimeException(e); } verify(interceptor, times(RETRY_COUNT + 1)).onError(same(operationContext), anyVararg()); verify(interceptor, times(RETRY_COUNT + 1)).after(operationContext, null); }); } private void assertException(Consumer<Throwable> assertion) throws Throwable { try { execute().block(); fail("was expecting a exception"); } catch (Exception e) { assertion.accept(Exceptions.unwrap(e)); } } private void stubExceptionOnBeforeInterceptor() throws Exception { doThrow(exception).when(operationInterceptor2).before(operationContext); } private void assertStatistics() { verify(configurationStats).addInflightOperation(); verify(configurationStats).discountInflightOperation(); } private void assertBefore() throws Exception { verifyInOrder(interceptor -> { try { interceptor.before(operationContext); } catch (Exception e) { throw new RuntimeException(e); } }); } private void assertOnSuccess(VerificationMode verificationMode) { verifyInOrder(interceptor -> interceptor.onSuccess(operationContext, result), verificationMode); } private void assertOnError(VerificationMode verificationMode) { verifyInOrder(interceptor -> interceptor.onError(same(operationContext), same(connectionException)), verificationMode); } private void assertAfter(Object expected) { verifyInOrder(interceptor -> interceptor.after(operationContext, expected)); } private void assertResult(Object result) { assertThat(result, is(sameInstance(this.result))); } private void stubException() throws Exception { when(operationExecutor.execute(operationContext)).thenReturn(error(connectionException)); } private void setInterceptors(Interceptable interceptable, Interceptor... interceptors) { when(interceptable.getInterceptors()).thenReturn(asList(interceptors)); } private void defineOrder(Interceptor... interceptors) { inOrder = inOrder(interceptors); orderedInterceptors = ImmutableList.copyOf(interceptors); } private void verifyInOrder(Consumer<Interceptor> consumer) { verifyInOrder(consumer, times(1)); } private void verifyInOrder(Consumer<Interceptor> consumer, VerificationMode verificationMode) { for (Interceptor interceptor : orderedInterceptors) { consumer.accept(inOrder.verify(interceptor, verificationMode)); } } private Mono<Object> execute() throws MuleException { return from(mediator.execute(operationExecutor, operationContext)); } }