mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
1697 lines
56 KiB
Java
1697 lines
56 KiB
Java
package org.briarproject.db;
|
|
|
|
import org.briarproject.BriarTestCase;
|
|
import org.briarproject.TestUtils;
|
|
import org.briarproject.api.TransportId;
|
|
import org.briarproject.api.contact.Contact;
|
|
import org.briarproject.api.contact.ContactId;
|
|
import org.briarproject.api.crypto.SecretKey;
|
|
import org.briarproject.api.db.ContactExistsException;
|
|
import org.briarproject.api.db.DatabaseComponent;
|
|
import org.briarproject.api.db.Metadata;
|
|
import org.briarproject.api.db.NoSuchContactException;
|
|
import org.briarproject.api.db.NoSuchGroupException;
|
|
import org.briarproject.api.db.NoSuchLocalAuthorException;
|
|
import org.briarproject.api.db.NoSuchMessageException;
|
|
import org.briarproject.api.db.NoSuchTransportException;
|
|
import org.briarproject.api.db.Transaction;
|
|
import org.briarproject.api.event.ContactAddedEvent;
|
|
import org.briarproject.api.event.ContactRemovedEvent;
|
|
import org.briarproject.api.event.ContactStatusChangedEvent;
|
|
import org.briarproject.api.event.EventBus;
|
|
import org.briarproject.api.event.GroupAddedEvent;
|
|
import org.briarproject.api.event.GroupRemovedEvent;
|
|
import org.briarproject.api.event.GroupVisibilityUpdatedEvent;
|
|
import org.briarproject.api.event.LocalAuthorAddedEvent;
|
|
import org.briarproject.api.event.LocalAuthorRemovedEvent;
|
|
import org.briarproject.api.event.MessageAddedEvent;
|
|
import org.briarproject.api.event.MessageRequestedEvent;
|
|
import org.briarproject.api.event.MessageSharedEvent;
|
|
import org.briarproject.api.event.MessageToAckEvent;
|
|
import org.briarproject.api.event.MessageToRequestEvent;
|
|
import org.briarproject.api.event.MessageStateChangedEvent;
|
|
import org.briarproject.api.event.MessagesAckedEvent;
|
|
import org.briarproject.api.event.MessagesSentEvent;
|
|
import org.briarproject.api.event.SettingsUpdatedEvent;
|
|
import org.briarproject.api.identity.Author;
|
|
import org.briarproject.api.identity.AuthorId;
|
|
import org.briarproject.api.identity.LocalAuthor;
|
|
import org.briarproject.api.lifecycle.ShutdownManager;
|
|
import org.briarproject.api.settings.Settings;
|
|
import org.briarproject.api.sync.Ack;
|
|
import org.briarproject.api.sync.ClientId;
|
|
import org.briarproject.api.sync.Group;
|
|
import org.briarproject.api.sync.GroupId;
|
|
import org.briarproject.api.sync.Message;
|
|
import org.briarproject.api.sync.MessageId;
|
|
import org.briarproject.api.sync.Offer;
|
|
import org.briarproject.api.sync.Request;
|
|
import org.briarproject.api.transport.IncomingKeys;
|
|
import org.briarproject.api.transport.OutgoingKeys;
|
|
import org.briarproject.api.transport.TransportKeys;
|
|
import org.jmock.Expectations;
|
|
import org.jmock.Mockery;
|
|
import org.junit.Test;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.Map;
|
|
|
|
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
|
import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
|
|
import static org.briarproject.api.sync.ValidationManager.State.UNKNOWN;
|
|
import static org.briarproject.api.sync.ValidationManager.State.VALID;
|
|
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
|
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
|
|
import static org.junit.Assert.assertEquals;
|
|
import static org.junit.Assert.assertFalse;
|
|
import static org.junit.Assert.assertNotNull;
|
|
import static org.junit.Assert.fail;
|
|
|
|
public class DatabaseComponentImplTest extends BriarTestCase {
|
|
|
|
private final Object txn = new Object();
|
|
private final ClientId clientId;
|
|
private final GroupId groupId;
|
|
private final Group group;
|
|
private final AuthorId authorId;
|
|
private final Author author;
|
|
private final AuthorId localAuthorId;
|
|
private final LocalAuthor localAuthor;
|
|
private final MessageId messageId, messageId1;
|
|
private final int size;
|
|
private final byte[] raw;
|
|
private final Message message;
|
|
private final Metadata metadata;
|
|
private final TransportId transportId;
|
|
private final int maxLatency;
|
|
private final ContactId contactId;
|
|
private final Contact contact;
|
|
|
|
public DatabaseComponentImplTest() {
|
|
clientId = new ClientId(TestUtils.getRandomId());
|
|
groupId = new GroupId(TestUtils.getRandomId());
|
|
byte[] descriptor = new byte[0];
|
|
group = new Group(groupId, clientId, descriptor);
|
|
authorId = new AuthorId(TestUtils.getRandomId());
|
|
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
|
localAuthorId = new AuthorId(TestUtils.getRandomId());
|
|
long timestamp = System.currentTimeMillis();
|
|
localAuthor = new LocalAuthor(localAuthorId, "Bob",
|
|
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
|
messageId = new MessageId(TestUtils.getRandomId());
|
|
messageId1 = new MessageId(TestUtils.getRandomId());
|
|
size = 1234;
|
|
raw = new byte[size];
|
|
message = new Message(messageId, groupId, timestamp, raw);
|
|
metadata = new Metadata();
|
|
metadata.put("foo", new byte[] {'b', 'a', 'r'});
|
|
transportId = new TransportId("id");
|
|
maxLatency = Integer.MAX_VALUE;
|
|
contactId = new ContactId(234);
|
|
contact = new Contact(contactId, author, localAuthorId, true, true);
|
|
}
|
|
|
|
private DatabaseComponent createDatabaseComponent(Database<Object> database,
|
|
EventBus eventBus, ShutdownManager shutdown) {
|
|
return new DatabaseComponentImpl<>(database, Object.class, eventBus,
|
|
shutdown);
|
|
}
|
|
|
|
@Test
|
|
@SuppressWarnings("unchecked")
|
|
public void testSimpleCalls() throws Exception {
|
|
final int shutdownHandle = 12345;
|
|
Mockery context = new Mockery();
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// open()
|
|
oneOf(database).open();
|
|
will(returnValue(false));
|
|
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
|
will(returnValue(shutdownHandle));
|
|
// startTransaction()
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
// addLocalAuthor()
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(false));
|
|
oneOf(database).addLocalAuthor(txn, localAuthor);
|
|
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
|
|
// addContact()
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsLocalAuthor(txn, authorId);
|
|
will(returnValue(false));
|
|
oneOf(database).containsContact(txn, authorId, localAuthorId);
|
|
will(returnValue(false));
|
|
oneOf(database).addContact(txn, author, localAuthorId, true, true);
|
|
will(returnValue(contactId));
|
|
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(
|
|
ContactStatusChangedEvent.class)));
|
|
// getContacts()
|
|
oneOf(database).getContacts(txn);
|
|
will(returnValue(Collections.singletonList(contact)));
|
|
// addGroup()
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(false));
|
|
oneOf(database).addGroup(txn, group);
|
|
oneOf(eventBus).broadcast(with(any(GroupAddedEvent.class)));
|
|
// addGroup() again
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
// getGroups()
|
|
oneOf(database).getGroups(txn, clientId);
|
|
will(returnValue(Collections.singletonList(group)));
|
|
// removeGroup()
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).getVisibility(txn, groupId);
|
|
will(returnValue(Collections.emptyList()));
|
|
oneOf(database).removeGroup(txn, groupId);
|
|
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(
|
|
GroupVisibilityUpdatedEvent.class)));
|
|
// removeContact()
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).removeContact(txn, contactId);
|
|
oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
|
|
// removeLocalAuthor()
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(true));
|
|
oneOf(database).removeLocalAuthor(txn, localAuthorId);
|
|
oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
|
|
// endTransaction()
|
|
oneOf(database).commitTransaction(txn);
|
|
// close()
|
|
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
|
oneOf(database).close();
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
assertFalse(db.open());
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addLocalAuthor(transaction, localAuthor);
|
|
assertEquals(contactId,
|
|
db.addContact(transaction, author, localAuthorId, true,
|
|
true));
|
|
assertEquals(Collections.singletonList(contact),
|
|
db.getContacts(transaction));
|
|
db.addGroup(transaction, group); // First time - listeners called
|
|
db.addGroup(transaction, group); // Second time - not called
|
|
assertEquals(Collections.singletonList(group),
|
|
db.getGroups(transaction, clientId));
|
|
db.removeGroup(transaction, group);
|
|
db.removeContact(transaction, contactId);
|
|
db.removeLocalAuthor(transaction, localAuthorId);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
db.close();
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testLocalMessagesAreNotStoredUnlessGroupExists()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(false));
|
|
oneOf(database).abortTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addLocalMessage(transaction, message, clientId, metadata, true);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testAddLocalMessage() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
|
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
|
oneOf(database).getVisibility(txn, groupId);
|
|
will(returnValue(Collections.singletonList(contactId)));
|
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
|
oneOf(database).commitTransaction(txn);
|
|
// The message was added, so the listeners should be called
|
|
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageStateChangedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addLocalMessage(transaction, message, clientId, metadata, true);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testVariousMethodsThrowExceptionIfContactIsMissing()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// Check whether the contact is in the DB (which it's not)
|
|
exactly(18).of(database).startTransaction();
|
|
will(returnValue(txn));
|
|
exactly(18).of(database).containsContact(txn, contactId);
|
|
will(returnValue(false));
|
|
exactly(18).of(database).abortTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addTransportKeys(transaction, contactId, createTransportKeys());
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.generateAck(transaction, contactId, 123);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.generateBatch(transaction, contactId, 123, 456);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.generateOffer(transaction, contactId, 123, 456);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.generateRequest(transaction, contactId, 123);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getContact(transaction, contactId);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageStatus(transaction, contactId, groupId);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageStatus(transaction, contactId, messageId);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.isVisibleToContact(transaction, contactId, groupId);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
Ack a = new Ack(Collections.singletonList(messageId));
|
|
db.receiveAck(transaction, contactId, a);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.receiveMessage(transaction, contactId, message);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
Offer o = new Offer(Collections.singletonList(messageId));
|
|
db.receiveOffer(transaction, contactId, o);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
Request r = new Request(Collections.singletonList(messageId));
|
|
db.receiveRequest(transaction, contactId, r);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.removeContact(transaction, contactId);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setContactActive(transaction, contactId, true);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
|
new byte[REORDERING_WINDOW_SIZE / 8]);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setVisibleToContact(transaction, contactId, groupId, true);
|
|
fail();
|
|
} catch (NoSuchContactException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// Check whether the pseudonym is in the DB (which it's not)
|
|
exactly(3).of(database).startTransaction();
|
|
will(returnValue(txn));
|
|
exactly(3).of(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(false));
|
|
exactly(3).of(database).abortTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addContact(transaction, author, localAuthorId, true, true);
|
|
fail();
|
|
} catch (NoSuchLocalAuthorException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getLocalAuthor(transaction, localAuthorId);
|
|
fail();
|
|
} catch (NoSuchLocalAuthorException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.removeLocalAuthor(transaction, localAuthorId);
|
|
fail();
|
|
} catch (NoSuchLocalAuthorException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testVariousMethodsThrowExceptionIfGroupIsMissing()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// Check whether the group is in the DB (which it's not)
|
|
exactly(9).of(database).startTransaction();
|
|
will(returnValue(txn));
|
|
exactly(9).of(database).containsGroup(txn, groupId);
|
|
will(returnValue(false));
|
|
exactly(9).of(database).abortTransaction(txn);
|
|
// This is needed for getMessageStatus(), isVisibleToContact(), and
|
|
// setVisibleToContact() to proceed
|
|
exactly(3).of(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.getGroup(transaction, groupId);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getGroupMetadata(transaction, groupId);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageMetadata(transaction, groupId);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageMetadata(transaction, groupId, new Metadata());
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageStatus(transaction, contactId, groupId);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.isVisibleToContact(transaction, contactId, groupId);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.mergeGroupMetadata(transaction, groupId, metadata);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.removeGroup(transaction, group);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setVisibleToContact(transaction, contactId, groupId, true);
|
|
fail();
|
|
} catch (NoSuchGroupException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testVariousMethodsThrowExceptionIfMessageIsMissing()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// Check whether the message is in the DB (which it's not)
|
|
exactly(10).of(database).startTransaction();
|
|
will(returnValue(txn));
|
|
exactly(10).of(database).containsMessage(txn, messageId);
|
|
will(returnValue(false));
|
|
exactly(10).of(database).abortTransaction(txn);
|
|
// This is needed for getMessageStatus() to proceed
|
|
exactly(1).of(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.deleteMessage(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.deleteMessageMetadata(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getRawMessage(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageMetadata(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getMessageStatus(transaction, contactId, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.mergeMessageMetadata(transaction, messageId, metadata);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setMessageShared(transaction, message, true);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setMessageState(transaction, message, clientId, VALID);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(true);
|
|
try {
|
|
db.getMessageDependencies(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(true);
|
|
try {
|
|
db.getMessageDependents(transaction, messageId);
|
|
fail();
|
|
} catch (NoSuchMessageException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testVariousMethodsThrowExceptionIfTransportIsMissing()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// startTransaction()
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
// addLocalAuthor()
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(false));
|
|
oneOf(database).addLocalAuthor(txn, localAuthor);
|
|
oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
|
|
// addContact()
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsLocalAuthor(txn, authorId);
|
|
will(returnValue(false));
|
|
oneOf(database).containsContact(txn, authorId, localAuthorId);
|
|
will(returnValue(false));
|
|
oneOf(database).addContact(txn, author, localAuthorId, true, true);
|
|
will(returnValue(contactId));
|
|
oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(
|
|
ContactStatusChangedEvent.class)));
|
|
// endTransaction()
|
|
oneOf(database).commitTransaction(txn);
|
|
// Check whether the transport is in the DB (which it's not)
|
|
exactly(4).of(database).startTransaction();
|
|
will(returnValue(txn));
|
|
exactly(2).of(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
exactly(4).of(database).containsTransport(txn, transportId);
|
|
will(returnValue(false));
|
|
exactly(4).of(database).abortTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addLocalAuthor(transaction, localAuthor);
|
|
assertEquals(contactId,
|
|
db.addContact(transaction, author, localAuthorId, true,
|
|
true));
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.getTransportKeys(transaction, transportId);
|
|
fail();
|
|
} catch (NoSuchTransportException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.incrementStreamCounter(transaction, contactId, transportId, 0);
|
|
fail();
|
|
} catch (NoSuchTransportException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.removeTransport(transaction, transportId);
|
|
fail();
|
|
} catch (NoSuchTransportException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
transaction = db.startTransaction(false);
|
|
try {
|
|
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
|
|
new byte[REORDERING_WINDOW_SIZE / 8]);
|
|
fail();
|
|
} catch (NoSuchTransportException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testGenerateAck() throws Exception {
|
|
final Collection<MessageId> messagesToAck = Arrays.asList(messageId,
|
|
messageId1);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessagesToAck(txn, contactId, 123);
|
|
will(returnValue(messagesToAck));
|
|
oneOf(database).lowerAckFlag(txn, contactId, messagesToAck);
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Ack a = db.generateAck(transaction, contactId, 123);
|
|
assertEquals(messagesToAck, a.getMessageIds());
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testGenerateBatch() throws Exception {
|
|
final byte[] raw1 = new byte[size];
|
|
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
|
final Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessagesToSend(txn, contactId, size * 2);
|
|
will(returnValue(ids));
|
|
oneOf(database).getRawMessage(txn, messageId);
|
|
will(returnValue(raw));
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId,
|
|
maxLatency);
|
|
oneOf(database).getRawMessage(txn, messageId1);
|
|
will(returnValue(raw1));
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId1,
|
|
maxLatency);
|
|
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
assertEquals(messages, db.generateBatch(transaction, contactId,
|
|
size * 2, maxLatency));
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testGenerateOffer() throws Exception {
|
|
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
|
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessagesToOffer(txn, contactId, 123);
|
|
will(returnValue(ids));
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId,
|
|
maxLatency);
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId1,
|
|
maxLatency);
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Offer o = db.generateOffer(transaction, contactId, 123, maxLatency);
|
|
assertEquals(ids, o.getMessageIds());
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testGenerateRequest() throws Exception {
|
|
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
|
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessagesToRequest(txn, contactId, 123);
|
|
will(returnValue(ids));
|
|
oneOf(database).removeOfferedMessages(txn, contactId, ids);
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Request r = db.generateRequest(transaction, contactId, 123);
|
|
assertEquals(ids, r.getMessageIds());
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testGenerateRequestedBatch() throws Exception {
|
|
final byte[] raw1 = new byte[size];
|
|
final Collection<MessageId> ids = Arrays.asList(messageId, messageId1);
|
|
final Collection<byte[]> messages = Arrays.asList(raw, raw1);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).getRequestedMessagesToSend(txn, contactId,
|
|
size * 2);
|
|
will(returnValue(ids));
|
|
oneOf(database).getRawMessage(txn, messageId);
|
|
will(returnValue(raw));
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId,
|
|
maxLatency);
|
|
oneOf(database).getRawMessage(txn, messageId1);
|
|
will(returnValue(raw1));
|
|
oneOf(database).updateExpiryTime(txn, contactId, messageId1,
|
|
maxLatency);
|
|
oneOf(database).lowerRequestedFlag(txn, contactId, ids);
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(MessagesSentEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
assertEquals(messages, db.generateRequestedBatch(transaction,
|
|
contactId, size * 2, maxLatency));
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveAck() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).raiseSeenFlag(txn, contactId, messageId);
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(MessagesAckedEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Ack a = new Ack(Collections.singletonList(messageId));
|
|
db.receiveAck(transaction, contactId, a);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveMessage() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).addMessage(txn, message, UNKNOWN, false);
|
|
oneOf(database).getVisibility(txn, groupId);
|
|
will(returnValue(Collections.singletonList(contactId)));
|
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
|
oneOf(database).raiseAckFlag(txn, contactId, messageId);
|
|
oneOf(database).commitTransaction(txn);
|
|
// The message was received and added
|
|
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.receiveMessage(transaction, contactId, message);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveDuplicateMessage() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
|
will(returnValue(true));
|
|
// The message wasn't stored but it must still be acked
|
|
oneOf(database).raiseAckFlag(txn, contactId, messageId);
|
|
oneOf(database).commitTransaction(txn);
|
|
// The message was received but not added
|
|
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.receiveMessage(transaction, contactId, message);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveMessageWithoutVisibleGroup() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
|
will(returnValue(false));
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.receiveMessage(transaction, contactId, message);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveOffer() throws Exception {
|
|
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
|
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
|
final MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
// There's room for two more offered messages
|
|
oneOf(database).countOfferedMessages(txn, contactId);
|
|
will(returnValue(MAX_OFFERED_MESSAGES - 2));
|
|
// The first message isn't visible - request it
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addOfferedMessage(txn, contactId, messageId);
|
|
// The second message is visible - ack it
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId1);
|
|
will(returnValue(true));
|
|
oneOf(database).raiseSeenFlag(txn, contactId, messageId1);
|
|
oneOf(database).raiseAckFlag(txn, contactId, messageId1);
|
|
// The third message isn't visible - request it
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId2);
|
|
will(returnValue(false));
|
|
oneOf(database).addOfferedMessage(txn, contactId, messageId2);
|
|
// The fourth message isn't visible, but there's no room to store it
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId3);
|
|
will(returnValue(false));
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(MessageToAckEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageToRequestEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Offer o = new Offer(Arrays.asList(messageId, messageId1,
|
|
messageId2, messageId3));
|
|
db.receiveOffer(transaction, contactId, o);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testReceiveRequest() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleMessage(txn, contactId, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).raiseRequestedFlag(txn, contactId, messageId);
|
|
oneOf(database).resetExpiryTime(txn, contactId, messageId);
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(MessageRequestedEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
Request r = new Request(Collections.singletonList(messageId));
|
|
db.receiveRequest(transaction, contactId, r);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testChangingVisibilityCallsListeners() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
|
will(returnValue(false)); // Not yet visible
|
|
oneOf(database).addVisibility(txn, contactId, groupId);
|
|
oneOf(database).getMessageIds(txn, groupId);
|
|
will(returnValue(Collections.singletonList(messageId)));
|
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
|
oneOf(database).commitTransaction(txn);
|
|
oneOf(eventBus).broadcast(with(any(
|
|
GroupVisibilityUpdatedEvent.class)));
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.setVisibleToContact(transaction, contactId, groupId, true);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testNotChangingVisibilityDoesNotCallListeners()
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsVisibleGroup(txn, contactId, groupId);
|
|
will(returnValue(true)); // Already visible
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.setVisibleToContact(transaction, contactId, groupId, true);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testTransportKeys() throws Exception {
|
|
final TransportKeys transportKeys = createTransportKeys();
|
|
final Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
|
contactId, transportKeys);
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// startTransaction()
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
// updateTransportKeys()
|
|
oneOf(database).containsContact(txn, contactId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsTransport(txn, transportId);
|
|
will(returnValue(true));
|
|
oneOf(database).updateTransportKeys(txn, keys);
|
|
// getTransportKeys()
|
|
oneOf(database).containsTransport(txn, transportId);
|
|
will(returnValue(true));
|
|
oneOf(database).getTransportKeys(txn, transportId);
|
|
will(returnValue(keys));
|
|
// endTransaction()
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.updateTransportKeys(transaction, keys);
|
|
assertEquals(keys, db.getTransportKeys(transaction, transportId));
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
private TransportKeys createTransportKeys() {
|
|
SecretKey inPrevTagKey = TestUtils.getSecretKey();
|
|
SecretKey inPrevHeaderKey = TestUtils.getSecretKey();
|
|
IncomingKeys inPrev = new IncomingKeys(inPrevTagKey, inPrevHeaderKey,
|
|
1, 123, new byte[4]);
|
|
SecretKey inCurrTagKey = TestUtils.getSecretKey();
|
|
SecretKey inCurrHeaderKey = TestUtils.getSecretKey();
|
|
IncomingKeys inCurr = new IncomingKeys(inCurrTagKey, inCurrHeaderKey,
|
|
2, 234, new byte[4]);
|
|
SecretKey inNextTagKey = TestUtils.getSecretKey();
|
|
SecretKey inNextHeaderKey = TestUtils.getSecretKey();
|
|
IncomingKeys inNext = new IncomingKeys(inNextTagKey, inNextHeaderKey,
|
|
3, 345, new byte[4]);
|
|
SecretKey outCurrTagKey = TestUtils.getSecretKey();
|
|
SecretKey outCurrHeaderKey = TestUtils.getSecretKey();
|
|
OutgoingKeys outCurr = new OutgoingKeys(outCurrTagKey, outCurrHeaderKey,
|
|
2, 456);
|
|
return new TransportKeys(transportId, inPrev, inCurr, inNext, outCurr);
|
|
}
|
|
|
|
@Test
|
|
public void testMergeSettings() throws Exception {
|
|
final Settings before = new Settings();
|
|
before.put("foo", "bar");
|
|
before.put("baz", "bam");
|
|
final Settings update = new Settings();
|
|
update.put("baz", "qux");
|
|
final Settings merged = new Settings();
|
|
merged.put("foo", "bar");
|
|
merged.put("baz", "qux");
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
context.checking(new Expectations() {{
|
|
// startTransaction()
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
// mergeSettings()
|
|
oneOf(database).getSettings(txn, "namespace");
|
|
will(returnValue(before));
|
|
oneOf(database).mergeSettings(txn, update, "namespace");
|
|
oneOf(eventBus).broadcast(with(any(SettingsUpdatedEvent.class)));
|
|
// mergeSettings() again
|
|
oneOf(database).getSettings(txn, "namespace");
|
|
will(returnValue(merged));
|
|
// endTransaction()
|
|
oneOf(database).commitTransaction(txn);
|
|
}});
|
|
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
// First merge should broadcast an event
|
|
db.mergeSettings(transaction, update, "namespace");
|
|
// Second merge should not broadcast an event
|
|
db.mergeSettings(transaction, update, "namespace");
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void testCannotStartReadTransactionDuringReadTransaction()
|
|
throws Exception {
|
|
testCannotStartTransactionDuringTransaction(true, true);
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void testCannotStartWriteTransactionDuringReadTransaction()
|
|
throws Exception {
|
|
testCannotStartTransactionDuringTransaction(true, false);
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void testCannotStartReadTransactionDuringWriteTransaction()
|
|
throws Exception {
|
|
testCannotStartTransactionDuringTransaction(false, true);
|
|
}
|
|
|
|
@Test(expected = IllegalStateException.class)
|
|
public void testCannotStartWriteTransactionDuringWriteTransaction()
|
|
throws Exception {
|
|
testCannotStartTransactionDuringTransaction(false, false);
|
|
}
|
|
|
|
private void testCannotStartTransactionDuringTransaction(
|
|
boolean firstTxnReadOnly, boolean secondTxnReadOnly)
|
|
throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
}});
|
|
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
assertNotNull(db.startTransaction(firstTxnReadOnly));
|
|
try {
|
|
db.startTransaction(secondTxnReadOnly);
|
|
fail();
|
|
} finally {
|
|
context.assertIsSatisfied();
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testCannotAddLocalIdentityAsContact() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(true));
|
|
// Contact is a local identity
|
|
oneOf(database).containsLocalAuthor(txn, authorId);
|
|
will(returnValue(true));
|
|
oneOf(database).abortTransaction(txn);
|
|
}});
|
|
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addContact(transaction, author, localAuthorId, true, true);
|
|
fail();
|
|
} catch (ContactExistsException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
public void testCannotAddDuplicateContact() throws Exception {
|
|
Mockery context = new Mockery();
|
|
@SuppressWarnings("unchecked")
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
|
|
context.checking(new Expectations() {{
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
oneOf(database).containsLocalAuthor(txn, localAuthorId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsLocalAuthor(txn, authorId);
|
|
will(returnValue(false));
|
|
// Contact already exists for this local identity
|
|
oneOf(database).containsContact(txn, authorId, localAuthorId);
|
|
will(returnValue(true));
|
|
oneOf(database).abortTransaction(txn);
|
|
}});
|
|
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addContact(transaction, author, localAuthorId, true, true);
|
|
fail();
|
|
} catch (ContactExistsException expected) {
|
|
// Expected
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
|
|
@Test
|
|
@SuppressWarnings("unchecked")
|
|
public void testMessageDependencies() throws Exception {
|
|
final int shutdownHandle = 12345;
|
|
Mockery context = new Mockery();
|
|
final Database<Object> database = context.mock(Database.class);
|
|
final ShutdownManager shutdown = context.mock(ShutdownManager.class);
|
|
final EventBus eventBus = context.mock(EventBus.class);
|
|
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
|
context.checking(new Expectations() {{
|
|
// open()
|
|
oneOf(database).open();
|
|
will(returnValue(false));
|
|
oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
|
|
will(returnValue(shutdownHandle));
|
|
// startTransaction()
|
|
oneOf(database).startTransaction();
|
|
will(returnValue(txn));
|
|
// addLocalMessage()
|
|
oneOf(database).containsGroup(txn, groupId);
|
|
will(returnValue(true));
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
|
oneOf(database).getVisibility(txn, groupId);
|
|
will(returnValue(Collections.singletonList(contactId)));
|
|
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
|
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
|
will(returnValue(false));
|
|
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
|
// addMessageDependencies()
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).addMessageDependency(txn, messageId, messageId1);
|
|
oneOf(database).addMessageDependency(txn, messageId, messageId2);
|
|
// getMessageDependencies()
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessageDependencies(txn, messageId);
|
|
// getMessageDependents()
|
|
oneOf(database).containsMessage(txn, messageId);
|
|
will(returnValue(true));
|
|
oneOf(database).getMessageDependents(txn, messageId);
|
|
// broadcast for message added event
|
|
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageStateChangedEvent.class)));
|
|
oneOf(eventBus).broadcast(with(any(MessageSharedEvent.class)));
|
|
// endTransaction()
|
|
oneOf(database).commitTransaction(txn);
|
|
// close()
|
|
oneOf(shutdown).removeShutdownHook(shutdownHandle);
|
|
oneOf(database).close();
|
|
}});
|
|
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
|
shutdown);
|
|
|
|
assertFalse(db.open());
|
|
Transaction transaction = db.startTransaction(false);
|
|
try {
|
|
db.addLocalMessage(transaction, message, clientId, metadata, true);
|
|
Collection<MessageId> dependencies = new ArrayList<>(2);
|
|
dependencies.add(messageId1);
|
|
dependencies.add(messageId2);
|
|
db.addMessageDependencies(transaction, message, dependencies);
|
|
db.getMessageDependencies(transaction, messageId);
|
|
db.getMessageDependents(transaction, messageId);
|
|
transaction.setComplete();
|
|
} finally {
|
|
db.endTransaction(transaction);
|
|
}
|
|
db.close();
|
|
|
|
context.assertIsSatisfied();
|
|
}
|
|
}
|