diff --git a/pom.xml b/pom.xml index 99a5f701..86b2aa7b 100755 --- a/pom.xml +++ b/pom.xml @@ -73,17 +73,49 @@ org.springframework.boot - spring-boot-test + spring-boot-starter-test test + - org.springframework - spring-test + org.springframework.cloud + spring-cloud-stream + test-jar + 2.1.0.RELEASE test + test-binder + + org.awaitility + awaitility + test + + + org.activiti.cloud.connector + activiti-cloud-starter-connector + test + + + + org.apache.maven.plugins + maven-jar-plugin + + + + + **/integration/* + + test-binder + + + test-jar + + + + org.apache.maven.plugins maven-compiler-plugin diff --git a/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationIT.java b/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationIT.java index 3a62bbb0..585b7b70 100644 --- a/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationIT.java +++ b/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationIT.java @@ -1,15 +1,14 @@ package org.activiti.cloud.runtime; -import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; @RunWith(SpringRunner.class) -@SpringBootTest(classes = RuntimeBundleApplication.class) +@SpringBootTest(classes = RuntimeBundleApplication.class, + properties = "spring.activiti.process-definition-location-prefix=file:/processes/") @DirtiesContext public class RuntimeBundleApplicationIT { diff --git a/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationTest.java b/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationTest.java new file mode 100644 index 00000000..7ecce922 --- /dev/null +++ b/src/test/java/org/activiti/cloud/runtime/RuntimeBundleApplicationTest.java @@ -0,0 +1,251 @@ +package org.activiti.cloud.runtime; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import org.activiti.api.process.model.ProcessInstance; +import org.activiti.api.process.model.builders.StartProcessPayloadBuilder; +import org.activiti.api.process.model.payloads.StartProcessPayload; +import org.activiti.api.process.runtime.ProcessRuntime; +import org.activiti.api.runtime.shared.security.SecurityManager; +import org.activiti.cloud.api.process.model.IntegrationRequest; +import org.activiti.cloud.api.process.model.IntegrationResult; +import org.activiti.cloud.connectors.starter.channels.IntegrationResultSender; +import org.activiti.cloud.connectors.starter.configuration.ConnectorProperties; +import org.activiti.cloud.connectors.starter.model.IntegrationResultBuilder; +import org.activiti.core.common.spring.identity.ExtendedInMemoryUserDetailsManager; +import org.activiti.engine.RuntimeService; +import org.awaitility.Awaitility; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.stream.annotation.EnableBinding; +import org.springframework.cloud.stream.annotation.Input; +import org.springframework.cloud.stream.annotation.StreamListener; +import org.springframework.cloud.stream.binder.test.TestChannelBinderConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.messaging.Message; +import org.springframework.messaging.SubscribableChannel; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextImpl; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +/** + * See: https://cloud.spring.io/spring-cloud-static/spring-cloud-stream/2.1.0.RELEASE/multi/multi__testing.html#spring_integration_test_binder + */ +@SpringBootTest(classes = {RuntimeBundleApplication.class, TestChannelBinderConfiguration.class}) +public class RuntimeBundleApplicationTest { + + private static final String CONNECTOR_PROCESS = "ConnectorProcess"; + private static final String BUSINESS_KEY = "businessKey"; + + @Autowired + private ProcessRuntime processRuntime; + + @Autowired + private RuntimeService runtimeService; + + @Autowired + private TestSecurityUtil securityUtil; + + private static CountDownLatch exampleConnectorConsumer; + + @Before + public void setUp() { + exampleConnectorConsumer = new CountDownLatch(1); + } + + @Test + public void testExampleConnectorProcess() throws InterruptedException { + // given + securityUtil.logInAs("user"); + + String businessKey = BUSINESS_KEY; + StartProcessPayload payload = new StartProcessPayloadBuilder().withProcessDefinitionKey(CONNECTOR_PROCESS) + .withBusinessKey(businessKey) + .build(); + + // when + ProcessInstance processInstance = processRuntime.start(payload); + // then + assertThat(processInstance).as("Should start process instance") + .isNotNull(); + // and + assertThat(exampleConnectorConsumer.await(1, TimeUnit.SECONDS)) + .as("Should execute cloud connector task") + .isTrue(); + // and + Awaitility.await().untilAsserted(() -> { + assertThat(runtimeService.createProcessInstanceQuery() + .processDefinitionKey(CONNECTOR_PROCESS) + .active() + .list()) + .as("Should complete process") + .isEmpty(); + }); + } + + /** + * Define Connector Mock Channel + */ + public static interface ExampleConnectorChannels { + String IMPLEMENTATION = "exampleConnectorConsumer"; + + @Input(IMPLEMENTATION) + SubscribableChannel input(); + } + + /** + * Define Mock Connector Consumer + */ + @EnableBinding(ExampleConnectorChannels.class) + public static class ExampleConnectorConsumer { + + @Autowired + IntegrationResultSender integrationResultSender; + + @Autowired + private ConnectorProperties connectorProperties; + + @StreamListener(ExampleConnectorChannels.IMPLEMENTATION) + public void perfromTask(IntegrationRequest event) throws JsonParseException, JsonMappingException, IOException { + + Map results = Collections.singletonMap("result", + event.getIntegrationContext() + .getBusinessKey()); + + Message message = IntegrationResultBuilder.resultFor(event, connectorProperties) + .withOutboundVariables(results) + .buildMessage(); + integrationResultSender.send(message); + + assertThat(results).containsEntry("result", BUSINESS_KEY); + + exampleConnectorConsumer.countDown(); + } + } + + @TestConfiguration + public static class TestSupportConfig { + + @Bean + public UserDetailsService myUserDetailsService() { + ExtendedInMemoryUserDetailsManager extendedInMemoryUserDetailsManager = new ExtendedInMemoryUserDetailsManager(); + + List userAuthorities = new ArrayList<>(); + userAuthorities.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_USER")); + userAuthorities.add(new SimpleGrantedAuthority("GROUP_activitiTeam")); + + extendedInMemoryUserDetailsManager.createUser(new User("user", + "password", + userAuthorities)); + + + List adminAuthorities = new ArrayList<>(); + adminAuthorities.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_ADMIN")); + + extendedInMemoryUserDetailsManager.createUser(new User("admin", + "password", + adminAuthorities)); + + List garthAuthorities = new ArrayList<>(); + garthAuthorities.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_USER")); + garthAuthorities.add(new SimpleGrantedAuthority("GROUP_doctor")); + + extendedInMemoryUserDetailsManager.createUser(new User("garth", + "password", + garthAuthorities)); + + //dean has role but no group + List deanAuthorities = new ArrayList<>(); + deanAuthorities.add(new SimpleGrantedAuthority("ROLE_ACTIVITI_USER")); + extendedInMemoryUserDetailsManager.createUser(new User("dean", + "password", + deanAuthorities)); + + return extendedInMemoryUserDetailsManager; + } + } + + @TestConfiguration + public static class TestSecurityUtil { + + private Logger logger = LoggerFactory.getLogger(TestSecurityUtil.class); + + @Autowired + private UserDetailsService userDetailsService; + + @Autowired + private SecurityManager securityManager; + + public void logInAs(String username) { + + UserDetails user = userDetailsService.loadUserByUsername(username); + if (user == null) { + throw new IllegalStateException("User " + username + " doesn't exist, please provide a valid user"); + } + logger.info("> Logged in as: " + username); + SecurityContextHolder.setContext(new SecurityContextImpl(new Authentication() { + @Override + public Collection getAuthorities() { + return user.getAuthorities(); + } + + @Override + public Object getCredentials() { + return user.getPassword(); + } + + @Override + public Object getDetails() { + return user; + } + + @Override + public Object getPrincipal() { + return user; + } + + @Override + public boolean isAuthenticated() { + return true; + } + + @Override + public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException { + + } + + @Override + public String getName() { + return user.getUsername(); + } + })); + org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username); + + assertThat(securityManager.getAuthenticatedUserId()).isEqualTo(username); + } + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 00466c75..278a3f4d 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -26,4 +26,8 @@ activiti.keycloak.admin-client-app=${ACT_KEYCLOAK_CLIENT_APP:admin-cli} activiti.keycloak.client-user=${ACT_KEYCLOAK_CLIENT_USER:client} activiti.keycloak.client-password=${ACT_KEYCLOAK_CLIENT_PASSWORD:client} -spring.activiti.process-definition-location-prefix=file:${ACT_RB_PROCESSES_PATH:/processes/} +# Unit test support for Example Connector +spring.cloud.stream.bindings.exampleConnectorConsumer.destination=Example Connector +spring.cloud.stream.bindings.exampleConnectorConsumer.content-type=application/json +spring.cloud.stream.bindings.exampleConnectorConsumer.group=${spring.application.name} +