Java Code Examples for io.vertx.junit5.VertxTestContext#failed()

The following examples show how to use io.vertx.junit5.VertxTestContext#failed() . You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example 1
Source File: MqttPublishTestBase.java    From hono with Eclipse Public License 2.0 6 votes vote down vote up
/**
 * Verifies that a number of messages published to Hono's MQTT adapter
 * using the short topic names can be successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessagesUsingShortTopicNames(final VertxTestContext ctx) throws InterruptedException {

    final String tenantId = helper.getRandomTenantId();
    final String deviceId = helper.getRandomDeviceId(tenantId);
    final Tenant tenant = new Tenant();
    final VertxTestContext setup = new VertxTestContext();

    helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, password).onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    doTestUploadMessages(
            ctx,
            tenantId,
            deviceId,
            connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password),
            true);
}
 
Example 2
Source File: MqttPublishTestBase.java    From hono with Eclipse Public License 2.0 6 votes vote down vote up
/**
 * Verifies that a number of messages published to Hono's MQTT adapter
 * using the standard topic names can be successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessages(final VertxTestContext ctx) throws InterruptedException {

    final String tenantId = helper.getRandomTenantId();
    final String deviceId = helper.getRandomDeviceId(tenantId);
    final Tenant tenant = new Tenant();

    final VertxTestContext setup = new VertxTestContext();

    helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, password).onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    doTestUploadMessages(
            ctx,
            tenantId,
            deviceId,
            connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password),
            false);
}
 
Example 3
Source File: AmqpUploadTestBase.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that a message containing a payload which has the <em>emtpy 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, deviceId, false)
    .map(s -> {
        sender = s;
        return s;
    })
    .onComplete(setup.completing());

    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();
    });

}
 
Example 4
Source File: MqttPublishTestBase.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that a number of messages published by a device authenticating with a client certificate can be
 * successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessagesUsingClientCertificate(final VertxTestContext ctx) throws InterruptedException {

    final SelfSignedCertificate deviceCert = SelfSignedCertificate.create(UUID.randomUUID().toString());
    final String tenantId = helper.getRandomTenantId();
    final String deviceId = helper.getRandomDeviceId(tenantId);
    final VertxTestContext setup = new VertxTestContext();

    helper.getCertificate(deviceCert.certificatePath())
    .compose(cert -> {
        final var tenant = Tenants.createTenantForTrustAnchor(cert);
        return helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, cert);
    }).onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    doTestUploadMessages(
        ctx,
        tenantId,
        deviceId,
        connectToAdapter(deviceCert),
        false);
}
 
Example 5
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
private void connectAndSubscribe(
        final VertxTestContext ctx,
        final String commandTargetDeviceId,
        final AmqpCommandEndpointConfiguration endpointConfig,
        final BiFunction<ProtonReceiver, ProtonSender, ProtonMessageHandler> commandConsumerFactory) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint notificationReceived = setup.checkpoint();

    connectToAdapter(tenantId, tenant, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        ctx.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = TimeUntilDisconnectNotification.fromMessage(msg).orElse(null);
        log.debug("received notification [{}]", notification);
        ctx.verify(() -> assertThat(notification).isNotNull());
        if (notification.getTtd() == -1) {
            notificationReceived.flag();
        }
    }))
    // use anonymous sender
    .compose(con -> createProducer(null))
    .compose(sender -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId)
        .map(recv -> {
            recv.handler(commandConsumerFactory.apply(recv, sender));
            recv.flow(50);
            return null;
        }))
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }
}
 
Example 6
Source File: TelemetryHttpIT.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that a number of telemetry messages uploaded to Hono's HTTP adapter
 * using QoS 1 can be successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadUsingQoS1(final VertxTestContext ctx) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final Tenant tenant = new Tenant();
    final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "binary/octet-stream")
            .add(HttpHeaders.AUTHORIZATION, authorization)
            .add(HttpHeaders.ORIGIN, ORIGIN_URI)
            .add(Constants.HEADER_QOS_LEVEL, "1");

    helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, PWD)
            .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    testUploadMessages(ctx, tenantId, count -> {
        return httpClient.create(
                getEndpointUri(),
                Buffer.buffer("hello " + count),
                requestHeaders,
                response -> response.statusCode() == HttpURLConnection.HTTP_ACCEPTED);
    });
}
 
Example 7
Source File: HttpTestBase.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that the adapter opens a connection if auto-provisioning is enabled for the device certificate.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testConnectSucceedsWithAutoProvisioning(final VertxTestContext ctx) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);

    helper.getCertificate(deviceCert.certificatePath())
            .compose(cert -> {

                final var tenant = Tenants.createTenantForTrustAnchor(cert);
                tenant.getTrustedCertificateAuthorities().get(0).setAutoProvisioningEnabled(true);
                return helper.registry.addTenant(tenantId, tenant);

            })
            .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    testUploadMessages(ctx, tenantId, count -> httpClientWithClientCert.create(
            getEndpointUri(),
            Buffer.buffer("hello " + count),
            requestHeaders,
            response -> response.statusCode() == HttpURLConnection.HTTP_ACCEPTED
                    && hasAccessControlExposedHeaders(response.headers())));
}
 
Example 8
Source File: HttpTestBase.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that a number of messages uploaded to Hono's HTTP adapter using client certificate based authentication
 * can be successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessagesUsingClientCertificate(final VertxTestContext ctx) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);

    helper.getCertificate(deviceCert.certificatePath())
   .compose(cert -> {

        final var tenant = Tenants.createTenantForTrustAnchor(cert);
        return helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, cert);

    })
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    testUploadMessages(ctx, tenantId, count -> {
        return httpClientWithClientCert.create(
                getEndpointUri(),
                Buffer.buffer("hello " + count),
                requestHeaders,
                response -> response.statusCode() == HttpURLConnection.HTTP_ACCEPTED
                        && hasAccessControlExposedHeaders(response.headers()));
    });
}
 
Example 9
Source File: HttpTestBase.java    From hono with Eclipse Public License 2.0 5 votes vote down vote up
/**
 * Verifies that a number of messages uploaded to Hono's HTTP adapter
 * using HTTP Basic auth can be successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessagesUsingBasicAuth(final VertxTestContext ctx) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final Tenant tenant = new Tenant();
    final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.AUTHORIZATION, authorization)
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);
    final MultiMap requestHeadersWithEncodedCredentials = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.AUTHORIZATION, getBasicAuthWithEncodedCredentials(tenantId, deviceId, PWD))
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);

    helper.registry
    .addDeviceForTenant(tenantId, tenant, deviceId, PWD)
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    testUploadMessages(ctx, tenantId,
            count -> {
                return httpClient.create(
                        getEndpointUri(),
                        Buffer.buffer("hello " + count),
                        count % 2 == 0 ? requestHeaders : requestHeadersWithEncodedCredentials,
                        response -> response.statusCode() == HttpURLConnection.HTTP_ACCEPTED
                            && hasAccessControlExposedHeaders(response.headers()));
            });
}
 
Example 10
Source File: Examples.java    From vertx-junit5 with Apache License 2.0 5 votes vote down vote up
@Test
void start_http_server() throws Throwable {
  VertxTestContext testContext = new VertxTestContext();

  vertx.createHttpServer()
    .requestHandler(req -> req.response().end())
    .listen(16969)
    .onComplete(testContext.succeedingThenComplete()); // <1>

  assertThat(testContext.awaitCompletion(5, TimeUnit.SECONDS)).isTrue(); // <2>
  if (testContext.failed()) {  // <3>
    throw testContext.causeOfFailure();
  }
}
 
Example 11
Source File: CommandAndControlMqttIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the adapter rejects malformed command messages sent by applications.
 *
 * @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 = 20)
public void testSendCommandFailsForMalformedMessage(
        final MqttCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint ready = setup.checkpoint(3);

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;

    helper.registry
            .addDeviceForTenant(tenantId, tenant, deviceId, password)
            .compose(ok -> connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), password))
            .compose(ok -> createConsumer(tenantId, msg -> {
                // expect empty notification with TTD -1
                setup.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
                final TimeUntilDisconnectNotification notification = TimeUntilDisconnectNotification
                        .fromMessage(msg).orElse(null);
                LOGGER.info("received notification [{}]", notification);
                if (notification.getTtd() == -1) {
                    ready.flag();
                }
            }))
            .compose(conAck -> subscribeToCommands(commandTargetDeviceId, msg -> {
                setup.failNow(new IllegalStateException("should not have received command"));
            }, endpointConfig, MqttQoS.AT_MOST_ONCE))
            .onComplete(ctx.succeeding(ok -> ready.flag()));

    final AtomicReference<MessageSender> sender = new AtomicReference<>();
    final String linkTargetAddress = endpointConfig.getSenderLinkTargetAddress(tenantId);

    helper.applicationClientFactory.createGenericMessageSender(linkTargetAddress)
    .map(s -> {
        LOGGER.debug("created generic sender for sending commands [target address: {}]", linkTargetAddress);
        sender.set(s);
        ready.flag();
        return s;
    });

    assertThat(setup.awaitCompletion(15, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }

    final Checkpoint failedAttempts = ctx.checkpoint(2);
    final String messageAddress = endpointConfig.getCommandMessageAddress(tenantId, commandTargetDeviceId);

    LOGGER.debug("sending command message lacking subject");
    final Message messageWithoutSubject = ProtonHelper.message("input data");
    messageWithoutSubject.setAddress(messageAddress);
    messageWithoutSubject.setMessageId("message-id");
    messageWithoutSubject.setReplyTo("reply/to/address");
    sender.get().sendAndWaitForOutcome(messageWithoutSubject).onComplete(ctx.failing(t -> {
        ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
        failedAttempts.flag();
    }));

    LOGGER.debug("sending command message lacking message ID and correlation ID");
    final Message messageWithoutId = ProtonHelper.message("input data");
    messageWithoutId.setAddress(messageAddress);
    messageWithoutId.setSubject("setValue");
    messageWithoutId.setReplyTo("reply/to/address");
    sender.get().sendAndWaitForOutcome(messageWithoutId).onComplete(ctx.failing(t -> {
        ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
        failedAttempts.flag();
    }));
}
 
Example 12
Source File: EventMqttIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that an event from a device for which a default TTL has been
 * specified cannot be consumed after the TTL has expired.
 *
 * @param ctx The vert.x test context.
 * @throws InterruptedException if test execution gets interrupted.
 */
