diff --git a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java index 51fb5051cc..ab12016078 100644 --- a/source/java/org/alfresco/util/BaseAlfrescoTestCase.java +++ b/source/java/org/alfresco/util/BaseAlfrescoTestCase.java @@ -19,9 +19,8 @@ package org.alfresco.util; -import junit.framework.TestCase; - import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.repository.ContentService; @@ -38,7 +37,7 @@ import org.springframework.context.ApplicationContext; * * @author Roy Wetherall */ -public abstract class BaseAlfrescoTestCase extends TestCase +public abstract class BaseAlfrescoTestCase extends RetryingTransactionHelperTestCase { /** * This context will be fetched each time, but almost always @@ -64,10 +63,15 @@ public abstract class BaseAlfrescoTestCase extends TestCase /** The root node reference */ protected NodeRef rootNodeRef; - + /** Action service */ protected ActionService actionService; + + /** Transaction service */ protected TransactionService transactionService; + /** Retrying transaction helper */ + protected RetryingTransactionHelper retryingTransactionHelper; + /** * By default will return the full context. * Override this if your test needs a different one. @@ -78,20 +82,25 @@ public abstract class BaseAlfrescoTestCase extends TestCase ctx = ApplicationContextHelper.getApplicationContext(); } + /** + * @see junit.framework.TestCase#setUp() + */ @Override protected void setUp() throws Exception { super.setUp(); setUpContext(); - // get the service register + // Get the service register this.serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - //Get a reference to the node service + + // Get content services this.nodeService = serviceRegistry.getNodeService(); this.contentService = serviceRegistry.getContentService(); this.authenticationComponent = (AuthenticationComponent)ctx.getBean("authenticationComponent"); this.actionService = (ActionService)ctx.getBean("actionService"); this.transactionService = serviceRegistry.getTransactionService(); + this.retryingTransactionHelper = (RetryingTransactionHelper)ctx.getBean("retryingTransactionHelper"); // Authenticate as the system user - this must be done before we create the store authenticationComponent.setSystemUserAsCurrentUser(); @@ -99,11 +108,21 @@ public abstract class BaseAlfrescoTestCase extends TestCase // Create the store and get the root node this.storeRef = this.nodeService.createStore(StoreRef.PROTOCOL_WORKSPACE, "Test_" + System.currentTimeMillis()); this.rootNodeRef = this.nodeService.getRootNode(this.storeRef); - - + } + /** + * @see org.alfresco.util.RetryingTransactionHelperTestCase#getRetryingTransactionHelper() + */ + @Override + public RetryingTransactionHelper getRetryingTransactionHelper() + { + return this.retryingTransactionHelper; + } + /** + * @see junit.framework.TestCase#tearDown() + */ @Override protected void tearDown() throws Exception { @@ -117,6 +136,5 @@ public abstract class BaseAlfrescoTestCase extends TestCase // Don't let this mask any previous exceptions } super.tearDown(); - } - + } } \ No newline at end of file diff --git a/source/java/org/alfresco/util/RetryingTransactionHelperTestCase.java b/source/java/org/alfresco/util/RetryingTransactionHelperTestCase.java new file mode 100644 index 0000000000..428b7cb321 --- /dev/null +++ b/source/java/org/alfresco/util/RetryingTransactionHelperTestCase.java @@ -0,0 +1,308 @@ +/** + * + */ +package org.alfresco.util; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Test case base class with helper methods for transactional tests. + * + * @author Roy Wetherall + */ +public abstract class RetryingTransactionHelperTestCase extends TestCase +{ + /** + * @return retrying transaction helper + */ + public abstract RetryingTransactionHelper getRetryingTransactionHelper(); + + /** + * Executes a test in a retrying transaction as the admin user. + * + * @param type of the object resulting from the test, can be set to {@link Void} if none. + * @param test test object to be executed within a retrying transaction + * @return A the result of the test + */ + protected A doTestInTransaction(final Test test) + { + return doTestInTransaction(test, AuthenticationUtil.getAdminUserName()); + } + + /** + * Executes a test in a retrying transaction as the admin user. + * + * @param type of the object resulting from the test, can be set to {@link Void} if none. + * @param test test object to be executed within a retrying transaction + * @param asUser user to execute the test as + * @return A the result of the test + */ + protected A doTestInTransaction(final Test test, final String asUser) + { + String origUser = AuthenticationUtil.getFullyAuthenticatedUser(); + AuthenticationUtil.setFullyAuthenticatedUser(asUser); + try + { + // Execute the run() method within a retrying transaction + RetryingTransactionCallback doRun = new RetryingTransactionCallback() + { + @Override + public A execute() throws Throwable + { + // Run test as user + return test.run(); + } + }; + final A result = getRetryingTransactionHelper().doInTransaction(doRun); + + // Execute the test() method within a retrying transaction + RetryingTransactionCallback doTest = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + // pass the result of the run into the test + test.test(result); + return null; + } + + }; + getRetryingTransactionHelper().doInTransaction(doTest); + + return result; + } + finally + { + if (origUser != null) + { + AuthenticationUtil.setFullyAuthenticatedUser(origUser); + } + else + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + } + } + + /** + * Executes a test in a retrying transaction. Run as admin user. + * + * @param test failure test object + */ + protected void doTestInTransaction(final FailureTest test) + { + doTestInTransaction(test, AuthenticationUtil.getAdminUserName()); + } + + /** + * Executes a test in a retrying transaction. + * + * @param test failure test object + * @param asUser user to run test as + */ + protected void doTestInTransaction(final FailureTest test, final String asUser) + { + String origUser = AuthenticationUtil.getFullyAuthenticatedUser(); + AuthenticationUtil.setFullyAuthenticatedUser(asUser); + try + { + RetryingTransactionCallback doRun = new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Class eType = test.getExpectedExceptionClass(); + try + { + test.run(); + } + catch (Throwable exception) + { + if (eType.isInstance(exception) == false) + { + // Genuine error so re-throw + throw exception; + } + + // Otherwise, it's an expected failure + return null; + } + + // Fail since not expected to succeed + fail(test.getMessage()); + return null; + } + }; + getRetryingTransactionHelper().doInTransaction(doRun); + } + finally + { + if (origUser != null) + { + AuthenticationUtil.setFullyAuthenticatedUser(origUser); + } + else + { + AuthenticationUtil.clearCurrentSecurityContext(); + } + } + } + + /** + * Class containing the code to run as test and the optional code to check the results of the + * test in a separate transaction if required. + */ + protected abstract class Test + { + /** Map containing model values. Used to pass data between run and test methods. */ + protected Map model = new HashMap(5); + + /** + * Helper method to set a string vaule in the model + * @param key model key + * @param value model value + * @return String model value + */ + protected String setString(String key, String value) + { + if (value != null) + { + model.put(key, value); + } + return value; + } + + /** + * Helper method to get a string value from the model + * @param key model key + * @return String model value, null if none + */ + protected String getString(String key) + { + return (String)model.get(key); + } + + /** + * Helper method to set node reference value in model + * @param key model key + * @param nodeRef node reference + * @return {@link NodeRef} node reference + */ + protected NodeRef setNodeRef(String key, NodeRef nodeRef) + { + if (nodeRef != null) + { + model.put(key, nodeRef); + } + return nodeRef; + } + + /** + * Helper method to get node reference value from model + * @param key mode key + * @return {@link NodeRef} node reference + */ + protected NodeRef getNodeRef(String key) + { + return (NodeRef)model.get(key); + } + + /** + * Body of the test is implemented here. + * @return A result of the test + * @throws Exception + */ + public abstract A run() throws Exception; + + /** + * If you wish to test the results of the above method within a new and separate + * transaction then implement the tests here. + * @param result result of the above method + * @throws Exception + */ + public void test(A result) throws Exception + { + // Default implementation does nothing. + // Override this if you want to test the results of the + // run method in a new transaction. + } + } + + /** + * Class containing test code that is expected to fail. + */ + protected abstract class FailureTest + { + /** Failure message */ + private String message = "This test was expected to fail."; + + /** Expected failure exception */ + private Class expectedExceptionClass = AlfrescoRuntimeException.class; + + /** + * @param message failure message + */ + public FailureTest(String message) + { + this.message = message; + } + + /** + * @param expectedExceptionClass expected exception class + */ + public FailureTest(Class expectedExceptionClass) + { + this.expectedExceptionClass = expectedExceptionClass; + } + + /** + * @param message failure message + * @param expectedExceptionClass expected exception class + */ + public FailureTest(String message, Class expectedExceptionClass) + { + this.message = message; + this.expectedExceptionClass = expectedExceptionClass; + } + + /** + * @return expected exception class + */ + public Class getExpectedExceptionClass() + { + return expectedExceptionClass; + } + + /** + * @return failure message + */ + public String getMessage() + { + return message; + } + + /** + * Default constructor + */ + public FailureTest() + { + super(); + } + + /** + * Code to test for failure + * + * @throws Exception + */ + public abstract void run() throws Exception; + } +} diff --git a/source/java/org/alfresco/util/TestWithUserUtils.java b/source/java/org/alfresco/util/TestWithUserUtils.java index 3c39703cf8..70d18fa5bf 100644 --- a/source/java/org/alfresco/util/TestWithUserUtils.java +++ b/source/java/org/alfresco/util/TestWithUserUtils.java @@ -31,7 +31,7 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; /** - * Utility class containing some useful methods to help when writing tets that require authenticated users + * Utility class containing some useful methods to help when writing tests that require authenticated users * * @author Roy Wetherall */ @@ -77,7 +77,7 @@ public abstract class TestWithUserUtils } /** - * Autneticate the user with the specified password + * Authenticate the user with the specified password * * @param userName the user name * @param password the password