package com.softwaremill.common.sqs;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.AmazonSQSClient;
import com.amazonaws.services.sqs.model.CreateQueueRequest;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.jayway.awaitility.Duration;
import org.elasticmq.Node;
import org.elasticmq.NodeAddress;
import org.elasticmq.NodeBuilder;
import org.elasticmq.rest.RestServer;
import org.elasticmq.rest.sqs.SQSRestServerBuilder;
import org.elasticmq.storage.inmemory.InMemoryStorage;
import org.hamcrest.*;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import com.softwaremill.common.util.Sleeper;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import static com.jayway.awaitility.Awaitility.await;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
import static org.joda.time.Duration.standardMinutes;
import static org.joda.time.Duration.standardSeconds;
/**
* @author Maciej Bilas
* @since 15/10/12 11:53
*/
public class SQSElasticMQIntegrationTest {
private static final int ELASTIMQ_PORT = 12365;
private static final String TEST_QUEUE = "test-queue";
private RestServer sqsServer;
private Node elasticNode;
private boolean queueCreated = false;
@BeforeClass
public void setupElasticMQ() {
elasticNode = NodeBuilder.withStorage(new InMemoryStorage());
sqsServer = new SQSRestServerBuilder(elasticNode.nativeClient(), ELASTIMQ_PORT,
new NodeAddress("http", "localhost", ELASTIMQ_PORT, "")).start();
}
private AmazonSQS createDefaultSQSClient() {
AmazonSQSClient sqsClient = new AmazonSQSClient(new BasicAWSCredentials("1234", "1234"));
sqsClient.setEndpoint(format("http://localhost:%s", ELASTIMQ_PORT));
return sqsClient;
}
@Test
public void shouldCreateAndObtainAQueue() {
// When
Queue testQueue = obtainTestQueue();
// Then
assertThat(testQueue.getURL()).isNotNull();
}
private Queue obtainTestQueue() {
AmazonSQS sqsClient = createDefaultSQSClient();
SQS sqs = new SQS(sqsClient);
if (!queueCreated) {
sqsClient.createQueue(new CreateQueueRequest(TEST_QUEUE));
queueCreated = true;
}
return sqs.getQueueByName(TEST_QUEUE);
}
@Test
public void shouldSendAReceiveAMessage() throws Exception {
// Given
final Queue queue = obtainTestQueue();
// When
queue.sendSerializable("foo bar");
// Then
await().until(receivedMessageCallable(queue), receivedMessageMatcher(queue, "foo bar", true));
}
private Matcher<Optional<ReceivedMessage>> receivedMessageMatcher(
final Queue queue, final Object expected, final boolean delete) {
return new BaseMatcher<Optional<ReceivedMessage>>() {
@SuppressWarnings("unchecked")
@Override
public boolean matches(Object o) {
if (!(o instanceof Optional))
return false;
Optional<ReceivedMessage> msg = (Optional<ReceivedMessage>) o;
if (msg.isPresent() && Objects.equal(msg.get().getMessage(), expected)) {
if (delete)
queue.deleteMessage(msg.get());
return true;
}
return false;
}
@Override
public void describeTo(Description description) {
description.appendText(expected.toString());
}
};
}
private Matcher<Optional<ReceivedMessage>> noMessageFound() {
return new BaseMatcher<Optional<ReceivedMessage>>() {
@Override
public boolean matches(Object o) {
return o instanceof Optional && !((Optional) o).isPresent();
}
@Override
public void describeTo(Description description) {
description.appendText("Got a message");
}
};
}
@Test
public void shouldSetTheVisibilityTimeoutOfAMessage() throws Exception {
// Given
final Queue queue = obtainTestQueue();
String testMessage = "foo bar baz";
// When
queue.setQueueVisibilityTimeout(2);
queue.sendSerializable(testMessage);
long beforeReceivingTheMessage = System.currentTimeMillis();
await().until(receivedMessageCallable(queue), receivedMessageMatcher(queue, testMessage, false));
// Then
// Assert that message is invisible for at least 1.5 seconds
await().atMost(new Duration(1500 - (System.currentTimeMillis() - beforeReceivingTheMessage),
TimeUnit.MILLISECONDS)).until(receivedMessageCallable(queue), noMessageFound()); // .5s tolerance
// Finally delete the message
await().until(receivedMessageCallable(queue), receivedMessageMatcher(queue, testMessage, true));
}
@Test/* Then */(expectedExceptions = IllegalArgumentException.class)
public void shouldFailIfDelayedMessageSentWithATimeoutGreaterThan15Minutes() throws Exception {
// Given
Queue queue = obtainTestQueue();
String testMessage = "Bazinga!";
// When
queue.sendSerializableDelayed(testMessage, standardMinutes(16));
}
@Test/* Then */(expectedExceptions = NullPointerException.class)
public void shouldThrowNPEIfDelayedMessageDelayIfNotProvided() {
// Given
Queue queue = obtainTestQueue();
String testMessage = "It's not what it looks like";
// When
queue.sendSerializableDelayed(testMessage, null);
}
@Test
public void shouldNotReceiveTheMessageFasterThanTheDelaySpecified() throws Exception {
// Given
final Queue queue = obtainTestQueue();
String testMessage = "I'm not insane, my mother had me tested!";
// When
long beforeSendingTheMessage = System.currentTimeMillis();
queue.sendSerializableDelayed(testMessage, standardSeconds(2));
// Then
// Assert that the message is not available in the queue for at least 1.5 seconds
Thread.sleep(1500 - (System.currentTimeMillis() - beforeSendingTheMessage));
MatcherAssert.assertThat(receivedMessageCallable(queue).call(), noMessageFound());
// Try to obtain the message
await().atMost(new Duration(1000, TimeUnit.MILLISECONDS)).until(receivedMessageCallable(queue),
receivedMessageMatcher(queue, testMessage, true));
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldThrowExceptionOnTooLowReceiveMessageWaitTimeValue() {
// Given
int receiveMessageWaitTime = -1;
// When
Queue queue = obtainTestQueue();
queue.setReceiveMessageWaitTime(receiveMessageWaitTime);
}
@Test(expectedExceptions = IllegalArgumentException.class)
public void shouldThrowExceptionOnTooHighReceiveMessageWaitTimeValue() {
// Given
int receiveMessageWaitTime = 21;
// When
Queue queue = obtainTestQueue();
queue.setReceiveMessageWaitTime(receiveMessageWaitTime);
}
@Test(enabled = false) // TomekD: enable this once ElasticMQ starts supporting ReceiveMessageWaitTimeSeconds parameter
public void shouldWaitUntilNewMessageAppears() {
// Given
Queue queue = obtainTestQueue();
queue.setReceiveMessageWaitTime(20);
String testMessage = "Message sent after queue was asked for new stuff";
// When
queue.sendSerializableDelayed(testMessage, standardSeconds(15));
Optional<ReceivedMessage> messageOptional = queue.receiveSingleMessage();
// Then
// Even if we requested message before it was actually sent, we should receive it because of long (20secs) message polling
assertThat(messageOptional.isPresent()).isTrue();
assertThat(testMessage.equals(messageOptional.get().getMessage())).isTrue();
queue.deleteMessage(messageOptional.get());
}
private Callable<Optional<ReceivedMessage>> receivedMessageCallable(final Queue queue) {
return new Callable<Optional<ReceivedMessage>>() {
@Override
public Optional<ReceivedMessage> call() throws Exception {
return queue.receiveSingleMessage();
}
};
}
@AfterClass(alwaysRun = true)
public void stopElasticMQ() {
sqsServer.stop();
elasticNode.shutdown();
}
}