@Test
public void testMessagesExpire(final VertxTestContext ctx) throws InterruptedException {

    // GIVEN a tenant for which all messages have a TTL of 500ms
    final String tenantId = helper.getRandomTenantId();
    final String deviceId = helper.getRandomDeviceId(tenantId);
    final Tenant tenant = new Tenant();
    tenant.setDefaults(Map.of(MessageHelper.SYS_HEADER_PROPERTY_TTL, 3)); // seconds
    final VertxTestContext setup = new VertxTestContext();

    helper.registry.addDeviceForTenant(tenantId, tenant, deviceId, "secret").onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }

    // WHEN a device that belongs to the tenant publishes an event
    final AtomicInteger receivedMessageCount = new AtomicInteger(0);
    connectToAdapter(IntegrationTestSupport.getUsername(deviceId, tenantId), "secret")
    .compose(connack -> send(tenantId, deviceId, Buffer.buffer("hello"), false, (sendAttempt, result) -> {
        if (sendAttempt.succeeded()) {
            LOGGER.info("successfully sent event [tenant-id: {}, device-id: {}", tenantId, deviceId);
            result.complete();
        } else {
            result.fail(sendAttempt.cause());
        }
    }))
    .compose(ok -> {
        final Promise<MessageConsumer> consumerCreated = Promise.promise();
        VERTX.setTimer(4000, tid -> {
            LOGGER.info("opening event consumer for tenant [{}]", tenantId);
            // THEN no messages can be consumed after the TTL has expired
            createConsumer(tenantId, msg -> receivedMessageCount.incrementAndGet())
            .onComplete(consumerCreated);
        });
        return consumerCreated.future();
    })
    .compose(c -> {
        final Promise<Void> done = Promise.promise();
        VERTX.setTimer(1000, tid -> {
            if (receivedMessageCount.get() > 0) {
                done.fail(new IllegalStateException("should not have received any events after TTL has expired"));
            } else {
                done.complete();
            }
        });
        return done.future();
    }).onComplete(ctx.completing());
}
 
