From 7de6edb5feb65b51e286650b4fba69100d7e381e Mon Sep 17 00:00:00 2001 From: Dennis Melzer Date: Wed, 22 Mar 2017 15:50:03 +0100 Subject: [PATCH] Feature dmf integration tests (#459) * Add rabbit mq integration test * Change DMF Api for ActionUpdateStatus JSON * Add test for invalid target messages. * Insert tests for AmqpAuthenticationMessageHandler Signed-off-by: Melanie Retter * - Added vhost creation to tests - Added missing test - Created beans for dmf client and vhost - refactoring Signed-off-by: Jonathan Philip Knoblauch * Completed missing tests Signed-off-by: Jonathan Philip Knoblauch * Create vhost after every test class Signed-off-by: SirWayne * Add readme.md Signed-off-by: SirWayne * Add check if rabbit mq management is available Signed-off-by: SirWayne * refactoring test Signed-off-by: Jonathan Philip Knoblauch * Create integration test for amqpAuthentication Add test description Signed-off-by: SirWayne * Fix sonar issue Add rabbitmq username and password Signed-off-by: SirWayne * Merge master Signed-off-by: SirWayne * Fix some code smells Signed-off-by: SirWayne * Make some member immutable Change executor Signed-off-by: SirWayne --- .../simulator/amqp/SpSenderService.java | 10 +- hawkbit-dmf-amqp/README.md | 10 + hawkbit-dmf-amqp/pom.xml | 23 +- .../AmqpAuthenticationMessageHandler.java | 12 +- .../amqp/AmqpControllerAuthentication.java | 11 +- .../amqp/AmqpMessageDispatcherService.java | 9 +- .../amqp/AmqpMessageHandlerService.java | 20 +- .../eclipse/hawkbit/amqp/BaseAmqpService.java | 50 +- .../hawkbit/AmqpTestConfiguration.java | 158 +++-- .../eclipse/hawkbit/RabbitMqSetupService.java | 117 ++++ .../amqp/AmqpMessageHandlerServiceTest.java | 8 +- .../hawkbit/amqp/BaseAmqpServiceTest.java | 96 +-- .../AbstractAmqpIntegrationTest.java | 70 ++ ...ticationMessageHandlerIntegrationTest.java | 443 +++++++++++++ ...ssageDispatcherServiceIntegrationTest.java | 116 ++++ ...pMessageHandlerServiceIntegrationTest.java | 618 ++++++++++++++++++ .../AmqpServiceIntegrationTest.java | 281 ++++++++ .../listener/DeadletterListener.java | 24 + .../integration/listener/ReplyToListener.java | 37 ++ .../listener/TestRabbitListener.java | 16 + .../matcher/SoftwareModuleJsonMatcher.java | 113 ++++ .../resources/application-test.properties | 33 +- .../dmf/json/model/ActionUpdateStatus.java | 29 +- pom.xml | 17 +- 24 files changed, 2124 insertions(+), 197 deletions(-) create mode 100644 hawkbit-dmf-amqp/README.md create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/RabbitMqSetupService.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpIntegrationTest.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpAuthenticationMessageHandlerIntegrationTest.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/DeadletterListener.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/ReplyToListener.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/TestRabbitListener.java create mode 100644 hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java diff --git a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java index 74e59e6f6..b242d8459 100644 --- a/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java +++ b/examples/hawkbit-device-simulator/src/main/java/org/eclipse/hawkbit/simulator/amqp/SpSenderService.java @@ -53,7 +53,7 @@ public class SpSenderService extends SenderService { public SpSenderService(final RabbitTemplate rabbitTemplate, final AmqpProperties amqpProperties, final SimulationProperties simulationProperties) { super(rabbitTemplate, amqpProperties); - this.spExchange = AmqpSettings.DMF_EXCHANGE; + spExchange = AmqpSettings.DMF_EXCHANGE; this.simulationProperties = simulationProperties; } @@ -226,15 +226,13 @@ public class SpSenderService extends SenderService { final List updateResultMessages, final Long actionId) { final MessageProperties messageProperties = new MessageProperties(); final Map headers = messageProperties.getHeaders(); - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionStatus(actionStatus); + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(actionId, actionStatus); headers.put(MessageHeaderKey.TYPE, MessageType.EVENT.name()); headers.put(MessageHeaderKey.TENANT, tenant); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); actionUpdateStatus.addMessage(updateResultMessages); - actionUpdateStatus.setActionId(actionId); return convertMessage(actionUpdateStatus, messageProperties); } @@ -242,14 +240,12 @@ public class SpSenderService extends SenderService { final List updateResultMessages) { final MessageProperties messageProperties = new MessageProperties(); final Map headers = messageProperties.getHeaders(); - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionStatus(actionStatus); + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(cacheValue.getActionId(), actionStatus); headers.put(MessageHeaderKey.TYPE, MessageType.EVENT.name()); headers.put(MessageHeaderKey.TENANT, cacheValue.getTenant()); headers.put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); headers.put(MessageHeaderKey.CONTENT_TYPE, MessageProperties.CONTENT_TYPE_JSON); actionUpdateStatus.addMessage(updateResultMessages); - actionUpdateStatus.setActionId(cacheValue.getActionId()); return convertMessage(actionUpdateStatus, messageProperties); } diff --git a/hawkbit-dmf-amqp/README.md b/hawkbit-dmf-amqp/README.md new file mode 100644 index 000000000..fb8e1cb1c --- /dev/null +++ b/hawkbit-dmf-amqp/README.md @@ -0,0 +1,10 @@ +# hawkBit device management federation API - implementation + +This is the AMQP implementation for the device management federation API. The implementation uses the spring-amqp project. + +# Integration Test + +This modules contains some integration tests for the device management federation API implementation which uses a RabbitMQ. If there is no RabbitMQ running on the system, all tests will be marked as skipped. You can disable this rule and the tests will fail if there is no RabbitMQ running. n order to disable the rule at runtime, set an environment variable RABBITMQ_SERVER_REQUIRED=true. +The default RabbitMQ hostname is localhost. To set another hostname, set the property spring.rabbitmq.host to the new hostname. +The default RabbitMQ username is guest. To set another username, set the property spring.rabbitmq.username to the new username. +The default RabbitMQ password is guest. To set another password, set the property spring.rabbitmq.password to the new password. \ No newline at end of file diff --git a/hawkbit-dmf-amqp/pom.xml b/hawkbit-dmf-amqp/pom.xml index 9547797f7..6f69afdc3 100644 --- a/hawkbit-dmf-amqp/pom.xml +++ b/hawkbit-dmf-amqp/pom.xml @@ -8,6 +8,7 @@ http://www.eclipse.org/legal/epl-v10.html --> + 4.0.0 @@ -65,7 +66,7 @@ org.slf4j slf4j-api - + org.springframework.boot @@ -107,21 +108,11 @@ allure-junit-adaptor test - - org.springframework.data - spring-data-rest-webmvc - test - org.springframework.security spring-security-aspects test - - org.springframework.boot - spring-boot-starter-test - test - org.springframework.security spring-security-config @@ -132,6 +123,16 @@ spring-context-support test + + org.springframework.amqp + spring-rabbit-junit + test + + + org.springframework.amqp + spring-rabbit-test + test + \ No newline at end of file diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java index 6e87996ad..3146b44cf 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpAuthenticationMessageHandler.java @@ -30,7 +30,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.AmqpRejectAndDontRequeueException; import org.springframework.amqp.core.Message; -import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.http.HttpStatus; @@ -96,7 +95,7 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { * the amqp message * @return the rpc message back to supplier. */ - @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue}", containerFactory = "listenerContainerFactory") + @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.authenticationReceiverQueue:authentication_receiver}", containerFactory = "listenerContainerFactory") public Message onAuthenticationRequest(final Message message) { checkContentTypeJson(message); final SecurityContext oldContext = SecurityContextHolder.getContext(); @@ -157,6 +156,11 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { private Optional findArtifactByFileResource( final FileResource fileResource) { + + if (fileResource == null) { + return Optional.empty(); + } + if (fileResource.getSha1() != null) { return artifactManagement.findFirstArtifactBySHA1(fileResource.getSha1()); } @@ -188,9 +192,7 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { private Message handleAuthenticationMessage(final Message message) { final DownloadResponse authentificationResponse = new DownloadResponse(); - final MessageProperties messageProperties = message.getMessageProperties(); final TenantSecurityToken secruityToken = convertMessage(message, TenantSecurityToken.class); - final FileResource fileResource = secruityToken.getFileResource(); try { SecurityContextHolder.getContext().setAuthentication(authenticationManager.doAuthenticate(secruityToken)); @@ -228,7 +230,7 @@ public class AmqpAuthenticationMessageHandler extends BaseAmqpService { authentificationResponse.setMessage(errorMessage); } - return getMessageConverter().toMessage(authentificationResponse, messageProperties); + return getMessageConverter().toMessage(authentificationResponse, message.getMessageProperties()); } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentication.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentication.java index 64abd2778..55a11719c 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentication.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthentication.java @@ -117,19 +117,18 @@ public class AmqpControllerAuthentication { /** * Performs authentication with the security token. * - * @param secruityToken + * @param securityToken * the authentication request object * @return the authentication object */ - public Authentication doAuthenticate(final TenantSecurityToken secruityToken) { - resolveTenant(secruityToken); - + public Authentication doAuthenticate(final TenantSecurityToken securityToken) { + resolveTenant(securityToken); PreAuthenticatedAuthenticationToken authentication = new PreAuthenticatedAuthenticationToken(null, null); for (final PreAuthentificationFilter filter : filterChain) { - final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, secruityToken); + final PreAuthenticatedAuthenticationToken authenticationRest = createAuthentication(filter, securityToken); if (authenticationRest != null) { authentication = authenticationRest; - authentication.setDetails(new TenantAwareAuthenticationDetails(secruityToken.getTenant(), true)); + authentication.setDetails(new TenantAwareAuthenticationDetails(securityToken.getTenant(), true)); break; } } diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java index 681274265..7b5f073f1 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java @@ -109,10 +109,10 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { LOG.debug("targetAssignDistributionSet retrieved for controller {}. I will forward it to DMF broker.", assignedEvent.getControllerId()); - targetManagement.findTargetByControllerID(assignedEvent.getControllerId()).ifPresent(target -> sendUpdateMessageToTarget(assignedEvent.getTenant(), - target, - assignedEvent.getActionId(), assignedEvent.getModules())); - + targetManagement.findTargetByControllerID(assignedEvent.getControllerId()) + .ifPresent(target -> sendUpdateMessageToTarget(assignedEvent.getTenant(), target, + assignedEvent.getActionId(), assignedEvent.getModules())); + } void sendUpdateMessageToTarget(final String tenant, final Target target, final Long actionId, @@ -165,7 +165,6 @@ public class AmqpMessageDispatcherService extends BaseAmqpService { if (!IpUtil.isAmqpUri(address)) { return; } - final Message message = getMessageConverter().toMessage(actionId, createConnectorMessageProperties(tenant, controllerId, EventTopic.CANCEL_DOWNLOAD)); diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java index 0622eccd2..47cbb7da5 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerService.java @@ -96,7 +96,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { * * @return a message if no message is send back to sender */ - @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue}", containerFactory = "listenerContainerFactory") + @RabbitListener(queues = "${hawkbit.dmf.rabbitmq.receiverQueue:dmf_receiver}", containerFactory = "listenerContainerFactory") public Message onMessage(final Message message, @Header(MessageHeaderKey.TYPE) final String type, @Header(MessageHeaderKey.TENANT) final String tenant) { return onMessage(message, type, tenant, getRabbitTemplate().getConnectionFactory().getVirtualHost()); @@ -169,7 +169,7 @@ public class AmqpMessageHandlerService extends BaseAmqpService { final String replyTo = message.getMessageProperties().getReplyTo(); if (StringUtils.isEmpty(replyTo)) { - logAndThrowMessageError(message, "No ReplyTo was set for the createThing TenantAwareEvent."); + logAndThrowMessageError(message, "No ReplyTo was set for the createThing message."); } final URI amqpUri = IpUtil.createAmqpUri(virtualHost, replyTo); @@ -180,8 +180,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService { } private void lookIfUpdateAvailable(final Target target) { + final Optional actionOptional = controllerManagement .findOldestActiveActionByTarget(target.getControllerId()); + if (!actionOptional.isPresent()) { return; } @@ -206,7 +208,6 @@ public class AmqpMessageHandlerService extends BaseAmqpService { * the topic of the event. */ private void handleIncomingEvent(final Message message, final EventTopic topic) { - switch (topic) { case UPDATE_ACTION_STATUS: updateActionStatus(message); @@ -294,11 +295,11 @@ public class AmqpMessageHandlerService extends BaseAmqpService { private Status hanldeCancelRejectedState(final Message message, final Action action) { if (action.isCancelingOrCanceled()) { return Status.CANCEL_REJECTED; - } else { - logAndThrowMessageError(message, - "Cancel recjected message is not allowed, if action is on state: " + action.getStatus()); - return null; } + logAndThrowMessageError(message, + "Cancel rejected message is not allowed, if action is on state: " + action.getStatus()); + return null; + } private void updateLastPollTime(final Target target) { @@ -321,13 +322,10 @@ public class AmqpMessageHandlerService extends BaseAmqpService { @SuppressWarnings("squid:S3655") private Action checkActionExist(final Message message, final ActionUpdateStatus actionUpdateStatus) { final Long actionId = actionUpdateStatus.getActionId(); + LOG.debug("Target notifies intermediate about action {} with status {}.", actionId, actionUpdateStatus.getActionStatus()); - if (actionId == null) { - logAndThrowMessageError(message, "Invalid message no action id"); - } - final Optional findActionWithDetails = controllerManagement.findActionWithDetails(actionId); if (!findActionWithDetails.isPresent()) { logAndThrowMessageError(message, diff --git a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java index b0bd1f962..e25ba30b9 100644 --- a/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java +++ b/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/BaseAmqpService.java @@ -8,11 +8,10 @@ */ package org.eclipse.hawkbit.amqp; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Map; +import javax.validation.constraints.NotNull; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.AmqpRejectAndDontRequeueException; @@ -20,6 +19,7 @@ import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.AbstractJavaTypeMapper; +import org.springframework.amqp.support.converter.MessageConversionException; import org.springframework.amqp.support.converter.MessageConverter; /** @@ -58,45 +58,27 @@ public class BaseAmqpService { * @return the converted object */ @SuppressWarnings("unchecked") - public T convertMessage(final Message message, final Class clazz) { - if (isMessageBodyEmpty(message)) { - return null; - } + public T convertMessage(@NotNull final Message message, final Class clazz) { + checkMessageBody(message); message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, clazz.getName()); return (T) rabbitTemplate.getMessageConverter().fromMessage(message); } - private static boolean isMessageBodyEmpty(final Message message) { - return message == null || message.getBody() == null || message.getBody().length == 0; - } - - /** - * Is needed to convert a incoming message to is originally list object - * type. - * - * @param message - * the message to convert. - * @param clazz - * the class of the list content. - * @return the list of converted objects - */ - @SuppressWarnings("unchecked") - public List convertMessageList(final Message message, final Class clazz) { - if (isMessageBodyEmpty(message)) { - return Collections.emptyList(); - } - message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CLASSID_FIELD_NAME, - ArrayList.class.getName()); - message.getMessageProperties().getHeaders().put(AbstractJavaTypeMapper.DEFAULT_CONTENT_CLASSID_FIELD_NAME, - clazz.getName()); - return (List) rabbitTemplate.getMessageConverter().fromMessage(message); - } - - public MessageConverter getMessageConverter() { + protected MessageConverter getMessageConverter() { return rabbitTemplate.getMessageConverter(); } + private static boolean isMessageBodyEmpty(final Message message) { + return message.getBody() == null || message.getBody().length == 0; + } + + protected void checkMessageBody(@NotNull final Message message) { + if (isMessageBodyEmpty(message)) { + throw new MessageConversionException("Message body cannot be null"); + } + } + protected String getStringHeaderKey(final Message message, final String key, final String errorMessageIfNull) { final Map header = message.getMessageProperties().getHeaders(); final Object value = header.get(key); diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java index 467c650be..95c1a5bf2 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/AmqpTestConfiguration.java @@ -8,23 +8,33 @@ */ package org.eclipse.hawkbit; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; +import java.net.MalformedURLException; +import java.net.URL; import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import org.eclipse.hawkbit.RabbitMqSetupService.AlivenessException; import org.eclipse.hawkbit.amqp.AmqpProperties; -import org.eclipse.hawkbit.amqp.AmqpSenderService; -import org.eclipse.hawkbit.amqp.DefaultAmqpSenderService; +import org.eclipse.hawkbit.api.HostnameResolver; +import org.eclipse.hawkbit.integration.listener.DeadletterListener; +import org.eclipse.hawkbit.integration.listener.ReplyToListener; import org.eclipse.hawkbit.repository.jpa.model.helper.SystemSecurityContextHolder; import org.eclipse.hawkbit.security.SystemSecurityContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.amqp.core.Binding; +import org.springframework.amqp.core.BindingBuilder; +import org.springframework.amqp.core.FanoutExchange; +import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.junit.BrokerRunning; +import org.springframework.amqp.rabbit.test.RabbitListenerTest; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; -import org.springframework.amqp.support.converter.MessageConverter; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -33,14 +43,21 @@ import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; import org.springframework.security.concurrent.DelegatingSecurityContextExecutorService; -import com.google.common.util.concurrent.ThreadFactoryBuilder; +import com.google.common.base.Throwables; /** * */ @Configuration @EnableConfigurationProperties({ AmqpProperties.class }) +@RabbitListenerTest public class AmqpTestConfiguration { + + private static final Logger LOG = LoggerFactory.getLogger(AmqpTestConfiguration.class); + + public static final String REPLY_TO_EXCHANGE = "reply.queue"; + public static final String REPLY_TO_QUEUE = "reply_queue"; + /** * @return the {@link SystemSecurityContext} singleton bean which make it * accessible in beans which cannot access the service directly, @@ -51,56 +68,15 @@ public class AmqpTestConfiguration { return SystemSecurityContextHolder.getInstance(); } - /** - * Method to set the Jackson2JsonMessageConverter. - * - * @return the Jackson2JsonMessageConverter - */ @Bean - public MessageConverter jsonMessageConverter() { - return new Jackson2JsonMessageConverter(); - } - - /** - * Create default amqp sender service bean. - * - * @param rabbitTemplate - * - * @return the default amqp sender service bean - */ - @Bean - @Autowired - public AmqpSenderService amqpSenderServiceBean(final RabbitTemplate rabbitTemplate) { - return new DefaultAmqpSenderService(rabbitTemplate); - } - - /** - * @return ExecutorService with security context availability in thread - * execution.. - */ - @Bean(destroyMethod = "shutdown") - @ConditionalOnMissingBean - public Executor asyncExecutor() { - return new DelegatingSecurityContextExecutorService(threadPoolExecutor()); - } - - /** - * @return central ThreadPoolExecutor for general purpose multi threaded - * operations. Tries an orderly shutdown when destroyed. - */ - private ThreadPoolExecutor threadPoolExecutor() { - final BlockingQueue blockingQueue = new ArrayBlockingQueue<>(10); - final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 10, 1000, TimeUnit.MILLISECONDS, - blockingQueue, new ThreadFactoryBuilder().setNameFormat("central-executor-pool-%d").build()); - - return threadPoolExecutor; + Executor asyncExecutor() { + return new DelegatingSecurityContextExecutorService(Executors.newSingleThreadExecutor()); } /** * @return {@link TaskExecutor} for task execution */ @Bean - @ConditionalOnMissingBean public TaskExecutor taskExecutor() { return new ConcurrentTaskExecutor(asyncExecutor()); } @@ -110,7 +86,6 @@ public class AmqpTestConfiguration { * {@link #threadPoolTaskScheduler()}. */ @Bean - @ConditionalOnMissingBean public ScheduledExecutorService scheduledExecutorService() { return threadPoolTaskScheduler().getScheduledExecutor(); } @@ -119,8 +94,83 @@ public class AmqpTestConfiguration { * @return {@link ThreadPoolTaskScheduler} for scheduled operations. */ @Bean - @ConditionalOnMissingBean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } + + @Bean + public HostnameResolver hostnameResolver(final HawkbitServerProperties serverProperties) { + return () -> { + try { + return new URL(serverProperties.getUrl()); + } catch (final MalformedURLException e) { + throw Throwables.propagate(e); + } + }; + } + + @Bean(name = "dmfClient") + public RabbitTemplate dmfClient(ConnectionFactory connectionFactory) { + final RabbitTemplate template = new RabbitTemplate(connectionFactory); + template.setMessageConverter(new Jackson2JsonMessageConverter()); + template.setReceiveTimeout(TimeUnit.SECONDS.toMillis(5)); + return template; + } + + @Bean + public Queue replyToQueue() { + return new Queue(REPLY_TO_QUEUE, false, false, true); + } + + @Bean + public FanoutExchange replyToExchange() { + return new FanoutExchange(REPLY_TO_EXCHANGE, false, true); + } + + @Bean + public Binding bindQueueToReplyToExchange() { + return BindingBuilder.bind(replyToQueue()).to(replyToExchange()); + } + + @Bean + public DeadletterListener deadletterListener() { + return new DeadletterListener(); + } + + @Bean + public ReplyToListener replyToListener() { + return new ReplyToListener(); + } + + @Bean + public ConnectionFactory rabbitConnectionFactory(final RabbitMqSetupService rabbitmqSetupService) + throws AlivenessException { + final CachingConnectionFactory factory = new CachingConnectionFactory(); + factory.setHost(rabbitmqSetupService.getHostname()); + factory.setPort(5672); + factory.setUsername(rabbitmqSetupService.getUsername()); + factory.setPassword(rabbitmqSetupService.getPassword()); + try { + factory.setVirtualHost(rabbitmqSetupService.createVirtualHost()); + } catch (final Exception e) { + Throwables.propagateIfInstanceOf(e, AlivenessException.class); + LOG.error("Cannot create virtual host {}", e.getMessage()); + } + return factory; + } + + @Bean + public RabbitMqSetupService rabbitmqSetupService(RabbitProperties properties) { + return new RabbitMqSetupService(properties); + } + + @Bean + public BrokerRunning brokerRunning(RabbitMqSetupService rabbitmqSetupService) { + final BrokerRunning brokerRunning = BrokerRunning.isRunning(); + brokerRunning.setHostName(rabbitmqSetupService.getHostname()); + brokerRunning.getConnectionFactory().setUsername(rabbitmqSetupService.getUsername()); + brokerRunning.getConnectionFactory().setPassword(rabbitmqSetupService.getPassword()); + return brokerRunning; + } + } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/RabbitMqSetupService.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/RabbitMqSetupService.java new file mode 100644 index 000000000..f8ab0b627 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/RabbitMqSetupService.java @@ -0,0 +1,117 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.util.UUID; + +import javax.annotation.PreDestroy; + +import org.springframework.boot.autoconfigure.amqp.RabbitProperties; +import org.springframework.util.StringUtils; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.base.Throwables; +import com.rabbitmq.http.client.Client; +import com.rabbitmq.http.client.domain.UserPermissions; + +/** + * Creates and deletes a new virtual host if the rabbit mq management api is + * available. + * + * + */ +public class RabbitMqSetupService { + + private static Client rabbitmqHttpClient; + private String virtualHost; + + private final String hostname; + + private String username; + + private String password; + + public RabbitMqSetupService(RabbitProperties properties) { + hostname = properties.getHost(); + username = properties.getUsername(); + if (StringUtils.isEmpty(username)) { + username = "guest"; + } + + password = properties.getPassword(); + if (StringUtils.isEmpty(password)) { + password = "guest"; + } + } + + private Client getRabbitmqHttpClient() { + if (rabbitmqHttpClient == null) { + try { + rabbitmqHttpClient = new Client("http://" + getHostname() + ":15672/api/", getUsername(), + getPassword()); + } catch (MalformedURLException | URISyntaxException e) { + throw Throwables.propagate(e); + } + } + return rabbitmqHttpClient; + } + + String createVirtualHost() throws JsonProcessingException, AlivenessException { + if (!getRabbitmqHttpClient().alivenessTest("/")) { + throw new AlivenessException(getHostname()); + + } + virtualHost = UUID.randomUUID().toString(); + getRabbitmqHttpClient().createVhost(virtualHost); + getRabbitmqHttpClient().updatePermissions(virtualHost, getUsername(), createUserPermissionsFullAccess()); + return virtualHost; + + } + + @PreDestroy + void deleteVirtualHost() { + if (StringUtils.isEmpty(virtualHost)) { + return; + } + getRabbitmqHttpClient().deleteVhost(virtualHost); + } + + public String getHostname() { + return hostname; + } + + public String getPassword() { + return password; + } + + public String getUsername() { + return username; + } + + private UserPermissions createUserPermissionsFullAccess() { + final UserPermissions permissions = new UserPermissions(); + permissions.setVhost(virtualHost); + permissions.setRead(".*"); + permissions.setConfigure(".*"); + permissions.setWrite(".*"); + return permissions; + } + + static class AlivenessException extends Exception { + private static final long serialVersionUID = 1L; + + public AlivenessException(String hostname) { + super("Aliveness test failed for " + hostname + + ":15672 guest/quest; rabbit mq management api not available"); + } + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java index 6289e5abe..db9e0e7e8 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java @@ -277,10 +277,10 @@ public class AmqpMessageHandlerServiceTest { @Test @Description("Tests the update of an action of a target without a exist action id") public void updateActionStatusWithoutActionId() { + when(controllerManagementMock.findActionWithDetails(any())).thenReturn(Optional.empty()); final MessageProperties messageProperties = createMessageProperties(MessageType.EVENT); messageProperties.setHeader(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ACTION_STATUS.name()); - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionStatus(ActionStatus.DOWNLOAD); + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.DOWNLOAD); final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(actionUpdateStatus, messageProperties); @@ -442,10 +442,8 @@ public class AmqpMessageHandlerServiceTest { } private ActionUpdateStatus createActionUpdateStatus(final ActionStatus status, final Long id) { - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionId(id); + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(id, status); actionUpdateStatus.setSoftwareModuleId(Long.valueOf(2)); - actionUpdateStatus.setActionStatus(status); return actionUpdateStatus; } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java index 88da037d5..bf67e67ef 100644 --- a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/BaseAmqpServiceTest.java @@ -9,12 +9,14 @@ package org.eclipse.hawkbit.amqp; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.when; -import java.util.ArrayList; -import java.util.List; - +import org.eclipse.hawkbit.dmf.json.model.ActionStatus; import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -24,6 +26,7 @@ import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; +import org.springframework.amqp.support.converter.MessageConversionException; import ru.yandex.qatools.allure.annotations.Description; import ru.yandex.qatools.allure.annotations.Features; @@ -49,57 +52,68 @@ public class BaseAmqpServiceTest { @Test @Description("Verify that the message conversion works") public void convertMessageTest() { - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionId(1L); - actionUpdateStatus.setSoftwareModuleId(2L); - actionUpdateStatus.addMessage("Message 1"); - actionUpdateStatus.addMessage("Message 2"); + final ActionUpdateStatus actionUpdateStatus = createActionStatus(); final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatus, - new MessageProperties()); - ActionUpdateStatus convertedActionUpdateStatus = baseAmqpService.convertMessage(message, + createJsonProperties()); + final ActionUpdateStatus convertedActionUpdateStatus = baseAmqpService.convertMessage(message, ActionUpdateStatus.class); - assertThat(convertedActionUpdateStatus).as("Converted Action Status is wrong") - .isEqualToComparingFieldByField(actionUpdateStatus); + assertThat(convertedActionUpdateStatus).isEqualToComparingFieldByField(actionUpdateStatus); - convertedActionUpdateStatus = baseAmqpService.convertMessage(null, ActionUpdateStatus.class); - assertThat(convertedActionUpdateStatus).as("Converted Object should be null when message is null").isNull(); - - convertedActionUpdateStatus = baseAmqpService.convertMessage(new Message(null, new MessageProperties()), - ActionUpdateStatus.class); - assertThat(convertedActionUpdateStatus).as("Converted Object should be null when message body is null") - .isNull(); } @Test - @Description("Verify that a conversion of a list from a message works") - public void convertMessageListTest() { - final List actionUpdateStatusList = new ArrayList<>(); - for (int i = 0; i < 5; i++) { - final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(); - actionUpdateStatus.setActionId(Long.valueOf(i)); - actionUpdateStatus.setSoftwareModuleId(Long.valueOf(i)); - actionUpdateStatusList.add(actionUpdateStatus); + @Description("Tests invalid null message content") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void convertMessageWithNullContent() { + try { + baseAmqpService.convertMessage(new Message(null, createJsonProperties()), ActionUpdateStatus.class); + fail("Expected MessageConversionException for inavlid JSON"); + } catch (final MessageConversionException e) { + // expected } - final Message message = rabbitTemplate.getMessageConverter().toMessage(actionUpdateStatusList, - new MessageProperties()); - List convertedActionUpdateStatus = baseAmqpService.convertMessageList(message, - ActionUpdateStatus.class); + } - assertThat(convertedActionUpdateStatus).as("Converted Action Status list is wrong") - .hasSameClassAs(actionUpdateStatusList); - assertThat(convertedActionUpdateStatus).as("Converted Action Status list is wrong") - .hasSameSizeAs(actionUpdateStatusList); + @Test + @Description("Tests invalid empty message content") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithEmptyContent() { + try { + baseAmqpService.convertMessage(new Message("".getBytes(), createJsonProperties()), + ActionUpdateStatus.class); + fail("Expected MessageConversionException for inavlid JSON"); + } catch (final MessageConversionException e) { + // expected + } + } - convertedActionUpdateStatus = baseAmqpService.convertMessageList(null, ActionUpdateStatus.class); - assertThat(convertedActionUpdateStatus).as("Converted list should be empty when message is null").isEmpty(); + private MessageProperties createJsonProperties() { + final MessageProperties messageProperties = new MessageProperties(); + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + return messageProperties; + } - convertedActionUpdateStatus = baseAmqpService.convertMessageList(new Message(null, new MessageProperties()), - ActionUpdateStatus.class); - assertThat(convertedActionUpdateStatus).as("Converted list should be empty when message body is null") - .isEmpty(); + @Test + @Description("Tests invalid json message content") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithInvalidJsonContent() { + try { + baseAmqpService.convertMessage(new Message("Invalid Json".getBytes(), createJsonProperties()), + ActionUpdateStatus.class); + fail("Expected MessageConversionException for inavlid JSON"); + } catch (final MessageConversionException e) { + // expected + } + } + + private ActionUpdateStatus createActionStatus() { + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.RUNNING); + actionUpdateStatus.setSoftwareModuleId(2L); + actionUpdateStatus.addMessage("Message 1"); + actionUpdateStatus.addMessage("Message 2"); + return actionUpdateStatus; } } diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpIntegrationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpIntegrationTest.java new file mode 100644 index 000000000..782faf542 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AbstractAmqpIntegrationTest.java @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration; + +import static java.util.concurrent.TimeUnit.SECONDS; + +import org.eclipse.hawkbit.AmqpTestConfiguration; +import org.eclipse.hawkbit.amqp.DmfApiConfiguration; +import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration; +import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest; +import org.junit.Before; +import org.junit.Rule; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.core.RabbitTemplate; +import org.springframework.amqp.rabbit.junit.BrokerRunning; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.SpringApplicationConfiguration; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.annotation.DirtiesContext.ClassMode; + +import com.jayway.awaitility.Awaitility; +import com.jayway.awaitility.core.ConditionFactory; + +@SpringApplicationConfiguration(classes = { RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class, + DmfApiConfiguration.class }) +// Dirty context is necessary to create a new vhost and recreate all necessary +// beans after every test class. +@DirtiesContext(classMode = ClassMode.AFTER_CLASS) +public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest { + + @Rule + @Autowired + public BrokerRunning brokerRunning; + + @Autowired + @Qualifier("dmfClient") + private RabbitTemplate dmfClient; + + @Before + public void setup() { + dmfClient.setExchange(getExchange()); + } + + protected abstract String getExchange(); + + protected RabbitTemplate getDmfClient() { + return dmfClient; + } + + protected ConditionFactory createConditionFactory() { + return Awaitility.await().atMost(2, SECONDS); + } + + protected Message createMessage(final Object payload, final MessageProperties messageProperties) { + if (payload == null) { + messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON); + return new Message(null, messageProperties); + } + return getDmfClient().getMessageConverter().toMessage(payload, messageProperties); + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpAuthenticationMessageHandlerIntegrationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpAuthenticationMessageHandlerIntegrationTest.java new file mode 100644 index 000000000..42414422e --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpAuthenticationMessageHandlerIntegrationTest.java @@ -0,0 +1,443 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; +import org.eclipse.hawkbit.dmf.json.model.DownloadResponse; +import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken; +import org.eclipse.hawkbit.dmf.json.model.TenantSecurityToken.FileResource; +import org.eclipse.hawkbit.repository.model.Artifact; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetWithActionType; +import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; +import org.junit.Before; +import org.junit.Test; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.context.annotation.Description; +import org.springframework.http.HttpStatus; +import org.springframework.util.StringUtils; + +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Device Management Federation API") +@Stories("Amqp Authentication Message Handler") +public class AmqpAuthenticationMessageHandlerIntegrationTest extends AbstractAmqpIntegrationTest { + + private static final String TARGET_SECRUITY_TOKEN = "12345"; + private static final String TARGET_TOKEN_HEADER = "TargetToken " + TARGET_SECRUITY_TOKEN; + private static final String TENANT_EXIST = "DEFAULT"; + private static final String TARGET = "NewDmfTarget"; + + @Before + public void testSetup() { + enableTargetTokenAuthentification(); + } + + @Test + @Description("Tests wrong content type. This message is invalid and should not be requeued. Additional the receive message is null") + public void wrongContentType() { + final Message createAndSendMessage = getDmfClient() + .sendAndReceive(new Message("".getBytes(), new MessageProperties())); + assertThat(createAndSendMessage).isNull(); + } + + @Test + @Description("Tests a null message. This message is invalid and should not be requeued. Additional the receive message is null") + public void securityTokenIsNull() { + final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(null); + assertThat(createAndSendMessage).isNull(); + } + + @Test + @Description("Tenant in the message is null. This message is invalid and should not be requeued. Additional the receive message is null") + public void securityTokenTenantIsNull() { + final TenantSecurityToken securityToken = createTenantSecurityToken(null, TARGET, + FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN)); + final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken); + assertThat(createAndSendMessage).isNull(); + } + + @Test + @Description("Target in the message is null.This message is invalid and should not requeued. Additional the receive message is null") + public void securityTokenFileResourceIsNull() { + enableAnonymousAuthentification(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, null); + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, null); + } + + @Test + @Description("Verify that login fails if the given credential not match.") + public void loginFailedBadCredentials() { + tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED, + false); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN)); + final Message createAndSendMessage = sendAndReceiveAuthenticationMessage(securityToken); + + verifyResult(createAndSendMessage, HttpStatus.FORBIDDEN, "Login failed"); + + } + + @Test + @Description("Verify that the receive message contains a 404 code,if the artifact could not found") + public void fileResourceGetSha1InSecurityTokenIsNull() { + enableAnonymousAuthentification(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(null)); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404 code , if the distributionSet is not assigned to the target") + public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsButIsNotAssigned() { + final Target target = createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final String sha1Hash = artifacts.get(0).getSha1Hash(); + final TenantSecurityToken securityToken = createTenantSecurityToken(target.getTenant(), target.getId(), null, + FileResource.createFileResourceBySha1(sha1Hash)); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=" + sha1Hash + ", artifactId=null, filename=null]not found "); + } + + @Test + @Description("Verify that the receive message contains a 404 code, if there is no artifact for the given sha1") + public void artifactForFileResourceSHA1NotFound() { + enableAnonymousAuthentification(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(TARGET_SECRUITY_TOKEN)); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=12345, artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404 code, if there is no existing target for the given controller id") + public void artifactForFileResourceSHA1FoundTargetNotExists() { + enableAnonymousAuthentification(); + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final String sha1Hash = artifacts.get(0).getSha1Hash(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(sha1Hash)); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=" + sha1Hash + ", artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404 code, if there is no existing artifact for the target") + public void artifactForFileResourceSHA1FoundTargetExistsButNotAssigned() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(artifacts.get(0).getSha1Hash())); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=2f532b93ed23b4341a81dc9b1ee8a1c44b5526ab, artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 200 code and a artifact for the existing controller id ") + public void artifactForFileResourceSHA1FoundTargetExistsIsAssigned() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final Artifact artifact = artifacts.get(0); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(artifact.getSha1Hash())); + + deploymentManagement.assignDistributionSet(distributionSet.getId(), + Arrays.asList(new TargetWithActionType(TARGET))); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + @Test + @Description("Verify that the receive message contains a 200 code and a artifact for the existing target id ") + public void artifactForFileResourceSHA1FoundByTargetIdTargetExistsIsAssigned() { + final Target target = createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final Artifact artifact = artifacts.get(0); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, target.getId(), null, + FileResource.createFileResourceBySha1(artifact.getSha1Hash())); + + deploymentManagement.assignDistributionSet(distributionSet.getId(), + Arrays.asList(new TargetWithActionType(TARGET))); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + @Test + @Description("Verify that the receive message contains a 200 code and a artifact without a controller id (anonymous enabled)") + public void anonymousAuthentification() { + enableAnonymousAuthentification(); + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final Artifact artifact = artifacts.get(0); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, null, null, + FileResource.createFileResourceBySha1(artifact.getSha1Hash())); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + @Test + @Description("Artifact is downloaded anonymous as there is not target id or controller id assigned to the target.") + public void targetTokenAuthentification() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final Artifact artifact = artifacts.get(0); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceBySha1(artifact.getSha1Hash())); + + deploymentManagement.assignDistributionSet(distributionSet.getId(), + Arrays.asList(new TargetWithActionType(TARGET))); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + @Test + @Description("Verify that the receive message contains a 404, if there is no artifact to the given filename") + public void artifactForFileResourceFileNameNotFound() { + enableAnonymousAuthentification(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceByFilename("Test.txt")); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=null, artifactId=null, filename=Test.txt]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404, if there is no distribution set assigned to the target") + public void artifactForFileResourceFileNameFoundTargetExistsButNotAssigned() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.createFileResourceByFilename(artifacts.get(0).getFilename())); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=null, artifactId=null, filename=filename0]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404, if there is no exisiting target") + public void artifactForFileResourceArtifactIdFoundTargetNotExists() { + enableAnonymousAuthentification(); + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifacts.get(0).getId()); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, "Artifact for resource FileResource [sha1=null, artifactId=" + + artifacts.get(0).getId() + ", filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 200 and a artifact for the given artifact id") + public void artifactForFileResourceArtifactIdFoundTargetExistsIsAssigned() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + final Artifact artifact = artifacts.get(0); + final FileResource fileResource = FileResource.createFileResourceByArtifactId(artifact.getId()); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource); + + deploymentManagement.assignDistributionSet(distributionSet.getId(), + Arrays.asList(new TargetWithActionType(TARGET))); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + @Test + @Description("Verify that the receive message contains a 404, if there is no artifact to the given softwareModuleFilename") + public void artifactForFileResourceSoftwareModuleFilenameNotFound() { + enableAnonymousAuthentification(); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, + FileResource.softwareModuleFilename(1L, "Test.txt")); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 404, if there is no existing target for the file resource") + public void artifactForFileResourceSoftwareModuleFilenameFoundTargetNotExists() { + enableAnonymousAuthentification(); + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + + final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get(); + final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule); + + final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(), + softwareModule.getArtifact(artifact.getId()).get().getFilename()); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyResult(returnMessage, HttpStatus.NOT_FOUND, + "Artifact for resource FileResource [sha1=null, artifactId=null, filename=null]not found "); + + } + + @Test + @Description("Verify that the receive message contains a 200 and a artifact, if there is a existing artifct fpt the for the given softwareModuleFilename") + public void artifactForFileResourceSoftwareModuleFilenameFoundTargetExistsIsAssigned() { + createTarget(TARGET); + + final DistributionSet distributionSet = createDistributionSet(); + final List artifacts = createArtifacts(distributionSet); + + final SoftwareModule softwareModule = distributionSet.getModules().stream().findFirst().get(); + final Artifact artifact = findArtifactOfSoftwareModule(artifacts, softwareModule); + + final FileResource fileResource = FileResource.softwareModuleFilename(softwareModule.getId(), + softwareModule.getArtifact(artifact.getId()).get().getFilename()); + final TenantSecurityToken securityToken = createTenantSecurityToken(TENANT_EXIST, TARGET, fileResource); + + deploymentManagement.assignDistributionSet(distributionSet.getId(), + Arrays.asList(new TargetWithActionType(TARGET))); + + final Message returnMessage = sendAndReceiveAuthenticationMessage(securityToken); + verifyOkResult(returnMessage, artifact); + + } + + private void verifyOkResult(Message returnMessage, Artifact artifact) { + final DownloadResponse convertedMessage = verifyResult(returnMessage, HttpStatus.OK, null); + assertThat(convertedMessage.getDownloadUrl()).isNotNull(); + assertThat(convertedMessage.getArtifact()).isNotNull(); + assertThat(convertedMessage.getArtifact().getHashes().getSha1()).isEqualTo(artifact.getSha1Hash()); + + } + + private void enableAnonymousAuthentification() { + tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED, + true); + tenantConfigurationManagement.addOrUpdateConfiguration( + TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, false); + } + + private void enableTargetTokenAuthentification() { + tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.ANONYMOUS_DOWNLOAD_MODE_ENABLED, + false); + tenantConfigurationManagement.addOrUpdateConfiguration( + TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED, true); + } + + private Target createTarget(final String controllerId) { + return targetManagement.createTarget( + entityFactory.target().create().controllerId(controllerId).securityToken(TARGET_SECRUITY_TOKEN)); + } + + private TenantSecurityToken createTenantSecurityToken(final String tenant, final String controllerId, + final FileResource fileResource) { + final TenantSecurityToken tenantSecurityToken = new TenantSecurityToken(tenant, controllerId, fileResource); + tenantSecurityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER); + return tenantSecurityToken; + } + + private TenantSecurityToken createTenantSecurityToken(final String tenant, final Long targetId, + final String controllerId, final FileResource fileResource) { + final TenantSecurityToken tenantSecurityToken = new TenantSecurityToken(tenant, null, controllerId, targetId, + fileResource); + tenantSecurityToken.putHeader(TenantSecurityToken.AUTHORIZATION_HEADER, TARGET_TOKEN_HEADER); + return tenantSecurityToken; + } + + private DistributionSet createDistributionSet() { + return testdataFactory.createDistributionSet("one"); + } + + private List createArtifacts(final DistributionSet distributionSet) { + final List artifacts = new ArrayList<>(); + for (final SoftwareModule module : distributionSet.getModules()) { + artifacts.addAll(testdataFactory.createArtifacts(module.getId())); + } + return artifacts; + } + + private DownloadResponse verifyResult(final Message returnMessage, final HttpStatus expectedStatus, + final String expectedMessage) { + final DownloadResponse convertedMessage = (DownloadResponse) getDmfClient().getMessageConverter() + .fromMessage(returnMessage); + assertThat(convertedMessage.getResponseCode()).isEqualTo(expectedStatus.value()); + + if (!StringUtils.isEmpty(expectedMessage)) { + assertThat(convertedMessage.getMessage()).isEqualTo(expectedMessage); + } + return convertedMessage; + } + + private Message sendAndReceiveAuthenticationMessage(final TenantSecurityToken securityToken) { + return getDmfClient().sendAndReceive(createMessage(securityToken, new MessageProperties())); + } + + private Artifact findArtifactOfSoftwareModule(final List artifacts, final SoftwareModule softwareModule) { + return artifacts.stream().filter(space -> space.getSoftwareModule().getId().equals(softwareModule.getId())) + .findFirst().get(); + } + + @Override + protected String getExchange() { + return AmqpSettings.AUTHENTICATION_EXCHANGE; + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java new file mode 100644 index 000000000..102c3c1e8 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageDispatcherServiceIntegrationTest.java @@ -0,0 +1,116 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; +import org.junit.Test; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Device Management Federation API") +@Stories("Amqp Message Dispatcher Service") +public class AmqpMessageDispatcherServiceIntegrationTest extends AmqpServiceIntegrationTest { + + @Test + @Description("Verify that a distribution assignment send a download and install message.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) + public void sendDownloadAndInstallStatus() { + registerTargetAndAssignDistributionSet(); + + createAndSendTarget(TENANT_EXIST); + waitUntilTargetStatusIsPending(); + assertDownloadAndInstallMessage(getDistributionSet().getModules()); + } + + @Test + @Description("Verify that a distribution assignment multiple times send cancel and assign events with right softwaremodules") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = CancelTargetAssignmentEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 2), @Expect(type = ActionUpdatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 6), + @Expect(type = DistributionSetCreatedEvent.class, count = 2), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 3) }) + public void assignDistributionSetMultipleTimes() { + final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(); + + final DistributionSet distributionSet2 = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + registerTargetAndAssignDistributionSet(distributionSet2.getId(), TargetUpdateStatus.PENDING, + getDistributionSet().getModules()); + assertCancelActionMessage(assignmentResult.getActions().get(0)); + + createAndSendTarget(TENANT_EXIST); + waitUntilTargetStatusIsPending(); + assertCancelActionMessage(assignmentResult.getActions().get(0)); + + } + + @Test + @Description("Verify that a cancel assignment send a cancel message.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = CancelTargetAssignmentEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 2) }) + public void sendCancelStatus() { + final Long actionId = registerTargetAndCancelActionId(); + + createAndSendTarget(TENANT_EXIST); + waitUntilTargetStatusIsPending(); + assertCancelActionMessage(actionId); + + } + + private void waitUntilTargetStatusIsPending() { + waitUntil(() -> { + final Optional findTargetByControllerID = targetManagement + .findTargetByControllerID(REGISTER_TARGET); + return findTargetByControllerID.isPresent() && TargetUpdateStatus.PENDING + .equals(findTargetByControllerID.get().getUpdateStatus()); + }); + } + + private void waitUntil(final Callable callable) { + createConditionFactory().until(() -> { + return securityRule.runAsPrivileged(callable); + }); + } + + private void createAndSendTarget(final String tenant) { + createAndSendTarget(REGISTER_TARGET, tenant); + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java new file mode 100644 index 000000000..98ecf833b --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpMessageHandlerServiceIntegrationTest.java @@ -0,0 +1,618 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; +import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; +import org.eclipse.hawkbit.dmf.amqp.api.MessageType; +import org.eclipse.hawkbit.dmf.json.model.ActionStatus; +import org.eclipse.hawkbit.dmf.json.model.ActionUpdateStatus; +import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate; +import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent; +import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.ActionUpdatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.CancelTargetAssignmentEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.SoftwareModuleCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent; +import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent; +import org.eclipse.hawkbit.repository.model.Action; +import org.eclipse.hawkbit.repository.model.Action.Status; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.repository.test.matcher.Expect; +import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; + +import ru.yandex.qatools.allure.annotations.Description; +import ru.yandex.qatools.allure.annotations.Features; +import ru.yandex.qatools.allure.annotations.Stories; + +@Features("Component Tests - Device Management Federation API") +@Stories("Amqp Message Handler Service") +public class AmqpMessageHandlerServiceIntegrationTest extends AmqpServiceIntegrationTest { + + @Test + @Description("Tests register target") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 2), + @Expect(type = TargetPollEvent.class, count = 3) }) + public void registerTargets() { + registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1); + + final String target2 = "Target2"; + registerAndAssertTargetWithExistingTenant(target2, 2); + final Long pollingTimeTarget2 = controllerManagement.findByControllerId(target2).get().getLastTargetQuery(); + registerSameTargetAndAssertBasedOnLastPolling(target2, 2, TargetUpdateStatus.REGISTERED, pollingTimeTarget2); + Mockito.verifyZeroInteractions(getDeadletterListener()); + } + + @Test + @Description("Tests register invalid target withy empty controller id. Tests register invalid target with null controller id") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void registerEmptyTarget() { + createAndSendTarget("", TENANT_EXIST); + assertAllTargetsCount(0); + verifyDeadLetterMessages(1); + + } + + @Test + @Description("Tests register invalid target with whitspace controller id. Tests register invalid target with null controller id") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void registerWhitespaceTarget() { + createAndSendTarget("Invalid Invalid", TENANT_EXIST); + assertAllTargetsCount(0); + verifyDeadLetterMessages(1); + + } + + @Test + @Description("Tests register invalid target with null controller id. Tests register invalid target with null controller id") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void registerInvalidNullTargets() { + createAndSendTarget(null, TENANT_EXIST); + assertAllTargetsCount(0); + verifyDeadLetterMessages(1); + + } + + @Test + @Description("Tests not allowed content-type in message. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void wrongContentType() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST); + createTargetMessage.getMessageProperties().setContentType("WrongContentType"); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests null reply to property in message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void missingReplyToProperty() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST); + createTargetMessage.getMessageProperties().setReplyTo(null); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests missing reply to property in message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void emptyReplyToProperty() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST); + createTargetMessage.getMessageProperties().setReplyTo(""); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests missing thing id property in message. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void missingThingIdProperty() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.THING_ID); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests null thing id property in message. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void nullThingIdProperty() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests missing tenant message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void missingTenantHeader() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TENANT); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests null tenant message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void nullTenantHeader() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, null); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests empty tenant message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void emptyTenantHeader() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, ""); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests tenant not exist. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void tenantNotExist() { + final Message createTargetMessage = createTargetMessage(REGISTER_TARGET, "TenantNotExist"); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertThat(systemManagement.findTenants()).hasSize(1); + } + + @Test + @Description("Tests missing type message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void missingTypeHeader() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TYPE); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests null type message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void nullTypeHeader() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, null); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests empty type message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void emptyTypeHeader() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, ""); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests invalid type message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void invalidTypeHeader() { + final Message createTargetMessage = createTargetMessage(null, TENANT_EXIST); + createTargetMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TYPE, "NotExist"); + getDmfClient().send(createTargetMessage); + + verifyDeadLetterMessages(1); + assertAllTargetsCount(0); + } + + @Test + @Description("Tests null topic message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void nullTopicHeader() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, ""); + eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, null); + getDmfClient().send(eventMessage); + + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests null topic message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void emptyTopicHeader() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, ""); + eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, ""); + getDmfClient().send(eventMessage); + + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests null topic message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void invalidTopicHeader() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, ""); + eventMessage.getMessageProperties().getHeaders().put(MessageHeaderKey.TOPIC, "NotExist"); + getDmfClient().send(eventMessage); + + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests missing topic message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void missingTopicHeader() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, ""); + eventMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.TOPIC); + getDmfClient().send(eventMessage); + + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests invalid null message content. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithNullContent() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, null); + getDmfClient().send(eventMessage); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests invalid empty message content. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithEmptyContent() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, ""); + getDmfClient().send(eventMessage); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests invalid json message content. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithInvalidJsonContent() { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, + "Invalid Content"); + getDmfClient().send(eventMessage); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests invalid topic message header. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 0) }) + public void updateActionStatusWithInvalidActionId() { + final ActionUpdateStatus actionUpdateStatus = new ActionUpdateStatus(1L, ActionStatus.RUNNING); + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, + actionUpdateStatus); + getDmfClient().send(eventMessage); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Tests register target and cancel a assignment") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) }) + public void finishActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.FINISHED, Status.FINISHED); + } + + @Test + @Description("Register a target and send a update action status (running). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 0), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void runningActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.RUNNING, Status.RUNNING); + } + + @Test + @Description("Register a target and send a update action status (download). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void downloadActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.DOWNLOAD, Status.DOWNLOAD); + } + + @Test + @Description("Register a target and send a update action status (error). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 2), @Expect(type = TargetPollEvent.class, count = 1) }) + public void errorActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.ERROR, Status.ERROR); + } + + @Test + @Description("Register a target and send a update action status (warning). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void warningActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.WARNING, Status.WARNING); + } + + @Test + @Description("Register a target and send a update action status (retrieved). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void retrievedActionStatus() { + registerTargetAndSendAndAssertUpdateActionStatus(ActionStatus.RETRIEVED, Status.RETRIEVED); + } + + @Test + @Description("Register a target and send a invalid update action status (cancel). This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void cancelNotAllowActionStatus() { + registerTargetAndSendActionStatus(ActionStatus.CANCELED); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Verfiy receiving a download and install message if a deployment is done before the target has polled the first time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void receiveDownLoadAndInstallMessageAfterAssignment() { + + // setup + controllerManagement.findOrRegisterTargetIfItDoesNotexist(REGISTER_TARGET, null); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + assignDistributionSet(distributionSet.getId(), REGISTER_TARGET); + + // test + registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, TargetUpdateStatus.PENDING, "bumlux"); + + // verify + assertDownloadAndInstallMessage(distributionSet.getModules()); + Mockito.verifyZeroInteractions(getDeadletterListener()); + } + + @Test + @Description("Verfiy receiving a cancel update message if a deployment is canceled before the target has polled the first time.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = CancelTargetAssignmentEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = TargetUpdatedEvent.class, count = 1), + @Expect(type = TargetPollEvent.class, count = 1) }) + public void receiveCancelUpdateMessageAfterAssignmentWasCanceled() { + + // Setup + controllerManagement.findOrRegisterTargetIfItDoesNotexist(REGISTER_TARGET, null); + final DistributionSet distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + final DistributionSetAssignmentResult distributionSetAssignmentResult = assignDistributionSet( + distributionSet.getId(), REGISTER_TARGET); + deploymentManagement.cancelAction(distributionSetAssignmentResult.getActions().get(0)); + + // test + registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, TargetUpdateStatus.PENDING, "bumlux"); + + // verify + assertCancelActionMessage(distributionSetAssignmentResult.getActions().get(0)); + Mockito.verifyZeroInteractions(getDeadletterListener()); + } + + @Test + @Description("Register a target and send a invalid update action status (canceled). The current status (pending) is not a canceling state. This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = CancelTargetAssignmentEvent.class, count = 1), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void actionNotExists() { + final Long actionId = registerTargetAndCancelActionId(); + final Long actionNotExist = actionId + 1; + + sendActionUpdateStatus(new ActionUpdateStatus(actionNotExist, ActionStatus.CANCELED)); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Register a target and send a invalid update action status (cancel_rejected). This message should forwarded to the deadletter queue") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void canceledRejectedNotAllowActionStatus() { + registerTargetAndSendActionStatus(ActionStatus.CANCEL_REJECTED); + verifyDeadLetterMessages(1); + } + + @Test + @Description("Register a target and send a valid update action status (cancel_rejected). Verfiy if the updated action status is correct.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetAssignDistributionSetEvent.class, count = 1), + @Expect(type = CancelTargetAssignmentEvent.class, count = 1), + @Expect(type = ActionUpdatedEvent.class, count = 1), @Expect(type = ActionCreatedEvent.class, count = 1), + @Expect(type = SoftwareModuleCreatedEvent.class, count = 3), + @Expect(type = DistributionSetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void canceledRejectedActionStatus() { + final Long actionId = registerTargetAndCancelActionId(); + + sendActionUpdateStatus(new ActionUpdateStatus(actionId, ActionStatus.CANCEL_REJECTED)); + assertAction(actionId, Status.RUNNING, Status.CANCELING, Status.CANCEL_REJECTED); + } + + @Test + @Description("Verify that sending an update controller attribute message to an existing target works.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 1), @Expect(type = TargetPollEvent.class, count = 1) }) + public void updateAttributes() { + + // setup + final String target = "ControllerAttributeTestTarget"; + registerAndAssertTargetWithExistingTenant(target, 1); + final AttributeUpdate controllerAttribute = new AttributeUpdate(); + controllerAttribute.getAttributes().put("test1", "testA"); + controllerAttribute.getAttributes().put("test2", "testB"); + + // test + sendUpdateAttributeMessage(target, TENANT_EXIST, controllerAttribute); + + // validate + assertUpdateAttributes(target, controllerAttribute.getAttributes()); + } + + @Test + @Description("Verify that sending an update controller attribute message with no thingid header to an existing target does not work.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 0), @Expect(type = TargetPollEvent.class, count = 1) }) + public void updateAttributesWithNoThingId() { + + // setup + final String target = "ControllerAttributeTestTarget"; + registerAndAssertTargetWithExistingTenant(target, 1); + final AttributeUpdate controllerAttribute = new AttributeUpdate(); + controllerAttribute.getAttributes().put("test1", "testA"); + controllerAttribute.getAttributes().put("test2", "testB"); + final Message createUpdateAttributesMessage = createUpdateAttributesMessage(null, TENANT_EXIST, + controllerAttribute); + createUpdateAttributesMessage.getMessageProperties().getHeaders().remove(MessageHeaderKey.THING_ID); + + // test + getDmfClient().send(createUpdateAttributesMessage); + + // verify + verifyDeadLetterMessages(1); + final AttributeUpdate controllerAttributeEmpty = new AttributeUpdate(); + assertUpdateAttributes(target, controllerAttributeEmpty.getAttributes()); + } + + @Test + @Description("Verify that sending an update controller attribute message with invalid body to an existing target does not work.") + @ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1), + @Expect(type = TargetUpdatedEvent.class, count = 0), @Expect(type = TargetPollEvent.class, count = 1) }) + public void updateAttributesWithWrongBody() { + + // setup + final String target = "ControllerAttributeTestTarget"; + registerAndAssertTargetWithExistingTenant(target, 1); + final AttributeUpdate controllerAttribute = new AttributeUpdate(); + controllerAttribute.getAttributes().put("test1", "testA"); + controllerAttribute.getAttributes().put("test2", "testB"); + final Message createUpdateAttributesMessageWrongBody = createUpdateAttributesMessageWrongBody(target, + TENANT_EXIST); + + // test + getDmfClient().send(createUpdateAttributesMessageWrongBody); + + // verify + verifyDeadLetterMessages(1); + } + + private Long registerTargetAndSendActionStatus(final ActionStatus sendActionStatus) { + final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(); + final Long actionId = assignmentResult.getActions().get(0); + sendActionUpdateStatus(new ActionUpdateStatus(actionId, sendActionStatus)); + return actionId; + } + + private void sendActionUpdateStatus(final ActionUpdateStatus actionStatus) { + final Message eventMessage = createEventMessage(TENANT_EXIST, EventTopic.UPDATE_ACTION_STATUS, actionStatus); + getDmfClient().send(eventMessage); + } + + private void registerTargetAndSendAndAssertUpdateActionStatus(final ActionStatus sendActionStatus, + final Status expectedActionStatus) { + final Long actionId = registerTargetAndSendActionStatus(sendActionStatus); + assertAction(actionId, Status.RUNNING, expectedActionStatus); + } + + private void assertAction(final Long actionId, final Status... expectedActionStates) { + final Action action = waitUntilIsPresent(() -> controllerManagement.findActionWithDetails(actionId)); + final List status = action.getActionStatus().stream().map(actionStatus -> actionStatus.getStatus()) + .collect(Collectors.toList()); + assertThat(status).containsOnly(expectedActionStates); + } + + private Message createEventMessage(final String tenant, final EventTopic eventTopic, final Object payload) { + final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); + messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString()); + messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, eventTopic.toString()); + + return createMessage(payload, messageProperties); + } + + private void sendUpdateAttributeMessage(final String target, final String tenant, + final AttributeUpdate attributeUpdate) { + final Message updateMessage = createUpdateAttributesMessage(target, tenant, attributeUpdate); + getDmfClient().send(updateMessage); + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java new file mode 100644 index 000000000..88988709b --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/AmqpServiceIntegrationTest.java @@ -0,0 +1,281 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; + +import org.eclipse.hawkbit.AmqpTestConfiguration; +import org.eclipse.hawkbit.dmf.amqp.api.AmqpSettings; +import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; +import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; +import org.eclipse.hawkbit.dmf.amqp.api.MessageType; +import org.eclipse.hawkbit.dmf.json.model.AttributeUpdate; +import org.eclipse.hawkbit.dmf.json.model.DownloadAndUpdateRequest; +import org.eclipse.hawkbit.integration.listener.DeadletterListener; +import org.eclipse.hawkbit.integration.listener.ReplyToListener; +import org.eclipse.hawkbit.matcher.SoftwareModuleJsonMatcher; +import org.eclipse.hawkbit.repository.model.DistributionSet; +import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult; +import org.eclipse.hawkbit.repository.model.Target; +import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.util.IpUtil; +import org.junit.Assert; +import org.junit.Before; +import org.mockito.Mockito; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.core.MessageProperties; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; +import org.springframework.amqp.rabbit.test.RabbitListenerTestHarness; +import org.springframework.beans.factory.annotation.Autowired; + +/** + * + * Common class for {@link AmqpMessageHandlerServiceIntegrationTest} and + * {@link AmqpMessageDispatcherServiceIntegrationTest}. + */ +public abstract class AmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest { + + protected static final String TENANT_EXIST = "DEFAULT"; + protected static final String REGISTER_TARGET = "NewDmfTarget"; + protected static final String CREATED_BY = "CONTROLLER_PLUG_AND_PLAY"; + + private DeadletterListener deadletterListener; + private ReplyToListener replyToListener; + private DistributionSet distributionSet; + + @Autowired + private RabbitListenerTestHarness harness; + + @Autowired + private ConnectionFactory connectionFactory; + + @Before + public void initListener() { + deadletterListener = harness.getSpy(DeadletterListener.LISTENER_ID); + assertThat(deadletterListener).isNotNull(); + Mockito.reset(deadletterListener); + replyToListener = harness.getSpy(ReplyToListener.LISTENER_ID); + assertThat(replyToListener).isNotNull(); + Mockito.reset(replyToListener); + getDmfClient().setExchange(AmqpSettings.DMF_EXCHANGE); + } + + protected T waitUntilIsPresent(final Callable> callable) { + createConditionFactory().until(() -> { + return securityRule.runAsPrivileged(() -> callable.call().isPresent()); + }); + + try { + return callable.call().get(); + } catch (final Exception e) { + return null; + } + } + + protected void verifyDeadLetterMessages(final int expectedMessages) { + createConditionFactory().until(() -> { + Mockito.verify(getDeadletterListener(), Mockito.times(expectedMessages)).handleMessage(Mockito.any()); + }); + } + + protected DeadletterListener getDeadletterListener() { + return deadletterListener; + } + + protected DistributionSet getDistributionSet() { + return distributionSet; + } + + protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet() { + distributionSet = testdataFactory.createDistributionSet(UUID.randomUUID().toString()); + return registerTargetAndAssignDistributionSet(distributionSet.getId(), TargetUpdateStatus.REGISTERED, + distributionSet.getModules()); + } + + protected DistributionSetAssignmentResult registerTargetAndAssignDistributionSet(final Long assignDs, + final TargetUpdateStatus expectedStatus, + final Set expectedSoftwareModulesInMessage) { + registerAndAssertTargetWithExistingTenant(REGISTER_TARGET, 1, expectedStatus, CREATED_BY); + + final DistributionSetAssignmentResult assignmentResult = assignDistributionSet(assignDs, REGISTER_TARGET); + assertDownloadAndInstallMessage(expectedSoftwareModulesInMessage); + return assignmentResult; + } + + protected void assertCancelActionMessage(final Long actionId) { + final Message replyMessage = assertReplyMessageHeader(EventTopic.CANCEL_DOWNLOAD); + + final Long actionUpdateStatus = (Long) getDmfClient().getMessageConverter().fromMessage(replyMessage); + assertThat(actionUpdateStatus).isEqualTo(actionId); + } + + protected void assertDownloadAndInstallMessage( + final Set dsModules) { + final Message replyMessage = assertReplyMessageHeader(EventTopic.DOWNLOAD_AND_INSTALL); + assertAllTargetsCount(1); + + final DownloadAndUpdateRequest downloadAndUpdateRequest = (DownloadAndUpdateRequest) getDmfClient() + .getMessageConverter().fromMessage(replyMessage); + + Assert.assertThat(dsModules, + SoftwareModuleJsonMatcher.containsExactly(downloadAndUpdateRequest.getSoftwareModules())); + + final Target updatedTarget = waitUntilIsPresent( + () -> targetManagement.findTargetByControllerID(REGISTER_TARGET)); + + assertThat(updatedTarget.getSecurityToken()).isEqualTo(downloadAndUpdateRequest.getTargetSecurityToken()); + } + + protected void createAndSendTarget(final String target, final String tenant) { + final Message message = createTargetMessage(target, tenant); + getDmfClient().send(message); + } + + protected void verifyReplyToListener() { + createConditionFactory().until(() -> { + Mockito.verify(replyToListener, Mockito.atLeast(1)).handleMessage(Mockito.any()); + }); + } + + protected Long cancelAction(final Long actionId) { + deploymentManagement.cancelAction(actionId); + assertCancelActionMessage(actionId); + return actionId; + } + + protected Long registerTargetAndCancelActionId() { + final DistributionSetAssignmentResult assignmentResult = registerTargetAndAssignDistributionSet(); + return cancelAction(assignmentResult.getActions().get(0)); + } + + protected void assertAllTargetsCount(final long expectedTargetsCount) { + assertThat(targetManagement.countTargetsAll()).isEqualTo(expectedTargetsCount); + } + + private Message assertReplyMessageHeader(final EventTopic eventTopic) { + verifyReplyToListener(); + final Message replyMessage = replyToListener.getMessages().get(eventTopic); + assertAllTargetsCount(1); + final Map headers = replyMessage.getMessageProperties().getHeaders(); + assertThat(headers.get(MessageHeaderKey.TOPIC)).isEqualTo(eventTopic.toString()); + assertThat(headers.get(MessageHeaderKey.THING_ID)).isEqualTo(REGISTER_TARGET); + assertThat(headers.get(MessageHeaderKey.TENANT)).isEqualTo(TENANT_EXIST); + assertThat(headers.get(MessageHeaderKey.TYPE)).isEqualTo(MessageType.EVENT.toString()); + return replyMessage; + } + + protected void registerAndAssertTargetWithExistingTenant(final String target, + final int existingTargetsAfterCreation) { + + registerAndAssertTargetWithExistingTenant(target, existingTargetsAfterCreation, TargetUpdateStatus.REGISTERED, + CREATED_BY); + + } + + protected void registerAndAssertTargetWithExistingTenant(final String target, + final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus, + final String createdBy) { + createAndSendTarget(target, TENANT_EXIST); + final Target registerdTarget = waitUntilIsPresent(() -> targetManagement.findTargetByControllerID(target)); + assertAllTargetsCount(existingTargetsAfterCreation); + assertTarget(registerdTarget, expectedTargetStatus, createdBy); + } + + protected void registerSameTargetAndAssertBasedOnLastPolling(final String target, + final int existingTargetsAfterCreation, final TargetUpdateStatus expectedTargetStatus, + final Long pollingTimeTargetOld) { + createAndSendTarget(target, TENANT_EXIST); + final Target registerdTarget = waitUntilIsPresent( + () -> findTargetBasedOnNewPolltime(target, pollingTimeTargetOld)); + assertAllTargetsCount(existingTargetsAfterCreation); + assertThat(registerdTarget.getUpdateStatus()).isEqualTo(expectedTargetStatus); + } + + private Optional findTargetBasedOnNewPolltime(final String controllerId, final Long pollingTimeTargetOld) { + final Optional target2 = controllerManagement.findByControllerId(controllerId); + final Long pollingTimeTargetNew = target2.get().getLastTargetQuery(); + if (pollingTimeTargetOld < pollingTimeTargetNew) { + return target2; + } + return Optional.empty(); + } + + private void assertTarget(final Target target, final TargetUpdateStatus updateStatus, final String createdBy) { + assertThat(target.getTenant()).isEqualTo(TENANT_EXIST); + assertThat(target.getDescription()).contains("Plug and Play"); + assertThat(target.getDescription()).contains(target.getControllerId()); + assertThat(target.getCreatedBy()).isEqualTo(createdBy); + assertThat(target.getUpdateStatus()).isEqualTo(updateStatus); + assertThat(target.getAddress()).isEqualTo( + IpUtil.createAmqpUri(connectionFactory.getVirtualHost(), AmqpTestConfiguration.REPLY_TO_EXCHANGE)); + } + + protected Message createTargetMessage(final String target, final String tenant) { + final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); + messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target); + messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.THING_CREATED.toString()); + messageProperties.setReplyTo(AmqpTestConfiguration.REPLY_TO_EXCHANGE); + + return createMessage("", messageProperties); + } + + protected MessageProperties createMessagePropertiesWithTenant(final String tenant) { + final MessageProperties messageProperties = new MessageProperties(); + messageProperties.getHeaders().put(MessageHeaderKey.TENANT, tenant); + return messageProperties; + } + + protected Message createUpdateAttributesMessage(final String target, final String tenant, + final AttributeUpdate attributeUpdate) { + final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); + messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target); + messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString()); + messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ATTRIBUTES.toString()); + + return createMessage(attributeUpdate, messageProperties); + + } + + protected Message createUpdateAttributesMessageWrongBody(final String target, final String tenant) { + final MessageProperties messageProperties = createMessagePropertiesWithTenant(tenant); + messageProperties.getHeaders().put(MessageHeaderKey.THING_ID, target); + messageProperties.getHeaders().put(MessageHeaderKey.TYPE, MessageType.EVENT.toString()); + messageProperties.getHeaders().put(MessageHeaderKey.TOPIC, EventTopic.UPDATE_ATTRIBUTES.toString()); + + return createMessage("wrongbody", messageProperties); + + } + + protected void assertUpdateAttributes(final String controllerId, final Map attributes) { + final Target findByControllerId = waitUntilIsPresent( + () -> controllerManagement.findByControllerId(controllerId)); + final Map controllerAttributes = targetManagement + .getControllerAttributes(findByControllerId.getControllerId()); + assertThat(controllerAttributes.size()).isEqualTo(attributes.size()); + attributes.forEach((k, v) -> assertKeyValueInMap(k, v, controllerAttributes)); + } + + private void assertKeyValueInMap(final String key, final String value, + final Map controllerAttributes) { + assertThat(controllerAttributes.containsKey(key)).isTrue(); + assertThat(controllerAttributes.get(key)).isEqualTo(value); + } + + @Override + protected String getExchange() { + return AmqpSettings.DMF_EXCHANGE; + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/DeadletterListener.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/DeadletterListener.java new file mode 100644 index 000000000..58a780bec --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/DeadletterListener.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration.listener; + +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitListener; + +public class DeadletterListener implements TestRabbitListener { + + public static final String LISTENER_ID = "deadletter"; + + @Override + @RabbitListener(id = "deadletter", queues = "dmf_connector_deadletter_ttl") + public void handleMessage(Message message) { + // currently the message is not needed + } + +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/ReplyToListener.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/ReplyToListener.java new file mode 100644 index 000000000..977cab179 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/ReplyToListener.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration.listener; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.hawkbit.AmqpTestConfiguration; +import org.eclipse.hawkbit.dmf.amqp.api.EventTopic; +import org.eclipse.hawkbit.dmf.amqp.api.MessageHeaderKey; +import org.springframework.amqp.core.Message; +import org.springframework.amqp.rabbit.annotation.RabbitListener; + +public class ReplyToListener implements TestRabbitListener { + + public static final String LISTENER_ID = "replyto"; + + private final Map messages = new HashMap<>(); + + @Override + @RabbitListener(id = LISTENER_ID, queues = AmqpTestConfiguration.REPLY_TO_QUEUE) + public void handleMessage(Message message) { + final EventTopic eventTopic = EventTopic + .valueOf(message.getMessageProperties().getHeaders().get(MessageHeaderKey.TOPIC).toString()); + messages.put(eventTopic, message); + } + + public Map getMessages() { + return messages; + } +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/TestRabbitListener.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/TestRabbitListener.java new file mode 100644 index 000000000..28264c17d --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/integration/listener/TestRabbitListener.java @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.integration.listener; + +import org.springframework.amqp.core.Message; + +public interface TestRabbitListener { + + void handleMessage(Message message); +} diff --git a/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java new file mode 100644 index 000000000..1bc28e7e2 --- /dev/null +++ b/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/matcher/SoftwareModuleJsonMatcher.java @@ -0,0 +1,113 @@ +/** + * Copyright (c) 2015 Bosch Software Innovations GmbH and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.eclipse.hawkbit.matcher; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import org.eclipse.hawkbit.repository.model.SoftwareModule; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Factory; + +/** + * Set matcher for {@link SoftwareModule} and a list of + * {@link org.eclipse.hawkbit.dmf.json.model.SoftwareModule}. + */ +public final class SoftwareModuleJsonMatcher { + + /** + * Creates a matcher that matches when the list of repository software + * modules arelogically equal to the specified JSON software modules. + *

+ * If the specified repository software modules are null then + * the created matcher will only match if the JSON software modules are + * null + *

+ * For example: + * + *

+     * List modules;
+     * List expectedModules;
+     * 
+     * assertThat(modules, containsExactly(expectedModules));
+     * 
+ * + * @param expectedModules + * the json sofware modules. + */ + @Factory + public static SoftwareModulesMatcher containsExactly( + final List expectedModules) { + return new SoftwareModulesMatcher(expectedModules); + } + + private static class SoftwareModulesMatcher extends BaseMatcher> { + + private final List expectedModules; + + public SoftwareModulesMatcher(List expectedModules) { + this.expectedModules = expectedModules; + } + + static boolean containsExactly(Object actual, + List expected) { + if (actual == null) { + return expected == null; + } + + @SuppressWarnings("unchecked") + final Collection modules = (Collection) actual; + + if (modules.size() != expected.size()) { + return false; + } + + for (final SoftwareModule repoSoftwareModule : modules) { + boolean containsElement = false; + + for (final org.eclipse.hawkbit.dmf.json.model.SoftwareModule jsonSoftwareModule : expected) { + if (!jsonSoftwareModule.getModuleId().equals(repoSoftwareModule.getId())) { + continue; + } + containsElement = true; + + if (!jsonSoftwareModule.getModuleType().equals(repoSoftwareModule.getType().getKey())) { + return false; + } + if (!jsonSoftwareModule.getModuleVersion().equals(repoSoftwareModule.getVersion())) { + return false; + } + if (jsonSoftwareModule.getArtifacts().size() != repoSoftwareModule.getArtifacts().size()) { + return false; + } + } + + if (!containsElement) { + return false; + } + + } + + return true; + } + + @Override + public boolean matches(Object actualValue) { + return containsExactly(actualValue, expectedModules); + } + + @Override + public void describeTo(Description description) { + description.appendValue(expectedModules); + } + } + +} diff --git a/hawkbit-dmf-amqp/src/test/resources/application-test.properties b/hawkbit-dmf-amqp/src/test/resources/application-test.properties index 93a0c0990..ee59db261 100644 --- a/hawkbit-dmf-amqp/src/test/resources/application-test.properties +++ b/hawkbit-dmf-amqp/src/test/resources/application-test.properties @@ -7,6 +7,9 @@ # http://www.eclipse.org/legal/epl-v10.html # +# RabbitMQ +spring.rabbitmq.host=localhost + # supported: H2, MYSQL hawkbit.server.database=H2 @@ -33,4 +36,32 @@ H2.spring.datasource.password=sa MYSQL.spring.datasource.url=jdbc:mysql://localhost:3306/sp_test MYSQL.spring.datasource.driverClassName=org.mariadb.jdbc.Driver MYSQL.spring.datasource.username=root -MYSQL.spring.datasource.password= \ No newline at end of file +MYSQL.spring.datasource.password= + +# Default tenant configuration properties +hawkbit.server.tenant.configuration.authentication-header-enabled.keyName=authentication.header.enabled +hawkbit.server.tenant.configuration.authentication-header-enabled.defaultValue=false +hawkbit.server.tenant.configuration.authentication-header-enabled.dataType=java.lang.Boolean +hawkbit.server.tenant.configuration.authentication-header-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator + +hawkbit.server.tenant.configuration.authentication-header-authority.keyName=authentication.header.authority +hawkbit.server.tenant.configuration.authentication-header-authority.defaultValue=false + +hawkbit.server.tenant.configuration.authentication-targettoken-enabled.keyName=authentication.targettoken.enabled +hawkbit.server.tenant.configuration.authentication-targettoken-enabled.defaultValue=false +hawkbit.server.tenant.configuration.authentication-targettoken-enabled.dataType=java.lang.Boolean +hawkbit.server.tenant.configuration.authentication-targettoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator + +hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.keyName=authentication.gatewaytoken.enabled +hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.defaultValue=false +hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.dataType=java.lang.Boolean +hawkbit.server.tenant.configuration.authentication-gatewaytoken-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator + +hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.keyName=authentication.gatewaytoken.key +hawkbit.server.tenant.configuration.authentication-gatewaytoken-key.defaultValue=false + +hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous.download.enabled +hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=false +hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean +hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator + diff --git a/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/ActionUpdateStatus.java b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/ActionUpdateStatus.java index a2024bfc6..473b3c3cd 100644 --- a/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/ActionUpdateStatus.java +++ b/hawkbit-dmf-api/src/main/java/org/eclipse/hawkbit/dmf/json/model/ActionUpdateStatus.java @@ -24,21 +24,24 @@ import com.fasterxml.jackson.annotation.JsonProperty; @JsonInclude(Include.NON_NULL) @JsonIgnoreProperties(ignoreUnknown = true) public class ActionUpdateStatus { - @JsonProperty(required = true) - private Long actionId; + + private final Long actionId; + private final ActionStatus actionStatus; + @JsonProperty private Long softwareModuleId; - @JsonProperty(required = true) - private ActionStatus actionStatus; + @JsonProperty private List message; - public Long getActionId() { - return actionId; + public ActionUpdateStatus(@JsonProperty(value = "actionId", required = true) Long actionId, + @JsonProperty(value = "actionStatus", required = true) ActionStatus actionStatus) { + this.actionId = actionId; + this.actionStatus = actionStatus; } - public void setActionId(final Long actionId) { - this.actionId = actionId; + public Long getActionId() { + return actionId; } public Long getSoftwareModuleId() { @@ -53,10 +56,6 @@ public class ActionUpdateStatus { return actionStatus; } - public void setActionStatus(final ActionStatus actionStatus) { - this.actionStatus = actionStatus; - } - public List getMessage() { if (message == null) { return Collections.emptyList(); @@ -78,12 +77,12 @@ public class ActionUpdateStatus { return false; } - if (this.message == null) { - this.message = new ArrayList<>(messages); + if (message == null) { + message = new ArrayList<>(messages); return true; } - return this.message.addAll(messages); + return message.addAll(messages); } } diff --git a/pom.xml b/pom.xml index a74b46a98..aa9402976 100644 --- a/pom.xml +++ b/pom.xml @@ -112,6 +112,7 @@ 2.0.0 9.3.14.v20161028 + 1.7.1.RELEASE @@ -714,8 +715,20 @@ mariadb-java-client ${mariadb-java-client.version} test - - + + + org.springframework.amqp + spring-rabbit-junit + ${spring-amqp.version} + test + + + org.springframework.amqp + spring-rabbit-test + ${spring-amqp.version} + test + + com.google.guava guava ${guava.version}