/*
* Copyright (c) 2011-2015 The original author or authors
* ------------------------------------------------------
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Apache License v2.0 which accompanies this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Apache License v2.0 is available at
* http://www.opensource.org/licenses/apache2.0.php
*
* You may elect to redistribute this code under either of these licenses.
*/
package io.vertx.ext.stomp.impl;
import com.jayway.awaitility.Awaitility;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.ext.stomp.*;
import io.vertx.ext.stomp.utils.Headers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test the client subscriptions and message delivery.
*
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class SubscriptionsUsingTopicTest {
private Vertx vertx;
private StompServer server;
private List<StompClient> clients = new ArrayList<>();
@Before
public void setUp() {
AsyncLock<StompServer> lock = new AsyncLock<>();
vertx = Vertx.vertx();
server = StompServer.create(vertx)
.handler(StompServerHandler.create(vertx))
.listen(lock.handler());
lock.waitForSuccess();
}
@After
public void tearDown() {
AsyncLock<Void> lock = new AsyncLock<>();
clients.forEach(StompClient::close);
clients.clear();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock<>();
vertx.close(lock.handler());
lock.waitForSuccess();
}
@Test
public void testSubscriptionAndReception() {
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/topic");
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
}
@Test
public void testThatCustomHeadersArePropagated() {
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Headers.create("foo", "bar", "toto", "titi"), Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/topic");
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader("foo")).isEqualTo("bar");
assertThat(frames.get(0).getHeader("toto")).isEqualTo("titi");
}
@Test
public void testSubscriptionAndTwoReceptions() {
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", (frames::add));
}));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2);
assertThat(frames).hasSize(2);
assertThat(frames.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/topic");
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.CONTENT_LENGTH)).isEqualTo("5"); // Content length as string.
assertThat(frames.get(1).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(1).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(1).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(1).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/topic");
assertThat(frames.get(1).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
}
@Test
public void testSendingWithoutDestination() {
AtomicBoolean failureDetected = new AtomicBoolean();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
try {
connection.send((String) null, Buffer.buffer("hello"));
} catch (IllegalArgumentException e) {
failureDetected.set(true);
}
}));
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(failureDetected::get);
}
@Test
public void testSendingWithHeadersButWithoutDestination() {
AtomicBoolean failureDetected = new AtomicBoolean();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
try {
connection.send(Headers.create("foo", "bar"), Buffer.buffer("hello"));
} catch (IllegalArgumentException e) {
failureDetected.set(true);
}
}));
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(failureDetected::get);
}
@Test
public void testWhenNoSubscriptions() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue2", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue2"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.ERROR);
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isEqualTo("/topic");
assertThat(frames.get(0).getBodyAsString()).contains("no subscriptions");
}
@Test
public void testMultipleSubscriptionsWithIds() {
server.options().setSendErrorOnNoSubscriptions(true);
Map<String, Frame> frames = new HashMap<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", Headers.create().add(Frame.ID, "0"), f -> frames.put("/topic", f));
connection.subscribe("/queue2", Headers.create().add(Frame.ID, "1"), f -> frames.put("/queue2", f));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue2"));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
connection.send("/queue2", Buffer.buffer("World"));
connection.errorHandler(f -> frames.put("error", f));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() >= 2);
assertThat(frames).hasSize(2);
assertThat(frames).doesNotContainKeys("error");
Frame frame1 = frames.get("/topic");
assertThat(frame1.getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frame1.getHeader(Frame.DESTINATION)).isEqualTo("/topic");
assertThat(frame1.getHeader(Frame.SUBSCRIPTION)).isEqualTo("0");
assertThat(frame1.getBodyAsString()).isEqualTo("Hello");
frame1 = frames.get("/queue2");
assertThat(frame1.getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frame1.getHeader(Frame.DESTINATION)).isEqualTo("/queue2");
assertThat(frame1.getHeader(Frame.SUBSCRIPTION)).isEqualTo("1");
assertThat(frame1.getBodyAsString()).isEqualTo("World");
}
@Test
public void testUnsubscriptionWithDefaultId() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", frame -> {
frames.add(frame);
connection.unsubscribe("/topic");
});
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.errorHandler(frames::add);
connection.send("/topic", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.errorHandler(frames::add);
connection.send("/topic", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2 && frames.get(1).getCommand() == Frame.Command.ERROR);
}
@Test
public void testUnsubscriptionWithCustomId() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", Headers.create(Frame.ID, "0"), frame -> {
frames.add(frame);
connection.unsubscribe("/topic", Headers.create(Frame.ID, "0"));
});
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !Helper.hasDestination(server.stompHandler().getDestinations(), "/topic"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2 && frames.get(1).getCommand() == Frame.Command.ERROR);
}
@Test
public void testSubscriptionsUsingTheSameDefaultId() {
AtomicBoolean failureDetected = new AtomicBoolean();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", frame -> {
});
try {
connection.subscribe("/topic", frame -> {
});
} catch (IllegalArgumentException e) {
failureDetected.set(true);
}
}));
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(failureDetected::get);
}
@Test
public void testSubscriptionsUsingTheSameCustomId() {
AtomicBoolean failureDetected = new AtomicBoolean();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", Headers.create("id", "0"), frame -> {
});
try {
connection.subscribe("/queue2", Headers.create("id", "0"), frame -> {
});
} catch (IllegalArgumentException e) {
failureDetected.set(true);
}
}));
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(failureDetected::get);
}
@Test
public void testSubscriptionsUsingTheSameDestinationButDifferentId() {
AtomicBoolean complete = new AtomicBoolean();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", Headers.create(Frame.ID, "0"), frame -> {
});
connection.subscribe("/topic", Headers.create(Frame.ID, "1"), frame -> {
});
complete.set(true);
}));
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(complete::get);
}
@Test
public void testClosingConnection() {
List<Frame> frames = new CopyOnWriteArrayList<>();
StompClient client = StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/topic", (frames::add));
connection.subscribe("/queue2", (frames::add));
});
clients.add(client);
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestinations().size() == 2);
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/topic", Buffer.buffer("Hello"));
}));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue2", Buffer.buffer("World"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2);
client.close();
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestinations().size() == 0);
}
}