Example 13
Source File: HttpTestBase.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the HTTP adapter delivers a command to a device and accepts the corresponding
 * response from the device.
 *
 * @param endpointConfig The endpoints to use for sending/receiving commands.
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@ParameterizedTest(name = IntegrationTestSupport.PARAMETERIZED_TEST_NAME_PATTERN)
@MethodSource("commandAndControlVariants")
public void testUploadMessagesWithTtdThatReplyWithOneWayCommand(
        final HttpCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final Tenant tenant = new Tenant();
    final VertxTestContext setup = new VertxTestContext();

    final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.AUTHORIZATION, authorization)
            .add(HttpHeaders.ORIGIN, ORIGIN_URI)
            .add(Constants.HEADER_TIME_TILL_DISCONNECT, "4");

    helper.registry
    .addDeviceForTenant(tenantId, tenant, deviceId, PWD)
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;
    final String subscribingDeviceId = endpointConfig.isSubscribeAsGatewayForSingleDevice() ? commandTargetDeviceId
            : deviceId;

    final AtomicInteger counter = new AtomicInteger();
    testUploadMessages(ctx, tenantId,
            msg -> {
                return TimeUntilDisconnectNotification.fromMessage(msg)
                        .map(notification -> {

                            logger.trace("received piggy backed message [ttd: {}]: {}", notification.getTtd(), msg);
                            ctx.verify(() -> {
                                assertThat(notification.getTenantId()).isEqualTo(tenantId);
                                assertThat(notification.getDeviceId()).isEqualTo(subscribingDeviceId);
                            });
                            // now ready to send a command
                            final JsonObject inputData = new JsonObject().put(COMMAND_JSON_KEY, (int) (Math.random() * 100));
                            return helper.sendOneWayCommand(
                                    tenantId,
                                    commandTargetDeviceId,
                                    COMMAND_TO_SEND,
                                    "application/json",
                                    inputData.toBuffer(),
                                    // set "forceCommandRerouting" message property so that half the command are rerouted via the AMQP network
                                    IntegrationTestSupport.newCommandMessageProperties(() -> counter.getAndIncrement() >= MESSAGES_TO_SEND / 2),
                                    notification.getMillisecondsUntilExpiry());
                        })
                        .orElseGet(() -> Future.succeededFuture());
            },
            count -> {
                final Buffer payload = Buffer.buffer("hello " + count);
                return sendHttpRequestForGatewayOrDevice(payload, requestHeaders, endpointConfig, commandTargetDeviceId, true)
                .map(responseHeaders -> {
                    ctx.verify(() -> {
                        // assert that the response contains a one-way command
                        assertThat(responseHeaders.get(Constants.HEADER_COMMAND))
                                .as("response #" + count + " doesn't contain command").isNotNull();
                        assertThat(responseHeaders.get(Constants.HEADER_COMMAND)).isEqualTo(COMMAND_TO_SEND);
                        assertThat(responseHeaders.get(HttpHeaders.CONTENT_TYPE)).isEqualTo("application/json");
                        assertThat(responseHeaders.get(Constants.HEADER_COMMAND_REQUEST_ID)).isNull();
                    });
                    return responseHeaders;
                });
            });
}
 
