use of io.vertx.junit5.Timeout in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandViaAmqpFailsForMalformedMessage.
/**
* Verifies that the adapter rejects malformed command messages sent by applications.
* <p>
* This test is applicable only if the messaging network type is AMQP.
*
* @param endpointConfig The endpoints to use for sending/receiving commands.
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("allCombinations")
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
@AssumeMessagingSystem(type = MessagingType.amqp)
public void testSendCommandViaAmqpFailsForMalformedMessage(final AmqpCommandEndpointConfiguration endpointConfig, final VertxTestContext ctx) throws InterruptedException {
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicReference<GenericSenderLink> amqpCmdSenderRef = new AtomicReference<>();
final VertxTestContext setup = new VertxTestContext();
final Checkpoint setupDone = setup.checkpoint();
final Checkpoint preconditions = setup.checkpoint(2);
connectToAdapter(tenantId, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
// expect empty notification with TTD -1
setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
final TimeUntilDisconnectNotification notification = msg.getTimeUntilDisconnectNotification().orElse(null);
log.debug("received notification [{}]", notification);
setup.verify(() -> assertThat(notification).isNotNull());
if (notification.getTtd() == -1) {
preconditions.flag();
}
})).compose(con -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId).map(recv -> {
recv.handler((delivery, msg) -> ctx.failNow(new IllegalStateException("should not have received command")));
return null;
})).compose(ok -> helper.createGenericAmqpMessageSender(endpointConfig.getNorthboundEndpoint(), tenantId)).map(s -> {
log.debug("created generic sender for sending commands [target address: {}]", endpointConfig.getSenderLinkTargetAddress(tenantId));
amqpCmdSenderRef.set(s);
preconditions.flag();
return s;
}).onComplete(setup.succeeding(v -> setupDone.flag()));
assertWithMessage("setup of adapter finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
final Checkpoint expectedFailures = ctx.checkpoint(2);
log.debug("sending command message lacking subject");
final Message messageWithoutSubject = ProtonHelper.message("input data");
messageWithoutSubject.setAddress(endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId));
messageWithoutSubject.setMessageId("message-id");
messageWithoutSubject.setReplyTo("reply/to/address");
amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutSubject, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
expectedFailures.flag();
}));
log.debug("sending command message lacking message ID and correlation ID");
final Message messageWithoutId = ProtonHelper.message("input data");
messageWithoutId.setAddress(endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId));
messageWithoutId.setSubject("setValue");
messageWithoutId.setReplyTo("reply/to/address");
amqpCmdSenderRef.get().sendAndWaitForOutcome(messageWithoutId, NoopSpan.INSTANCE).onComplete(ctx.failing(t -> {
ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
expectedFailures.flag();
}));
}
use of io.vertx.junit5.Timeout in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandFailsForCommandNotAcknowledgedByDevice.
/**
* Verifies that the adapter forwards the <em>released</em> disposition back to the
* application if the device hasn't sent a disposition update for the delivery of
* the command message sent to the device.
* <p>
* If Kafka is used, this means a corresponding error command response is published.
*
* @param ctx The vert.x test context.
* @throws InterruptedException if not all commands and responses are exchanged in time.
*/
@Test
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testSendCommandFailsForCommandNotAcknowledgedByDevice(final VertxTestContext ctx) throws InterruptedException {
final AmqpCommandEndpointConfiguration endpointConfig = new AmqpCommandEndpointConfiguration(SubscriberRole.DEVICE);
final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway() ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5) : deviceId;
final AtomicInteger receivedMessagesCounter = new AtomicInteger(0);
final int totalNoOfCommandsToSend = 2;
// command handler won't send a disposition update
connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig, (cmdReceiver, cmdResponseSender) -> createNotSendingDeliveryUpdateCommandConsumer(ctx, cmdReceiver, receivedMessagesCounter), totalNoOfCommandsToSend);
if (ctx.failed()) {
return;
}
final String replyId = "reply-id";
final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
final AtomicInteger commandsSent = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong();
final long start = System.currentTimeMillis();
final long commandTimeout = IntegrationTestSupport.getSendCommandTimeout();
final Handler<Void> failureNotificationReceivedHandler = v -> {
lastReceivedTimestamp.set(System.currentTimeMillis());
commandsFailed.countDown();
};
final VertxTestContext setup = new VertxTestContext();
final Future<MessageConsumer> kafkaAsyncErrorResponseConsumer = IntegrationTestSupport.isUsingKafkaMessaging() ? helper.createDeliveryFailureCommandResponseConsumer(ctx, tenantId, HttpURLConnection.HTTP_UNAVAILABLE, response -> failureNotificationReceivedHandler.handle(null), null) : Future.succeededFuture(null);
kafkaAsyncErrorResponseConsumer.onComplete(setup.succeedingThenComplete());
assertWithMessage("setup of command response consumer finished within %s seconds", IntegrationTestSupport.getTestSetupTimeout()).that(setup.awaitCompletion(IntegrationTestSupport.getTestSetupTimeout(), TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
ctx.failNow(setup.causeOfFailure());
return;
}
while (commandsSent.get() < totalNoOfCommandsToSend) {
final CountDownLatch commandSent = new CountDownLatch(1);
context.runOnContext(go -> {
final String correlationId = String.valueOf(commandsSent.getAndIncrement());
final Buffer msg = Buffer.buffer("value: " + commandsSent.get());
helper.applicationClient.sendAsyncCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain", msg, correlationId, replyId, null).onComplete(sendAttempt -> {
if (IntegrationTestSupport.isUsingAmqpMessaging()) {
if (sendAttempt.succeeded()) {
log.debug("sending command {} succeeded unexpectedly", commandsSent.get());
} else {
if (sendAttempt.cause() instanceof ServerErrorException && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE && !(sendAttempt.cause() instanceof SendMessageTimeoutException)) {
log.debug("sending command {} failed as expected: {}", commandsSent.get(), sendAttempt.cause().toString());
failureNotificationReceivedHandler.handle(null);
} else {
log.debug("sending command {} failed with an unexpected error", commandsSent.get(), sendAttempt.cause());
}
}
} else if (sendAttempt.failed()) {
log.debug("sending command {} via Kafka failed unexpectedly", commandsSent.get(), sendAttempt.cause());
}
commandSent.countDown();
});
});
commandSent.await();
}
final long timeToWait = 300 + (totalNoOfCommandsToSend * commandTimeout);
if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
log.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
}
assertThat(receivedMessagesCounter.get()).isEqualTo(totalNoOfCommandsToSend);
final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
log.info("commands sent: {}, commands failed: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
Optional.ofNullable(kafkaAsyncErrorResponseConsumer.result()).map(MessageConsumer::close).orElseGet(Future::succeededFuture).onComplete(ar -> {
if (commandsCompleted == commandsSent.get()) {
ctx.completeNow();
} else {
ctx.failNow(new IllegalStateException("did not complete all commands sent"));
}
});
}
use of io.vertx.junit5.Timeout in project hono by eclipse.
the class CommandAndControlAmqpIT method testSendCommandSucceeds.
private void testSendCommandSucceeds(final VertxTestContext ctx, final String commandTargetDeviceId, final AmqpCommandEndpointConfiguration endpointConfig, final BiFunction<ProtonReceiver, ProtonSender, ProtonMessageHandler> commandConsumerFactory, final Function<Buffer, Future<Void>> commandSender, final int totalNoOfCommandsToSend) throws InterruptedException {
connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig, commandConsumerFactory, totalNoOfCommandsToSend);
if (ctx.failed()) {
return;
}
final Checkpoint sendCommandsSucceeded = ctx.checkpoint();
final CountDownLatch commandsSucceeded = new CountDownLatch(totalNoOfCommandsToSend);
final AtomicInteger commandsSent = new AtomicInteger(0);
final AtomicLong lastReceivedTimestamp = new AtomicLong();
final long start = System.currentTimeMillis();
while (commandsSent.get() < totalNoOfCommandsToSend) {
final int currentMessage = commandsSent.incrementAndGet();
final CountDownLatch commandSent = new CountDownLatch(1);
context.runOnContext(go -> {
final Buffer payload = Buffer.buffer("value: " + currentMessage);
commandSender.apply(payload).onComplete(sendAttempt -> {
if (sendAttempt.failed()) {
log.debug("error sending command {}", currentMessage, sendAttempt.cause());
} else {
lastReceivedTimestamp.set(System.currentTimeMillis());
commandsSucceeded.countDown();
log.debug("sent command no {}", currentMessage);
if (commandsSucceeded.getCount() % 20 == 0) {
log.info("commands succeeded: {}", totalNoOfCommandsToSend - commandsSucceeded.getCount());
}
}
commandSent.countDown();
});
});
commandSent.await();
if (currentMessage % 20 == 0) {
log.info("commands sent: " + currentMessage);
}
}
final long timeToWait = totalNoOfCommandsToSend * 500;
if (!commandsSucceeded.await(timeToWait, TimeUnit.MILLISECONDS)) {
log.info("Timeout of {} milliseconds reached, stop waiting for commands to succeed", timeToWait);
}
final long commandsCompleted = totalNoOfCommandsToSend - commandsSucceeded.getCount();
log.info("commands sent: {}, commands succeeded: {} after {} milliseconds", commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
if (commandsCompleted == commandsSent.get()) {
sendCommandsSucceeded.flag();
} else {
ctx.failNow(new IllegalStateException("did not complete all commands sent"));
}
}
use of io.vertx.junit5.Timeout in project hono by eclipse.
the class AmqpUploadTestBase method testAdapterRejectsBadInboundMessage.
/**
* Verifies that a message containing a payload which has the <em>empty notification</em>
* content type is rejected by the adapter.
*
* @param context The Vert.x context for running asynchronous tests.
* @throws InterruptedException if test is interrupted while running.
*/
@Test
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testAdapterRejectsBadInboundMessage(final VertxTestContext context) throws InterruptedException {
final String tenantId = helper.getRandomTenantId();
final String deviceId = helper.getRandomDeviceId(tenantId);
final VertxTestContext setup = new VertxTestContext();
setupProtocolAdapter(tenantId, new Tenant(), deviceId, ProtonQoS.AT_LEAST_ONCE).map(s -> {
setup.verify(() -> {
final UnsignedLong maxMessageSize = s.getRemoteMaxMessageSize();
assertWithMessage("max-message-size included in adapter's attach frame").that(maxMessageSize).isNotNull();
assertWithMessage("max-message-size").that(maxMessageSize.longValue()).isGreaterThan(0);
});
sender = s;
return s;
}).onComplete(setup.succeedingThenComplete());
assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
if (setup.failed()) {
context.failNow(setup.causeOfFailure());
return;
}
final Message msg = ProtonHelper.message("some payload");
msg.setContentType(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION);
msg.setAddress(getEndpointName());
sender.send(msg, delivery -> {
context.verify(() -> {
assertThat(delivery.getRemoteState()).isInstanceOf(Rejected.class);
final Rejected rejected = (Rejected) delivery.getRemoteState();
final ErrorCondition error = rejected.getError();
assertThat((Object) error.getCondition()).isEqualTo(Constants.AMQP_BAD_REQUEST);
});
context.completeNow();
});
}
use of io.vertx.junit5.Timeout in project hono by eclipse.
the class AmqpUploadTestBase method testAutoProvisioningViaGateway.
/**
* Verifies that an edge device is auto-provisioned if it connects via a gateway equipped with the corresponding
* authority.
*
* @param ctx The Vert.x test context.
*/
@Test
@Timeout(timeUnit = TimeUnit.SECONDS, value = 10)
public void testAutoProvisioningViaGateway(final VertxTestContext ctx) {
final String tenantId = helper.getRandomTenantId();
final String gatewayId = helper.getRandomDeviceId(tenantId);
final Device gateway = new Device().setAuthorities(Collections.singleton(RegistryManagementConstants.AUTHORITY_AUTO_PROVISIONING_ENABLED));
final String username = IntegrationTestSupport.getUsername(gatewayId, tenantId);
final String edgeDeviceId = helper.getRandomDeviceId(tenantId);
final Promise<Void> provisioningNotificationReceived = Promise.promise();
helper.createAutoProvisioningMessageConsumers(ctx, provisioningNotificationReceived, tenantId, edgeDeviceId).compose(ok -> helper.registry.addDeviceForTenant(tenantId, new Tenant(), gatewayId, gateway, DEVICE_PASSWORD)).compose(ok -> connectToAdapter(username, DEVICE_PASSWORD)).compose(con -> createProducer(null, ProtonQoS.AT_LEAST_ONCE)).compose(sender -> {
final Message msg = ProtonHelper.message("apFoobar");
msg.setContentType("text/plain");
msg.setAddress(String.format("%s/%s/%s", getEndpointName(), tenantId, edgeDeviceId));
final Promise<Void> result = Promise.promise();
sender.send(msg, delivery -> {
ctx.verify(() -> assertThat(delivery.getRemoteState()).isInstanceOf(Accepted.class));
result.complete();
});
return result.future();
}).compose(ok -> provisioningNotificationReceived.future()).compose(ok -> helper.registry.getRegistrationInfo(tenantId, edgeDeviceId)).onComplete(ctx.succeeding(registrationResult -> {
ctx.verify(() -> {
final var info = registrationResult.bodyAsJsonObject();
IntegrationTestSupport.assertDeviceStatusProperties(info.getJsonObject(RegistryManagementConstants.FIELD_STATUS), true);
});
ctx.completeNow();
}));
}
Aggregations