From 67d9f3a7c2d701e20fbb58073b6e007fd41da173 Mon Sep 17 00:00:00 2001
From: Torsten Grote <t@grobox.de>
Date: Tue, 22 Nov 2016 18:22:31 -0200
Subject: [PATCH] Add integration tests for GroupInvitationManager

---
 .../briarproject/BriarIntegrationTest.java    |   2 +
 .../BriarIntegrationTestComponent.java        |   5 +
 .../GroupInvitationIntegrationTest.java       | 278 ++++++++++++++++++
 .../PrivateGroupIntegrationTest.java          | 197 +++++++++++++
 4 files changed, 482 insertions(+)
 create mode 100644 briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java
 create mode 100644 briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java

diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
index 1e0486df4c..fcd3205e73 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTest.java
@@ -39,6 +39,7 @@ import org.briarproject.introduction.IntroductionGroupFactory;
 import org.briarproject.introduction.IntroductionModule;
 import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.privategroup.PrivateGroupModule;
+import org.briarproject.privategroup.invitation.GroupInvitationModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.sharing.SharingModule;
 import org.briarproject.sync.SyncModule;
@@ -180,6 +181,7 @@ public abstract class BriarIntegrationTest extends BriarTestCase {
 		component.inject(new CryptoModule.EagerSingletons());
 		component.inject(new ContactModule.EagerSingletons());
 		component.inject(new ForumModule.EagerSingletons());
+		component.inject(new GroupInvitationModule.EagerSingletons());
 		component.inject(new IntroductionModule.EagerSingletons());
 		component.inject(new PropertiesModule.EagerSingletons());
 		component.inject(new PrivateGroupModule.EagerSingletons());
diff --git a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
index 835c345bd7..7d4cdf2a8a 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BriarIntegrationTestComponent.java
@@ -14,6 +14,7 @@ import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.introduction.IntroductionManager;
 import org.briarproject.api.lifecycle.LifecycleManager;
 import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.SyncSessionFactory;
 import org.briarproject.blogs.BlogsModule;
@@ -78,6 +79,8 @@ public interface BriarIntegrationTestComponent {
 
 	void inject(ForumModule.EagerSingletons init);
 
+	void inject(GroupInvitationModule.EagerSingletons init);
+
 	void inject(IntroductionModule.EagerSingletons init);
 
 	void inject(LifecycleModule.EagerSingletons init);
@@ -114,6 +117,8 @@ public interface BriarIntegrationTestComponent {
 
 	ForumManager getForumManager();
 
+	GroupInvitationManager getGroupInvitationManager();
+
 	IntroductionManager getIntroductionManager();
 
 	MessageTracker getMessageTracker();
diff --git a/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java
new file mode 100644
index 0000000000..72026d3b33
--- /dev/null
+++ b/briar-android-tests/src/test/java/org/briarproject/GroupInvitationIntegrationTest.java
@@ -0,0 +1,278 @@
+package org.briarproject;
+
+import org.briarproject.api.clients.ProtocolStateException;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.privategroup.invitation.GroupInvitationItem;
+import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
+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.jetbrains.annotations.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static junit.framework.TestCase.fail;
+import static org.briarproject.TestUtils.assertGroupCount;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class GroupInvitationIntegrationTest extends BriarIntegrationTest {
+
+	private PrivateGroup privateGroup0;
+	private PrivateGroupManager groupManager0, groupManager1;
+	private GroupInvitationManager groupInvitationManager0,
+			groupInvitationManager1;
+
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+
+		groupManager0 = c0.getPrivateGroupManager();
+		groupManager1 = c1.getPrivateGroupManager();
+		groupInvitationManager0 = c0.getGroupInvitationManager();
+		groupInvitationManager1 = c1.getGroupInvitationManager();
+
+		privateGroup0 =
+				privateGroupFactory.createPrivateGroup("Testgroup", author0);
+		long joinTime = clock.currentTimeMillis();
+		GroupMessage joinMsg0 = groupMessageFactory
+				.createJoinMessage(privateGroup0.getId(), joinTime, author0);
+		groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
+	}
+
+	@Test
+	public void testSendInvitation() throws Exception {
+		long timestamp = clock.currentTimeMillis();
+		String msg = "Hi!";
+		sendInvitation(timestamp, msg);
+
+		sync0To1(1, true);
+
+		Collection<GroupInvitationItem> invitations =
+				groupInvitationManager1.getInvitations();
+		assertEquals(1, invitations.size());
+		GroupInvitationItem item = invitations.iterator().next();
+		assertEquals(contact0From1, item.getCreator());
+		assertEquals(privateGroup0, item.getShareable());
+		assertEquals(privateGroup0.getId(), item.getId());
+		assertEquals(privateGroup0.getName(), item.getName());
+		assertFalse(item.isSubscribed());
+
+		Collection<InvitationMessage> messages =
+				groupInvitationManager1.getInvitationMessages(contactId0From1);
+		assertEquals(1, messages.size());
+		GroupInvitationRequest request =
+				(GroupInvitationRequest) messages.iterator().next();
+		assertEquals(msg, request.getMessage());
+		assertEquals(author0, request.getCreator());
+		assertEquals(timestamp, request.getTimestamp());
+		assertEquals(contactId0From1, request.getContactId());
+		assertEquals(privateGroup0.getName(), request.getGroupName());
+		assertFalse(request.isLocal());
+		assertFalse(request.isRead());
+	}
+
+	@Test
+	public void testInvitationDecline() throws Exception {
+		long timestamp = clock.currentTimeMillis();
+		sendInvitation(timestamp, null);
+
+		sync0To1(1, true);
+		assertFalse(groupInvitationManager1.getInvitations().isEmpty());
+
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, false);
+
+		Collection<InvitationMessage> messages =
+				groupInvitationManager1.getInvitationMessages(contactId0From1);
+		assertEquals(2, messages.size());
+		for (InvitationMessage m : messages) {
+			if (m instanceof GroupInvitationResponse) {
+				GroupInvitationResponse response = (GroupInvitationResponse) m;
+				assertEquals(contactId0From1, response.getContactId());
+				assertTrue(response.isLocal());
+				assertFalse(response.wasAccepted());
+			}
+		}
+
+		sync1To0(1, true);
+
+		messages =
+				groupInvitationManager0.getInvitationMessages(contactId1From0);
+		assertEquals(2, messages.size());
+		for (InvitationMessage m : messages) {
+			if (m instanceof GroupInvitationResponse) {
+				GroupInvitationResponse response = (GroupInvitationResponse) m;
+				assertEquals(contactId0From1, response.getContactId());
+				assertFalse(response.isLocal());
+				assertFalse(response.wasAccepted());
+			}
+		}
+		// no invitations are open
+		assertTrue(groupInvitationManager1.getInvitations().isEmpty());
+		// no groups were added
+		assertEquals(0, groupManager1.getPrivateGroups().size());
+	}
+
+	@Test
+	public void testInvitationAccept() throws Exception {
+		long timestamp = clock.currentTimeMillis();
+		sendInvitation(timestamp, null);
+
+		sync0To1(1, true);
+		assertFalse(groupInvitationManager1.getInvitations().isEmpty());
+
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+
+		Collection<InvitationMessage> messages =
+				groupInvitationManager1.getInvitationMessages(contactId0From1);
+		assertEquals(2, messages.size());
+		for (InvitationMessage m : messages) {
+			if (m instanceof GroupInvitationResponse) {
+				GroupInvitationResponse response = (GroupInvitationResponse) m;
+				assertTrue(response.wasAccepted());
+			}
+		}
+
+		sync1To0(1, true);
+
+		messages =
+				groupInvitationManager0.getInvitationMessages(contactId1From0);
+		assertEquals(2, messages.size());
+		for (InvitationMessage m : messages) {
+			if (m instanceof GroupInvitationResponse) {
+				GroupInvitationResponse response = (GroupInvitationResponse) m;
+				assertTrue(response.wasAccepted());
+			}
+		}
+		// no invitations are open
+		assertTrue(groupInvitationManager1.getInvitations().isEmpty());
+		// group was added
+		Collection<PrivateGroup> groups = groupManager1.getPrivateGroups();
+		assertEquals(1, groups.size());
+		assertEquals(privateGroup0, groups.iterator().next());
+	}
+
+	@Test
+	public void testGroupCount() throws Exception {
+		long timestamp = clock.currentTimeMillis();
+		sendInvitation(timestamp, null);
+
+		// 0 has one read outgoing message
+		Group g1 = groupInvitationManager0.getContactGroup(contact1From0);
+		assertGroupCount(messageTracker0, g1.getId(), 1, 0, timestamp);
+
+		sync0To1(1, true);
+
+		// 1 has one unread message
+		Group g0 = groupInvitationManager1.getContactGroup(contact0From1);
+		assertGroupCount(messageTracker1, g0.getId(), 1, 1, timestamp);
+		InvitationMessage m =
+				groupInvitationManager1.getInvitationMessages(contactId0From1)
+						.iterator().next();
+
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+
+		// 1 has two messages, one still unread
+		assertGroupCount(messageTracker1, g0.getId(), 2, 1);
+
+		// now all messages should be read
+		groupInvitationManager1.setReadFlag(g0.getId(), m.getId(), true);
+		assertGroupCount(messageTracker1, g0.getId(), 2, 0);
+
+		sync1To0(1, true);
+
+		// now 0 has two messages, one of them unread
+		assertGroupCount(messageTracker0, g1.getId(), 2, 1);
+	}
+
+	@Test
+	public void testMultipleInvitations() throws Exception {
+		sendInvitation(clock.currentTimeMillis(), null);
+
+		// invitation is not allowed before the first hasn't been answered
+		assertFalse(groupInvitationManager0
+				.isInvitationAllowed(contact1From0, privateGroup0.getId()));
+
+		// deliver invitation and response
+		sync0To1(1, true);
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, false);
+		sync1To0(1, true);
+
+		// after invitation was declined, inviting again is possible
+		assertTrue(groupInvitationManager0
+				.isInvitationAllowed(contact1From0, privateGroup0.getId()));
+
+		// send and accept the second invitation
+		sendInvitation(clock.currentTimeMillis(), "Second Invitation");
+		sync0To1(1, true);
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+		sync1To0(1, true);
+
+		// invitation is not allowed since the member joined the group now
+		assertFalse(groupInvitationManager0
+				.isInvitationAllowed(contact1From0, privateGroup0.getId()));
+
+		// don't allow another invitation request
+		try {
+			sendInvitation(clock.currentTimeMillis(), "Third Invitation");
+			fail();
+		} catch (ProtocolStateException e) {
+			// expected
+		}
+	}
+
+	@Test(expected = ProtocolStateException.class)
+	public void testInvitationsWithSameTimestamp() throws Exception {
+		long timestamp = clock.currentTimeMillis();
+		sendInvitation(timestamp, null);
+		sync0To1(1, true);
+
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, false);
+		sync1To0(1, true);
+
+		sendInvitation(timestamp, "Second Invitation");
+		sync0To1(1, true);
+
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+	}
+
+	@Test
+	public void testCreatorLeavesBeforeInvitationAnswered() throws Exception {
+		sendInvitation(clock.currentTimeMillis(), null);
+		sync0To1(1, true);
+
+		groupManager0.removePrivateGroup(privateGroup0.getId());
+		assertEquals(0, groupManager0.getPrivateGroups().size());
+//		sync0To1(1, true);
+		// FIXME
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+		sync1To0(1, true);
+
+		assertEquals(0, groupManager1.getPrivateGroups().size());
+	}
+
+	private void sendInvitation(long timestamp, @Nullable String msg) throws
+			DbException {
+		byte[] signature = groupInvitationFactory.signInvitation(contact1From0,
+				privateGroup0.getId(), timestamp, author0.getPrivateKey());
+		groupInvitationManager0
+				.sendInvitation(privateGroup0.getId(), contactId1From0, msg,
+						timestamp, signature);
+	}
+
+}
diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java
new file mode 100644
index 0000000000..05234da663
--- /dev/null
+++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupIntegrationTest.java
@@ -0,0 +1,197 @@
+package org.briarproject;
+
+import org.briarproject.api.contact.Contact;
+import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.db.DbException;
+import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.privategroup.GroupMember;
+import org.briarproject.api.privategroup.GroupMessage;
+import org.briarproject.api.privategroup.GroupMessageHeader;
+import org.briarproject.api.privategroup.JoinMessageHeader;
+import org.briarproject.api.privategroup.PrivateGroup;
+import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.privategroup.invitation.GroupInvitationManager;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collection;
+
+import static org.briarproject.api.identity.Author.Status.OURSELVES;
+import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_US;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * This class tests how PrivateGroupManager and GroupInvitationManager
+ * play together.
+ */
+public class PrivateGroupIntegrationTest extends BriarIntegrationTest {
+
+	private GroupId groupId0;
+	private PrivateGroup privateGroup0;
+	private PrivateGroupManager groupManager0, groupManager1, groupManager2;
+	private GroupInvitationManager groupInvitationManager0,
+			groupInvitationManager1, groupInvitationManager2;
+
+	@Before
+	public void setUp() throws Exception {
+		super.setUp();
+
+		groupManager0 = c0.getPrivateGroupManager();
+		groupManager1 = c1.getPrivateGroupManager();
+		groupManager2 = c2.getPrivateGroupManager();
+		groupInvitationManager0 = c0.getGroupInvitationManager();
+		groupInvitationManager1 = c1.getGroupInvitationManager();
+		groupInvitationManager2 = c2.getGroupInvitationManager();
+
+		privateGroup0 =
+				privateGroupFactory.createPrivateGroup("Test Group", author0);
+		groupId0 = privateGroup0.getId();
+		long joinTime = clock.currentTimeMillis();
+		GroupMessage joinMsg0 = groupMessageFactory
+				.createJoinMessage(groupId0, joinTime, author0);
+		groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
+	}
+
+	@Test
+	public void testMembership() throws Exception {
+		sendInvitation(contactId1From0, clock.currentTimeMillis(), "Hi!");
+
+		// our group has only one member (ourselves)
+		Collection<GroupMember> members = groupManager0.getMembers(groupId0);
+		assertEquals(1, members.size());
+		assertEquals(author0, members.iterator().next().getAuthor());
+		assertEquals(OURSELVES, members.iterator().next().getStatus());
+
+		sync0To1(1, true);
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+		sync1To0(1, true);
+
+		// sync group join messages
+		sync0To1(2, true); // + one invitation protocol join message
+		sync1To0(1, true);
+
+		// now the group has two members
+		members = groupManager0.getMembers(groupId0);
+		assertEquals(2, members.size());
+		members = groupManager1.getMembers(groupId0);
+		assertEquals(2, members.size());
+
+		// make sure 1's member list is as expected
+		for (GroupMember m : members) {
+			if (m.getStatus() != OURSELVES) {
+				assertEquals(author0.getId(), m.getAuthor().getId());
+			} else {
+				assertEquals(author1.getId(), m.getAuthor().getId());
+			}
+		}
+	}
+
+	@Test
+	public void testRevealContacts() throws Exception {
+		// invite two contacts
+		sendInvitation(contactId1From0, clock.currentTimeMillis(), "Hi 1!");
+		sendInvitation(contactId2From0, clock.currentTimeMillis(), "Hi 2!");
+		sync0To1(1, true);
+		sync0To2(1, true);
+
+		// accept both invitations
+		groupInvitationManager1
+				.respondToInvitation(contactId0From1, privateGroup0, true);
+		groupInvitationManager2
+				.respondToInvitation(contactId0From2, privateGroup0, true);
+		sync1To0(1, true);
+		sync2To0(1, true);
+
+		// sync group join messages
+		sync0To1(2, true); // + one invitation protocol join message
+		assertEquals(2, groupManager1.getMembers(groupId0).size());
+		sync1To0(1, true);
+		assertEquals(2, groupManager0.getMembers(groupId0).size());
+		sync0To2(3, true); // 2 join messages and 1 invite join message
+		assertEquals(3, groupManager2.getMembers(groupId0).size());
+		sync2To0(1, true);
+		assertEquals(3, groupManager0.getMembers(groupId0).size());
+		sync0To1(1, true);
+		assertEquals(3, groupManager1.getMembers(groupId0).size());
+
+		// 1 and 2 add each other as contacts
+		addContacts1And2();
+
+		// their relationship is still invisible
+		assertEquals(INVISIBLE,
+				getGroupMember(groupManager1, author2.getId()).getVisibility());
+		assertEquals(INVISIBLE,
+				getGroupMember(groupManager2, author1.getId()).getVisibility());
+
+		// 1 reveals the contact relationship to 2
+		assertTrue(contactId2From1 != null);
+		groupInvitationManager1.revealRelationship(contactId2From1, groupId0);
+		sync1To2(1, true);
+		sync2To1(1, true);
+
+		// their relationship is now revealed
+		assertEquals(REVEALED_BY_US,
+				getGroupMember(groupManager1, author2.getId()).getVisibility());
+		assertEquals(REVEALED_BY_CONTACT,
+				getGroupMember(groupManager2, author1.getId()).getVisibility());
+
+		// 2 sends a message to the group
+		long time = clock.currentTimeMillis();
+		String body = "This is a test message!";
+		MessageId previousMsgId = groupManager2.getPreviousMsgId(groupId0);
+		GroupMessage msg = groupMessageFactory
+				.createGroupMessage(groupId0, time, null, author2, body,
+						previousMsgId);
+		groupManager2.addLocalMessage(msg);
+
+		// 1 has only the three join messages in the group
+		Collection<GroupMessageHeader> headers =
+				groupManager1.getHeaders(groupId0);
+		assertEquals(3, headers.size());
+
+		// message should sync to 1 without creator (0) being involved
+		sync2To1(1, true);
+		headers = groupManager1.getHeaders(groupId0);
+		assertEquals(4, headers.size());
+		for (GroupMessageHeader h : headers) {
+			if (h instanceof JoinMessageHeader) continue;
+			assertEquals(time, h.getTimestamp());
+			assertEquals(groupId0, h.getGroupId());
+			assertEquals(author2.getId(), h.getAuthor().getId());
+		}
+
+		// message should sync from 1 to 0 without 2 being involved
+		sync1To0(1, true);
+		headers = groupManager0.getHeaders(groupId0);
+		assertEquals(4, headers.size());
+	}
+
+	private void sendInvitation(ContactId c, long timestamp,
+			@Nullable String msg) throws DbException {
+		Contact contact = contactManager0.getContact(c);
+		byte[] signature = groupInvitationFactory
+				.signInvitation(contact, groupId0, timestamp,
+						author0.getPrivateKey());
+		groupInvitationManager0
+				.sendInvitation(groupId0, c, msg, timestamp, signature);
+	}
+
+	private GroupMember getGroupMember(PrivateGroupManager groupManager,
+			AuthorId a) throws DbException {
+		Collection<GroupMember> members = groupManager.getMembers(groupId0);
+		for (GroupMember m : members) {
+			if (m.getAuthor().getId().equals(a)) return m;
+		}
+		fail();
+		throw new RuntimeException();
+	}
+
+}
-- 
GitLab