Example 14
Source File: HealthServiceIntegrationTest.java    From cassandra-sidecar with Apache License 2.0 4 votes vote down vote up
@DisplayName("Down on startup, then comes up")
@Test
public void testDownHostTurnsOn() throws Throwable
{
    VertxTestContext testContext = new VertxTestContext();
    BoundCluster bc = injector.getInstance(BoundCluster.class);
    BoundNode node = bc.node(0);
    HealthCheck check = injector.getInstance(HealthCheck.class);
    HealthService service = injector.getInstance(HealthService.class);
    Server server = injector.getInstance(Server.class);

    try
    {
        WebClient client = WebClient.create(vertx);
        long start = System.currentTimeMillis();
        client.get(port, "localhost", "/api/v1/__health")
              .as(BodyCodec.string())
              .send(testContext.succeeding(response -> testContext.verify(() ->
              {
                  assertEquals(503, response.statusCode());

                  node.start();
                  while ((System.currentTimeMillis() - start) < (1000 * 60 * 2) && !check.get())
                      Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
                  service.refreshNow();
                  client.get(port, "localhost", "/api/v1/__health")
                        .as(BodyCodec.string())
                        .send(testContext.succeeding(upResponse -> testContext.verify(() ->
                        {
                            assertEquals(200, upResponse.statusCode());
                            testContext.completeNow();
                        })));
              })));
        assertTrue(testContext.awaitCompletion(125, TimeUnit.SECONDS));
        if (testContext.failed())
        {
            throw testContext.causeOfFailure();
        }
    }
    finally
    {
        service.stop();
        server.close();
    }
}
 
