Add an integration test for message persistence.
This commit is contained in:
parent
eaa2060d84
commit
a97e0982e3
|
@ -0,0 +1,165 @@
|
||||||
|
package org.whispersystems.textsecuregcm.storage;
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString;
|
||||||
|
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||||
|
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||||
|
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||||
|
import io.lettuce.core.cluster.SlotHash;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.jdbi.v3.core.Jdbi;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||||
|
import org.whispersystems.textsecuregcm.entities.MessageProtos;
|
||||||
|
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
|
||||||
|
import org.whispersystems.textsecuregcm.redis.AbstractRedisClusterTest;
|
||||||
|
|
||||||
|
import java.sql.PreparedStatement;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class MessagePersisterIntegrationTest extends AbstractRedisClusterTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("messagedb.xml"));
|
||||||
|
|
||||||
|
private ScheduledExecutorService scheduledExecutorService;
|
||||||
|
private MessagesCache messagesCache;
|
||||||
|
private MessagesManager messagesManager;
|
||||||
|
private MessagePersister messagePersister;
|
||||||
|
private Account account;
|
||||||
|
|
||||||
|
private static final Duration PERSIST_DELAY = Duration.ofMinutes(10);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Override
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
super.setUp();
|
||||||
|
|
||||||
|
getRedisCluster().useCluster(connection -> {
|
||||||
|
connection.sync().flushall();
|
||||||
|
connection.sync().masters().commands().configSet("notify-keyspace-events", "K$glz");
|
||||||
|
});
|
||||||
|
|
||||||
|
final Messages messages = new Messages(new FaultTolerantDatabase("messages-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration()));
|
||||||
|
final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||||
|
|
||||||
|
scheduledExecutorService = Executors.newScheduledThreadPool(4);
|
||||||
|
messagesCache = new MessagesCache(getRedisCluster(), scheduledExecutorService);
|
||||||
|
messagesManager = new MessagesManager(messages, messagesCache, mock(PushLatencyManager.class));
|
||||||
|
messagePersister = new MessagePersister(messagesCache, messagesManager, accountsManager, scheduledExecutorService, PERSIST_DELAY);
|
||||||
|
|
||||||
|
account = mock(Account.class);
|
||||||
|
|
||||||
|
final UUID accountUuid = UUID.randomUUID();
|
||||||
|
|
||||||
|
when(account.getNumber()).thenReturn("+18005551234");
|
||||||
|
when(account.getUuid()).thenReturn(accountUuid);
|
||||||
|
when(accountsManager.get(accountUuid)).thenReturn(Optional.of(account));
|
||||||
|
|
||||||
|
messagesCache.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
@Override
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
super.tearDown();
|
||||||
|
|
||||||
|
scheduledExecutorService.shutdown();
|
||||||
|
scheduledExecutorService.awaitTermination(15, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 15_000)
|
||||||
|
public void testScheduledPersistMessages() throws Exception {
|
||||||
|
final int messageCount = 377;
|
||||||
|
final List<MessageProtos.Envelope> expectedMessages = new ArrayList<>(messageCount);
|
||||||
|
final Instant now = Instant.now();
|
||||||
|
|
||||||
|
for (int i = 0; i < messageCount; i++) {
|
||||||
|
final UUID messageGuid = UUID.randomUUID();
|
||||||
|
final long timestamp = now.minus(PERSIST_DELAY.multipliedBy(2)).toEpochMilli() + i;
|
||||||
|
|
||||||
|
final MessageProtos.Envelope message = generateRandomMessage(messageGuid, timestamp);
|
||||||
|
|
||||||
|
messagesCache.insert(messageGuid, account.getUuid(), 1, message);
|
||||||
|
expectedMessages.add(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedisCluster().useCluster(connection -> connection.sync().set(MessagesCache.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(SlotHash.getSlot(MessagesCache.getMessageQueueKey(account.getUuid(), 1)) - 1)));
|
||||||
|
|
||||||
|
final AtomicBoolean messagesPersisted = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
messagesManager.addMessageAvailabilityListener(account.getUuid(), 1, new MessageAvailabilityListener() {
|
||||||
|
@Override
|
||||||
|
public void handleNewMessagesAvailable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleNewEphemeralMessageAvailable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMessagesPersisted() {
|
||||||
|
synchronized (messagesPersisted) {
|
||||||
|
messagesPersisted.set(true);
|
||||||
|
messagesPersisted.notifyAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
messagePersister.start();
|
||||||
|
|
||||||
|
synchronized (messagesPersisted) {
|
||||||
|
while (!messagesPersisted.get()) {
|
||||||
|
messagesPersisted.wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
messagePersister.stop();
|
||||||
|
|
||||||
|
final List<MessageProtos.Envelope> persistedMessages = new ArrayList<>(messageCount);
|
||||||
|
|
||||||
|
try (final PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM messages WHERE destination = ? ORDER BY timestamp ASC")) {
|
||||||
|
statement.setString(1, account.getNumber());
|
||||||
|
|
||||||
|
try (final ResultSet resultSet = statement.executeQuery()) {
|
||||||
|
while (resultSet.next()) {
|
||||||
|
persistedMessages.add(MessageProtos.Envelope.newBuilder()
|
||||||
|
.setServerGuid(resultSet.getString("guid"))
|
||||||
|
.setType(MessageProtos.Envelope.Type.valueOf(resultSet.getInt("type")))
|
||||||
|
.setTimestamp(resultSet.getLong("timestamp"))
|
||||||
|
.setServerTimestamp(resultSet.getLong("server_timestamp"))
|
||||||
|
.setContent(ByteString.copyFrom(resultSet.getBytes("content")))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(expectedMessages, persistedMessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageProtos.Envelope generateRandomMessage(final UUID messageGuid, final long timestamp) {
|
||||||
|
return MessageProtos.Envelope.newBuilder()
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.setServerTimestamp(timestamp)
|
||||||
|
.setContent(ByteString.copyFromUtf8(RandomStringUtils.randomAlphanumeric(256)))
|
||||||
|
.setType(MessageProtos.Envelope.Type.CIPHERTEXT)
|
||||||
|
.setServerGuid(messageGuid.toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue