diff --git a/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImplTest.java b/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImplTest.java new file mode 100644 index 0000000000000000000000000000000000000000..93ffeb3bf813ebf2e23c1739db86aafc68b653fd --- /dev/null +++ b/briar-tests/src/org/briarproject/privategroup/invitation/GroupInvitationManagerImplTest.java @@ -0,0 +1,924 @@ +package org.briarproject.privategroup.invitation; + +import org.briarproject.BriarMockTestCase; +import org.briarproject.TestUtils; +import org.briarproject.api.FormatException; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.clients.ContactGroupFactory; +import org.briarproject.api.clients.MessageTracker; +import org.briarproject.api.clients.SessionId; +import org.briarproject.api.contact.Contact; +import org.briarproject.api.contact.ContactId; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfEntry; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataParser; +import org.briarproject.api.db.DatabaseComponent; +import org.briarproject.api.db.DbException; +import org.briarproject.api.db.Metadata; +import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.privategroup.PrivateGroup; +import org.briarproject.api.privategroup.PrivateGroupFactory; +import org.briarproject.api.privategroup.PrivateGroupManager; +import org.briarproject.api.privategroup.invitation.GroupInvitationItem; +import org.briarproject.api.privategroup.invitation.GroupInvitationRequest; +import org.briarproject.api.privategroup.invitation.GroupInvitationResponse; +import org.briarproject.api.sharing.InvitationMessage; +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.jetbrains.annotations.Nullable; +import org.jmock.AbstractExpectations; +import org.jmock.Expectations; +import org.jmock.lib.legacy.ClassImposteriser; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import static junit.framework.TestCase.fail; +import static org.briarproject.TestUtils.getRandomBytes; +import static org.briarproject.TestUtils.getRandomId; +import static org.briarproject.TestUtils.getRandomString; +import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID; +import static org.briarproject.api.sync.Group.Visibility.SHARED; +import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; +import static org.briarproject.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID; +import static org.briarproject.privategroup.invitation.MessageType.ABORT; +import static org.briarproject.privategroup.invitation.MessageType.INVITE; +import static org.briarproject.privategroup.invitation.MessageType.JOIN; +import static org.briarproject.privategroup.invitation.MessageType.LEAVE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class GroupInvitationManagerImplTest extends BriarMockTestCase { + + private final GroupInvitationManagerImpl groupInvitationManager; + 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 PrivateGroupFactory privateGroupFactory = + context.mock(PrivateGroupFactory.class); + private final PrivateGroupManager privateGroupManager = + context.mock(PrivateGroupManager.class); + private final MessageParser messageParser = + context.mock(MessageParser.class); + private final SessionParser sessionParser = + context.mock(SessionParser.class); + private final SessionEncoder sessionEncoder = + context.mock(SessionEncoder.class); + private final ProtocolEngineFactory engineFactory = + context.mock(ProtocolEngineFactory.class); + private final CreatorProtocolEngine creatorEngine; + private final InviteeProtocolEngine inviteeEngine; + private final PeerProtocolEngine peerEngine; + private final CreatorSession creatorSession; + private final InviteeSession inviteeSession; + private final PeerSession peerSession; + private final Group localGroup = + new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5)); + private final Transaction txn = new Transaction(null, false); + private final ContactId contactId = new ContactId(0); + private final Author author = + new Author(new AuthorId(getRandomId()), getRandomString(5), + getRandomBytes(5)); + private final Contact contact = + new Contact(contactId, author, new AuthorId(getRandomId()), true, + true); + private final Group contactGroup = + new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5)); + private final Group privateGroup = + new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5)); + private final BdfDictionary meta = BdfDictionary.of(new BdfEntry("f", "o")); + private final Message message = + new Message(new MessageId(getRandomId()), contactGroup.getId(), + 0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1)); + private final BdfList body = new BdfList(); + private final Message storageMessage = + new Message(new MessageId(getRandomId()), contactGroup.getId(), + 0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1)); + + public GroupInvitationManagerImplTest() { + context.setImposteriser(ClassImposteriser.INSTANCE); + creatorEngine = context.mock(CreatorProtocolEngine.class); + inviteeEngine = context.mock(InviteeProtocolEngine.class); + peerEngine = context.mock(PeerProtocolEngine.class); + + creatorSession = context.mock(CreatorSession.class); + inviteeSession = context.mock(InviteeSession.class); + peerSession = context.mock(PeerSession.class); + + context.checking(new Expectations() {{ + oneOf(engineFactory).createCreatorEngine(); + will(returnValue(creatorEngine)); + oneOf(engineFactory).createInviteeEngine(); + will(returnValue(inviteeEngine)); + oneOf(engineFactory).createPeerEngine(); + will(returnValue(peerEngine)); + oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID); + will(returnValue(localGroup)); + }}); + MetadataParser metadataParser = context.mock(MetadataParser.class); + MessageTracker messageTracker = context.mock(MessageTracker.class); + groupInvitationManager = + new GroupInvitationManagerImpl(db, clientHelper, metadataParser, + messageTracker, contactGroupFactory, + privateGroupFactory, privateGroupManager, messageParser, + sessionParser, sessionEncoder, engineFactory); + } + + @Test + public void testCreateLocalState() throws Exception { + context.checking(new Expectations() {{ + oneOf(db).addGroup(txn, localGroup); + oneOf(db).getContacts(txn); + will(returnValue(Collections.singletonList(contact))); + }}); + expectAddingContact(contact, true); + groupInvitationManager.createLocalState(txn); + } + + private void expectAddingContact(final Contact c, + final boolean contactExists) throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c); + will(returnValue(contactGroup)); + oneOf(db).containsGroup(txn, contactGroup.getId()); + will(returnValue(contactExists)); + }}); + if (contactExists) return; + + final BdfDictionary meta = BdfDictionary + .of(new BdfEntry(GROUP_KEY_CONTACT_ID, c.getId().getInt())); + context.checking(new Expectations() {{ + oneOf(db).addGroup(txn, contactGroup); + oneOf(db).setGroupVisibility(txn, c.getId(), contactGroup.getId(), + SHARED); + oneOf(clientHelper) + .mergeGroupMetadata(txn, contactGroup.getId(), meta); + oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID); + will(returnValue(Collections.singletonList(privateGroup))); + oneOf(privateGroupManager) + .isMember(txn, privateGroup.getId(), c.getAuthor()); + will(returnValue(true)); + }}); + expectAddingMember(privateGroup.getId(), c); + } + + private void expectAddingMember(final GroupId g, final Contact c) + throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c); + will(returnValue(contactGroup)); + }}); + expectGetSession(Collections.<MessageId, BdfDictionary>emptyMap(), + new SessionId(g.getBytes())); + + context.checking(new Expectations() {{ + oneOf(peerEngine).onMemberAddedAction(with(equal(txn)), + with(any(PeerSession.class))); + will(returnValue(peerSession)); + }}); + expectStoreSession(peerSession, storageMessage.getId()); + expectCreateStorageId(); + } + + private void expectCreateStorageId() throws DbException { + context.checking(new Expectations() {{ + oneOf(clientHelper) + .createMessageForStoringMetadata(contactGroup.getId()); + will(returnValue(storageMessage)); + oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(), + false); + }}); + } + + private void expectStoreSession(final Session session, + final MessageId storageId) throws Exception { + context.checking(new Expectations() {{ + oneOf(sessionEncoder).encodeSession(session); + will(returnValue(meta)); + oneOf(clientHelper).mergeMessageMetadata(txn, storageId, meta); + }}); + } + + private void expectGetSession(final Map<MessageId, BdfDictionary> result, + final SessionId sessionId) throws Exception { + final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u")); + context.checking(new Expectations() {{ + oneOf(sessionParser).getSessionQuery(sessionId); + will(returnValue(query)); + oneOf(clientHelper) + .getMessageMetadataAsDictionary(txn, contactGroup.getId(), + query); + will(returnValue(result)); + }}); + } + + @Test + public void testAddingContact() throws Exception { + expectAddingContact(contact, false); + groupInvitationManager.addingContact(txn, contact); + } + + @Test + public void testRemovingContact() throws Exception { + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(db).removeGroup(txn, contactGroup); + }}); + groupInvitationManager.removingContact(txn, contact); + } + + @Test(expected = FormatException.class) + public void testIncomingUnknownMessage() throws Exception { + expectFirstIncomingMessage(Role.INVITEE, ABORT); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingFirstInviteMessage() throws Exception { + expectFirstIncomingMessage(Role.INVITEE, INVITE); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingFirstJoinMessage() throws Exception { + expectFirstIncomingMessage(Role.PEER, JOIN); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingInviteMessage() throws Exception { + expectIncomingMessage(Role.INVITEE, INVITE); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingJoinMessage() throws Exception { + expectIncomingMessage(Role.INVITEE, JOIN); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingJoinMessageForCreator() throws Exception { + expectIncomingMessage(Role.CREATOR, JOIN); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingLeaveMessage() throws Exception { + expectIncomingMessage(Role.INVITEE, LEAVE); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + @Test + public void testIncomingAbortMessage() throws Exception { + expectIncomingMessage(Role.INVITEE, ABORT); + groupInvitationManager.incomingMessage(txn, message, body, meta); + } + + private void expectFirstIncomingMessage(Role role, MessageType type) + throws Exception { + expectIncomingMessage(role, type, + Collections.<MessageId, BdfDictionary>emptyMap()); + } + + private void expectIncomingMessage(Role role, MessageType type) + throws Exception { + BdfDictionary state = BdfDictionary.of(new BdfEntry("state", "test")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + expectIncomingMessage(role, type, states); + } + + private void expectIncomingMessage(final Role role, + final MessageType type, final Map<MessageId, BdfDictionary> states) + throws Exception { + final MessageMetadata messageMetadata = + context.mock(MessageMetadata.class); + context.checking(new Expectations() {{ + oneOf(messageParser).parseMetadata(meta); + will(returnValue(messageMetadata)); + oneOf(messageMetadata).getPrivateGroupId(); + will(returnValue(privateGroup.getId())); + }}); + expectGetSession(states, + new SessionId(privateGroup.getId().getBytes())); + + Session session; + if (states.isEmpty()) { + session = expectHandleFirstMessage(role, messageMetadata, type); + if (session != null) { + expectCreateStorageId(); + expectStoreSession(session, storageMessage.getId()); + } + } else { + assertEquals(1, states.size()); + session = expectHandleMessage(role, messageMetadata, + states.values().iterator().next(), type); + expectStoreSession(session, storageMessage.getId()); + } + } + + @Nullable + private Session expectHandleFirstMessage(Role role, + final MessageMetadata messageMetadata, final MessageType type) + throws Exception { + context.checking(new Expectations() {{ + oneOf(messageMetadata).getPrivateGroupId(); + will(returnValue(privateGroup.getId())); + oneOf(messageMetadata).getMessageType(); + will(returnValue(type)); + }}); + if (type == ABORT || type == LEAVE) return null; + + AbstractProtocolEngine engine; + Session session; + if (type == INVITE) { + assertEquals(Role.INVITEE, role); + engine = inviteeEngine; + session = inviteeSession; + } else if (type == JOIN) { + assertEquals(Role.PEER, role); + engine = peerEngine; + session = peerSession; + } else { + throw new RuntimeException(); + } + expectIndividualMessage(type, engine, session); + return session; + } + + @Nullable + private Session expectHandleMessage(final Role role, + final MessageMetadata messageMetadata, final BdfDictionary state, + final MessageType type) throws Exception { + context.checking(new Expectations() {{ + oneOf(messageMetadata).getMessageType(); + will(returnValue(type)); + oneOf(sessionParser).getRole(state); + will(returnValue(role)); + }}); + if (role == Role.CREATOR) { + context.checking(new Expectations() {{ + oneOf(sessionParser) + .parseCreatorSession(contactGroup.getId(), state); + will(returnValue(creatorSession)); + }}); + expectIndividualMessage(type, creatorEngine, creatorSession); + return creatorSession; + } else if (role == Role.INVITEE) { + context.checking(new Expectations() {{ + oneOf(sessionParser) + .parseInviteeSession(contactGroup.getId(), state); + will(returnValue(inviteeSession)); + }}); + expectIndividualMessage(type, inviteeEngine, inviteeSession); + return inviteeSession; + } else if (role == Role.PEER) { + context.checking(new Expectations() {{ + oneOf(sessionParser) + .parsePeerSession(contactGroup.getId(), state); + will(returnValue(peerSession)); + }}); + expectIndividualMessage(type, peerEngine, peerSession); + return peerSession; + } else { + fail(); + throw new RuntimeException(); + } + } + + private <S extends Session> void expectIndividualMessage( + final MessageType type, final ProtocolEngine<S> engine, + final S session) throws Exception { + if (type == INVITE) { + final InviteMessage msg = context.mock(InviteMessage.class); + context.checking(new Expectations() {{ + oneOf(messageParser).parseInviteMessage(message, body); + will(returnValue(msg)); + oneOf(engine).onInviteMessage(with(equal(txn)), + with(AbstractExpectations.<S>anything()), + with(equal(msg))); + will(returnValue(session)); + }}); + } else if (type == JOIN) { + final JoinMessage msg = context.mock(JoinMessage.class); + context.checking(new Expectations() {{ + oneOf(messageParser).parseJoinMessage(message, body); + will(returnValue(msg)); + oneOf(engine).onJoinMessage(with(equal(txn)), + with(AbstractExpectations.<S>anything()), + with(equal(msg))); + will(returnValue(session)); + }}); + } else if (type == LEAVE) { + final LeaveMessage msg = context.mock(LeaveMessage.class); + context.checking(new Expectations() {{ + oneOf(messageParser).parseLeaveMessage(message, body); + will(returnValue(msg)); + oneOf(engine).onLeaveMessage(with(equal(txn)), + with(AbstractExpectations.<S>anything()), + with(equal(msg))); + will(returnValue(session)); + }}); + } else if (type == ABORT) { + final AbortMessage msg = context.mock(AbortMessage.class); + context.checking(new Expectations() {{ + oneOf(messageParser).parseAbortMessage(message, body); + will(returnValue(msg)); + oneOf(engine).onAbortMessage(with(equal(txn)), + with(AbstractExpectations.<S>anything()), + with(equal(msg))); + will(returnValue(session)); + }}); + } else { + fail(); + } + } + + @Test + public void testSendFirstInvitation() throws Exception { + final String msg = "Invitation text for first invitation"; + final long time = 42L; + final byte[] signature = TestUtils.getRandomBytes(42); + Map<MessageId, BdfDictionary> states = Collections.emptyMap(); + + expectGetSession(states, + new SessionId(privateGroup.getId().getBytes())); + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + }}); + expectCreateStorageId(); + context.checking(new Expectations() {{ + oneOf(creatorEngine).onInviteAction(with(same(txn)), + with(any(CreatorSession.class)), with(equal(msg)), + with(equal(time)), with(equal(signature))); + will(returnValue(creatorSession)); + }}); + expectStoreSession(creatorSession, storageMessage.getId()); + context.checking(new Expectations() {{ + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + groupInvitationManager.sendInvitation(privateGroup.getId(), contactId, + msg, time, signature); + } + + @Test + public void testSendSubsequentInvitation() throws Exception { + final String msg = "Invitation text for subsequent invitation"; + final long time = 43L; + final byte[] signature = TestUtils.getRandomBytes(43); + final BdfDictionary state = + BdfDictionary.of(new BdfEntry("state", "test")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + + expectGetSession(states, + new SessionId(privateGroup.getId().getBytes())); + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(sessionParser) + .parseCreatorSession(contactGroup.getId(), state); + will(returnValue(creatorSession)); + oneOf(creatorEngine).onInviteAction(with(same(txn)), + with(any(CreatorSession.class)), with(equal(msg)), + with(equal(time)), with(equal(signature))); + will(returnValue(creatorSession)); + }}); + expectStoreSession(creatorSession, storageMessage.getId()); + context.checking(new Expectations() {{ + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + groupInvitationManager.sendInvitation(privateGroup.getId(), contactId, + msg, time, signature); + } + + @Test(expected = IllegalArgumentException.class) + public void testRespondToInvitationWithoutSession() throws Exception { + final SessionId sessionId = new SessionId(getRandomId()); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(db).endTransaction(txn); + }}); + expectGetSession(Collections.<MessageId, BdfDictionary>emptyMap(), + sessionId); + + groupInvitationManager.respondToInvitation(contactId, sessionId, true); + } + + @Test + public void testAcceptInvitationWithSession() throws Exception { + final boolean accept = true; + SessionId sessionId = new SessionId(getRandomId()); + BdfDictionary state = BdfDictionary.of(new BdfEntry("f", "o")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + + expectRespondToInvitation(states, sessionId, accept); + groupInvitationManager + .respondToInvitation(contactId, sessionId, accept); + } + + @Test + public void testDeclineInvitationWithSession() throws Exception { + final boolean accept = false; + SessionId sessionId = new SessionId(getRandomId()); + BdfDictionary state = BdfDictionary.of(new BdfEntry("f", "o")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + + expectRespondToInvitation(states, sessionId, accept); + groupInvitationManager + .respondToInvitation(contactId, sessionId, accept); + } + + @Test + public void testRespondToInvitationWithGroupId() throws Exception { + final boolean accept = true; + final PrivateGroup g = context.mock(PrivateGroup.class); + SessionId sessionId = new SessionId(privateGroup.getId().getBytes()); + BdfDictionary state = BdfDictionary.of(new BdfEntry("f", "o")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + + context.checking(new Expectations() {{ + oneOf(g).getId(); + will(returnValue(privateGroup.getId())); + }}); + expectRespondToInvitation(states, sessionId, accept); + groupInvitationManager.respondToInvitation(contactId, g, accept); + } + + private void expectRespondToInvitation( + final Map<MessageId, BdfDictionary> states, + final SessionId sessionId, final boolean accept) throws Exception { + expectGetSession(states, sessionId); + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + }}); + + if (states.isEmpty()) return; + assertEquals(1, states.size()); + + final BdfDictionary state = states.values().iterator().next(); + context.checking(new Expectations() {{ + oneOf(sessionParser) + .parseInviteeSession(contactGroup.getId(), state); + will(returnValue(inviteeSession)); + if (accept) oneOf(inviteeEngine).onJoinAction(txn, inviteeSession); + else oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession); + will(returnValue(inviteeSession)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + expectStoreSession(inviteeSession, storageMessage.getId()); + } + + @Test + public void testRevealRelationship() throws Exception { + SessionId sessionId = new SessionId(privateGroup.getId().getBytes()); + final BdfDictionary state = BdfDictionary.of(new BdfEntry("f", "o")); + Map<MessageId, BdfDictionary> states = + Collections.singletonMap(storageMessage.getId(), state); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(sessionParser).parsePeerSession(contactGroup.getId(), state); + will(returnValue(peerSession)); + oneOf(peerEngine).onJoinAction(txn, peerSession); + will(returnValue(peerSession)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + expectGetSession(states, sessionId); + expectStoreSession(peerSession, storageMessage.getId()); + + groupInvitationManager + .revealRelationship(contactId, privateGroup.getId()); + } + + @Test(expected = IllegalArgumentException.class) + public void testRevealRelationshipWithoutSession() throws Exception { + SessionId sessionId = new SessionId(privateGroup.getId().getBytes()); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(false); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(db).endTransaction(txn); + }}); + expectGetSession(Collections.<MessageId, BdfDictionary>emptyMap(), + sessionId); + + groupInvitationManager + .revealRelationship(contactId, privateGroup.getId()); + } + + @Test + public void testGetInvitationMessages() throws Exception { + final BdfDictionary query = new BdfDictionary(); + final BdfDictionary d1 = BdfDictionary.of(new BdfEntry("m1", "d")); + final BdfDictionary d2 = BdfDictionary.of(new BdfEntry("m2", "d")); + final Map<MessageId, BdfDictionary> results = new HashMap<>(); + results.put(message.getId(), d1); + results.put(storageMessage.getId(), d2); + long time1 = 1L, time2 = 2L; + final MessageMetadata meta1 = + new MessageMetadata(INVITE, privateGroup.getId(), time1, true, + true, true, true); + final MessageMetadata meta2 = + new MessageMetadata(JOIN, privateGroup.getId(), time2, true, + true, true, true); + final BdfList list1 = BdfList.of(1); + + context.checking(new Expectations() {{ + oneOf(db).startTransaction(true); + will(returnValue(txn)); + oneOf(db).getContact(txn, contactId); + will(returnValue(contact)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(messageParser).getMessagesVisibleInUiQuery(); + will(returnValue(query)); + oneOf(clientHelper) + .getMessageMetadataAsDictionary(txn, contactGroup.getId(), + query); + will(returnValue(results)); + // first message + oneOf(messageParser).parseMetadata(d1); + will(returnValue(meta1)); + oneOf(db).getMessageStatus(txn, contactId, message.getId()); + oneOf(clientHelper).getMessage(txn, message.getId()); + will(returnValue(message)); + oneOf(clientHelper).toList(message); + will(returnValue(list1)); + oneOf(messageParser).parseInviteMessage(message, list1); + // second message + oneOf(messageParser).parseMetadata(d2); + will(returnValue(meta2)); + oneOf(db).getMessageStatus(txn, contactId, storageMessage.getId()); + // end transaction + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + Collection<InvitationMessage> messages = + groupInvitationManager.getInvitationMessages(contactId); + assertEquals(2, messages.size()); + for (InvitationMessage m : messages) { + assertEquals(contactGroup.getId(), m.getGroupId()); + assertEquals(contactId, m.getContactId()); + if (m.getId().equals(message.getId())) { + assertTrue(m instanceof GroupInvitationRequest); + assertEquals(time1, m.getTimestamp()); + } else if (m.getId().equals(storageMessage.getId())) { + assertTrue(m instanceof GroupInvitationResponse); + assertEquals(time2, m.getTimestamp()); + } + } + } + + @Test + public void testGetInvitations() throws Exception { + final BdfDictionary query = new BdfDictionary(); + BdfDictionary d1 = BdfDictionary.of(new BdfEntry("m1", "d")); + BdfDictionary d2 = BdfDictionary.of(new BdfEntry("m2", "d")); + final Map<MessageId, BdfDictionary> results = new HashMap<>(); + results.put(message.getId(), d1); + results.put(storageMessage.getId(), d2); + final BdfList list1 = BdfList.of(1); + final BdfList list2 = BdfList.of(2); + long time1 = 1L, time2 = 2L; + final InviteMessage m1 = + new InviteMessage(message.getId(), contactGroup.getId(), + privateGroup.getId(), time1, "test", author, + getRandomBytes(5), null, getRandomBytes(5)); + final InviteMessage m2 = + new InviteMessage(message.getId(), contactGroup.getId(), + privateGroup.getId(), time2, "test", author, + getRandomBytes(5), null, getRandomBytes(5)); + final PrivateGroup g = context.mock(PrivateGroup.class); + + context.checking(new Expectations() {{ + oneOf(messageParser).getInvitesAvailableToAnswerQuery(); + will(returnValue(query)); + oneOf(db).startTransaction(true); + will(returnValue(txn)); + oneOf(db).getContacts(txn); + will(returnValue(Collections.singletonList(contact))); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(clientHelper) + .getMessageMetadataAsDictionary(txn, contactGroup.getId(), + query); + will(returnValue(results)); + // message 1 + oneOf(clientHelper).getMessage(txn, message.getId()); + will(returnValue(message)); + oneOf(clientHelper).toList(message); + will(returnValue(list1)); + oneOf(messageParser).parseInviteMessage(message, list1); + will(returnValue(m1)); + oneOf(privateGroupFactory) + .createPrivateGroup(m1.getGroupName(), m1.getCreator(), + m1.getSalt()); + will(returnValue(g)); + // message 2 + oneOf(clientHelper).getMessage(txn, storageMessage.getId()); + will(returnValue(storageMessage)); + oneOf(clientHelper).toList(storageMessage); + will(returnValue(list2)); + oneOf(messageParser).parseInviteMessage(storageMessage, list2); + will(returnValue(m2)); + oneOf(privateGroupFactory) + .createPrivateGroup(m2.getGroupName(), m2.getCreator(), + m2.getSalt()); + will(returnValue(g)); + // end transaction + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + + Collection<GroupInvitationItem> items = + groupInvitationManager.getInvitations(); + assertEquals(2, items.size()); + for (GroupInvitationItem i : items) { + assertEquals(contact, i.getCreator()); + assertEquals(author, i.getCreator().getAuthor()); + } + } + + @Test + public void testIsInvitationAllowed() throws Exception { + expectIsInvitationAllowed(CreatorState.START); + assertTrue(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + } + + @Test + public void testIsNotInvitationAllowed() throws Exception { + expectIsInvitationAllowed(CreatorState.DISSOLVED); + assertFalse(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + + expectIsInvitationAllowed(CreatorState.ERROR); + assertFalse(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + + expectIsInvitationAllowed(CreatorState.INVITED); + assertFalse(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + + expectIsInvitationAllowed(CreatorState.JOINED); + assertFalse(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + + expectIsInvitationAllowed(CreatorState.LEFT); + assertFalse(groupInvitationManager + .isInvitationAllowed(contact, privateGroup.getId())); + } + + private void expectIsInvitationAllowed(final CreatorState state) + throws Exception { + final SessionId sessionId = + new SessionId(privateGroup.getId().getBytes()); + final BdfDictionary meta = BdfDictionary.of(new BdfEntry("m", "d")); + final Map<MessageId, BdfDictionary> results = new HashMap<>(); + results.put(message.getId(), meta); + + expectGetSession(results, sessionId); + context.checking(new Expectations() {{ + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(db).startTransaction(true); + will(returnValue(txn)); + oneOf(sessionParser) + .parseCreatorSession(contactGroup.getId(), meta); + will(returnValue(creatorSession)); + oneOf(creatorSession).getState(); + will(returnValue(state)); + oneOf(db).commitTransaction(txn); + oneOf(db).endTransaction(txn); + }}); + } + + @Test + public void testAddingMember() throws Exception { + expectAddingMember(privateGroup.getId(), contact); + context.checking(new Expectations() {{ + oneOf(db).getContactsByAuthorId(txn, author.getId()); + will(returnValue(Collections.singletonList(contact))); + }}); + groupInvitationManager.addingMember(txn, privateGroup.getId(), author); + } + + @Test + public void testRemovingGroupEndsSessions() throws Exception { + final Contact contact2 = + new Contact(new ContactId(2), author, author.getId(), true, + true); + final Contact contact3 = + new Contact(new ContactId(3), author, author.getId(), true, + true); + final Collection<Contact> contacts = new ArrayList<>(); + contacts.add(contact); + contacts.add(contact2); + contacts.add(contact3); + final MessageId mId2 = new MessageId(getRandomId()); + final MessageId mId3 = new MessageId(getRandomId()); + final BdfDictionary meta1 = BdfDictionary.of(new BdfEntry("m1", "d")); + final BdfDictionary meta2 = BdfDictionary.of(new BdfEntry("m2", "d")); + final BdfDictionary meta3 = BdfDictionary.of(new BdfEntry("m3", "d")); + + expectGetSession( + Collections.singletonMap(storageMessage.getId(), meta1), + new SessionId(privateGroup.getId().getBytes())); + expectGetSession( + Collections.singletonMap(mId2, meta2), + new SessionId(privateGroup.getId().getBytes())); + expectGetSession( + Collections.singletonMap(mId3, meta3), + new SessionId(privateGroup.getId().getBytes())); + context.checking(new Expectations() {{ + oneOf(db).getContacts(txn); + will(returnValue(contacts)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact); + will(returnValue(contactGroup)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2); + will(returnValue(contactGroup)); + oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact3); + will(returnValue(contactGroup)); + // session 1 + oneOf(sessionParser).getRole(meta1); + will(returnValue(Role.CREATOR)); + oneOf(sessionParser) + .parseCreatorSession(contactGroup.getId(), meta1); + will(returnValue(creatorSession)); + oneOf(creatorEngine).onLeaveAction(txn, creatorSession); + will(returnValue(creatorSession)); + // session 2 + oneOf(sessionParser).getRole(meta2); + will(returnValue(Role.INVITEE)); + oneOf(sessionParser) + .parseInviteeSession(contactGroup.getId(), meta2); + will(returnValue(inviteeSession)); + oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession); + will(returnValue(inviteeSession)); + // session 3 + oneOf(sessionParser).getRole(meta3); + will(returnValue(Role.PEER)); + oneOf(sessionParser) + .parsePeerSession(contactGroup.getId(), meta3); + will(returnValue(peerSession)); + oneOf(peerEngine).onLeaveAction(txn, peerSession); + will(returnValue(peerSession)); + }}); + expectStoreSession(creatorSession, storageMessage.getId()); + expectStoreSession(inviteeSession, mId2); + expectStoreSession(peerSession, mId3); + groupInvitationManager.removingGroup(txn, privateGroup.getId()); + } + +}