Example 15
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the adapter forwards commands and responses hence and forth between
 * an application and a device that have been sent using the async API.
 *
 * @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")
public void testSendAsyncCommandsSucceeds(
        final AmqpCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;

    connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig,
            (cmdReceiver, cmdResponseSender) -> createCommandConsumer(ctx, cmdReceiver, cmdResponseSender));

    final String replyId = "reply-id";
    final int totalNoOfCommandsToSend = 60;
    final CountDownLatch commandsSucceeded = new CountDownLatch(totalNoOfCommandsToSend);
    final AtomicInteger commandsSent = new AtomicInteger(0);
    final AtomicLong lastReceivedTimestamp = new AtomicLong();

    final VertxTestContext setup = new VertxTestContext();

    final Future<MessageConsumer> asyncResponseConsumer = helper.applicationClientFactory.createAsyncCommandResponseConsumer(
            tenantId,
            replyId,
            response -> {
                lastReceivedTimestamp.set(System.currentTimeMillis());
                commandsSucceeded.countDown();
                if (commandsSucceeded.getCount() % 20 == 0) {
                    log.info("command responses received: {}", totalNoOfCommandsToSend - commandsSucceeded.getCount());
                }
            },
            null);
    final Future<AsyncCommandClient> asyncCommandClient = helper.applicationClientFactory.getOrCreateAsyncCommandClient(tenantId);

    CompositeFuture.all(asyncResponseConsumer, asyncCommandClient).onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }

    final long start = System.currentTimeMillis();

    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: " + correlationId);
            asyncCommandClient.result().sendAsyncCommand(
                    commandTargetDeviceId,
                    "setValue",
                    "text/plain",
                    msg,
                    correlationId,
                    replyId,
                    null)
            .onComplete(sendAttempt -> {
                if (sendAttempt.failed()) {
                    log.debug("error sending command {}", correlationId, sendAttempt.cause());
                }
                if (commandsSent.get() % 20 == 0) {
                    log.info("commands sent: " + commandsSent.get());
                }
                commandSent.countDown();
            });
        });

        commandSent.await();
    }

    final long timeToWait = totalNoOfCommandsToSend * 200;
    if (!commandsSucceeded.await(timeToWait, TimeUnit.MILLISECONDS)) {
        log.info("Timeout of {} milliseconds reached, stop waiting for command responses", timeToWait);
    }
    final long commandsCompleted = totalNoOfCommandsToSend - commandsSucceeded.getCount();
    log.info("commands sent: {}, responses received: {} after {} milliseconds",
            commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
    if (commandsCompleted == commandsSent.get()) {
        ctx.completeNow();
    } else {
        ctx.failNow(new IllegalStateException("did not complete all commands sent"));
    }
}
 
Example 16
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the adapter rejects malformed command messages sent by applications.
 *
 * @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)
public void testSendCommandFailsForMalformedMessage(
        final AmqpCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;

    final AtomicReference<MessageSender> sender = new AtomicReference<>();
    final String targetAddress = endpointConfig.getSenderLinkTargetAddress(tenantId);

    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint preconditions = setup.checkpoint(2);

    connectToAdapter(tenantId, tenant, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        ctx.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = TimeUntilDisconnectNotification.fromMessage(msg).orElse(null);
        log.debug("received notification [{}]", notification);
        ctx.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.applicationClientFactory.createGenericMessageSender(targetAddress))
    .map(s -> {
        log.debug("created generic sender for sending commands [target address: {}]", targetAddress);
        sender.set(s);
        preconditions.flag();
        return s;
    })
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }

    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");
    sender.get().sendAndWaitForOutcome(messageWithoutSubject).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");
    sender.get().sendAndWaitForOutcome(messageWithoutId).onComplete(ctx.failing(t -> {
        ctx.verify(() -> assertThat(t).isInstanceOf(ClientErrorException.class));
        expectedFailures.flag();
    }));
}
 
