/**
* Copyright (c) 2013-2016, The SeedStack authors <http://seedstack.org>
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.seedstack.seed.core.internal.transaction;
import com.google.common.collect.Sets;
import com.google.inject.Injector;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Before;
import org.junit.Test;
import org.mockito.internal.util.reflection.Whitebox;
import org.seedstack.seed.transaction.spi.ExceptionHandler;
import org.seedstack.seed.transaction.spi.TransactionHandler;
import org.seedstack.seed.transaction.spi.TransactionManager;
import org.seedstack.seed.transaction.spi.TransactionMetadata;
import org.seedstack.seed.transaction.spi.TransactionMetadataResolver;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@SuppressWarnings("unchecked")
public abstract class AbstractTransactionManagerTest {
protected TransactionMetadata transactionMetadata;
protected TransactionMetadataResolver transactionMetadataResolver;
protected TransactionHandler transactionHandler;
private ExceptionHandler exceptionHandler;
private TransactionManager underTest;
@Before
public void before() throws Exception {
transactionHandler = mock(TransactionHandler.class);
exceptionHandler = mock(ExceptionHandler.class);
transactionMetadata = new TransactionMetadata().defaults();
transactionMetadata.setHandler(TransactionHandler.class);
transactionMetadataResolver = mock(TransactionMetadataResolver.class);
when(transactionMetadataResolver.resolve(any(MethodInvocation.class), any(TransactionMetadata.class))).thenReturn(transactionMetadata);
Injector injector = mock(Injector.class);
when(injector.getInstance(TransactionHandler.class)).thenReturn(transactionHandler);
when(injector.getInstance(TransactionMetadata.class)).thenReturn(new TransactionMetadata());
when(injector.getInstance(ExceptionHandler.class)).thenReturn(exceptionHandler);
underTest = doProvideTransactionManager();
Whitebox.setInternalState(underTest, "transactionMetadataResolvers", Sets.newHashSet(transactionMetadataResolver));
Whitebox.setInternalState(underTest, "injector", injector);
}
protected abstract TransactionManager doProvideTransactionManager() throws Exception;
protected abstract void doAssertRollbackOccurred() throws Exception;
protected abstract void doAssertCommitOccurred() throws Exception;
protected void invoke(TransactionalMethods.Enum methodToCall) throws Throwable {
underTest.getMethodInterceptor().invoke(methodToCall.getMethodInvocation());
}
protected void invokeWithArguments(TransactionalMethods.Enum methodToCall, Object[] arguments) throws Throwable {
MethodInvocation methodInvocation = methodToCall.getMethodInvocation();
((SimpleMethodInvocation) methodInvocation).setArguments(arguments);
underTest.getMethodInterceptor().invoke(methodInvocation);
}
@Test
public void exception_handler_can_mask_exception() throws Throwable {
transactionMetadata.setExceptionHandler(ExceptionHandler.class);
when(exceptionHandler.handleException(any(Exception.class), any(TransactionMetadata.class), any())).thenReturn(true);
try {
invoke(TransactionalMethods.Enum.FAIL);
} catch (Exception e) {
fail("exception should have been handled");
}
verify(exceptionHandler).handleException(any(Exception.class), any(TransactionMetadata.class), any());
}
@Test
public void exception_handler_can_process_exception_without_masking_it() throws Throwable {
transactionMetadata.setExceptionHandler(ExceptionHandler.class);
when(exceptionHandler.handleException(any(Exception.class), any(TransactionMetadata.class), any())).thenReturn(false);
try {
invoke(TransactionalMethods.Enum.FAIL);
fail("exception should have been propagated");
} catch (Exception e) {
// nothing here
}
verify(exceptionHandler).handleException(any(Exception.class), any(TransactionMetadata.class), any());
}
@Test
public void rollback_on_all_exceptions_by_default() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.DEFAULT_ROLLBACK, new Object[]{new MyException()});
fail("exception should have been propagated");
} catch (Exception e) {
assertThat(e).isInstanceOf(MyException.class);
}
doAssertRollbackOccurred();
}
@Test
public void no_rollback_on_exceptions_not_matching_rollback_for() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.ROLLBACK_FOR, new Object[]{new MyException()});
} catch (Exception e) {
fail("exception should not have been propagated");
}
doAssertCommitOccurred();
}
@Test
public void rollback_on_exceptions_matching_rollback_for() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.ROLLBACK_FOR, new Object[]{new IllegalArgumentException()});
fail("exception should have been propagated");
} catch (Exception e) {
assertThat(e).isInstanceOf(IllegalArgumentException.class);
}
doAssertRollbackOccurred();
}
@Test
public void rollback_on_exceptions_matching_rollback_for_but_not_matching_no_rollback_for() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.NO_ROLLBACK_FOR, new Object[]{new MyException()});
fail("exception should have been propagated");
} catch (Exception e) {
assertThat(e).isInstanceOf(MyException.class);
}
doAssertRollbackOccurred();
}
@Test
public void no_rollback_on_exceptions_matching_rollback_for_and_matching_no_rollback_for_too() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.NO_ROLLBACK_FOR, new Object[]{new IllegalArgumentException()});
} catch (Exception e) {
fail("exception should not have been propagated");
}
doAssertCommitOccurred();
}
@Test
public void error_always_results_in_rollback() throws Throwable {
try {
invokeWithArguments(TransactionalMethods.Enum.DEFAULT_ROLLBACK, new Object[]{new MyError()});
fail("error should have been propagated");
} catch (Throwable t) {
assertThat(t).isInstanceOf(MyError.class);
}
doAssertRollbackOccurred();
}
private class MyException extends Exception {}
private class MyError extends Error {}
}