package org.finra.herd.service.activiti.task;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import com.google.common.base.Objects;
import org.activiti.engine.delegate.DelegateExecution;
import org.activiti.engine.repository.ProcessDefinition;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.finra.herd.dao.JobDefinitionDao;
import org.finra.herd.model.ObjectNotFoundException;
import org.finra.herd.model.dto.ApplicationUser;
import org.finra.herd.model.dto.SecurityUserWrapper;
import org.finra.herd.model.jpa.JobDefinitionEntity;
import org.finra.herd.service.AbstractServiceTest;
import org.finra.herd.service.ActivitiService;
import org.finra.herd.service.activiti.ActivitiHelper;
import org.finra.herd.service.activiti.ActivitiRuntimeHelper;
import org.finra.herd.service.helper.ConfigurationDaoHelper;
import org.finra.herd.service.helper.HerdErrorInformationExceptionHandler;
import org.finra.herd.service.helper.JobDefinitionDaoHelper;
import org.finra.herd.service.helper.UserNamespaceAuthorizationHelper;
/**
* Test suite which targets BaseJavaDelegate specifically. This test suite uses Mockito injection annotations (ie. does not depend on Spring injection and
* dependencies are CGLIB proxies created by Mockito).
* <p/>
* This test suite updated Spring security context which means it EACH TEST CASE MUST BE CLEANED UP after execution. Without doing so may cause subsequent tests
* to not execute properly.
*/
public class BaseJavaDelegateTest extends AbstractServiceTest
{
@Mock
private ActivitiHelper activitiHelper;
@Mock
private ActivitiRuntimeHelper activitiRuntimeHelper;
@Mock
private ActivitiService activitiService;
@Autowired
@InjectMocks
private MockJavaDelegate baseJavaDelegate;
@Mock
private ConfigurationDaoHelper configurationDaoHelper;
@Mock
private DelegateExecution delegateExecution;
@Mock
private HerdErrorInformationExceptionHandler errorInformationExceptionHandler;
@Mock
private JobDefinitionDao jobDefinitionDao;
@Mock
private JobDefinitionDaoHelper jobDefinitionDaoHelper;
@Mock
private UserNamespaceAuthorizationHelper userNamespaceAuthorizationHelper;
@After
public void after()
{
SecurityContextHolder.clearContext();
}
@Before
public void before()
{
initMocks(this);
}
@Test
public void testExecute() throws Exception
{
// Set up expected values.
String jobDefinitionNamespace = "jobDefinitionNamespace";
String jobDefinitionName = "jobDefinitionName";
String processDefinitionId = "processDefinitionId";
String processDefinitionKey = String.format("%s.%s", jobDefinitionNamespace, jobDefinitionName);
String updatedBy = "updatedBy";
JobDefinitionEntity jobDefinitionEntity = new JobDefinitionEntity();
jobDefinitionEntity.setUpdatedBy(updatedBy);
// Mock dependency methods.
when(delegateExecution.getProcessDefinitionId()).thenReturn(processDefinitionId);
ProcessDefinition processDefinition = mock(ProcessDefinition.class);
when(processDefinition.getKey()).thenReturn(processDefinitionKey);
when(activitiService.getProcessDefinitionById(any())).thenReturn(processDefinition);
when(jobDefinitionDaoHelper.getJobDefinitionEntity(any(), any())).thenReturn(jobDefinitionEntity);
// Execute test method.
baseJavaDelegate.execute(delegateExecution);
// Verify dependencies were invoked correctly.
InOrder inOrder = inOrder(configurationDaoHelper, activitiService, jobDefinitionDaoHelper, userNamespaceAuthorizationHelper, activitiRuntimeHelper);
inOrder.verify(configurationDaoHelper).checkNotAllowedMethod(baseJavaDelegate.getClass().getCanonicalName());
inOrder.verify(activitiService).getProcessDefinitionById(processDefinitionId);
inOrder.verify(jobDefinitionDaoHelper).getJobDefinitionEntity(jobDefinitionNamespace, jobDefinitionName);
inOrder.verify(userNamespaceAuthorizationHelper).buildNamespaceAuthorizations(applicationUserUserIdEq(updatedBy));
inOrder.verify(activitiRuntimeHelper).setTaskSuccessInWorkflow(delegateExecution);
inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(configurationDaoHelper, activitiService, jobDefinitionDaoHelper, userNamespaceAuthorizationHelper, activitiRuntimeHelper);
// Assert that security context is cleared at the end of execute.
assertNull(SecurityContextHolder.getContext().getAuthentication());
}
@Test
public void testSetSecurityContext() throws Exception
{
// Set up expected values.
String jobDefinitionNamespace = "jobDefinitionNamespace";
String jobDefinitionName = "jobDefinitionName";
String processDefinitionId = "processDefinitionId";
String processDefinitionKey = String.format("%s.%s", jobDefinitionNamespace, jobDefinitionName);
String updatedBy = "updatedBy";
JobDefinitionEntity jobDefinitionEntity = new JobDefinitionEntity();
jobDefinitionEntity.setUpdatedBy(updatedBy);
// Mock dependency methods.
when(delegateExecution.getProcessDefinitionId()).thenReturn(processDefinitionId);
ProcessDefinition processDefinition = mock(ProcessDefinition.class);
when(processDefinition.getKey()).thenReturn(processDefinitionKey);
when(activitiService.getProcessDefinitionById(any())).thenReturn(processDefinition);
when(jobDefinitionDaoHelper.getJobDefinitionEntity(any(), any())).thenReturn(jobDefinitionEntity);
// Clear the security context.
SecurityContextHolder.clearContext();
// Execute test method.
baseJavaDelegate.setSecurityContext(delegateExecution);
// Verify dependencies were invoked correctly.
InOrder inOrder = inOrder(activitiService, jobDefinitionDaoHelper, userNamespaceAuthorizationHelper);
inOrder.verify(activitiService).getProcessDefinitionById(processDefinitionId);
inOrder.verify(jobDefinitionDaoHelper).getJobDefinitionEntity(jobDefinitionNamespace, jobDefinitionName);
inOrder.verify(userNamespaceAuthorizationHelper).buildNamespaceAuthorizations(applicationUserUserIdEq(updatedBy));
inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(activitiService, jobDefinitionDaoHelper, userNamespaceAuthorizationHelper);
// Assert correct user is in the security context.
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
assertAuthenticationUserIdEquals(updatedBy, authentication);
}
@Test
public void testSetSecurityContextProcessDefinitionNoExists() throws Exception
{
// Set up expected values.
String processDefinitionId = "processDefinitionId";
// Mock dependency methods.
when(delegateExecution.getProcessDefinitionId()).thenReturn(processDefinitionId);
when(activitiService.getProcessDefinitionById(any())).thenReturn(null);
// Clear the security context.
SecurityContextHolder.clearContext();
// Try to execute the test method when process definition does not exist.
try
{
baseJavaDelegate.setSecurityContext(delegateExecution);
fail();
}
catch (ObjectNotFoundException e)
{
assertEquals(String.format("Failed to find Activiti process definition for processDefinitionId=\"%s\".", processDefinitionId), e.getMessage());
}
// Verify dependencies were invoked correctly.
InOrder inOrder = inOrder(activitiService);
inOrder.verify(activitiService).getProcessDefinitionById(processDefinitionId);
inOrder.verifyNoMoreInteractions();
verifyNoMoreInteractions(activitiService);
// Assert that security context is not set.
assertNull(SecurityContextHolder.getContext().getAuthentication());
}
/**
* Returns a Mockito matcher which matches when the ApplicationUser's user ID equals the given user ID.
*
* @param userId The user ID to match
*
* @return Mockito proxy matcher of ApplicationUser
*/
private ApplicationUser applicationUserUserIdEq(String userId)
{
return argThat(new ArgumentMatcher<ApplicationUser>()
{
@Override
public boolean matches(Object argument)
{
ApplicationUser applicationUser = (ApplicationUser) argument;
return Objects.equal(userId, applicationUser.getUserId());
}
});
}
/**
* Asserts the given actual authentication's user ID is equal to the given expected user ID
*
* @param expectedUserId Expected user ID
* @param actualAuthentication Actual authentication object
*/
private void assertAuthenticationUserIdEquals(String expectedUserId, Authentication actualAuthentication)
{
assertNotNull(actualAuthentication);
assertEquals(PreAuthenticatedAuthenticationToken.class, actualAuthentication.getClass());
PreAuthenticatedAuthenticationToken preAuthenticatedAuthenticationToken = (PreAuthenticatedAuthenticationToken) actualAuthentication;
Object principal = preAuthenticatedAuthenticationToken.getPrincipal();
assertNotNull(principal);
assertEquals(SecurityUserWrapper.class, principal.getClass());
SecurityUserWrapper securityUserWrapper = (SecurityUserWrapper) principal;
assertEquals(expectedUserId, securityUserWrapper.getUsername());
assertNotNull(securityUserWrapper.getApplicationUser());
assertEquals(expectedUserId, securityUserWrapper.getApplicationUser().getUserId());
}
}