Example 17
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the adapter immediately forwards the <em>released</em> disposition
 * if there is no credit left for sending the command to the device.
 *
 * @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)
public void testSendCommandFailsWhenNoCredit(
        final AmqpCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;

    final VertxTestContext setup = new VertxTestContext();
    final Checkpoint preconditions = setup.checkpoint(1);

    connectToAdapter(tenantId, tenant, deviceId, password, () -> createEventConsumer(tenantId, msg -> {
        // expect empty notification with TTD -1
        ctx.verify(() -> assertThat(msg.getContentType()).isEqualTo(EventConstants.CONTENT_TYPE_EMPTY_NOTIFICATION));
        final TimeUntilDisconnectNotification notification = TimeUntilDisconnectNotification.fromMessage(msg).orElse(null);
        log.debug("received notification [{}]", notification);
        ctx.verify(() -> assertThat(notification).isNotNull());
        if (notification.getTtd() == -1) {
            preconditions.flag();
        }
    }))
    .compose(con -> subscribeToCommands(endpointConfig, tenantId, commandTargetDeviceId)
        .map(recv -> {
            recv.handler((delivery, msg) -> {
                log.debug("received command [name: {}, reply-to: {}, correlation-id: {}]", msg.getSubject(), msg.getReplyTo(),
                        msg.getCorrelationId());
                ProtonHelper.accepted(delivery, true);
                // don't send credits
            });
            recv.flow(1); // just give 1 initial credit
            return null;
        }))
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
    }

    final Checkpoint expectedSteps = ctx.checkpoint(2);

    // send first command
    helper.sendOneWayCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain",
            Buffer.buffer("cmd"), null, 200)
    // first command shall succeed because there's one initial credit
    .onComplete(ctx.succeeding(v -> {
        expectedSteps.flag();
        // send second command
        helper.sendOneWayCommand(tenantId, commandTargetDeviceId, "setValue", "text/plain",
                Buffer.buffer("cmd"), null, 200)
        // second command shall fail because there's no credit left
        .onComplete(ctx.failing(t -> {
            ctx.verify(() -> {
                assertThat(t).isInstanceOf(ServerErrorException.class);
                assertThat(((ServerErrorException) t).getErrorCode()).isEqualTo(HttpURLConnection.HTTP_UNAVAILABLE);
                // with no explicit credit check, the AMQP adapter would just run into the "waiting for delivery update" timeout (after 1s)
                // and the error here would be caused by a request timeout in the sendOneWayCommand() method above (after 200ms already)
                assertThat(t.getMessage()).doesNotContain("timed out");
            });
            expectedSteps.flag();
        }));
    }));
}
 
Example 18
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that the adapter forwards the <em>rejected</em> disposition, received from a device, back to the
 * application.
 *
 * @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)
