From fb6b487212e4d564ddcb3d7565329b72627c9051 Mon Sep 17 00:00:00 2001 From: akwizgran <michael@briarproject.org> Date: Tue, 24 Apr 2018 17:31:21 +0100 Subject: [PATCH] Unit tests for client versioning manager. --- .../ClientVersioningManagerImplTest.java | 669 ++++++++++++++++++ 1 file changed, 669 insertions(+) create mode 100644 bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java diff --git a/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java new file mode 100644 index 0000000000..7494873d9c --- /dev/null +++ b/bramble-core/src/test/java/org/briarproject/bramble/versioning/ClientVersioningManagerImplTest.java @@ -0,0 +1,669 @@ +package org.briarproject.bramble.versioning; + +import org.briarproject.bramble.api.client.ClientHelper; +import org.briarproject.bramble.api.client.ContactGroupFactory; +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.data.BdfDictionary; +import org.briarproject.bramble.api.data.BdfEntry; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.db.DatabaseComponent; +import org.briarproject.bramble.api.db.Metadata; +import org.briarproject.bramble.api.db.Transaction; +import org.briarproject.bramble.api.sync.ClientId; +import org.briarproject.bramble.api.sync.Group; +import org.briarproject.bramble.api.sync.Group.Visibility; +import org.briarproject.bramble.api.sync.Message; +import org.briarproject.bramble.api.sync.MessageId; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.versioning.ClientVersioningManager.ClientVersioningHook; +import org.briarproject.bramble.test.BrambleMockTestCase; +import org.jmock.Expectations; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.Collections.singletonMap; +import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE; +import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED; +import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.CLIENT_ID; +import static org.briarproject.bramble.api.versioning.ClientVersioningManager.MAJOR_VERSION; +import static org.briarproject.bramble.test.TestUtils.getAuthor; +import static org.briarproject.bramble.test.TestUtils.getClientId; +import static org.briarproject.bramble.test.TestUtils.getGroup; +import static org.briarproject.bramble.test.TestUtils.getLocalAuthor; +import static org.briarproject.bramble.test.TestUtils.getMessage; +import static org.briarproject.bramble.test.TestUtils.getRandomId; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_LOCAL; +import static org.briarproject.bramble.versioning.ClientVersioningConstants.MSG_KEY_UPDATE_VERSION; +import static org.junit.Assert.assertFalse; + +public class ClientVersioningManagerImplTest extends BrambleMockTestCase { + + private final DatabaseComponent db = context.mock(DatabaseComponent.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final ContactGroupFactory contactGroupFactory = + context.mock(ContactGroupFactory.class); + private final Clock clock = context.mock(Clock.class); + private final ClientVersioningHook hook = + context.mock(ClientVersioningHook.class); + + private final Group localGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Group contactGroup = getGroup(CLIENT_ID, MAJOR_VERSION); + private final Contact contact = new Contact(new ContactId(123), + getAuthor(), getLocalAuthor().getId(), true, true); + private final ClientId clientId = getClientId(); + private final long now = System.currentTimeMillis(); + private final Transaction txn = new Transaction(null, false); + + private ClientVersioningManagerImpl createInstance() throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID, + MAJOR_VERSION); + will(returnValue(localGroup)); + }}); + return new ClientVersioningManagerImpl(db, clientHelper, + contactGroupFactory, clock); + } + + @Test + public void testCreatesGroupsAtStartup() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(false)); + oneOf(db).addGroup(txn, localGroup); + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + }}); + expectAddingContact(); + + ClientVersioningManagerImpl c = createInstance(); + c.createLocalState(txn); + } + + @Test + public void testDoesNotCreateGroupsAtStartupIfAlreadyCreated() + throws Exception { + context.checking(new Expectations() {{ + oneOf(db).containsGroup(txn, localGroup.getId()); + will(returnValue(true)); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.createLocalState(txn); + } + + @Test + public void testCreatesContactGroupWhenAddingContact() throws Exception { + expectAddingContact(); + + ClientVersioningManagerImpl c = createInstance(); + c.addingContact(txn, contact); + } + + private void expectAddingContact() throws Exception { + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + long now = System.currentTimeMillis(); + BdfList localUpdateBody = BdfList.of(new BdfList(), 1L); + Message localUpdate = getMessage(contactGroup.getId()); + BdfDictionary localUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).addGroup(txn, contactGroup); + oneOf(db).setGroupVisibility(txn, contact.getId(), + contactGroup.getId(), SHARED); + oneOf(clientHelper).mergeGroupMetadata(txn, contactGroup.getId(), + groupMeta); + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + localUpdateBody); + will(returnValue(localUpdate)); + oneOf(clientHelper).addLocalMessage(txn, localUpdate, + localUpdateMeta, true); + }}); + } + + @Test + public void testRemovesGroupWhenRemovingContact() throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.removingContact(txn, contact); + } + + @Test + public void testStoresClientVersionsAtFirstStartup() throws Exception { + BdfList localVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + Message localVersions = getMessage(localGroup.getId()); + MessageId localUpdateId = new MessageId(getRandomId()); + BdfDictionary localUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfList localUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // No client versions have been stored yet + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(emptyList())); + // Store the client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + localVersionsBody); + will(returnValue(localVersions)); + oneOf(db).addLocalMessage(txn, localVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(localUpdateId, localUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, localUpdateId); + will(returnValue(localUpdateBody)); + // Latest local update is up-to-date, no visibilities have changed + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testComparesClientVersionsAtSubsequentStartup() + throws Exception { + MessageId localVersionsId = new MessageId(getRandomId()); + BdfList localVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(localVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, localVersionsId); + will(returnValue(localVersionsBody)); + // Client versions are up-to-date + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testStoresClientVersionsAtSubsequentStartupIfChanged() + throws Exception { + // The client had minor version 234 in the old client versions + BdfList oldLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + // The client has minor version 345 in the new client versions + BdfList newLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 345)); + // The client had minor version 234 in the old local update + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + // The client has minor version 345 in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 345, false)), 2L); + + MessageId oldLocalVersionsId = new MessageId(getRandomId()); + Message newLocalVersions = getMessage(localGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(oldLocalVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId); + will(returnValue(oldLocalVersionsBody)); + // Delete the old client versions + oneOf(db).removeMessage(txn, oldLocalVersionsId); + // Store the new client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + newLocalVersionsBody); + will(returnValue(newLocalVersions)); + oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(oldLocalUpdateId, + oldLocalUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Delete the latest local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // No visibilities have changed + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 345, hook); + c.startService(); + } + + @Test + public void testActivatesNewClientAtStartupIfAlreadyAdvertisedByContact() + throws Exception { + testActivatesNewClientAtStartup(false, VISIBLE); + } + + @Test + public void testActivatesNewClientAtStartupIfAlreadyActivatedByContact() + throws Exception { + testActivatesNewClientAtStartup(true, SHARED); + } + + private void testActivatesNewClientAtStartup(boolean remoteActive, + Visibility visibility) throws Exception { + // The client was missing from the old client versions + BdfList oldLocalVersionsBody = new BdfList(); + // The client is included in the new client versions + BdfList newLocalVersionsBody = + BdfList.of(BdfList.of(clientId.getString(), 123, 234)); + // The client was missing from the old local update + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + // The client was included in the old remote update + BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 345, remoteActive)), 1L); + // The client is active in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 2L); + + MessageId oldLocalVersionsId = new MessageId(getRandomId()); + Message newLocalVersions = getMessage(localGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(localGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + // Load the old client versions + oneOf(db).getMessageIds(txn, localGroup.getId()); + will(returnValue(singletonList(oldLocalVersionsId))); + oneOf(clientHelper).getMessageAsList(txn, oldLocalVersionsId); + will(returnValue(oldLocalVersionsBody)); + // Delete the old client versions + oneOf(db).removeMessage(txn, oldLocalVersionsId); + // Store the new client versions + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(localGroup.getId(), now, + newLocalVersionsBody); + will(returnValue(newLocalVersions)); + oneOf(db).addLocalMessage(txn, newLocalVersions, new Metadata(), + false); + // Inform contacts that client versions have changed + oneOf(db).getContacts(txn); + will(returnValue(singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, + MAJOR_VERSION, contact); + will(returnValue(contactGroup)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the latest local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + c.startService(); + } + + @Test + public void testDeletesObsoleteRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Delete the new remote update, which is obsolete + oneOf(db).deleteMessage(txn, newRemoteUpdate.getId()); + oneOf(db).deleteMessageMetadata(txn, newRemoteUpdate.getId()); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testDeletesPreviousRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // No states or visibilities have changed + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testAcceptsFirstRemoteUpdate() throws Exception { + Message newRemoteUpdate = getMessage(contactGroup.getId()); + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfList oldLocalUpdateBody = BdfList.of(new BdfList(), 1L); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates (no remote update) + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(singletonMap(oldLocalUpdateId, + oldLocalUpdateMeta))); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // No states or visibilities have changed + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testActivatesClientOnIncomingMessageWhenAdvertisedByContact() + throws Exception { + testActivatesClientOnIncomingMessage(false, VISIBLE); + } + + @Test + public void testActivatesClientOnIncomingMessageWhenActivatedByContact() + throws Exception { + testActivatesClientOnIncomingMessage(true, SHARED); + } + + private void testActivatesClientOnIncomingMessage(boolean remoteActive, + Visibility visibility) throws Exception { + // The client was missing from the old remote update + BdfList oldRemoteUpdateBody = BdfList.of(new BdfList(), 1L); + // The client was inactive in the old local update + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 1L); + // The client is included in the new remote update + BdfList newRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, remoteActive)), 2L); + // The client is active in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 2L); + + Message newRemoteUpdate = getMessage(contactGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // Delete the old local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(groupMeta)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(hook).onClientVisibilityChanging(txn, contact, visibility); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } + + @Test + public void testDeactivatesClientOnIncomingMessage() throws Exception { + // The client was active in the old local and remote updates + BdfList oldLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 1L); + BdfList oldRemoteUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, true)), 1L); + // The client is missing from the new remote update + BdfList newRemoteUpdateBody = BdfList.of(new BdfList(), 2L); + // The client is inactive in the new local update + BdfList newLocalUpdateBody = BdfList.of(BdfList.of( + BdfList.of(clientId.getString(), 123, 234, false)), 2L); + + Message newRemoteUpdate = getMessage(contactGroup.getId()); + MessageId oldLocalUpdateId = new MessageId(getRandomId()); + BdfDictionary oldLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, true)); + MessageId oldRemoteUpdateId = new MessageId(getRandomId()); + BdfDictionary oldRemoteUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 1L), + new BdfEntry(MSG_KEY_LOCAL, false)); + Map<MessageId, BdfDictionary> messageMetadata = new HashMap<>(); + messageMetadata.put(oldLocalUpdateId, oldLocalUpdateMeta); + messageMetadata.put(oldRemoteUpdateId, oldRemoteUpdateMeta); + Message newLocalUpdate = getMessage(contactGroup.getId()); + BdfDictionary newLocalUpdateMeta = BdfDictionary.of( + new BdfEntry(MSG_KEY_UPDATE_VERSION, 2L), + new BdfEntry(MSG_KEY_LOCAL, true)); + BdfDictionary groupMeta = BdfDictionary.of( + new BdfEntry(GROUP_KEY_CONTACT_ID, contact.getId().getInt())); + + context.checking(new Expectations() {{ + oneOf(clientHelper).toList(newRemoteUpdate); + will(returnValue(newRemoteUpdateBody)); + // Find the latest local and remote updates + oneOf(clientHelper).getMessageMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(messageMetadata)); + // Load the latest local update + oneOf(clientHelper).getMessageAsList(txn, oldLocalUpdateId); + will(returnValue(oldLocalUpdateBody)); + // Load the latest remote update + oneOf(clientHelper).getMessageAsList(txn, oldRemoteUpdateId); + will(returnValue(oldRemoteUpdateBody)); + // Delete the old remote update + oneOf(db).deleteMessage(txn, oldRemoteUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldRemoteUpdateId); + // Delete the old local update + oneOf(db).deleteMessage(txn, oldLocalUpdateId); + oneOf(db).deleteMessageMetadata(txn, oldLocalUpdateId); + // Store the new local update + oneOf(clock).currentTimeMillis(); + will(returnValue(now)); + oneOf(clientHelper).createMessage(contactGroup.getId(), now, + newLocalUpdateBody); + will(returnValue(newLocalUpdate)); + oneOf(clientHelper).addLocalMessage(txn, newLocalUpdate, + newLocalUpdateMeta, true); + // The client's visibility has changed + oneOf(clientHelper).getGroupMetadataAsDictionary(txn, + contactGroup.getId()); + will(returnValue(groupMeta)); + oneOf(db).getContact(txn, contact.getId()); + will(returnValue(contact)); + oneOf(hook).onClientVisibilityChanging(txn, contact, INVISIBLE); + }}); + + ClientVersioningManagerImpl c = createInstance(); + c.registerClient(clientId, 123, 234, hook); + assertFalse(c.incomingMessage(txn, newRemoteUpdate, new Metadata())); + } +} -- GitLab