mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Move integration tests to their proper packages
This commit is contained in:
@@ -0,0 +1,358 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
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.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.sync.event.MessageStateChangedEvent;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.briar.api.blog.BlogFactory;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.forum.ForumPostFactory;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
|
||||
import org.briarproject.briar.blog.BlogModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
import org.briarproject.briar.introduction.IntroductionModule;
|
||||
import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.briarproject.TestPluginConfigModule.MAX_LATENCY;
|
||||
import static org.briarproject.TestUtils.getSecretKey;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public abstract class BriarIntegrationTest<C extends BriarIntegrationTestComponent>
|
||||
extends BriarTestCase {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BriarIntegrationTest.class.getName());
|
||||
|
||||
@Nullable
|
||||
protected ContactId contactId1From2, contactId2From1;
|
||||
protected ContactId contactId0From1, contactId0From2, contactId1From0,
|
||||
contactId2From0;
|
||||
protected Contact contact0From1, contact0From2, contact1From0,
|
||||
contact2From0;
|
||||
protected LocalAuthor author0, author1, author2;
|
||||
protected ContactManager contactManager0, contactManager1, contactManager2;
|
||||
protected IdentityManager identityManager0, identityManager1,
|
||||
identityManager2;
|
||||
protected DatabaseComponent db0, db1, db2;
|
||||
protected MessageTracker messageTracker0, messageTracker1, messageTracker2;
|
||||
|
||||
private LifecycleManager lifecycleManager0, lifecycleManager1,
|
||||
lifecycleManager2;
|
||||
private SyncSessionFactory sync0, sync1, sync2;
|
||||
|
||||
@Inject
|
||||
protected Clock clock;
|
||||
@Inject
|
||||
protected CryptoComponent crypto;
|
||||
@Inject
|
||||
protected ClientHelper clientHelper;
|
||||
@Inject
|
||||
protected AuthorFactory authorFactory;
|
||||
@Inject
|
||||
protected ContactGroupFactory contactGroupFactory;
|
||||
@Inject
|
||||
protected PrivateGroupFactory privateGroupFactory;
|
||||
@Inject
|
||||
protected GroupMessageFactory groupMessageFactory;
|
||||
@Inject
|
||||
protected GroupInvitationFactory groupInvitationFactory;
|
||||
@Inject
|
||||
protected BlogFactory blogFactory;
|
||||
@Inject
|
||||
protected BlogPostFactory blogPostFactory;
|
||||
@Inject
|
||||
protected ForumPostFactory forumPostFactory;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile Waiter validationWaiter;
|
||||
private volatile Waiter deliveryWaiter;
|
||||
|
||||
protected final static int TIMEOUT = 15000;
|
||||
protected C c0, c1, c2;
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final String AUTHOR0 = "Author 0";
|
||||
private final String AUTHOR1 = "Author 1";
|
||||
private final String AUTHOR2 = "Author 2";
|
||||
|
||||
protected File t0Dir = new File(testDir, AUTHOR0);
|
||||
protected File t1Dir = new File(testDir, AUTHOR1);
|
||||
protected File t2Dir = new File(testDir, AUTHOR2);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
createComponents();
|
||||
|
||||
identityManager0 = c0.getIdentityManager();
|
||||
identityManager1 = c1.getIdentityManager();
|
||||
identityManager2 = c2.getIdentityManager();
|
||||
contactManager0 = c0.getContactManager();
|
||||
contactManager1 = c1.getContactManager();
|
||||
contactManager2 = c2.getContactManager();
|
||||
messageTracker0 = c0.getMessageTracker();
|
||||
messageTracker1 = c1.getMessageTracker();
|
||||
messageTracker2 = c2.getMessageTracker();
|
||||
db0 = c0.getDatabaseComponent();
|
||||
db1 = c1.getDatabaseComponent();
|
||||
db2 = c2.getDatabaseComponent();
|
||||
sync0 = c0.getSyncSessionFactory();
|
||||
sync1 = c1.getSyncSessionFactory();
|
||||
sync2 = c2.getSyncSessionFactory();
|
||||
|
||||
// initialize waiters fresh for each test
|
||||
validationWaiter = new Waiter();
|
||||
deliveryWaiter = new Waiter();
|
||||
|
||||
startLifecycles();
|
||||
|
||||
getDefaultIdentities();
|
||||
addDefaultContacts();
|
||||
listenToEvents();
|
||||
}
|
||||
|
||||
abstract protected void createComponents();
|
||||
|
||||
protected void injectEagerSingletons(
|
||||
BriarIntegrationTestComponent component) {
|
||||
component.inject(new BlogModule.EagerSingletons());
|
||||
component.inject(new ContactModule.EagerSingletons());
|
||||
component.inject(new CryptoModule.EagerSingletons());
|
||||
component.inject(new ForumModule.EagerSingletons());
|
||||
component.inject(new GroupInvitationModule.EagerSingletons());
|
||||
component.inject(new IdentityModule.EagerSingletons());
|
||||
component.inject(new IntroductionModule.EagerSingletons());
|
||||
component.inject(new LifecycleModule.EagerSingletons());
|
||||
component.inject(new MessagingModule.EagerSingletons());
|
||||
component.inject(new PrivateGroupModule.EagerSingletons());
|
||||
component.inject(new PropertiesModule.EagerSingletons());
|
||||
component.inject(new SharingModule.EagerSingletons());
|
||||
component.inject(new SyncModule.EagerSingletons());
|
||||
component.inject(new SystemModule.EagerSingletons());
|
||||
component.inject(new TransportModule.EagerSingletons());
|
||||
}
|
||||
|
||||
private void startLifecycles() throws InterruptedException {
|
||||
// Start the lifecycle manager and wait for it to finish
|
||||
lifecycleManager0 = c0.getLifecycleManager();
|
||||
lifecycleManager1 = c1.getLifecycleManager();
|
||||
lifecycleManager2 = c2.getLifecycleManager();
|
||||
lifecycleManager0.startServices(AUTHOR0);
|
||||
lifecycleManager1.startServices(AUTHOR1);
|
||||
lifecycleManager2.startServices(AUTHOR2);
|
||||
lifecycleManager0.waitForStartup();
|
||||
lifecycleManager1.waitForStartup();
|
||||
lifecycleManager2.waitForStartup();
|
||||
}
|
||||
|
||||
private void listenToEvents() {
|
||||
Listener listener0 = new Listener();
|
||||
c0.getEventBus().addListener(listener0);
|
||||
Listener listener1 = new Listener();
|
||||
c1.getEventBus().addListener(listener1);
|
||||
Listener listener2 = new Listener();
|
||||
c2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
private class Listener implements EventListener {
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageStateChangedEvent) {
|
||||
MessageStateChangedEvent event = (MessageStateChangedEvent) e;
|
||||
if (!event.isLocal()) {
|
||||
if (event.getState() == DELIVERED) {
|
||||
LOG.info("Delivered new message");
|
||||
deliveryWaiter.resume();
|
||||
} else if (event.getState() == INVALID ||
|
||||
event.getState() == PENDING) {
|
||||
LOG.info("Validated new " + event.getState().name() +
|
||||
" message");
|
||||
validationWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void getDefaultIdentities() throws DbException {
|
||||
author0 = identityManager0.getLocalAuthor();
|
||||
author1 = identityManager1.getLocalAuthor();
|
||||
author2 = identityManager2.getLocalAuthor();
|
||||
}
|
||||
|
||||
protected void addDefaultContacts() throws DbException {
|
||||
contactId1From0 = contactManager0
|
||||
.addContact(author1, author0.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact1From0 = contactManager0.getContact(contactId1From0);
|
||||
contactId0From1 = contactManager1
|
||||
.addContact(author0, author1.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact0From1 = contactManager1.getContact(contactId0From1);
|
||||
contactId2From0 = contactManager0
|
||||
.addContact(author2, author0.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact2From0 = contactManager0.getContact(contactId2From0);
|
||||
contactId0From2 = contactManager2
|
||||
.addContact(author0, author2.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contact0From2 = contactManager2.getContact(contactId0From2);
|
||||
}
|
||||
|
||||
protected void addContacts1And2() throws DbException {
|
||||
contactId2From1 = contactManager1
|
||||
.addContact(author2, author1.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
contactId1From2 = contactManager2
|
||||
.addContact(author1, author2.getId(), getSecretKey(),
|
||||
clock.currentTimeMillis(), true, true, true);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
stopLifecycles();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private void stopLifecycles() throws InterruptedException {
|
||||
// Clean up
|
||||
lifecycleManager0.stopServices();
|
||||
lifecycleManager1.stopServices();
|
||||
lifecycleManager2.stopServices();
|
||||
lifecycleManager0.waitForShutdown();
|
||||
lifecycleManager1.waitForShutdown();
|
||||
lifecycleManager2.waitForShutdown();
|
||||
}
|
||||
|
||||
protected void sync0To1(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
syncMessage(sync0, contactId0From1, sync1, contactId1From0, num, valid);
|
||||
}
|
||||
|
||||
protected void sync0To2(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
syncMessage(sync0, contactId0From2, sync2, contactId2From0, num, valid);
|
||||
}
|
||||
|
||||
protected void sync1To0(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
syncMessage(sync1, contactId1From0, sync0, contactId0From1, num, valid);
|
||||
}
|
||||
|
||||
protected void sync2To0(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
syncMessage(sync2, contactId2From0, sync0, contactId0From2, num, valid);
|
||||
}
|
||||
|
||||
protected void sync2To1(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
assertNotNull(contactId2From1);
|
||||
assertNotNull(contactId1From2);
|
||||
syncMessage(sync2, contactId2From1, sync1, contactId1From2, num, valid);
|
||||
}
|
||||
|
||||
protected void sync1To2(int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
assertNotNull(contactId2From1);
|
||||
assertNotNull(contactId1From2);
|
||||
syncMessage(sync1, contactId1From2, sync2, contactId2From1, num, valid);
|
||||
}
|
||||
|
||||
private void syncMessage(SyncSessionFactory fromSync, ContactId fromId,
|
||||
SyncSessionFactory toSync, ContactId toId, int num, boolean valid)
|
||||
throws IOException, TimeoutException {
|
||||
|
||||
// Debug output
|
||||
String from = "0";
|
||||
if (fromSync == sync1) from = "1";
|
||||
else if (fromSync == sync2) from = "2";
|
||||
String to = "0";
|
||||
if (toSync == sync1) to = "1";
|
||||
else if (toSync == sync2) to = "2";
|
||||
LOG.info("TEST: Sending message from " + from + " to " + to);
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
// Create an outgoing sync session
|
||||
SyncSession sessionFrom =
|
||||
fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
|
||||
// Write whatever needs to be written
|
||||
sessionFrom.run();
|
||||
out.close();
|
||||
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
|
||||
// Create an incoming sync session
|
||||
SyncSession sessionTo = toSync.createIncomingSession(fromId, in);
|
||||
// Read whatever needs to be read
|
||||
sessionTo.run();
|
||||
in.close();
|
||||
|
||||
if (valid) {
|
||||
deliveryWaiter.await(TIMEOUT, num);
|
||||
} else {
|
||||
validationWaiter.await(TIMEOUT, num);
|
||||
}
|
||||
}
|
||||
|
||||
protected void removeAllContacts() throws DbException {
|
||||
contactManager0.removeContact(contactId1From0);
|
||||
contactManager0.removeContact(contactId2From0);
|
||||
contactManager1.removeContact(contactId0From1);
|
||||
contactManager2.removeContact(contactId0From2);
|
||||
assertNotNull(contactId2From1);
|
||||
contactManager1.removeContact(contactId2From1);
|
||||
assertNotNull(contactId1From2);
|
||||
contactManager2.removeContact(contactId1From2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.briarproject.briar.blog.BlogModule;
|
||||
import org.briarproject.briar.client.BriarClientModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
import org.briarproject.briar.introduction.IntroductionModule;
|
||||
import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
TestDatabaseModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
TestSeedProviderModule.class,
|
||||
BlogModule.class,
|
||||
BriarClientModule.class,
|
||||
ClientModule.class,
|
||||
ContactModule.class,
|
||||
CryptoModule.class,
|
||||
DataModule.class,
|
||||
DatabaseModule.class,
|
||||
EventModule.class,
|
||||
ForumModule.class,
|
||||
GroupInvitationModule.class,
|
||||
IdentityModule.class,
|
||||
IntroductionModule.class,
|
||||
LifecycleModule.class,
|
||||
MessagingModule.class,
|
||||
PrivateGroupModule.class,
|
||||
PropertiesModule.class,
|
||||
SharingModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class,
|
||||
TransportModule.class
|
||||
})
|
||||
public interface BriarIntegrationTestComponent {
|
||||
|
||||
void inject(BriarIntegrationTest<BriarIntegrationTestComponent> init);
|
||||
|
||||
void inject(BlogModule.EagerSingletons init);
|
||||
|
||||
void inject(ContactModule.EagerSingletons init);
|
||||
|
||||
void inject(CryptoModule.EagerSingletons init);
|
||||
|
||||
void inject(ForumModule.EagerSingletons init);
|
||||
|
||||
void inject(GroupInvitationModule.EagerSingletons init);
|
||||
|
||||
void inject(IdentityModule.EagerSingletons init);
|
||||
|
||||
void inject(IntroductionModule.EagerSingletons init);
|
||||
|
||||
void inject(LifecycleModule.EagerSingletons init);
|
||||
|
||||
void inject(MessagingModule.EagerSingletons init);
|
||||
|
||||
void inject(PrivateGroupModule.EagerSingletons init);
|
||||
|
||||
void inject(PropertiesModule.EagerSingletons init);
|
||||
|
||||
void inject(SharingModule.EagerSingletons init);
|
||||
|
||||
void inject(SyncModule.EagerSingletons init);
|
||||
|
||||
void inject(SystemModule.EagerSingletons init);
|
||||
|
||||
void inject(TransportModule.EagerSingletons init);
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
EventBus getEventBus();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
ClientHelper getClientHelper();
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
SyncSessionFactory getSyncSessionFactory();
|
||||
|
||||
DatabaseComponent getDatabaseComponent();
|
||||
|
||||
BlogManager getBlogManager();
|
||||
|
||||
BlogSharingManager getBlogSharingManager();
|
||||
|
||||
ForumSharingManager getForumSharingManager();
|
||||
|
||||
ForumManager getForumManager();
|
||||
|
||||
GroupInvitationManager getGroupInvitationManager();
|
||||
|
||||
IntroductionManager getIntroductionManager();
|
||||
|
||||
MessageTracker getMessageTracker();
|
||||
|
||||
MessageQueueManager getMessageQueueManager();
|
||||
|
||||
PrivateGroupManager getPrivateGroupManager();
|
||||
|
||||
TransportPropertyManager getTransportPropertyManager();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
package org.briarproject.briar.blog;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogCommentHeader;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPost;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.briarproject.TestUtils.getRandomString;
|
||||
import static org.briarproject.briar.api.blog.MessageType.COMMENT;
|
||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_COMMENT;
|
||||
import static org.briarproject.briar.api.blog.MessageType.WRAPPED_POST;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BlogManagerIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private BlogManager blogManager0, blogManager1;
|
||||
private Blog blog0, blog1;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
author0 = identityManager0.getLocalAuthor();
|
||||
author1 = identityManager1.getLocalAuthor();
|
||||
|
||||
blogManager0 = c0.getBlogManager();
|
||||
blogManager1 = c1.getBlogManager();
|
||||
|
||||
blog0 = blogFactory.createBlog(author0);
|
||||
blog1 = blogFactory.createBlog(author1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersonalBlogInitialisation() throws Exception {
|
||||
Collection<Blog> blogs0 = blogManager0.getBlogs();
|
||||
assertEquals(3, blogs0.size());
|
||||
Iterator<Blog> i0 = blogs0.iterator();
|
||||
assertEquals(author0, i0.next().getAuthor());
|
||||
assertEquals(author1, i0.next().getAuthor());
|
||||
assertEquals(author2, i0.next().getAuthor());
|
||||
|
||||
Collection<Blog> blogs1 = blogManager1.getBlogs();
|
||||
assertEquals(2, blogs1.size());
|
||||
Iterator<Blog> i1 = blogs1.iterator();
|
||||
assertEquals(author1, i1.next().getAuthor());
|
||||
assertEquals(author0, i1.next().getAuthor());
|
||||
|
||||
assertEquals(blog0, blogManager0.getPersonalBlog(author0));
|
||||
assertEquals(blog0, blogManager1.getPersonalBlog(author0));
|
||||
assertEquals(blog1, blogManager0.getPersonalBlog(author1));
|
||||
assertEquals(blog1, blogManager1.getPersonalBlog(author1));
|
||||
|
||||
assertEquals(blog0, blogManager0.getBlog(blog0.getId()));
|
||||
assertEquals(blog0, blogManager1.getBlog(blog0.getId()));
|
||||
assertEquals(blog1, blogManager0.getBlog(blog1.getId()));
|
||||
assertEquals(blog1, blogManager1.getBlog(blog1.getId()));
|
||||
|
||||
assertEquals(1, blogManager0.getBlogs(author0).size());
|
||||
assertEquals(1, blogManager1.getBlogs(author0).size());
|
||||
assertEquals(1, blogManager0.getBlogs(author1).size());
|
||||
assertEquals(1, blogManager1.getBlogs(author1).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlogPost() throws Exception {
|
||||
// check that blog0 has no posts
|
||||
final String body = getRandomString(42);
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog0.getId());
|
||||
assertEquals(0, headers0.size());
|
||||
|
||||
// add a post to blog0
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// check that post is now in blog0
|
||||
headers0 = blogManager0.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers0.size());
|
||||
|
||||
// check that body is there
|
||||
assertEquals(body, blogManager0.getPostBody(p.getMessage().getId()));
|
||||
|
||||
// make sure that blog0 at author1 doesn't have the post yet
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(0, headers1.size());
|
||||
|
||||
// sync the post over
|
||||
sync0To1(1, true);
|
||||
|
||||
// make sure post arrived
|
||||
headers1 = blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
assertEquals(POST, headers1.iterator().next().getType());
|
||||
|
||||
// check that body is there
|
||||
assertEquals(body, blogManager1.getPostBody(p.getMessage().getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlogPostInWrongBlog() throws Exception {
|
||||
// add a post to blog1
|
||||
final String body = getRandomString(42);
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog1.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// check that post is now in blog1
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog1.getId());
|
||||
assertEquals(1, headers0.size());
|
||||
|
||||
// sync the post over
|
||||
sync0To1(1, false);
|
||||
|
||||
// make sure post did not arrive, because of wrong signature
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog1.getId());
|
||||
assertEquals(0, headers1.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanNotRemoveContactsPersonalBlog() throws Exception {
|
||||
assertFalse(blogManager0.canBeRemoved(blog1.getId()));
|
||||
assertFalse(blogManager1.canBeRemoved(blog0.getId()));
|
||||
|
||||
// the following two calls should throw a DbException now
|
||||
thrown.expect(DbException.class);
|
||||
|
||||
blogManager0.removeBlog(blog1);
|
||||
blogManager1.removeBlog(blog0);
|
||||
|
||||
// blogs have not been removed
|
||||
assertEquals(2, blogManager0.getBlogs().size());
|
||||
assertEquals(2, blogManager1.getBlogs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlogComment() throws Exception {
|
||||
// add a post to blog0
|
||||
final String body = getRandomString(42);
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// sync the post over
|
||||
sync0To1(1, true);
|
||||
|
||||
// make sure post arrived
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
assertEquals(POST, headers1.iterator().next().getType());
|
||||
|
||||
// 1 adds a comment to that blog post
|
||||
String comment = "This is a comment on a blog post!";
|
||||
blogManager1
|
||||
.addLocalComment(author1, blog1.getId(), comment,
|
||||
headers1.iterator().next());
|
||||
|
||||
// sync comment over
|
||||
sync1To0(2, true);
|
||||
|
||||
// make sure comment and wrapped post arrived
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog1.getId());
|
||||
assertEquals(1, headers0.size());
|
||||
assertEquals(COMMENT, headers0.iterator().next().getType());
|
||||
BlogCommentHeader h = (BlogCommentHeader) headers0.iterator().next();
|
||||
assertEquals(author0, h.getParent().getAuthor());
|
||||
|
||||
// ensure that body can be retrieved from wrapped post
|
||||
MessageId parentId = h.getParentId();
|
||||
assertNotNull(parentId);
|
||||
assertEquals(body, blogManager0.getPostBody(parentId));
|
||||
|
||||
// 1 has only their own comment in their blog
|
||||
headers1 = blogManager1.getPostHeaders(blog1.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlogCommentOnOwnPost() throws Exception {
|
||||
// add a post to blog0
|
||||
final String body = getRandomString(42);
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// get header of own post
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers0.size());
|
||||
BlogPostHeader header = headers0.iterator().next();
|
||||
|
||||
// add a comment on own post
|
||||
String comment = "This is a comment on my own blog post!";
|
||||
blogManager0
|
||||
.addLocalComment(author0, blog0.getId(), comment, header);
|
||||
|
||||
// sync the post and comment over
|
||||
sync0To1(2, true);
|
||||
|
||||
// make sure post arrived
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(2, headers1.size());
|
||||
for (BlogPostHeader h : headers1) {
|
||||
if (h.getType() == POST) {
|
||||
assertEquals(body, blogManager1.getPostBody(h.getId()));
|
||||
} else {
|
||||
assertEquals(comment, ((BlogCommentHeader) h).getComment());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommentOnComment() throws Exception {
|
||||
// add a post to blog0
|
||||
final String body = getRandomString(42);
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// sync the post over
|
||||
sync0To1(1, true);
|
||||
|
||||
// make sure post arrived
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
assertEquals(POST, headers1.iterator().next().getType());
|
||||
|
||||
// 1 reblogs that blog post
|
||||
blogManager1
|
||||
.addLocalComment(author1, blog1.getId(), null,
|
||||
headers1.iterator().next());
|
||||
|
||||
// sync comment over
|
||||
sync1To0(2, true);
|
||||
|
||||
// make sure comment and wrapped post arrived
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog1.getId());
|
||||
assertEquals(1, headers0.size());
|
||||
|
||||
// get header of comment
|
||||
BlogPostHeader cHeader = headers0.iterator().next();
|
||||
assertEquals(COMMENT, cHeader.getType());
|
||||
|
||||
// comment on the comment
|
||||
String comment = "This is a comment on a reblogged post.";
|
||||
blogManager0
|
||||
.addLocalComment(author0, blog0.getId(), comment, cHeader);
|
||||
|
||||
// sync comment over
|
||||
sync0To1(3, true);
|
||||
|
||||
// check that comment arrived
|
||||
headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(2, headers1.size());
|
||||
|
||||
// get header of comment
|
||||
cHeader = null;
|
||||
for (BlogPostHeader h : headers1) {
|
||||
if (h.getType() == COMMENT) {
|
||||
cHeader = h;
|
||||
}
|
||||
}
|
||||
assertTrue(cHeader != null);
|
||||
|
||||
// another comment on the comment
|
||||
String comment2 = "This is a comment on a comment.";
|
||||
blogManager1.addLocalComment(author1, blog1.getId(), comment2, cHeader);
|
||||
|
||||
// sync comment over
|
||||
sync1To0(4, true);
|
||||
|
||||
// make sure new comment arrived
|
||||
headers0 =
|
||||
blogManager0.getPostHeaders(blog1.getId());
|
||||
assertEquals(2, headers0.size());
|
||||
boolean satisfied = false;
|
||||
for (BlogPostHeader h : headers0) {
|
||||
assertEquals(COMMENT, h.getType());
|
||||
BlogCommentHeader c = (BlogCommentHeader) h;
|
||||
if (c.getComment() != null && c.getComment().equals(comment2)) {
|
||||
assertEquals(author0, c.getParent().getAuthor());
|
||||
assertEquals(WRAPPED_COMMENT, c.getParent().getType());
|
||||
assertEquals(comment,
|
||||
((BlogCommentHeader) c.getParent()).getComment());
|
||||
assertEquals(WRAPPED_COMMENT,
|
||||
((BlogCommentHeader) c.getParent()).getParent()
|
||||
.getType());
|
||||
assertEquals(WRAPPED_POST,
|
||||
((BlogCommentHeader) ((BlogCommentHeader) c
|
||||
.getParent()).getParent()).getParent()
|
||||
.getType());
|
||||
satisfied = true;
|
||||
}
|
||||
}
|
||||
assertTrue(satisfied);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCommentOnOwnComment() throws Exception {
|
||||
// add a post to blog0
|
||||
final String body = getRandomString(42);
|
||||
BlogPost p = blogPostFactory
|
||||
.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
|
||||
author0, body);
|
||||
blogManager0.addLocalPost(p);
|
||||
|
||||
// sync the post over
|
||||
sync0To1(1, true);
|
||||
|
||||
// make sure post arrived
|
||||
Collection<BlogPostHeader> headers1 =
|
||||
blogManager1.getPostHeaders(blog0.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
assertEquals(POST, headers1.iterator().next().getType());
|
||||
|
||||
// 1 reblogs that blog post with a comment
|
||||
String comment = "This is a comment on a post.";
|
||||
blogManager1
|
||||
.addLocalComment(author1, blog1.getId(), comment,
|
||||
headers1.iterator().next());
|
||||
|
||||
// get comment from own blog
|
||||
headers1 = blogManager1.getPostHeaders(blog1.getId());
|
||||
assertEquals(1, headers1.size());
|
||||
assertEquals(COMMENT, headers1.iterator().next().getType());
|
||||
BlogCommentHeader ch = (BlogCommentHeader) headers1.iterator().next();
|
||||
assertEquals(comment, ch.getComment());
|
||||
|
||||
comment = "This is a comment on a post with a comment.";
|
||||
blogManager1.addLocalComment(author1, blog1.getId(), comment, ch);
|
||||
|
||||
// sync both comments over (2 comments + 1 wrapped post)
|
||||
sync1To0(3, true);
|
||||
|
||||
// make sure both comments arrived
|
||||
Collection<BlogPostHeader> headers0 =
|
||||
blogManager0.getPostHeaders(blog1.getId());
|
||||
assertEquals(2, headers0.size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
package org.briarproject.briar.forum;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ForumManagerTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private ForumManager forumManager0, forumManager1;
|
||||
private ForumSharingManager forumSharingManager0, forumSharingManager1;
|
||||
private Forum forum0;
|
||||
private GroupId groupId0;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
forumManager0 = c0.getForumManager();
|
||||
forumManager1 = c1.getForumManager();
|
||||
forumSharingManager0 = c0.getForumSharingManager();
|
||||
forumSharingManager1 = c1.getForumSharingManager();
|
||||
|
||||
|
||||
forum0 = forumManager0.addForum("Test Forum");
|
||||
groupId0 = forum0.getId();
|
||||
// share forum
|
||||
forumSharingManager0.sendInvitation(groupId0, contactId1From0, null);
|
||||
sync0To1(1, true);
|
||||
forumSharingManager1.respondToInvitation(forum0, contact0From1, true);
|
||||
sync1To0(1, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
private ForumPost createForumPost(GroupId groupId,
|
||||
@Nullable ForumPost parent, String body, long ms) throws Exception {
|
||||
return forumPostFactory.createPost(groupId, ms,
|
||||
parent == null ? null : parent.getMessage().getId(),
|
||||
author0, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForumPost() throws Exception {
|
||||
assertEquals(1, forumManager0.getForums().size());
|
||||
final long ms1 = clock.currentTimeMillis() - 1000L;
|
||||
final String body1 = "some forum text";
|
||||
final long ms2 = clock.currentTimeMillis();
|
||||
final String body2 = "some other forum text";
|
||||
ForumPost post1 =
|
||||
createForumPost(forum0.getGroup().getId(), null, body1, ms1);
|
||||
assertEquals(ms1, post1.getMessage().getTimestamp());
|
||||
ForumPost post2 =
|
||||
createForumPost(forum0.getGroup().getId(), post1, body2, ms2);
|
||||
assertEquals(ms2, post2.getMessage().getTimestamp());
|
||||
forumManager0.addLocalPost(post1);
|
||||
forumManager0.setReadFlag(forum0.getGroup().getId(),
|
||||
post1.getMessage().getId(), true);
|
||||
assertGroupCount(messageTracker0, forum0.getGroup().getId(), 1, 0,
|
||||
post1.getMessage().getTimestamp());
|
||||
forumManager0.addLocalPost(post2);
|
||||
forumManager0.setReadFlag(forum0.getGroup().getId(),
|
||||
post2.getMessage().getId(), false);
|
||||
assertGroupCount(messageTracker0, forum0.getGroup().getId(), 2, 1,
|
||||
post2.getMessage().getTimestamp());
|
||||
forumManager0.setReadFlag(forum0.getGroup().getId(),
|
||||
post2.getMessage().getId(), false);
|
||||
assertGroupCount(messageTracker0, forum0.getGroup().getId(), 2, 1,
|
||||
post2.getMessage().getTimestamp());
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager0.getPostHeaders(forum0.getGroup().getId());
|
||||
assertEquals(2, headers.size());
|
||||
for (ForumPostHeader h : headers) {
|
||||
final String hBody = forumManager0.getPostBody(h.getId());
|
||||
|
||||
boolean isPost1 = h.getId().equals(post1.getMessage().getId());
|
||||
boolean isPost2 = h.getId().equals(post2.getMessage().getId());
|
||||
assertTrue(isPost1 || isPost2);
|
||||
if (isPost1) {
|
||||
assertEquals(h.getTimestamp(), ms1);
|
||||
assertEquals(body1, hBody);
|
||||
assertNull(h.getParentId());
|
||||
assertTrue(h.isRead());
|
||||
} else {
|
||||
assertEquals(h.getTimestamp(), ms2);
|
||||
assertEquals(body2, hBody);
|
||||
assertEquals(h.getParentId(), post2.getParent());
|
||||
assertFalse(h.isRead());
|
||||
}
|
||||
}
|
||||
forumManager0.removeForum(forum0);
|
||||
assertEquals(0, forumManager0.getForums().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForumPostDelivery() throws Exception {
|
||||
// add one forum post
|
||||
long time = clock.currentTimeMillis();
|
||||
ForumPost post1 = createForumPost(groupId0, null, "a", time);
|
||||
forumManager0.addLocalPost(post1);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
assertGroupCount(messageTracker0, groupId0, 1, 0, time);
|
||||
assertGroupCount(messageTracker1, groupId0, 0, 0, 0);
|
||||
|
||||
// send post to 1
|
||||
sync0To1(1, true);
|
||||
assertEquals(1, forumManager1.getPostHeaders(groupId0).size());
|
||||
assertGroupCount(messageTracker1, groupId0, 1, 1, time);
|
||||
|
||||
// add another forum post
|
||||
long time2 = clock.currentTimeMillis();
|
||||
ForumPost post2 = createForumPost(groupId0, null, "b", time2);
|
||||
forumManager1.addLocalPost(post2);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(2, forumManager1.getPostHeaders(groupId0).size());
|
||||
assertGroupCount(messageTracker0, groupId0, 1, 0, time);
|
||||
assertGroupCount(messageTracker1, groupId0, 2, 1, time2);
|
||||
|
||||
// send post to 0
|
||||
sync1To0(1, true);
|
||||
assertEquals(2, forumManager1.getPostHeaders(groupId0).size());
|
||||
assertGroupCount(messageTracker0, groupId0, 2, 1, time2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForumPostDeliveredAfterParent() throws Exception {
|
||||
// add one forum post without the parent
|
||||
long time = clock.currentTimeMillis();
|
||||
ForumPost post1 = createForumPost(groupId0, null, "a", time);
|
||||
ForumPost post2 = createForumPost(groupId0, post1, "a", time);
|
||||
forumManager0.addLocalPost(post2);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
|
||||
// send post to 1 without waiting for message delivery
|
||||
sync0To1(1, false);
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
|
||||
// now add the parent post as well
|
||||
forumManager0.addLocalPost(post1);
|
||||
assertEquals(2, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
|
||||
// and send it over to 1 and wait for a second message to be delivered
|
||||
sync0To1(2, true);
|
||||
assertEquals(2, forumManager1.getPostHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForumPostWithParentInOtherGroup() throws Exception {
|
||||
// share a second forum
|
||||
Forum forum1 = forumManager0.addForum("Test Forum1");
|
||||
GroupId g1 = forum1.getId();
|
||||
forumSharingManager0.sendInvitation(g1, contactId1From0, null);
|
||||
sync0To1(1, true);
|
||||
forumSharingManager1.respondToInvitation(forum1, contact0From1, true);
|
||||
sync1To0(1, true);
|
||||
|
||||
// add one forum post with a parent in another forum
|
||||
long time = clock.currentTimeMillis();
|
||||
ForumPost post1 = createForumPost(g1, null, "a", time);
|
||||
ForumPost post = createForumPost(groupId0, post1, "b", time);
|
||||
forumManager0.addLocalPost(post);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
|
||||
// send the child post to 1
|
||||
sync0To1(1, false);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
|
||||
// now also add the parent post which is in another group
|
||||
forumManager0.addLocalPost(post1);
|
||||
assertEquals(1, forumManager0.getPostHeaders(g1).size());
|
||||
assertEquals(0, forumManager1.getPostHeaders(g1).size());
|
||||
|
||||
// send posts to 1
|
||||
sync0To1(1, true);
|
||||
assertEquals(1, forumManager0.getPostHeaders(groupId0).size());
|
||||
assertEquals(1, forumManager0.getPostHeaders(g1).size());
|
||||
// the next line is critical, makes sure post doesn't show up
|
||||
assertEquals(0, forumManager1.getPostHeaders(groupId0).size());
|
||||
assertEquals(1, forumManager1.getPostHeaders(g1).size());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,983 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.crypto.KeyPair;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
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.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.properties.TransportProperties;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroductionManager;
|
||||
import org.briarproject.briar.api.introduction.IntroductionMessage;
|
||||
import org.briarproject.briar.api.introduction.IntroductionRequest;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionAbortedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NONCE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TRANSPORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.briarproject.briar.introduction.IntroduceeManager.SIGNING_LABEL_RESPONSE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IntroductionIntegrationTest
|
||||
extends BriarIntegrationTest<IntroductionIntegrationTestComponent> {
|
||||
|
||||
@Inject
|
||||
IntroductionGroupFactory introductionGroupFactory;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile IntroductionManager introductionManager0;
|
||||
private volatile IntroductionManager introductionManager1;
|
||||
private volatile IntroductionManager introductionManager2;
|
||||
private volatile Waiter eventWaiter;
|
||||
|
||||
private IntroducerListener listener0;
|
||||
private IntroduceeListener listener1;
|
||||
private IntroduceeListener listener2;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(IntroductionIntegrationTest.class.getName());
|
||||
|
||||
interface StateVisitor {
|
||||
boolean visit(BdfDictionary response);
|
||||
}
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
introductionManager0 = c0.getIntroductionManager();
|
||||
introductionManager1 = c1.getIntroductionManager();
|
||||
introductionManager2 = c2.getIntroductionManager();
|
||||
|
||||
// initialize waiter fresh for each test
|
||||
eventWaiter = new Waiter();
|
||||
|
||||
addTransportProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
IntroductionIntegrationTestComponent component =
|
||||
DaggerIntroductionIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerIntroductionIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionSession() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
Contact introducee1 = contact1From0;
|
||||
Contact introducee2 = contact2From0;
|
||||
introductionManager0
|
||||
.makeIntroduction(introducee1, introducee2, "Hi!", time);
|
||||
|
||||
// check that messages are tracked properly
|
||||
Group g1 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee1);
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee2);
|
||||
assertGroupCount(messageTracker0, g1.getId(), 1, 0, time);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 1, 0, time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
|
||||
// sync second request message
|
||||
sync0To2(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener2.requestReceived);
|
||||
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
|
||||
|
||||
// sync second response
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
|
||||
|
||||
// sync forwarded responses to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
|
||||
|
||||
// sync first ACK and its forward
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// sync second ACK and its forward
|
||||
sync2To0(1, true);
|
||||
sync0To1(1, true);
|
||||
|
||||
// wait for introduction to succeed
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
assertTrue(listener1.succeeded);
|
||||
assertTrue(listener2.succeeded);
|
||||
|
||||
assertTrue(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertTrue(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
// make sure that introduced contacts are not verified
|
||||
for (Contact c : contactManager1.getActiveContacts()) {
|
||||
if (c.getAuthor().equals(author2)) {
|
||||
assertFalse(c.isVerified());
|
||||
}
|
||||
}
|
||||
for (Contact c : contactManager2.getActiveContacts()) {
|
||||
if (c.getAuthor().equals(author1)) {
|
||||
assertFalse(c.isVerified());
|
||||
}
|
||||
}
|
||||
|
||||
assertDefaultUiMessages();
|
||||
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
|
||||
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
assertGroupCount(messageTracker2, g2.getId(), 2, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionSessionFirstDecline() throws Exception {
|
||||
addListeners(false, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
Contact introducee1 = contact1From0;
|
||||
Contact introducee2 = contact2From0;
|
||||
introductionManager0
|
||||
.makeIntroduction(introducee1, introducee2, null, time);
|
||||
|
||||
// sync request messages
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// wait for requests to arrive
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertTrue(listener2.requestReceived);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
|
||||
// sync second response
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
|
||||
// sync first forwarded response
|
||||
sync0To2(1, true);
|
||||
|
||||
// note how the introducer does not forward the second response,
|
||||
// because after the first decline the protocol finished
|
||||
|
||||
assertFalse(listener1.succeeded);
|
||||
assertFalse(listener2.succeeded);
|
||||
|
||||
assertFalse(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertFalse(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
Group g1 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee1);
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(introducee2);
|
||||
assertEquals(2,
|
||||
introductionManager0.getIntroductionMessages(contactId1From0)
|
||||
.size());
|
||||
assertGroupCount(messageTracker0, g1.getId(), 2, 1);
|
||||
assertEquals(2,
|
||||
introductionManager0.getIntroductionMessages(contactId2From0)
|
||||
.size());
|
||||
assertGroupCount(messageTracker0, g2.getId(), 2, 1);
|
||||
assertEquals(2,
|
||||
introductionManager1.getIntroductionMessages(contactId0From1)
|
||||
.size());
|
||||
assertGroupCount(messageTracker1, g1.getId(), 2, 1);
|
||||
// introducee2 should also have the decline response of introducee1
|
||||
assertEquals(3,
|
||||
introductionManager2.getIntroductionMessages(contactId0From2)
|
||||
.size());
|
||||
assertGroupCount(messageTracker2, g2.getId(), 3, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionSessionSecondDecline() throws Exception {
|
||||
addListeners(true, false);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, null, time);
|
||||
|
||||
// sync request messages
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// wait for requests to arrive
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertTrue(listener2.requestReceived);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
|
||||
// sync second response
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
|
||||
// sync both forwarded response
|
||||
sync0To2(1, true);
|
||||
sync0To1(1, true);
|
||||
|
||||
assertFalse(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertFalse(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
assertEquals(2,
|
||||
introductionManager0.getIntroductionMessages(contactId1From0)
|
||||
.size());
|
||||
assertEquals(2,
|
||||
introductionManager0.getIntroductionMessages(contactId2From0)
|
||||
.size());
|
||||
// introducee1 also sees the decline response from introducee2
|
||||
assertEquals(3,
|
||||
introductionManager1.getIntroductionMessages(contactId0From1)
|
||||
.size());
|
||||
assertEquals(2,
|
||||
introductionManager2.getIntroductionMessages(contactId0From2)
|
||||
.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionSessionDelayedFirstDecline() throws Exception {
|
||||
addListeners(false, false);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, null, time);
|
||||
|
||||
// sync request messages
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// wait for requests to arrive
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertTrue(listener2.requestReceived);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
|
||||
// sync second response
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
|
||||
// sync first forwarded response
|
||||
sync0To2(1, true);
|
||||
|
||||
// note how the second response will not be forwarded anymore
|
||||
|
||||
assertFalse(contactManager1
|
||||
.contactExists(author2.getId(), author1.getId()));
|
||||
assertFalse(contactManager2
|
||||
.contactExists(author1.getId(), author2.getId()));
|
||||
|
||||
// since introducee2 was already in FINISHED state when
|
||||
// introducee1's response arrived, she ignores and deletes it
|
||||
assertDefaultUiMessages();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseAndAckInOneSession() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response1Received);
|
||||
|
||||
// don't let 2 answer the request right away
|
||||
// to have the response arrive first
|
||||
listener2.answerRequests = false;
|
||||
|
||||
// sync second request message and first response
|
||||
sync0To2(2, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener2.requestReceived);
|
||||
|
||||
// answer request manually
|
||||
introductionManager2
|
||||
.acceptIntroduction(contactId0From2, listener2.sessionId, time);
|
||||
|
||||
// sync second response and ACK and make sure there is no abort
|
||||
sync2To0(2, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.response2Received);
|
||||
assertFalse(listener0.aborted);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroductionToSameContact() throws Exception {
|
||||
addListeners(true, false);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact1From0, null, time);
|
||||
|
||||
// sync request messages
|
||||
sync0To1(1, false);
|
||||
|
||||
// we should not get any event, because the request will be discarded
|
||||
assertFalse(listener1.requestReceived);
|
||||
|
||||
// make really sure we don't have that request
|
||||
assertTrue(introductionManager1.getIntroductionMessages(contactId0From1)
|
||||
.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionIdReuse() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get SessionId
|
||||
List<IntroductionMessage> list = new ArrayList<IntroductionMessage>(
|
||||
introductionManager1.getIntroductionMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
assertTrue(list.get(0) instanceof IntroductionRequest);
|
||||
IntroductionRequest msg = (IntroductionRequest) list.get(0);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
|
||||
// get contact group
|
||||
Group group =
|
||||
introductionGroupFactory.createIntroductionGroup(contact1From0);
|
||||
|
||||
// create new message with same SessionId
|
||||
BdfDictionary d = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_REQUEST),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(GROUP_ID, group.getId()),
|
||||
new BdfEntry(NAME, TestUtils.getRandomString(42)),
|
||||
new BdfEntry(PUBLIC_KEY,
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
|
||||
);
|
||||
|
||||
// reset request received state
|
||||
listener1.requestReceived = false;
|
||||
|
||||
// add the message to the queue
|
||||
MessageSender sender0 = c0.getMessageSender();
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
sender0.sendMessage(txn, d);
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
|
||||
// actually send message
|
||||
sync0To1(1, false);
|
||||
|
||||
// make sure it does not arrive
|
||||
assertFalse(listener1.requestReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroducerRemovedCleanup() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get database and local group for introducee
|
||||
Group group1 = introductionGroupFactory.createLocalGroup();
|
||||
|
||||
// get local session state messages
|
||||
Map<MessageId, Metadata> map;
|
||||
Transaction txn = db1.startTransaction(false);
|
||||
try {
|
||||
map = db1.getMessageMetadata(txn, group1.getId());
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
// check that we have one session state
|
||||
assertEquals(1, map.size());
|
||||
|
||||
// introducee1 removes introducer
|
||||
contactManager1.removeContact(contactId0From1);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db1.startTransaction(false);
|
||||
try {
|
||||
map = db1.getMessageMetadata(txn, group1.getId());
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
// make sure local state got deleted
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntroduceesRemovedCleanup() throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// get database and local group for introducee
|
||||
Group group1 = introductionGroupFactory.createLocalGroup();
|
||||
|
||||
// get local session state messages
|
||||
Map<MessageId, Metadata> map;
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// check that we have one session state
|
||||
assertEquals(1, map.size());
|
||||
|
||||
// introducer removes introducee1
|
||||
contactManager0.removeContact(contactId1From0);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is still there
|
||||
assertEquals(1, map.size());
|
||||
|
||||
// introducer removes other introducee
|
||||
contactManager0.removeContact(contactId2From0);
|
||||
|
||||
// get local session state messages again
|
||||
txn = db0.startTransaction(false);
|
||||
try {
|
||||
map = db0.getMessageMetadata(txn, group1.getId());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
// make sure local state is gone now
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
private void testModifiedResponse(StateVisitor visitor)
|
||||
throws Exception {
|
||||
addListeners(true, true);
|
||||
|
||||
// make introduction
|
||||
long time = clock.currentTimeMillis();
|
||||
introductionManager0
|
||||
.makeIntroduction(contact1From0, contact2From0, "Hi!", time);
|
||||
|
||||
// sync request messages
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
eventWaiter.await(TIMEOUT, 2);
|
||||
|
||||
// sync first response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// get response to be forwarded
|
||||
ClientHelper ch = c0.getClientHelper(); // need 0's ClientHelper here
|
||||
Entry<MessageId, BdfDictionary> resp =
|
||||
getMessageFor(ch, contact2From0, TYPE_RESPONSE);
|
||||
MessageId responseId = resp.getKey();
|
||||
BdfDictionary response = resp.getValue();
|
||||
|
||||
// adapt outgoing message queue to removed message
|
||||
Group g2 = introductionGroupFactory
|
||||
.createIntroductionGroup(contact2From0);
|
||||
decreaseOutgoingMessageCounter(ch, g2.getId(), 1);
|
||||
|
||||
// allow visitor to modify response
|
||||
boolean earlyAbort = visitor.visit(response);
|
||||
|
||||
// replace original response with modified one
|
||||
MessageSender sender0 = c0.getMessageSender();
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
db0.deleteMessage(txn, responseId);
|
||||
sender0.sendMessage(txn, response);
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
|
||||
// sync second response
|
||||
sync2To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// sync forwarded responses to introducees
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// sync first ACK and forward it
|
||||
sync1To0(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
// introducee2 should have detected the fake now
|
||||
// and deleted introducee1 again
|
||||
Collection<Contact> contacts2;
|
||||
txn = db2.startTransaction(true);
|
||||
try {
|
||||
contacts2 = db2.getContacts(txn);
|
||||
db2.commitTransaction(txn);
|
||||
} finally {
|
||||
db2.endTransaction(txn);
|
||||
}
|
||||
assertEquals(1, contacts2.size());
|
||||
|
||||
// sync introducee2's ack and following abort
|
||||
sync2To0(2, true);
|
||||
|
||||
// ensure introducer got the abort
|
||||
assertTrue(listener0.aborted);
|
||||
|
||||
// sync abort messages to introducees
|
||||
sync0To1(2, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
if (earlyAbort) {
|
||||
assertTrue(listener1.aborted);
|
||||
assertTrue(listener2.aborted);
|
||||
} else {
|
||||
assertTrue(listener2.aborted);
|
||||
// when aborted late, introducee1 keeps the contact,
|
||||
// so introducer can not make contacts disappear by aborting
|
||||
Collection<Contact> contacts1;
|
||||
txn = db1.startTransaction(true);
|
||||
try {
|
||||
contacts1 = db1.getContacts(txn);
|
||||
db1.commitTransaction(txn);
|
||||
} finally {
|
||||
db1.endTransaction(txn);
|
||||
}
|
||||
assertEquals(2, contacts1.size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedTransportProperties() throws Exception {
|
||||
testModifiedResponse(new StateVisitor() {
|
||||
@Override
|
||||
public boolean visit(BdfDictionary response) {
|
||||
BdfDictionary tp = response.getDictionary(TRANSPORT, null);
|
||||
tp.put("fakeId",
|
||||
BdfDictionary.of(new BdfEntry("fake", "fake")));
|
||||
response.put(TRANSPORT, tp);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedTimestamp() throws Exception {
|
||||
testModifiedResponse(new StateVisitor() {
|
||||
@Override
|
||||
public boolean visit(BdfDictionary response) {
|
||||
long timestamp = response.getLong(TIME, 0L);
|
||||
response.put(TIME, timestamp + 1);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedEphemeralPublicKey() throws Exception {
|
||||
testModifiedResponse(new StateVisitor() {
|
||||
@Override
|
||||
public boolean visit(BdfDictionary response) {
|
||||
KeyPair keyPair = crypto.generateSignatureKeyPair();
|
||||
response.put(E_PUBLIC_KEY, keyPair.getPublic().getEncoded());
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testModifiedEphemeralPublicKeyWithFakeMac()
|
||||
throws Exception {
|
||||
// initialize a real introducee manager
|
||||
MessageSender messageSender = c2.getMessageSender();
|
||||
TransportPropertyManager tpManager = c2.getTransportPropertyManager();
|
||||
IntroduceeManager manager2 =
|
||||
new IntroduceeManager(messageSender, db2, clientHelper, clock,
|
||||
crypto, tpManager, authorFactory, contactManager2,
|
||||
identityManager2, introductionGroupFactory);
|
||||
|
||||
// create keys
|
||||
KeyPair keyPair1 = crypto.generateSignatureKeyPair();
|
||||
KeyPair eKeyPair1 = crypto.generateAgreementKeyPair();
|
||||
byte[] ePublicKeyBytes1 = eKeyPair1.getPublic().getEncoded();
|
||||
KeyPair eKeyPair2 = crypto.generateAgreementKeyPair();
|
||||
byte[] ePublicKeyBytes2 = eKeyPair2.getPublic().getEncoded();
|
||||
|
||||
// Nonce 1
|
||||
SecretKey secretKey =
|
||||
crypto.deriveMasterSecret(ePublicKeyBytes2, eKeyPair1, true);
|
||||
byte[] nonce1 = crypto.deriveSignatureNonce(secretKey, true);
|
||||
|
||||
// Signature 1
|
||||
byte[] sig1 = crypto.sign(SIGNING_LABEL_RESPONSE, nonce1,
|
||||
keyPair1.getPrivate().getEncoded());
|
||||
|
||||
// MAC 1
|
||||
SecretKey macKey1 = crypto.deriveMacKey(secretKey, true);
|
||||
BdfDictionary tp1 = BdfDictionary.of(new BdfEntry("fake", "fake"));
|
||||
long time1 = clock.currentTimeMillis();
|
||||
BdfList toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
|
||||
ePublicKeyBytes1, tp1, time1);
|
||||
byte[] toMac = clientHelper.toByteArray(toMacList);
|
||||
byte[] mac1 = crypto.mac(macKey1, toMac);
|
||||
|
||||
// create only relevant part of state for introducee2
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, keyPair1.getPublic().getEncoded());
|
||||
state.put(TRANSPORT, tp1);
|
||||
state.put(TIME, time1);
|
||||
state.put(E_PUBLIC_KEY, ePublicKeyBytes1);
|
||||
state.put(MAC, mac1);
|
||||
state.put(MAC_KEY, macKey1.getBytes());
|
||||
state.put(NONCE, nonce1);
|
||||
state.put(SIGNATURE, sig1);
|
||||
|
||||
// MAC and signature verification should pass
|
||||
manager2.verifyMac(state);
|
||||
manager2.verifySignature(state);
|
||||
|
||||
// replace ephemeral key pair and recalculate matching keys and nonce
|
||||
KeyPair eKeyPair1f = crypto.generateAgreementKeyPair();
|
||||
byte[] ePublicKeyBytes1f = eKeyPair1f.getPublic().getEncoded();
|
||||
secretKey =
|
||||
crypto.deriveMasterSecret(ePublicKeyBytes2, eKeyPair1f, true);
|
||||
nonce1 = crypto.deriveSignatureNonce(secretKey, true);
|
||||
|
||||
// recalculate MAC
|
||||
macKey1 = crypto.deriveMacKey(secretKey, true);
|
||||
toMacList = BdfList.of(keyPair1.getPublic().getEncoded(),
|
||||
ePublicKeyBytes1f, tp1, time1);
|
||||
toMac = clientHelper.toByteArray(toMacList);
|
||||
mac1 = crypto.mac(macKey1, toMac);
|
||||
|
||||
// update state with faked information
|
||||
state.put(E_PUBLIC_KEY, ePublicKeyBytes1f);
|
||||
state.put(MAC, mac1);
|
||||
state.put(MAC_KEY, macKey1.getBytes());
|
||||
state.put(NONCE, nonce1);
|
||||
|
||||
// MAC verification should still pass
|
||||
manager2.verifyMac(state);
|
||||
|
||||
// Signature can not be verified, because we don't have private
|
||||
// long-term key to fake it
|
||||
try {
|
||||
manager2.verifySignature(state);
|
||||
fail();
|
||||
} catch (GeneralSecurityException e) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
private void addTransportProperties()
|
||||
throws DbException, IOException, TimeoutException {
|
||||
TransportPropertyManager tpm0 = c0.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm1 = c1.getTransportPropertyManager();
|
||||
TransportPropertyManager tpm2 = c2.getTransportPropertyManager();
|
||||
TransportProperties tp = new TransportProperties(
|
||||
Collections.singletonMap("key", "value"));
|
||||
|
||||
tpm0.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
sync0To1(1, true);
|
||||
sync0To2(1, true);
|
||||
|
||||
tpm1.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
sync1To0(1, true);
|
||||
|
||||
tpm2.mergeLocalProperties(TRANSPORT_ID, tp);
|
||||
sync2To0(1, true);
|
||||
}
|
||||
|
||||
private void assertDefaultUiMessages() throws DbException {
|
||||
assertEquals(2, introductionManager0.getIntroductionMessages(
|
||||
contactId1From0).size());
|
||||
assertEquals(2, introductionManager0.getIntroductionMessages(
|
||||
contactId2From0).size());
|
||||
assertEquals(2, introductionManager1.getIntroductionMessages(
|
||||
contactId0From1).size());
|
||||
assertEquals(2, introductionManager2.getIntroductionMessages(
|
||||
contactId0From2).size());
|
||||
}
|
||||
|
||||
private void addListeners(boolean accept1, boolean accept2) {
|
||||
// listen to events
|
||||
listener0 = new IntroducerListener();
|
||||
c0.getEventBus().addListener(listener0);
|
||||
listener1 = new IntroduceeListener(1, accept1);
|
||||
c1.getEventBus().addListener(listener1);
|
||||
listener2 = new IntroduceeListener(2, accept2);
|
||||
c2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
private class IntroduceeListener implements EventListener {
|
||||
|
||||
private volatile boolean requestReceived = false;
|
||||
private volatile boolean succeeded = false;
|
||||
private volatile boolean aborted = false;
|
||||
private volatile boolean answerRequests = true;
|
||||
private volatile SessionId sessionId;
|
||||
|
||||
private final int introducee;
|
||||
private final boolean accept;
|
||||
|
||||
private IntroduceeListener(int introducee, boolean accept) {
|
||||
this.introducee = introducee;
|
||||
this.accept = accept;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof IntroductionRequestReceivedEvent) {
|
||||
IntroductionRequestReceivedEvent introEvent =
|
||||
((IntroductionRequestReceivedEvent) e);
|
||||
requestReceived = true;
|
||||
IntroductionRequest ir = introEvent.getIntroductionRequest();
|
||||
ContactId contactId = introEvent.getContactId();
|
||||
sessionId = ir.getSessionId();
|
||||
long time = clock.currentTimeMillis();
|
||||
try {
|
||||
if (introducee == 1 && answerRequests) {
|
||||
if (accept) {
|
||||
introductionManager1
|
||||
.acceptIntroduction(contactId, sessionId,
|
||||
time);
|
||||
} else {
|
||||
introductionManager1
|
||||
.declineIntroduction(contactId, sessionId,
|
||||
time);
|
||||
}
|
||||
} else if (introducee == 2 && answerRequests) {
|
||||
if (accept) {
|
||||
introductionManager2
|
||||
.acceptIntroduction(contactId, sessionId,
|
||||
time);
|
||||
} else {
|
||||
introductionManager2
|
||||
.declineIntroduction(contactId, sessionId,
|
||||
time);
|
||||
}
|
||||
}
|
||||
} catch (DbException exception) {
|
||||
eventWaiter.rethrow(exception);
|
||||
} catch (FormatException exception) {
|
||||
eventWaiter.rethrow(exception);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
}
|
||||
} else if (e instanceof IntroductionSucceededEvent) {
|
||||
succeeded = true;
|
||||
Contact contact = ((IntroductionSucceededEvent) e).getContact();
|
||||
eventWaiter
|
||||
.assertFalse(contact.getId().equals(contactId0From1));
|
||||
eventWaiter.assertTrue(contact.isActive());
|
||||
eventWaiter.resume();
|
||||
} else if (e instanceof IntroductionAbortedEvent) {
|
||||
aborted = true;
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class IntroducerListener implements EventListener {
|
||||
|
||||
private volatile boolean response1Received = false;
|
||||
private volatile boolean response2Received = false;
|
||||
private volatile boolean aborted = false;
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof IntroductionResponseReceivedEvent) {
|
||||
ContactId c =
|
||||
((IntroductionResponseReceivedEvent) e)
|
||||
.getContactId();
|
||||
if (c.equals(contactId1From0)) {
|
||||
response1Received = true;
|
||||
} else if (c.equals(contactId2From0)) {
|
||||
response2Received = true;
|
||||
}
|
||||
eventWaiter.resume();
|
||||
} else if (e instanceof IntroductionAbortedEvent) {
|
||||
aborted = true;
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void decreaseOutgoingMessageCounter(ClientHelper ch, GroupId g,
|
||||
int num) throws FormatException, DbException {
|
||||
BdfDictionary gD = ch.getGroupMetadataAsDictionary(g);
|
||||
LOG.warning(gD.toString());
|
||||
BdfDictionary queue = gD.getDictionary(QUEUE_STATE_KEY);
|
||||
queue.put("nextOut", queue.getLong("nextOut") - num);
|
||||
gD.put(QUEUE_STATE_KEY, queue);
|
||||
ch.mergeGroupMetadata(g, gD);
|
||||
}
|
||||
|
||||
private Entry<MessageId, BdfDictionary> getMessageFor(ClientHelper ch,
|
||||
Contact contact, long type) throws FormatException, DbException {
|
||||
Entry<MessageId, BdfDictionary> response = null;
|
||||
Group g = introductionGroupFactory
|
||||
.createIntroductionGroup(contact);
|
||||
Map<MessageId, BdfDictionary> map =
|
||||
ch.getMessageMetadataAsDictionary(g.getId());
|
||||
for (Entry<MessageId, BdfDictionary> entry : map.entrySet()) {
|
||||
if (entry.getValue().getLong(TYPE) == type) {
|
||||
response = entry;
|
||||
}
|
||||
}
|
||||
assertTrue(response != null);
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.properties.PropertiesModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.blog.BlogModule;
|
||||
import org.briarproject.briar.client.BriarClientModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
import org.briarproject.briar.messaging.MessagingModule;
|
||||
import org.briarproject.briar.privategroup.PrivateGroupModule;
|
||||
import org.briarproject.briar.privategroup.invitation.GroupInvitationModule;
|
||||
import org.briarproject.briar.sharing.SharingModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
TestDatabaseModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
TestSeedProviderModule.class,
|
||||
BlogModule.class,
|
||||
BriarClientModule.class,
|
||||
ClientModule.class,
|
||||
ContactModule.class,
|
||||
CryptoModule.class,
|
||||
DataModule.class,
|
||||
DatabaseModule.class,
|
||||
EventModule.class,
|
||||
ForumModule.class,
|
||||
GroupInvitationModule.class,
|
||||
IdentityModule.class,
|
||||
IntroductionModule.class,
|
||||
LifecycleModule.class,
|
||||
MessagingModule.class,
|
||||
PrivateGroupModule.class,
|
||||
PropertiesModule.class,
|
||||
SharingModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class,
|
||||
TransportModule.class
|
||||
})
|
||||
interface IntroductionIntegrationTestComponent
|
||||
extends BriarIntegrationTestComponent {
|
||||
|
||||
void inject(IntroductionIntegrationTest init);
|
||||
|
||||
MessageSender getMessageSender();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.forum.ForumConstants;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostFactory;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class MessageSizeIntegrationTest extends BriarTestCase {
|
||||
|
||||
@Inject
|
||||
CryptoComponent crypto;
|
||||
@Inject
|
||||
AuthorFactory authorFactory;
|
||||
@Inject
|
||||
PrivateMessageFactory privateMessageFactory;
|
||||
@Inject
|
||||
ForumPostFactory forumPostFactory;
|
||||
|
||||
public MessageSizeIntegrationTest() throws Exception {
|
||||
MessageSizeIntegrationTestComponent component =
|
||||
DaggerMessageSizeIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
injectEagerSingletons(component);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPrivateMessageFitsIntoPacket() throws Exception {
|
||||
// Create a maximum-length private message
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
long timestamp = Long.MAX_VALUE;
|
||||
String body =
|
||||
StringUtils.fromUtf8(new byte[MAX_PRIVATE_MESSAGE_BODY_LENGTH]);
|
||||
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
||||
groupId, timestamp, body);
|
||||
// Check the size of the serialised message
|
||||
int length = message.getMessage().getRaw().length;
|
||||
assertTrue(
|
||||
length > UniqueId.LENGTH + 8 + MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForumPostFitsIntoPacket() throws Exception {
|
||||
// Create a maximum-length author
|
||||
String authorName = TestUtils.getRandomString(
|
||||
MAX_AUTHOR_NAME_LENGTH);
|
||||
byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
|
||||
PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
|
||||
LocalAuthor author = authorFactory
|
||||
.createLocalAuthor(authorName, authorPublic,
|
||||
privateKey.getEncoded());
|
||||
// Create a maximum-length forum post
|
||||
GroupId groupId = new GroupId(getRandomId());
|
||||
long timestamp = Long.MAX_VALUE;
|
||||
MessageId parent = new MessageId(getRandomId());
|
||||
String body = TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
|
||||
ForumPost post = forumPostFactory.createPost(groupId,
|
||||
timestamp, parent, author, body);
|
||||
// Check the size of the serialised message
|
||||
int length = post.getMessage().getRaw().length;
|
||||
assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH
|
||||
+ MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH
|
||||
+ ForumConstants.MAX_CONTENT_TYPE_LENGTH
|
||||
+ MAX_FORUM_POST_BODY_LENGTH);
|
||||
assertTrue(length <= MAX_PACKET_PAYLOAD_LENGTH);
|
||||
}
|
||||
|
||||
private static void injectEagerSingletons(
|
||||
MessageSizeIntegrationTestComponent component) {
|
||||
component.inject(new SystemModule.EagerSingletons());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.briar.client.BriarClientModule;
|
||||
import org.briarproject.briar.forum.ForumModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
TestDatabaseModule.class,
|
||||
TestLifecycleModule.class,
|
||||
TestSeedProviderModule.class,
|
||||
BriarClientModule.class,
|
||||
ClientModule.class,
|
||||
CryptoModule.class,
|
||||
DataModule.class,
|
||||
DatabaseModule.class,
|
||||
EventModule.class,
|
||||
ForumModule.class,
|
||||
IdentityModule.class,
|
||||
MessagingModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class
|
||||
})
|
||||
interface MessageSizeIntegrationTestComponent {
|
||||
|
||||
void inject(MessageSizeIntegrationTest testCase);
|
||||
|
||||
void inject(SystemModule.EagerSingletons init);
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.sync.event.MessageAddedEvent;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static org.briarproject.TestPluginConfigModule.MAX_LATENCY;
|
||||
import static org.briarproject.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SimplexMessagingIntegrationTest extends BriarTestCase {
|
||||
|
||||
private final static String ALICE = "Alice";
|
||||
private final static String BOB = "Bob";
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File aliceDir = new File(testDir, "alice");
|
||||
private final File bobDir = new File(testDir, "bob");
|
||||
private final SecretKey master = TestUtils.getSecretKey();
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
private final AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
|
||||
private final AuthorId bobId = new AuthorId(TestUtils.getRandomId());
|
||||
|
||||
private SimplexMessagingIntegrationTestComponent alice, bob;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
assertTrue(testDir.mkdirs());
|
||||
alice = DaggerSimplexMessagingIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(aliceDir)).build();
|
||||
injectEagerSingletons(alice);
|
||||
alice.inject(new SystemModule.EagerSingletons());
|
||||
bob = DaggerSimplexMessagingIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(bobDir)).build();
|
||||
injectEagerSingletons(bob);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteAndRead() throws Exception {
|
||||
read(write());
|
||||
}
|
||||
|
||||
|
||||
private byte[] write() throws Exception {
|
||||
// Instantiate Alice's services
|
||||
LifecycleManager lifecycleManager = alice.getLifecycleManager();
|
||||
IdentityManager identityManager = alice.getIdentityManager();
|
||||
ContactManager contactManager = alice.getContactManager();
|
||||
MessagingManager messagingManager = alice.getMessagingManager();
|
||||
KeyManager keyManager = alice.getKeyManager();
|
||||
PrivateMessageFactory privateMessageFactory =
|
||||
alice.getPrivateMessageFactory();
|
||||
StreamWriterFactory streamWriterFactory =
|
||||
alice.getStreamWriterFactory();
|
||||
SyncSessionFactory syncSessionFactory = alice.getSyncSessionFactory();
|
||||
|
||||
// Start the lifecycle manager
|
||||
lifecycleManager.startServices(null);
|
||||
lifecycleManager.waitForStartup();
|
||||
// Add an identity for Alice
|
||||
LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
||||
identityManager.registerLocalAuthor(aliceAuthor);
|
||||
// Add Bob as a contact
|
||||
Author bobAuthor = new Author(bobId, BOB,
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = contactManager.addContact(bobAuthor,
|
||||
aliceAuthor.getId(), master, timestamp, true, true, true);
|
||||
|
||||
// Send Bob a message
|
||||
GroupId groupId = messagingManager.getConversationId(contactId);
|
||||
String body = "Hi Bob!";
|
||||
PrivateMessage message = privateMessageFactory.createPrivateMessage(
|
||||
groupId, timestamp, body);
|
||||
messagingManager.addLocalMessage(message);
|
||||
// Get a stream context
|
||||
StreamContext ctx = keyManager.getStreamContext(contactId,
|
||||
TRANSPORT_ID);
|
||||
assertNotNull(ctx);
|
||||
// Create a stream writer
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
OutputStream streamWriter = streamWriterFactory.createStreamWriter(
|
||||
out, ctx);
|
||||
// Create an outgoing sync session
|
||||
SyncSession session = syncSessionFactory.createSimplexOutgoingSession(
|
||||
contactId, MAX_LATENCY, streamWriter);
|
||||
// Write whatever needs to be written
|
||||
session.run();
|
||||
streamWriter.close();
|
||||
|
||||
// Clean up
|
||||
lifecycleManager.stopServices();
|
||||
lifecycleManager.waitForShutdown();
|
||||
|
||||
// Return the contents of the stream
|
||||
return out.toByteArray();
|
||||
}
|
||||
|
||||
private void read(byte[] stream) throws Exception {
|
||||
// Instantiate Bob's services
|
||||
LifecycleManager lifecycleManager = bob.getLifecycleManager();
|
||||
IdentityManager identityManager = bob.getIdentityManager();
|
||||
ContactManager contactManager = bob.getContactManager();
|
||||
KeyManager keyManager = bob.getKeyManager();
|
||||
StreamReaderFactory streamReaderFactory = bob.getStreamReaderFactory();
|
||||
SyncSessionFactory syncSessionFactory = bob.getSyncSessionFactory();
|
||||
|
||||
// Start the lifecyle manager
|
||||
lifecycleManager.startServices(null);
|
||||
lifecycleManager.waitForStartup();
|
||||
// Add an identity for Bob
|
||||
LocalAuthor bobAuthor = new LocalAuthor(bobId, BOB,
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
|
||||
identityManager.registerLocalAuthor(bobAuthor);
|
||||
// Add Alice as a contact
|
||||
Author aliceAuthor = new Author(aliceId, ALICE,
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
ContactId contactId = contactManager.addContact(aliceAuthor,
|
||||
bobAuthor.getId(), master, timestamp, false, true, true);
|
||||
// Set up an event listener
|
||||
MessageListener listener = new MessageListener();
|
||||
bob.getEventBus().addListener(listener);
|
||||
// Read and recognise the tag
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(stream);
|
||||
byte[] tag = new byte[TAG_LENGTH];
|
||||
int read = in.read(tag);
|
||||
assertEquals(tag.length, read);
|
||||
StreamContext ctx = keyManager.getStreamContext(TRANSPORT_ID, tag);
|
||||
assertNotNull(ctx);
|
||||
// Create a stream reader
|
||||
InputStream streamReader = streamReaderFactory.createStreamReader(
|
||||
in, ctx);
|
||||
// Create an incoming sync session
|
||||
SyncSession session = syncSessionFactory.createIncomingSession(
|
||||
contactId, streamReader);
|
||||
// No messages should have been added yet
|
||||
assertFalse(listener.messageAdded);
|
||||
// Read whatever needs to be read
|
||||
session.run();
|
||||
streamReader.close();
|
||||
// The private message from Alice should have been added
|
||||
assertTrue(listener.messageAdded);
|
||||
|
||||
// Clean up
|
||||
lifecycleManager.stopServices();
|
||||
lifecycleManager.waitForShutdown();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
private static void injectEagerSingletons(
|
||||
SimplexMessagingIntegrationTestComponent component) {
|
||||
component.inject(new MessagingModule.EagerSingletons());
|
||||
component.inject(new SystemModule.EagerSingletons());
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private static class MessageListener implements EventListener {
|
||||
|
||||
private volatile boolean messageAdded = false;
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof MessageAddedEvent) messageAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.transport.KeyManager;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
import org.briarproject.bramble.api.transport.StreamWriterFactory;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
import org.briarproject.bramble.db.DatabaseModule;
|
||||
import org.briarproject.bramble.event.EventModule;
|
||||
import org.briarproject.bramble.identity.IdentityModule;
|
||||
import org.briarproject.bramble.lifecycle.LifecycleModule;
|
||||
import org.briarproject.bramble.sync.SyncModule;
|
||||
import org.briarproject.bramble.system.SystemModule;
|
||||
import org.briarproject.bramble.transport.TransportModule;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
import org.briarproject.briar.client.BriarClientModule;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Component;
|
||||
|
||||
@Singleton
|
||||
@Component(modules = {
|
||||
TestDatabaseModule.class,
|
||||
TestPluginConfigModule.class,
|
||||
TestSeedProviderModule.class,
|
||||
BriarClientModule.class,
|
||||
ClientModule.class,
|
||||
ContactModule.class,
|
||||
CryptoModule.class,
|
||||
DataModule.class,
|
||||
DatabaseModule.class,
|
||||
EventModule.class,
|
||||
IdentityModule.class,
|
||||
LifecycleModule.class,
|
||||
MessagingModule.class,
|
||||
SyncModule.class,
|
||||
SystemModule.class,
|
||||
TransportModule.class
|
||||
})
|
||||
interface SimplexMessagingIntegrationTestComponent {
|
||||
|
||||
void inject(MessagingModule.EagerSingletons init);
|
||||
|
||||
void inject(SystemModule.EagerSingletons init);
|
||||
|
||||
LifecycleManager getLifecycleManager();
|
||||
|
||||
IdentityManager getIdentityManager();
|
||||
|
||||
ContactManager getContactManager();
|
||||
|
||||
MessagingManager getMessagingManager();
|
||||
|
||||
KeyManager getKeyManager();
|
||||
|
||||
PrivateMessageFactory getPrivateMessageFactory();
|
||||
|
||||
EventBus getEventBus();
|
||||
|
||||
StreamWriterFactory getStreamWriterFactory();
|
||||
|
||||
StreamReaderFactory getStreamReaderFactory();
|
||||
|
||||
SyncSessionFactory getSyncSessionFactory();
|
||||
}
|
||||
@@ -0,0 +1,440 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationRequest;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationResponse;
|
||||
import org.briarproject.briar.api.sharing.InvitationMessage;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class GroupInvitationIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private PrivateGroup privateGroup0;
|
||||
private PrivateGroupManager groupManager0, groupManager1;
|
||||
private GroupInvitationManager groupInvitationManager0,
|
||||
groupInvitationManager1;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@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());
|
||||
boolean foundResponse = false;
|
||||
for (InvitationMessage m : messages) {
|
||||
if (m instanceof GroupInvitationResponse) {
|
||||
foundResponse = true;
|
||||
GroupInvitationResponse response = (GroupInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertTrue(response.isLocal());
|
||||
assertFalse(response.wasAccepted());
|
||||
}
|
||||
}
|
||||
assertTrue(foundResponse);
|
||||
|
||||
sync1To0(1, true);
|
||||
|
||||
messages =
|
||||
groupInvitationManager0.getInvitationMessages(contactId1From0);
|
||||
assertEquals(2, messages.size());
|
||||
foundResponse = false;
|
||||
for (InvitationMessage m : messages) {
|
||||
if (m instanceof GroupInvitationResponse) {
|
||||
foundResponse = true;
|
||||
GroupInvitationResponse response = (GroupInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertFalse(response.isLocal());
|
||||
assertFalse(response.wasAccepted());
|
||||
}
|
||||
}
|
||||
assertTrue(foundResponse);
|
||||
|
||||
// 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());
|
||||
boolean foundResponse = false;
|
||||
for (InvitationMessage m : messages) {
|
||||
if (m instanceof GroupInvitationResponse) {
|
||||
foundResponse = true;
|
||||
GroupInvitationResponse response = (GroupInvitationResponse) m;
|
||||
assertTrue(response.wasAccepted());
|
||||
}
|
||||
}
|
||||
assertTrue(foundResponse);
|
||||
|
||||
sync1To0(1, true);
|
||||
|
||||
messages =
|
||||
groupInvitationManager0.getInvitationMessages(contactId1From0);
|
||||
assertEquals(2, messages.size());
|
||||
foundResponse = false;
|
||||
for (InvitationMessage m : messages) {
|
||||
if (m instanceof GroupInvitationResponse) {
|
||||
foundResponse = true;
|
||||
GroupInvitationResponse response = (GroupInvitationResponse) m;
|
||||
assertTrue(response.wasAccepted());
|
||||
}
|
||||
}
|
||||
assertTrue(foundResponse);
|
||||
|
||||
// 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(expected = ProtocolStateException.class)
|
||||
public void testCreatorLeavesBeforeInvitationAccepted() throws Exception {
|
||||
// Creator invites invitee to join group
|
||||
sendInvitation(clock.currentTimeMillis(), null);
|
||||
|
||||
// Creator's invite message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Creator leaves group
|
||||
assertEquals(1, groupManager0.getPrivateGroups().size());
|
||||
groupManager0.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager0.getPrivateGroups().size());
|
||||
|
||||
// Creator's leave message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Invitee accepts invitation, but it's no longer open - exception is
|
||||
// thrown as the action has failed
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
groupInvitationManager1
|
||||
.respondToInvitation(contactId0From1, privateGroup0, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatorLeavesBeforeInvitationDeclined() throws Exception {
|
||||
// Creator invites invitee to join group
|
||||
sendInvitation(clock.currentTimeMillis(), null);
|
||||
|
||||
// Creator's invite message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Creator leaves group
|
||||
assertEquals(1, groupManager0.getPrivateGroups().size());
|
||||
groupManager0.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager0.getPrivateGroups().size());
|
||||
|
||||
// Creator's leave message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Invitee declines invitation, but it's no longer open - no exception
|
||||
// as the action has succeeded
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
groupInvitationManager1
|
||||
.respondToInvitation(contactId0From1, privateGroup0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatorLeavesConcurrentlyWithInvitationAccepted()
|
||||
throws Exception {
|
||||
// Creator invites invitee to join group
|
||||
sendInvitation(clock.currentTimeMillis(), null);
|
||||
|
||||
// Creator's invite message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Creator leaves group
|
||||
assertEquals(1, groupManager0.getPrivateGroups().size());
|
||||
groupManager0.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager0.getPrivateGroups().size());
|
||||
|
||||
// Invitee accepts invitation
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
groupInvitationManager1
|
||||
.respondToInvitation(contactId0From1, privateGroup0, true);
|
||||
assertEquals(1, groupManager1.getPrivateGroups().size());
|
||||
assertFalse(groupManager1.isDissolved(privateGroup0.getId()));
|
||||
|
||||
// Invitee's join message is delivered to creator
|
||||
sync1To0(1, true);
|
||||
|
||||
// Creator's leave message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Group is marked as dissolved
|
||||
assertTrue(groupManager1.isDissolved(privateGroup0.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatorLeavesConcurrentlyWithInvitationDeclined()
|
||||
throws Exception {
|
||||
// Creator invites invitee to join group
|
||||
sendInvitation(clock.currentTimeMillis(), null);
|
||||
|
||||
// Creator's invite message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Creator leaves group
|
||||
assertEquals(1, groupManager0.getPrivateGroups().size());
|
||||
groupManager0.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager0.getPrivateGroups().size());
|
||||
|
||||
// Invitee declines invitation
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
groupInvitationManager1
|
||||
.respondToInvitation(contactId0From1, privateGroup0, false);
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
|
||||
// Invitee's leave message is delivered to creator
|
||||
sync1To0(1, true);
|
||||
|
||||
// Creator's leave message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreatorLeavesConcurrentlyWithMemberLeaving()
|
||||
throws Exception {
|
||||
// Creator invites invitee to join group
|
||||
sendInvitation(clock.currentTimeMillis(), null);
|
||||
|
||||
// Creator's invite message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Invitee responds to invitation
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
groupInvitationManager1
|
||||
.respondToInvitation(contactId0From1, privateGroup0, true);
|
||||
assertEquals(1, groupManager1.getPrivateGroups().size());
|
||||
|
||||
// Invitee's join message is delivered to creator
|
||||
sync1To0(1, true);
|
||||
|
||||
// Creator leaves group
|
||||
assertEquals(1, groupManager0.getPrivateGroups().size());
|
||||
groupManager0.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager0.getPrivateGroups().size());
|
||||
|
||||
// Invitee leaves group
|
||||
groupManager1.removePrivateGroup(privateGroup0.getId());
|
||||
assertEquals(0, groupManager1.getPrivateGroups().size());
|
||||
|
||||
// Creator's leave message is delivered to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// Invitee's leave message is delivered to creator
|
||||
sync1To0(1, true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_CONTACT;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_US;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* This class tests how PrivateGroupManager and GroupInvitationManager
|
||||
* play together.
|
||||
*/
|
||||
public class PrivateGroupIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private GroupId groupId0;
|
||||
private PrivateGroup privateGroup0;
|
||||
private PrivateGroupManager groupManager0, groupManager1, groupManager2;
|
||||
private GroupInvitationManager groupInvitationManager0,
|
||||
groupInvitationManager1, groupInvitationManager2;
|
||||
|
||||
@Before
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@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());
|
||||
for (GroupMember m : members) {
|
||||
if (m.getStatus() == OURSELVES) {
|
||||
assertEquals(author0.getId(), m.getAuthor().getId());
|
||||
} else {
|
||||
assertEquals(author1.getId(), m.getAuthor().getId());
|
||||
}
|
||||
}
|
||||
|
||||
members = groupManager1.getMembers(groupId0);
|
||||
assertEquals(2, members.size());
|
||||
for (GroupMember m : members) {
|
||||
if (m.getStatus() == OURSELVES) {
|
||||
assertEquals(author1.getId(), m.getAuthor().getId());
|
||||
} else {
|
||||
assertEquals(author0.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());
|
||||
boolean foundPost = false;
|
||||
for (GroupMessageHeader h : headers) {
|
||||
if (h instanceof JoinMessageHeader) continue;
|
||||
foundPost = true;
|
||||
assertEquals(time, h.getTimestamp());
|
||||
assertEquals(groupId0, h.getGroupId());
|
||||
assertEquals(author2.getId(), h.getAuthor().getId());
|
||||
}
|
||||
assertTrue(foundPost);
|
||||
|
||||
// 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;
|
||||
}
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,570 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
import org.briarproject.briar.api.privategroup.GroupMember;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.JoinMessageHeader;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import static org.briarproject.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.INVISIBLE;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_CONTACT;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.REVEALED_BY_US;
|
||||
import static org.briarproject.briar.api.privategroup.Visibility.VISIBLE;
|
||||
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class PrivateGroupManagerIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private PrivateGroup privateGroup0;
|
||||
private GroupId groupId0;
|
||||
private PrivateGroupManager groupManager0, groupManager1, groupManager2;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
groupManager0 = c0.getPrivateGroupManager();
|
||||
groupManager1 = c1.getPrivateGroupManager();
|
||||
groupManager2 = c2.getPrivateGroupManager();
|
||||
|
||||
privateGroup0 =
|
||||
privateGroupFactory.createPrivateGroup("Testgroup", author0);
|
||||
groupId0 = privateGroup0.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendingMessage() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// create and add test message
|
||||
long time = clock.currentTimeMillis();
|
||||
String body = "This is a test message!";
|
||||
MessageId previousMsgId =
|
||||
groupManager0.getPreviousMsgId(groupId0);
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, time, null, author0, body,
|
||||
previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
assertEquals(msg.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, true);
|
||||
|
||||
// assert that message arrived as expected
|
||||
Collection<GroupMessageHeader> headers =
|
||||
groupManager1.getHeaders(groupId0);
|
||||
assertEquals(3, headers.size());
|
||||
GroupMessageHeader header = null;
|
||||
for (GroupMessageHeader h : headers) {
|
||||
if (!(h instanceof JoinMessageHeader)) {
|
||||
header = h;
|
||||
}
|
||||
}
|
||||
assertTrue(header != null);
|
||||
assertFalse(header.isRead());
|
||||
assertEquals(author0, header.getAuthor());
|
||||
assertEquals(time, header.getTimestamp());
|
||||
assertEquals(VERIFIED, header.getAuthorStatus());
|
||||
assertEquals(body, groupManager1.getMessageBody(header.getId()));
|
||||
GroupCount count = groupManager1.getGroupCount(groupId0);
|
||||
assertEquals(2, count.getUnreadCount());
|
||||
assertEquals(time, count.getLatestMsgTime());
|
||||
assertEquals(3, count.getMsgCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageWithWrongPreviousMsgId() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// create and add test message with no previousMsgId
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(), null,
|
||||
author0, "test", null);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
// create and add test message with random previousMsgId
|
||||
MessageId previousMsgId = new MessageId(getRandomId());
|
||||
msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(), null,
|
||||
author0, "test", previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
// create and add test message with wrong previousMsgId
|
||||
previousMsgId = groupManager1.getPreviousMsgId(groupId0);
|
||||
msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(), null,
|
||||
author0, "test", previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageWithWrongParentMsgId() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// create and add test message with random parentMsgId
|
||||
MessageId parentMsgId = new MessageId(getRandomId());
|
||||
MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0);
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(),
|
||||
parentMsgId, author0, "test", previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
// create and add test message with wrong parentMsgId
|
||||
parentMsgId = previousMsgId;
|
||||
msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, clock.currentTimeMillis(),
|
||||
parentMsgId, author0, "test", previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageWithWrongTimestamp() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// create and add test message with wrong timestamp
|
||||
MessageId previousMsgId = groupManager0.getPreviousMsgId(groupId0);
|
||||
GroupMessage msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, 42, null, author0, "test",
|
||||
previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(2, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
// create and add test message with good timestamp
|
||||
long time = clock.currentTimeMillis();
|
||||
msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, time, null, author0, "test",
|
||||
previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, true);
|
||||
assertEquals(3, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
// create and add test message with same timestamp as previous message
|
||||
previousMsgId = msg.getMessage().getId();
|
||||
msg = groupMessageFactory
|
||||
.createGroupMessage(groupId0, time, previousMsgId, author0,
|
||||
"test2", previousMsgId);
|
||||
groupManager0.addLocalMessage(msg);
|
||||
|
||||
// sync test message
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that message did not arrive
|
||||
assertEquals(3, groupManager1.getHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongJoinMessages1() throws Exception {
|
||||
// author0 joins privateGroup0 with wrong join message
|
||||
long joinTime = clock.currentTimeMillis();
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||
joinTime, getRandomBytes(12));
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// share the group with 1
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0 with wrong timestamp
|
||||
joinTime = clock.currentTimeMillis();
|
||||
long inviteTime = joinTime;
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
byte[] creatorSignature = groupInvitationFactory
|
||||
.signInvitation(c1, privateGroup0.getId(), inviteTime,
|
||||
author0.getPrivateKey());
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// share the group with 0
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
|
||||
// sync join messages
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that 0 never joined the group from 1's perspective
|
||||
assertEquals(1, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
sync1To0(1, false);
|
||||
|
||||
// assert that 1 never joined the group from 0's perspective
|
||||
assertEquals(1, groupManager0.getHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongJoinMessages2() throws Exception {
|
||||
// author0 joins privateGroup0 with wrong member's join message
|
||||
long joinTime = clock.currentTimeMillis();
|
||||
long inviteTime = joinTime - 1;
|
||||
BdfList toSign = groupInvitationFactory
|
||||
.createInviteToken(author0.getId(), author0.getId(),
|
||||
privateGroup0.getId(), inviteTime);
|
||||
byte[] creatorSignature = clientHelper
|
||||
.sign(SIGNING_LABEL_INVITE, toSign, author0.getPrivateKey());
|
||||
// join message should not include invite time and creator's signature
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// share the group with 1
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0 with wrong signature in join message
|
||||
joinTime = clock.currentTimeMillis();
|
||||
inviteTime = joinTime - 1;
|
||||
// signature uses joiner's key, not creator's key
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
creatorSignature = groupInvitationFactory
|
||||
.signInvitation(c1, privateGroup0.getId(), inviteTime,
|
||||
author1.getPrivateKey());
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// share the group with 0
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
|
||||
// sync join messages
|
||||
sync0To1(1, false);
|
||||
|
||||
// assert that 0 never joined the group from 1's perspective
|
||||
assertEquals(1, groupManager1.getHeaders(groupId0).size());
|
||||
|
||||
sync1To0(1, false);
|
||||
|
||||
// assert that 1 never joined the group from 0's perspective
|
||||
assertEquals(1, groupManager0.getHeaders(groupId0).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMembers() throws Exception {
|
||||
addGroup();
|
||||
|
||||
Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
|
||||
assertEquals(2, members0.size());
|
||||
for (GroupMember m : members0) {
|
||||
if (m.getAuthor().equals(author0)) {
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
} else {
|
||||
assertEquals(author1, m.getAuthor());
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
|
||||
assertEquals(2, members1.size());
|
||||
for (GroupMember m : members1) {
|
||||
if (m.getAuthor().equals(author1)) {
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
} else {
|
||||
assertEquals(author0, m.getAuthor());
|
||||
assertEquals(VISIBLE, m.getVisibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJoinMessages() throws Exception {
|
||||
addGroup();
|
||||
|
||||
Collection<GroupMessageHeader> headers0 =
|
||||
groupManager0.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers0) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
// all relationships of the creator are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
Collection<GroupMessageHeader> headers1 =
|
||||
groupManager1.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers1) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author1))
|
||||
// we are visible to ourselves
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
else
|
||||
// our relationship to the creator is visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevealingRelationships() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// share the group with 2
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setGroupVisibility(txn0, contactId2From0, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author2 joins privateGroup0
|
||||
long joinTime = clock.currentTimeMillis();
|
||||
long inviteTime = joinTime - 1;
|
||||
Contact c2 = contactManager0.getContact(contactId2From0);
|
||||
byte[] creatorSignature = groupInvitationFactory
|
||||
.signInvitation(c2, privateGroup0.getId(), inviteTime,
|
||||
author0.getPrivateKey());
|
||||
GroupMessage joinMsg2 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author2,
|
||||
inviteTime, creatorSignature);
|
||||
Transaction txn2 = db2.startTransaction(false);
|
||||
groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2, false);
|
||||
|
||||
// share the group with 0
|
||||
db2.setGroupVisibility(txn2, contactId0From1, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db2.commitTransaction(txn2);
|
||||
db2.endTransaction(txn2);
|
||||
|
||||
// sync join messages
|
||||
sync2To0(1, true);
|
||||
sync0To2(2, true);
|
||||
sync0To1(1, true);
|
||||
|
||||
// check that everybody sees everybody else as joined
|
||||
Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
|
||||
assertEquals(3, members0.size());
|
||||
Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
|
||||
assertEquals(3, members1.size());
|
||||
Collection<GroupMember> members2 = groupManager2.getMembers(groupId0);
|
||||
assertEquals(3, members2.size());
|
||||
|
||||
// assert that contact relationship is not revealed initially
|
||||
for (GroupMember m : members1) {
|
||||
if (m.getAuthor().equals(author2)) {
|
||||
assertEquals(INVISIBLE, m.getVisibility());
|
||||
}
|
||||
}
|
||||
for (GroupMember m : members2) {
|
||||
if (m.getAuthor().equals(author1)) {
|
||||
assertEquals(INVISIBLE, m.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
// reveal contact relationship
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
groupManager1
|
||||
.relationshipRevealed(txn1, groupId0, author2.getId(), false);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
txn2 = db2.startTransaction(false);
|
||||
groupManager2
|
||||
.relationshipRevealed(txn2, groupId0, author1.getId(), true);
|
||||
db2.commitTransaction(txn2);
|
||||
db2.endTransaction(txn2);
|
||||
|
||||
// assert that contact relationship is now revealed properly
|
||||
members1 = groupManager1.getMembers(groupId0);
|
||||
for (GroupMember m : members1) {
|
||||
if (m.getAuthor().equals(author2)) {
|
||||
assertEquals(REVEALED_BY_US, m.getVisibility());
|
||||
}
|
||||
}
|
||||
members2 = groupManager2.getMembers(groupId0);
|
||||
for (GroupMember m : members2) {
|
||||
if (m.getAuthor().equals(author1)) {
|
||||
assertEquals(REVEALED_BY_CONTACT, m.getVisibility());
|
||||
}
|
||||
}
|
||||
|
||||
// assert that join messages reflect revealed relationship
|
||||
Collection<GroupMessageHeader> headers1 =
|
||||
groupManager1.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers1) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author2))
|
||||
// 1 revealed the relationship to 2
|
||||
assertEquals(REVEALED_BY_US, j.getVisibility());
|
||||
else
|
||||
// 1's other relationship (to 1 and creator) are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
Collection<GroupMessageHeader> headers2 =
|
||||
groupManager2.getHeaders(groupId0);
|
||||
for (GroupMessageHeader h : headers2) {
|
||||
if (h instanceof JoinMessageHeader) {
|
||||
JoinMessageHeader j = (JoinMessageHeader) h;
|
||||
if (h.getAuthor().equals(author1))
|
||||
// 2's relationship was revealed by 1
|
||||
assertEquals(REVEALED_BY_CONTACT, j.getVisibility());
|
||||
else
|
||||
// 2's other relationship (to 2 and creator) are visible
|
||||
assertEquals(VISIBLE, j.getVisibility());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDissolveGroup() throws Exception {
|
||||
addGroup();
|
||||
|
||||
// group is not dissolved initially
|
||||
assertFalse(groupManager1.isDissolved(groupId0));
|
||||
|
||||
// creator dissolves group
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
groupManager1.markGroupDissolved(txn1, groupId0);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
|
||||
// group is dissolved now
|
||||
assertTrue(groupManager1.isDissolved(groupId0));
|
||||
}
|
||||
|
||||
private void addGroup() throws Exception {
|
||||
// author0 joins privateGroup0
|
||||
long joinTime = clock.currentTimeMillis();
|
||||
GroupMessage joinMsg0 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author0);
|
||||
groupManager0.addPrivateGroup(privateGroup0, joinMsg0, true);
|
||||
assertEquals(joinMsg0.getMessage().getId(),
|
||||
groupManager0.getPreviousMsgId(groupId0));
|
||||
|
||||
// share the group with 1
|
||||
Transaction txn0 = db0.startTransaction(false);
|
||||
db0.setGroupVisibility(txn0, contactId1From0, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db0.commitTransaction(txn0);
|
||||
db0.endTransaction(txn0);
|
||||
|
||||
// author1 joins privateGroup0
|
||||
joinTime = clock.currentTimeMillis();
|
||||
long inviteTime = joinTime - 1;
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
byte[] creatorSignature = groupInvitationFactory
|
||||
.signInvitation(c1, privateGroup0.getId(), inviteTime,
|
||||
author0.getPrivateKey());
|
||||
GroupMessage joinMsg1 = groupMessageFactory
|
||||
.createJoinMessage(privateGroup0.getId(), joinTime, author1,
|
||||
inviteTime, creatorSignature);
|
||||
groupManager1.addPrivateGroup(privateGroup0, joinMsg1, false);
|
||||
|
||||
// share the group with 0
|
||||
Transaction txn1 = db1.startTransaction(false);
|
||||
db1.setGroupVisibility(txn1, contactId0From1, privateGroup0.getId(),
|
||||
SHARED);
|
||||
db1.commitTransaction(txn1);
|
||||
db1.endTransaction(txn1);
|
||||
assertEquals(joinMsg1.getMessage().getId(),
|
||||
groupManager1.getPreviousMsgId(groupId0));
|
||||
|
||||
// sync join messages
|
||||
sync0To1(1, true);
|
||||
sync1To0(1, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,519 @@
|
||||
package org.briarproject.briar.sharing;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogInvitationRequest;
|
||||
import org.briarproject.briar.api.blog.BlogInvitationResponse;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.blog.event.BlogInvitationRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.blog.event.BlogInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.InvitationMessage;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class BlogSharingIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private BlogManager blogManager1;
|
||||
private Blog blog0, blog1, blog2;
|
||||
private SharerListener listener0;
|
||||
private InviteeListener listener1;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile BlogSharingManager blogSharingManager0;
|
||||
private volatile BlogSharingManager blogSharingManager1;
|
||||
private volatile BlogSharingManager blogSharingManager2;
|
||||
private volatile Waiter eventWaiter;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
BlogManager blogManager0 = c0.getBlogManager();
|
||||
blogManager1 = c1.getBlogManager();
|
||||
blogSharingManager0 = c0.getBlogSharingManager();
|
||||
blogSharingManager1 = c1.getBlogSharingManager();
|
||||
blogSharingManager2 = c2.getBlogSharingManager();
|
||||
|
||||
blog0 = blogManager0.getPersonalBlog(author0);
|
||||
blog1 = blogManager0.getPersonalBlog(author1);
|
||||
blog2 = blogManager0.getPersonalBlog(author2);
|
||||
|
||||
// initialize waiters fresh for each test
|
||||
eventWaiter = new Waiter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPersonalBlogCannotBeSharedWithOwner() throws Exception {
|
||||
listenToEvents(true);
|
||||
|
||||
assertFalse(blogSharingManager0.canBeShared(blog1.getId(),
|
||||
contact1From0));
|
||||
assertFalse(blogSharingManager0.canBeShared(blog2.getId(),
|
||||
contact2From0));
|
||||
assertFalse(blogSharingManager1.canBeShared(blog0.getId(),
|
||||
contact0From1));
|
||||
assertFalse(blogSharingManager2.canBeShared(blog0.getId(),
|
||||
contact0From2));
|
||||
|
||||
// create invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog1.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync invitation
|
||||
sync0To1(1, false);
|
||||
// make sure the invitee ignored the request for their own blog
|
||||
assertFalse(listener1.requestReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulSharing() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// invitee has own blog and that of the sharer
|
||||
assertEquals(2, blogManager1.getBlogs().size());
|
||||
|
||||
// get sharing group and assert group message count
|
||||
GroupId g = contactGroupFactory.createContactGroup(CLIENT_ID,
|
||||
contact1From0)
|
||||
.getId();
|
||||
assertGroupCount(messageTracker0, g, 1, 0);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
assertGroupCount(messageTracker1, g, 2, 1);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
assertGroupCount(messageTracker0, g, 2, 1);
|
||||
|
||||
// blog was added successfully
|
||||
assertEquals(0, blogSharingManager0.getInvitations().size());
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
|
||||
// invitee has one invitation message from sharer
|
||||
List<InvitationMessage> list =
|
||||
new ArrayList<InvitationMessage>(blogSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
// check other things are alright with the message
|
||||
for (InvitationMessage m : list) {
|
||||
if (m instanceof BlogInvitationRequest) {
|
||||
BlogInvitationRequest invitation =
|
||||
(BlogInvitationRequest) m;
|
||||
assertFalse(invitation.isAvailable());
|
||||
assertEquals(blog2.getAuthor().getName(),
|
||||
invitation.getBlogAuthorName());
|
||||
assertEquals(contactId1From0, invitation.getContactId());
|
||||
assertEquals("Hi!", invitation.getMessage());
|
||||
} else {
|
||||
BlogInvitationResponse response =
|
||||
(BlogInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertTrue(response.wasAccepted());
|
||||
assertTrue(response.isLocal());
|
||||
}
|
||||
}
|
||||
// sharer has own invitation message and response
|
||||
assertEquals(2,
|
||||
blogSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
// blog can not be shared again
|
||||
assertFalse(blogSharingManager0.canBeShared(blog2.getId(),
|
||||
contact1From0));
|
||||
assertFalse(blogSharingManager1.canBeShared(blog2.getId(),
|
||||
contact0From1));
|
||||
|
||||
// group message count is still correct
|
||||
assertGroupCount(messageTracker0, g, 2, 1);
|
||||
assertGroupCount(messageTracker1, g, 2, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclinedSharing() throws Exception {
|
||||
// initialize and let invitee deny all requests
|
||||
listenToEvents(false);
|
||||
|
||||
// send invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, null);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// blog was not added
|
||||
assertEquals(0, blogSharingManager0.getInvitations().size());
|
||||
assertEquals(2, blogManager1.getBlogs().size());
|
||||
// blog is no longer available to invitee who declined
|
||||
assertEquals(0, blogSharingManager1.getInvitations().size());
|
||||
|
||||
// invitee has one invitation message from sharer and one response
|
||||
List<InvitationMessage> list =
|
||||
new ArrayList<InvitationMessage>(blogSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
// check things are alright with the message
|
||||
for (InvitationMessage m : list) {
|
||||
if (m instanceof BlogInvitationRequest) {
|
||||
BlogInvitationRequest invitation =
|
||||
(BlogInvitationRequest) m;
|
||||
assertFalse(invitation.isAvailable());
|
||||
assertEquals(blog2.getAuthor().getName(),
|
||||
invitation.getBlogAuthorName());
|
||||
assertEquals(contactId1From0, invitation.getContactId());
|
||||
assertEquals(null, invitation.getMessage());
|
||||
} else {
|
||||
BlogInvitationResponse response =
|
||||
(BlogInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertFalse(response.wasAccepted());
|
||||
assertTrue(response.isLocal());
|
||||
}
|
||||
}
|
||||
// sharer has own invitation message and response
|
||||
assertEquals(2,
|
||||
blogSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
// blog can be shared again
|
||||
assertTrue(
|
||||
blogSharingManager0.canBeShared(blog2.getId(), contact1From0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInviteeLeavesAfterFinished() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// blog was added successfully
|
||||
assertEquals(0, blogSharingManager0.getInvitations().size());
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
assertTrue(blogManager1.getBlogs().contains(blog2));
|
||||
|
||||
// sharer shares blog with invitee
|
||||
assertTrue(blogSharingManager0.getSharedWith(blog2.getId())
|
||||
.contains(contact1From0));
|
||||
// invitee gets blog shared by sharer
|
||||
assertTrue(blogSharingManager1.getSharedWith(blog2.getId())
|
||||
.contains(contact0From1));
|
||||
|
||||
// invitee un-subscribes from blog
|
||||
blogManager1.removeBlog(blog2);
|
||||
|
||||
// send leave message to sharer
|
||||
sync1To0(1, true);
|
||||
|
||||
// blog is gone
|
||||
assertEquals(0, blogSharingManager0.getInvitations().size());
|
||||
assertEquals(2, blogManager1.getBlogs().size());
|
||||
|
||||
// sharer no longer shares blog with invitee
|
||||
assertFalse(blogSharingManager0.getSharedWith(blog2.getId())
|
||||
.contains(contact1From0));
|
||||
// invitee no longer has blog shared by sharer
|
||||
try {
|
||||
blogSharingManager1.getSharedWith(blog2.getId());
|
||||
fail();
|
||||
} catch (NoSuchGroupException e) {
|
||||
// expected
|
||||
}
|
||||
// blog can be shared again
|
||||
assertTrue(
|
||||
blogSharingManager0.canBeShared(blog2.getId(), contact1From0));
|
||||
assertTrue(
|
||||
blogSharingManager1.canBeShared(blog2.getId(), contact0From1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvitationForExistingBlog() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// 1 and 2 are adding each other
|
||||
addContacts1And2();
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
|
||||
// sharer sends invitation for 2's blog to 1
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// make sure blog2 is shared by 0 and 2
|
||||
Collection<Contact> contacts =
|
||||
blogSharingManager1.getSharedWith(blog2.getId());
|
||||
assertEquals(2, contacts.size());
|
||||
assertTrue(contacts.contains(contact0From1));
|
||||
|
||||
// make sure 1 knows that they have blog2 already
|
||||
Collection<InvitationMessage> messages =
|
||||
blogSharingManager1.getInvitationMessages(contactId0From1);
|
||||
assertEquals(2, messages.size());
|
||||
assertEquals(blog2, blogManager1.getBlog(blog2.getId()));
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// blog was not added, because it was there already
|
||||
assertEquals(0, blogSharingManager0.getInvitations().size());
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingSharedBlog() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// blog was added successfully and is shared both ways
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
Collection<Contact> sharedWith =
|
||||
blogSharingManager0.getSharedWith(blog2.getId());
|
||||
assertEquals(2, sharedWith.size());
|
||||
assertTrue(sharedWith.contains(contact1From0));
|
||||
assertTrue(sharedWith.contains(contact2From0));
|
||||
Collection<Contact> sharedBy =
|
||||
blogSharingManager1.getSharedWith(blog2.getId());
|
||||
assertEquals(1, sharedBy.size());
|
||||
assertEquals(contact0From1, sharedBy.iterator().next());
|
||||
|
||||
// shared blog can be removed
|
||||
assertTrue(blogManager1.canBeRemoved(blog2.getId()));
|
||||
|
||||
// invitee removes blog again
|
||||
blogManager1.removeBlog(blog2);
|
||||
|
||||
// sync LEAVE message
|
||||
sync1To0(1, true);
|
||||
|
||||
// sharer does not share this blog anymore with invitee
|
||||
sharedWith =
|
||||
blogSharingManager0.getSharedWith(blog2.getId());
|
||||
assertEquals(1, sharedWith.size());
|
||||
assertTrue(sharedWith.contains(contact2From0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedBlogBecomesPermanent() throws Exception {
|
||||
// let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// invitee only sees two blogs
|
||||
assertEquals(2, blogManager1.getBlogs().size());
|
||||
|
||||
// sharer sends invitation for 2's blog to 1
|
||||
blogSharingManager0
|
||||
.sendInvitation(blog2.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// make sure blog2 is shared by 0
|
||||
Collection<Contact> contacts =
|
||||
blogSharingManager1.getSharedWith(blog2.getId());
|
||||
assertEquals(1, contacts.size());
|
||||
assertTrue(contacts.contains(contact0From1));
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// blog was added and can be removed
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
assertTrue(blogManager1.canBeRemoved(blog2.getId()));
|
||||
|
||||
// 1 and 2 are adding each other
|
||||
addContacts1And2();
|
||||
assertEquals(3, blogManager1.getBlogs().size());
|
||||
|
||||
// now blog can not be removed anymore
|
||||
assertFalse(blogManager1.canBeRemoved(blog2.getId()));
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class SharerListener implements EventListener {
|
||||
|
||||
private volatile boolean responseReceived = false;
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof BlogInvitationResponseReceivedEvent) {
|
||||
BlogInvitationResponseReceivedEvent event =
|
||||
(BlogInvitationResponseReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId1From0, event.getContactId());
|
||||
responseReceived = true;
|
||||
eventWaiter.resume();
|
||||
}
|
||||
// this is only needed for tests where a blog is re-shared
|
||||
else if (e instanceof BlogInvitationRequestReceivedEvent) {
|
||||
BlogInvitationRequestReceivedEvent event =
|
||||
(BlogInvitationRequestReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId1From0, event.getContactId());
|
||||
Blog b = event.getShareable();
|
||||
try {
|
||||
Contact c = contactManager0.getContact(contactId1From0);
|
||||
blogSharingManager0.respondToInvitation(b, c, true);
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class InviteeListener implements EventListener {
|
||||
|
||||
private volatile boolean requestReceived = false;
|
||||
|
||||
private final boolean accept, answer;
|
||||
|
||||
private InviteeListener(boolean accept, boolean answer) {
|
||||
this.accept = accept;
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
private InviteeListener(boolean accept) {
|
||||
this(accept, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof BlogInvitationRequestReceivedEvent) {
|
||||
BlogInvitationRequestReceivedEvent event =
|
||||
(BlogInvitationRequestReceivedEvent) e;
|
||||
requestReceived = true;
|
||||
if (!answer) return;
|
||||
Blog b = event.getShareable();
|
||||
try {
|
||||
eventWaiter.assertEquals(1,
|
||||
blogSharingManager1.getInvitations().size());
|
||||
Contact c =
|
||||
contactManager1.getContact(event.getContactId());
|
||||
blogSharingManager1.respondToInvitation(b, c, accept);
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
// this is only needed for tests where a blog is re-shared
|
||||
else if (e instanceof BlogInvitationResponseReceivedEvent) {
|
||||
BlogInvitationResponseReceivedEvent event =
|
||||
(BlogInvitationResponseReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId0From1, event.getContactId());
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listenToEvents(boolean accept) throws DbException {
|
||||
listener0 = new SharerListener();
|
||||
c0.getEventBus().addListener(listener0);
|
||||
listener1 = new InviteeListener(accept);
|
||||
c1.getEventBus().addListener(listener1);
|
||||
SharerListener listener2 = new SharerListener();
|
||||
c2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
package org.briarproject.briar.sharing;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.Event;
|
||||
import org.briarproject.bramble.api.event.EventListener;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.DaggerBriarIntegrationTestComponent;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.forum.Forum;
|
||||
import org.briarproject.briar.api.forum.ForumInvitationRequest;
|
||||
import org.briarproject.briar.api.forum.ForumInvitationResponse;
|
||||
import org.briarproject.briar.api.forum.ForumManager;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostHeader;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.forum.event.ForumInvitationRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.forum.event.ForumInvitationResponseReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.InvitationMessage;
|
||||
import org.briarproject.briar.api.sharing.SharingInvitationItem;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static junit.framework.Assert.assertNotNull;
|
||||
import static org.briarproject.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.TestUtils.getRandomString;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||
import static org.briarproject.briar.api.forum.ForumSharingManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ForumSharingIntegrationTest
|
||||
extends BriarIntegrationTest<BriarIntegrationTestComponent> {
|
||||
|
||||
private ForumManager forumManager0, forumManager1;
|
||||
private SharerListener listener0, listener2;
|
||||
private InviteeListener listener1;
|
||||
private Forum forum0;
|
||||
|
||||
// objects accessed from background threads need to be volatile
|
||||
private volatile ForumSharingManager forumSharingManager0;
|
||||
private volatile ForumSharingManager forumSharingManager1;
|
||||
private volatile ForumSharingManager forumSharingManager2;
|
||||
private volatile Waiter eventWaiter;
|
||||
|
||||
private boolean respond = true;
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Before
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
forumManager0 = c0.getForumManager();
|
||||
forumManager1 = c1.getForumManager();
|
||||
forumSharingManager0 = c0.getForumSharingManager();
|
||||
forumSharingManager1 = c1.getForumSharingManager();
|
||||
forumSharingManager2 = c2.getForumSharingManager();
|
||||
|
||||
// initialize waiter fresh for each test
|
||||
eventWaiter = new Waiter();
|
||||
|
||||
addContacts1And2();
|
||||
addForumForSharer();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void createComponents() {
|
||||
BriarIntegrationTestComponent component =
|
||||
DaggerBriarIntegrationTestComponent.builder().build();
|
||||
component.inject(this);
|
||||
|
||||
c0 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
|
||||
injectEagerSingletons(c0);
|
||||
|
||||
c1 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
|
||||
injectEagerSingletons(c1);
|
||||
|
||||
c2 = DaggerBriarIntegrationTestComponent.builder()
|
||||
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
|
||||
injectEagerSingletons(c2);
|
||||
}
|
||||
|
||||
private void addForumForSharer() throws DbException {
|
||||
forum0 = forumManager0.addForum("Test Forum");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulSharing() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
// invitee has one invitation message from sharer
|
||||
List<InvitationMessage> list =
|
||||
new ArrayList<InvitationMessage>(forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
// check other things are alright with the forum message
|
||||
for (InvitationMessage m : list) {
|
||||
if (m instanceof ForumInvitationRequest) {
|
||||
ForumInvitationRequest invitation =
|
||||
(ForumInvitationRequest) m;
|
||||
assertFalse(invitation.isAvailable());
|
||||
assertEquals(forum0.getName(), invitation.getForumName());
|
||||
assertEquals(contactId1From0, invitation.getContactId());
|
||||
assertEquals("Hi!", invitation.getMessage());
|
||||
} else {
|
||||
ForumInvitationResponse response =
|
||||
(ForumInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertTrue(response.wasAccepted());
|
||||
assertTrue(response.isLocal());
|
||||
}
|
||||
}
|
||||
// sharer has own invitation message and response
|
||||
assertEquals(2,
|
||||
forumSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
// forum can not be shared again
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
assertFalse(forumSharingManager0.canBeShared(forum0.getId(), c1));
|
||||
Contact c0 = contactManager1.getContact(contactId0From1);
|
||||
assertFalse(forumSharingManager1.canBeShared(forum0.getId(), c0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclinedSharing() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(false);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, null);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was not added
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
assertEquals(0, forumManager1.getForums().size());
|
||||
// forum is no longer available to invitee who declined
|
||||
assertEquals(0, forumSharingManager1.getInvitations().size());
|
||||
|
||||
// invitee has one invitation message from sharer and one response
|
||||
List<InvitationMessage> list =
|
||||
new ArrayList<InvitationMessage>(forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
// check things are alright with the forum message
|
||||
for (InvitationMessage m : list) {
|
||||
if (m instanceof ForumInvitationRequest) {
|
||||
ForumInvitationRequest invitation =
|
||||
(ForumInvitationRequest) m;
|
||||
assertFalse(invitation.isAvailable());
|
||||
assertEquals(forum0.getName(), invitation.getForumName());
|
||||
assertEquals(contactId1From0, invitation.getContactId());
|
||||
assertEquals(null, invitation.getMessage());
|
||||
} else {
|
||||
ForumInvitationResponse response =
|
||||
(ForumInvitationResponse) m;
|
||||
assertEquals(contactId0From1, response.getContactId());
|
||||
assertFalse(response.wasAccepted());
|
||||
assertTrue(response.isLocal());
|
||||
}
|
||||
}
|
||||
// sharer has own invitation message and response
|
||||
assertEquals(2,
|
||||
forumSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
// forum can be shared again
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInviteeLeavesAfterFinished() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
assertTrue(forumManager1.getForums().contains(forum0));
|
||||
|
||||
// sharer shares forum with invitee
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(c1));
|
||||
// invitee gets forum shared by sharer
|
||||
Contact contact0 = contactManager1.getContact(contactId1From0);
|
||||
assertTrue(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(contact0));
|
||||
|
||||
// invitee un-subscribes from forum
|
||||
forumManager1.removeForum(forum0);
|
||||
|
||||
// send leave message to sharer
|
||||
sync1To0(1, true);
|
||||
|
||||
// forum is gone
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
assertEquals(0, forumManager1.getForums().size());
|
||||
|
||||
// sharer no longer shares forum with invitee
|
||||
assertFalse(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(c1));
|
||||
// invitee no longer gets forum shared by sharer
|
||||
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(contact0));
|
||||
// forum can be shared again
|
||||
assertTrue(forumSharingManager0.canBeShared(forum0.getId(), c1));
|
||||
Contact c0 = contactManager1.getContact(contactId0From1);
|
||||
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharerLeavesAfterFinished() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, null);
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
assertTrue(forumManager1.getForums().contains(forum0));
|
||||
|
||||
// sharer shares forum with invitee
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
assertTrue(forumSharingManager0.getSharedWith(forum0.getId())
|
||||
.contains(c1));
|
||||
// invitee gets forum shared by sharer
|
||||
Contact contact0 = contactManager1.getContact(contactId1From0);
|
||||
assertTrue(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(contact0));
|
||||
|
||||
// sharer un-subscribes from forum
|
||||
forumManager0.removeForum(forum0);
|
||||
|
||||
// send leave message to invitee
|
||||
sync0To1(1, true);
|
||||
|
||||
// forum is gone for sharer, but not invitee
|
||||
assertEquals(0, forumManager0.getForums().size());
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
// invitee no longer shares forum with sharer
|
||||
Contact c0 = contactManager1.getContact(contactId0From1);
|
||||
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(c0));
|
||||
// sharer no longer gets forum shared by invitee
|
||||
assertFalse(forumSharingManager1.getSharedWith(forum0.getId())
|
||||
.contains(contact0));
|
||||
// forum can be shared again
|
||||
assertTrue(forumSharingManager1.canBeShared(forum0.getId(), c0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharerLeavesBeforeResponse() throws Exception {
|
||||
// initialize except event listeners
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, null);
|
||||
|
||||
// sharer un-subscribes from forum
|
||||
forumManager0.removeForum(forum0);
|
||||
|
||||
// prevent invitee response before syncing messages
|
||||
respond = false;
|
||||
|
||||
// sync first request message and leave message
|
||||
sync0To1(2, true);
|
||||
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// ensure that invitee has no forum invitations available
|
||||
assertEquals(0, forumSharingManager1.getInvitations().size());
|
||||
assertEquals(0, forumManager1.getForums().size());
|
||||
|
||||
// Try again, this time allow the response
|
||||
addForumForSharer();
|
||||
respond = true;
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, null);
|
||||
|
||||
// sharer un-subscribes from forum
|
||||
forumManager0.removeForum(forum0);
|
||||
|
||||
// sync first request message and leave message
|
||||
sync0To1(2, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// ensure that invitee has no forum invitations available
|
||||
assertEquals(0, forumSharingManager1.getInvitations().size());
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSessionIdReuse() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
|
||||
// reset event received state
|
||||
listener1.requestReceived = false;
|
||||
|
||||
// get SessionId from invitation
|
||||
List<InvitationMessage> list = new ArrayList<InvitationMessage>(
|
||||
forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
InvitationMessage msg = list.get(0);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
assertEquals(sessionId, list.get(1).getSessionId());
|
||||
|
||||
// get all sorts of stuff needed to send a message
|
||||
MessageQueueManager queue = c0.getMessageQueueManager();
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
|
||||
long time = clock.currentTimeMillis();
|
||||
BdfList bodyList =
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId.getBytes(),
|
||||
getRandomString(42), getRandomBytes(FORUM_SALT_LENGTH));
|
||||
byte[] body = clientHelper.toByteArray(bodyList);
|
||||
|
||||
// add the message to the queue
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
queue.sendMessage(txn, group, time, body, new Metadata());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
|
||||
// actually send the message
|
||||
sync0To1(1, false);
|
||||
// make sure there was no new request received
|
||||
assertFalse(listener1.requestReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharingSameForumWithEachOther() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
assertEquals(2,
|
||||
forumSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
|
||||
// invitee now shares same forum back
|
||||
forumSharingManager1.sendInvitation(forum0.getId(),
|
||||
contactId0From1,
|
||||
"I am re-sharing this forum with you.");
|
||||
|
||||
// sync re-share invitation
|
||||
sync1To0(1, false);
|
||||
|
||||
// make sure that no new request was received
|
||||
assertFalse(listener0.requestReceived);
|
||||
assertEquals(2,
|
||||
forumSharingManager0.getInvitationMessages(contactId1From0)
|
||||
.size());
|
||||
assertEquals(0, forumSharingManager0.getInvitations().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharingSameForumWithEachOtherAtSameTime() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// invitee adds the same forum
|
||||
Transaction txn = db1.startTransaction(false);
|
||||
db1.addGroup(txn, forum0.getGroup());
|
||||
db1.commitTransaction(txn);
|
||||
db1.endTransaction(txn);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// invitee now shares same forum back
|
||||
forumSharingManager1.sendInvitation(forum0.getId(),
|
||||
contactId0From1, "I am re-sharing this forum with you.");
|
||||
|
||||
// find out who should be Alice, because of random keys
|
||||
Bytes key0 = new Bytes(author0.getPublicKey());
|
||||
Bytes key1 = new Bytes(author1.getPublicKey());
|
||||
|
||||
// only now sync first request message
|
||||
boolean alice = key1.compareTo(key0) < 0;
|
||||
if (alice) {
|
||||
sync0To1(1, false);
|
||||
assertFalse(listener1.requestReceived);
|
||||
} else {
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
}
|
||||
|
||||
// sync second invitation
|
||||
alice = key0.compareTo(key1) < 0;
|
||||
if (alice) {
|
||||
sync1To0(1, false);
|
||||
assertFalse(listener0.requestReceived);
|
||||
|
||||
// sharer did not receive request, but response to own request
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
assertEquals(2, forumSharingManager0
|
||||
.getInvitationMessages(contactId1From0).size());
|
||||
assertEquals(3, forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1).size());
|
||||
} else {
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.requestReceived);
|
||||
|
||||
// send response from sharer to invitee and make sure it arrived
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.responseReceived);
|
||||
|
||||
assertEquals(3, forumSharingManager0
|
||||
.getInvitationMessages(contactId1From0).size());
|
||||
assertEquals(2, forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1).size());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContactRemoved() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener1.requestReceived);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
|
||||
// forum was added successfully
|
||||
assertEquals(1, forumManager1.getForums().size());
|
||||
assertEquals(1,
|
||||
forumSharingManager0.getSharedWith(forum0.getId()).size());
|
||||
|
||||
// remember SessionId from invitation
|
||||
List<InvitationMessage> list = new ArrayList<InvitationMessage>(
|
||||
forumSharingManager1
|
||||
.getInvitationMessages(contactId0From1));
|
||||
assertEquals(2, list.size());
|
||||
InvitationMessage msg = list.get(0);
|
||||
SessionId sessionId = msg.getSessionId();
|
||||
assertEquals(sessionId, list.get(1).getSessionId());
|
||||
|
||||
// contacts now remove each other
|
||||
removeAllContacts();
|
||||
|
||||
// make sure sharer does share the forum with nobody now
|
||||
assertEquals(0,
|
||||
forumSharingManager0.getSharedWith(forum0.getId()).size());
|
||||
|
||||
// contacts add each other again
|
||||
addDefaultContacts();
|
||||
addContacts1And2();
|
||||
|
||||
// get all sorts of stuff needed to send a message
|
||||
MessageQueueManager queue = c0.getMessageQueueManager();
|
||||
Contact c1 = contactManager0.getContact(contactId1From0);
|
||||
Group group = contactGroupFactory.createContactGroup(CLIENT_ID, c1);
|
||||
long time = clock.currentTimeMillis();
|
||||
|
||||
// construct a new message re-using the old SessionId
|
||||
BdfList bodyList = BdfList.of(SHARE_MSG_TYPE_INVITATION,
|
||||
sessionId.getBytes(),
|
||||
getRandomString(42),
|
||||
getRandomBytes(FORUM_SALT_LENGTH)
|
||||
);
|
||||
byte[] body = clientHelper.toByteArray(bodyList);
|
||||
|
||||
// add the message to the queue
|
||||
Transaction txn = db0.startTransaction(false);
|
||||
try {
|
||||
queue.sendMessage(txn, group, time, body, new Metadata());
|
||||
db0.commitTransaction(txn);
|
||||
} finally {
|
||||
db0.endTransaction(txn);
|
||||
}
|
||||
|
||||
// actually send the message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
// make sure the new request was received with the same sessionId
|
||||
// as proof that the state got deleted along with contacts
|
||||
assertTrue(listener1.requestReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwoContactsShareSameForum() throws Exception {
|
||||
// second sharer adds the same forum
|
||||
Transaction txn = db2.startTransaction(false);
|
||||
db2.addGroup(txn, forum0.getGroup());
|
||||
db2.commitTransaction(txn);
|
||||
db2.endTransaction(txn);
|
||||
|
||||
// add listeners
|
||||
listener0 = new SharerListener();
|
||||
c0.getEventBus().addListener(listener0);
|
||||
listener1 = new InviteeListener(true, false);
|
||||
c1.getEventBus().addListener(listener1);
|
||||
listener2 = new SharerListener();
|
||||
c2.getEventBus().addListener(listener2);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
|
||||
// second sharer sends invitation for same forum
|
||||
assertTrue(contactId1From2 != null);
|
||||
forumSharingManager2
|
||||
.sendInvitation(forum0.getId(), contactId1From2, null);
|
||||
// sync second request message
|
||||
sync2To1(1, true);
|
||||
|
||||
// make sure we now have two invitations to the same forum available
|
||||
Collection<SharingInvitationItem> forums =
|
||||
forumSharingManager1.getInvitations();
|
||||
assertEquals(1, forums.size());
|
||||
assertEquals(2, forums.iterator().next().getNewSharers().size());
|
||||
assertEquals(forum0, forums.iterator().next().getShareable());
|
||||
assertEquals(2,
|
||||
forumSharingManager1.getSharedWith(forum0.getId()).size());
|
||||
|
||||
// make sure both sharers actually share the forum
|
||||
Collection<Contact> contacts =
|
||||
forumSharingManager1.getSharedWith(forum0.getId());
|
||||
assertEquals(2, contacts.size());
|
||||
|
||||
// answer second request
|
||||
assertNotNull(contactId2From1);
|
||||
Contact contact2From1 = contactManager1.getContact(contactId2From1);
|
||||
forumSharingManager1.respondToInvitation(forum0, contact2From1, true);
|
||||
// sync response
|
||||
sync1To2(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener2.responseReceived);
|
||||
|
||||
// answer first request
|
||||
Contact c0 =
|
||||
contactManager1.getContact(contactId0From1);
|
||||
forumSharingManager1.respondToInvitation(forum0, c0, true);
|
||||
// sync response
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
assertTrue(listener0.responseReceived);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSyncAfterReSharing() throws Exception {
|
||||
// initialize and let invitee accept all requests
|
||||
listenToEvents(true);
|
||||
|
||||
// send invitation
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// sharer posts into the forum
|
||||
long time = clock.currentTimeMillis();
|
||||
String body = getRandomString(42);
|
||||
ForumPost p = forumPostFactory
|
||||
.createPost(forum0.getId(), time, null, author0,
|
||||
body);
|
||||
forumManager0.addLocalPost(p);
|
||||
|
||||
// sync forum post
|
||||
sync0To1(1, true);
|
||||
|
||||
// make sure forum post arrived
|
||||
Collection<ForumPostHeader> headers =
|
||||
forumManager1.getPostHeaders(forum0.getId());
|
||||
assertEquals(1, headers.size());
|
||||
ForumPostHeader header = headers.iterator().next();
|
||||
assertEquals(p.getMessage().getId(), header.getId());
|
||||
assertEquals(author0, header.getAuthor());
|
||||
|
||||
// now invitee creates a post
|
||||
time = clock.currentTimeMillis();
|
||||
body = getRandomString(42);
|
||||
p = forumPostFactory
|
||||
.createPost(forum0.getId(), time, null, author1,
|
||||
body);
|
||||
forumManager1.addLocalPost(p);
|
||||
|
||||
// sync forum post
|
||||
sync1To0(1, true);
|
||||
|
||||
// make sure forum post arrived
|
||||
headers = forumManager1.getPostHeaders(forum0.getId());
|
||||
assertEquals(2, headers.size());
|
||||
boolean found = false;
|
||||
for (ForumPostHeader h : headers) {
|
||||
if (p.getMessage().getId().equals(h.getId())) {
|
||||
found = true;
|
||||
assertEquals(author1, h.getAuthor());
|
||||
}
|
||||
}
|
||||
assertTrue(found);
|
||||
|
||||
// contacts remove each other
|
||||
removeAllContacts();
|
||||
|
||||
// contacts add each other back
|
||||
addDefaultContacts();
|
||||
addContacts1And2();
|
||||
|
||||
// send invitation again
|
||||
forumSharingManager0
|
||||
.sendInvitation(forum0.getId(), contactId1From0, "Hi!");
|
||||
|
||||
// sync first request message
|
||||
sync0To1(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// sync response back
|
||||
sync1To0(1, true);
|
||||
eventWaiter.await(TIMEOUT, 1);
|
||||
|
||||
// now invitee creates a post
|
||||
time = clock.currentTimeMillis();
|
||||
body = getRandomString(42);
|
||||
p = forumPostFactory
|
||||
.createPost(forum0.getId(), time, null, author1,
|
||||
body);
|
||||
forumManager1.addLocalPost(p);
|
||||
|
||||
// sync forum post
|
||||
sync1To0(1, true);
|
||||
|
||||
// make sure forum post arrived
|
||||
headers = forumManager1.getPostHeaders(forum0.getId());
|
||||
assertEquals(3, headers.size());
|
||||
found = false;
|
||||
for (ForumPostHeader h : headers) {
|
||||
if (p.getMessage().getId().equals(h.getId())) {
|
||||
found = true;
|
||||
assertEquals(author1, h.getAuthor());
|
||||
}
|
||||
}
|
||||
assertTrue(found);
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class SharerListener implements EventListener {
|
||||
|
||||
private volatile boolean requestReceived = false;
|
||||
private volatile boolean responseReceived = false;
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
responseReceived = true;
|
||||
eventWaiter.resume();
|
||||
}
|
||||
// this is only needed for tests where a forum is re-shared
|
||||
else if (e instanceof ForumInvitationRequestReceivedEvent) {
|
||||
ForumInvitationRequestReceivedEvent event =
|
||||
(ForumInvitationRequestReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId1From0, event.getContactId());
|
||||
requestReceived = true;
|
||||
Forum f = event.getShareable();
|
||||
try {
|
||||
Contact c = contactManager0.getContact(contactId1From0);
|
||||
forumSharingManager0.respondToInvitation(f, c, true);
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class InviteeListener implements EventListener {
|
||||
|
||||
private volatile boolean requestReceived = false;
|
||||
private volatile boolean responseReceived = false;
|
||||
|
||||
private final boolean accept, answer;
|
||||
|
||||
private InviteeListener(boolean accept, boolean answer) {
|
||||
this.accept = accept;
|
||||
this.answer = answer;
|
||||
}
|
||||
|
||||
private InviteeListener(boolean accept) {
|
||||
this(accept, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ForumInvitationRequestReceivedEvent) {
|
||||
ForumInvitationRequestReceivedEvent event =
|
||||
(ForumInvitationRequestReceivedEvent) e;
|
||||
requestReceived = true;
|
||||
if (!answer) return;
|
||||
Forum f = event.getShareable();
|
||||
try {
|
||||
eventWaiter.assertEquals(1,
|
||||
forumSharingManager1.getInvitations().size());
|
||||
SharingInvitationItem invitation =
|
||||
forumSharingManager1.getInvitations().iterator()
|
||||
.next();
|
||||
eventWaiter.assertEquals(f, invitation.getShareable());
|
||||
if (respond) {
|
||||
Contact c =
|
||||
contactManager1
|
||||
.getContact(event.getContactId());
|
||||
forumSharingManager1.respondToInvitation(f, c, accept);
|
||||
}
|
||||
} catch (DbException ex) {
|
||||
eventWaiter.rethrow(ex);
|
||||
} finally {
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
// this is only needed for tests where a forum is re-shared
|
||||
else if (e instanceof ForumInvitationResponseReceivedEvent) {
|
||||
ForumInvitationResponseReceivedEvent event =
|
||||
(ForumInvitationResponseReceivedEvent) e;
|
||||
eventWaiter.assertEquals(contactId0From1, event.getContactId());
|
||||
responseReceived = true;
|
||||
eventWaiter.resume();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void listenToEvents(boolean accept) throws DbException {
|
||||
listener0 = new SharerListener();
|
||||
c0.getEventBus().addListener(listener0);
|
||||
listener1 = new InviteeListener(accept);
|
||||
c1.getEventBus().addListener(listener1);
|
||||
listener2 = new SharerListener();
|
||||
c2.getEventBus().addListener(listener2);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user