public void testSendCommandFailsForCommandRejectedByDevice(
        final AmqpCommandEndpointConfiguration endpointConfig,
        final VertxTestContext ctx) throws InterruptedException {

    final String commandTargetDeviceId = endpointConfig.isSubscribeAsGateway()
            ? helper.setupGatewayDeviceBlocking(tenantId, deviceId, 5)
            : deviceId;

    connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig,
            (cmdReceiver, cmdResponseSender) -> createRejectingCommandConsumer(ctx, cmdReceiver));

    final int totalNoOfCommandsToSend = 3;
    final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
    final AtomicInteger commandsSent = new AtomicInteger(0);
    final AtomicLong lastReceivedTimestamp = new AtomicLong();
    final long start = System.currentTimeMillis();

    final VertxTestContext commandClientCreation = new VertxTestContext();
    final Future<CommandClient> commandClient = helper.applicationClientFactory.getOrCreateCommandClient(tenantId, "test-client")
            .onSuccess(c -> c.setRequestTimeout(300))
            .onComplete(commandClientCreation.completing());

    assertThat(commandClientCreation.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (commandClientCreation.failed()) {
        ctx.failNow(commandClientCreation.causeOfFailure());
    }

    while (commandsSent.get() < totalNoOfCommandsToSend) {
        final CountDownLatch commandSent = new CountDownLatch(1);
        context.runOnContext(go -> {
            final Buffer msg = Buffer.buffer("value: " + commandsSent.getAndIncrement());
            final Future<BufferResult> sendCmdFuture = commandClient.result().sendCommand(commandTargetDeviceId, "setValue", "text/plain",
                    msg, null);
            sendCmdFuture.onComplete(sendAttempt -> {
                if (sendAttempt.succeeded()) {
                    log.debug("sending command {} succeeded unexpectedly", commandsSent.get());
                } else {
                    if (sendAttempt.cause() instanceof ClientErrorException
                            && ((ClientErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_BAD_REQUEST
                            && REJECTED_COMMAND_ERROR_MESSAGE.equals(sendAttempt.cause().getMessage())) {
                        log.debug("sending command {} failed as expected: {}", commandsSent.get(),
                                sendAttempt.cause().toString());
                        lastReceivedTimestamp.set(System.currentTimeMillis());
                        commandsFailed.countDown();
                        if (commandsFailed.getCount() % 20 == 0) {
                            log.info("commands failed as expected: {}",
                                    totalNoOfCommandsToSend - commandsFailed.getCount());
                        }
                    } else {
                        log.debug("sending command {} failed with an unexpected error", commandsSent.get(),
                                sendAttempt.cause());
                    }
                }
                if (commandsSent.get() % 20 == 0) {
                    log.info("commands sent: " + commandsSent.get());
                }
                commandSent.countDown();
            });
        });

        commandSent.await();
    }

    final long timeToWait = totalNoOfCommandsToSend * 300 + 300;
    if (!commandsFailed.await(timeToWait, TimeUnit.MILLISECONDS)) {
        log.info("Timeout of {} milliseconds reached, stop waiting for commands", timeToWait);
    }
    final long commandsCompleted = totalNoOfCommandsToSend - commandsFailed.getCount();
    log.info("commands sent: {}, commands failed: {} after {} milliseconds",
            commandsSent.get(), commandsCompleted, lastReceivedTimestamp.get() - start);
    if (commandsCompleted == commandsSent.get()) {
        ctx.completeNow();
    } else {
        ctx.failNow(new IllegalStateException("did not complete all commands sent"));
    }
}
 
Example 19
Source File: CommandAndControlAmqpIT.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * 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.
 *
 * @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);
    // command handler won't send a disposition update
    connectAndSubscribe(ctx, commandTargetDeviceId, endpointConfig,
            (cmdReceiver, cmdResponseSender) -> createNotSendingDeliveryUpdateCommandConsumer(ctx, cmdReceiver, receivedMessagesCounter));

    final int totalNoOfCommandsToSend = 2;
    final CountDownLatch commandsFailed = new CountDownLatch(totalNoOfCommandsToSend);
    final AtomicInteger commandsSent = new AtomicInteger(0);
    final AtomicLong lastReceivedTimestamp = new AtomicLong();
    final long start = System.currentTimeMillis();

    final VertxTestContext commandClientCreation = new VertxTestContext();
    final Future<CommandClient> commandClient = helper.applicationClientFactory.getOrCreateCommandClient(tenantId, "test-client")
            .onSuccess(c -> c.setRequestTimeout(1300)) // have to wait more than AmqpAdapterProperties.DEFAULT_SEND_MESSAGE_TO_DEVICE_TIMEOUT (1000ms) for the first command message
            .onComplete(commandClientCreation.completing());

    assertThat(commandClientCreation.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (commandClientCreation.failed()) {
        ctx.failNow(commandClientCreation.causeOfFailure());
    }

    while (commandsSent.get() < totalNoOfCommandsToSend) {
        final CountDownLatch commandSent = new CountDownLatch(1);
        context.runOnContext(go -> {
            final Buffer msg = Buffer.buffer("value: " + commandsSent.getAndIncrement());
            final Future<BufferResult> sendCmdFuture = commandClient.result().sendCommand(commandTargetDeviceId, "setValue", "text/plain",
                    msg, null);
            sendCmdFuture.onComplete(sendAttempt -> {
                if (sendAttempt.succeeded()) {
                    log.debug("sending command {} succeeded unexpectedly", commandsSent.get());
                } else {
                    if (sendAttempt.cause() instanceof ServerErrorException
                            && ((ServerErrorException) sendAttempt.cause()).getErrorCode() == HttpURLConnection.HTTP_UNAVAILABLE) {
                        log.debug("sending command {} failed as expected: {}", commandsSent.get(),
                                sendAttempt.cause().toString());
                        lastReceivedTimestamp.set(System.currentTimeMillis());
                        commandsFailed.countDown();
                        if (commandsFailed.getCount() % 20 == 0) {
                            log.info("commands failed as expected: {}",
                                    totalNoOfCommandsToSend - commandsFailed.getCount());
                        }
                    } else {
                        log.debug("sending command {} failed with an unexpected error", commandsSent.get(),
                                sendAttempt.cause());
                    }
                }
                if (commandsSent.get() % 20 == 0) {
                    log.info("commands sent: " + commandsSent.get());
                }
                commandSent.countDown();
            });
        });

        commandSent.await();
    }

    // have to wait more than AmqpAdapterProperties.DEFAULT_SEND_MESSAGE_TO_DEVICE_TIMEOUT (1000ms) for each command message
    final long timeToWait = 300 + (totalNoOfCommandsToSend * 1300);
    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);
    if (commandsCompleted == commandsSent.get()) {
        ctx.completeNow();
    } else {
        ctx.failNow(new IllegalStateException("did not complete all commands sent"));
    }
}
 
Example 20
Source File: HttpTestBase.java    From hono with Eclipse Public License 2.0 4 votes vote down vote up
/**
 * Verifies that a number of messages uploaded to the HTTP adapter via a gateway using HTTP Basic auth can be
 * successfully consumed via the AMQP Messaging Network.
 *
 * @param ctx The test context.
 * @throws InterruptedException if the test fails.
 */
@Test
public void testUploadMessagesViaGateway(final VertxTestContext ctx) throws InterruptedException {

    // GIVEN a device that is connected via two gateways
    final Tenant tenant = new Tenant();
    final String gatewayOneId = helper.getRandomDeviceId(tenantId);
    final String gatewayTwoId = helper.getRandomDeviceId(tenantId);
    final Device device = new Device();
    device.setVia(Arrays.asList(gatewayOneId, gatewayTwoId));

    final VertxTestContext setup = new VertxTestContext();
    helper.registry.addDeviceForTenant(tenantId, tenant, gatewayOneId, PWD)
    .compose(ok -> helper.registry.addDeviceToTenant(tenantId, gatewayTwoId, PWD))
    .compose(ok -> helper.registry.registerDevice(tenantId, deviceId, device))
    .onComplete(setup.completing());

    assertThat(setup.awaitCompletion(5, TimeUnit.SECONDS)).isTrue();
    if (setup.failed()) {
        ctx.failNow(setup.causeOfFailure());
        return;
    }

    final MultiMap requestHeadersOne = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.AUTHORIZATION, getBasicAuth(tenantId, gatewayOneId, PWD))
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);

    final MultiMap requestHeadersTwo = MultiMap.caseInsensitiveMultiMap()
            .add(HttpHeaders.CONTENT_TYPE, "text/plain")
            .add(HttpHeaders.AUTHORIZATION, getBasicAuth(tenantId, gatewayTwoId, PWD))
            .add(HttpHeaders.ORIGIN, ORIGIN_URI);

    final String uri = String.format("%s/%s/%s", getEndpointUri(), tenantId, deviceId);

    testUploadMessages(
            ctx,
            tenantId,
            count -> {
                final MultiMap headers = (count.intValue() & 1) == 0 ? requestHeadersOne : requestHeadersTwo;
                return httpClient.update( // GW uses PUT when acting on behalf of a device
                        uri,
                        Buffer.buffer("hello " + count),
                        headers,
                        status -> status == HttpURLConnection.HTTP_ACCEPTED);
            });
}