mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-14 19:59:05 +01:00
Move all unit tests to their modules and remove briar-tests
This commit is contained in:
@@ -2,8 +2,7 @@ package org.briarproject.briar;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
@@ -60,11 +59,11 @@ 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.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.briarproject.briar.TestPluginConfigModule.MAX_LATENCY;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.bramble.BrambleTestCase;
|
||||
|
||||
public abstract class BriarTestCase extends BrambleTestCase {
|
||||
|
||||
public BriarTestCase() {
|
||||
super();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class BriarTestUtils {
|
||||
|
||||
public static void assertGroupCount(MessageTracker tracker, GroupId g,
|
||||
long msgCount, long unreadCount, long latestMsgTime)
|
||||
throws DbException {
|
||||
GroupCount groupCount = tracker.getGroupCount(g);
|
||||
assertEquals(msgCount, groupCount.getMsgCount());
|
||||
assertEquals(unreadCount, groupCount.getUnreadCount());
|
||||
assertEquals(latestMsgTime, groupCount.getLatestMsgTime());
|
||||
}
|
||||
|
||||
public static void assertGroupCount(MessageTracker tracker, GroupId g,
|
||||
long msgCount, long unreadCount) throws DbException {
|
||||
GroupCount c1 = tracker.getGroupCount(g);
|
||||
assertEquals(msgCount, c1.getMsgCount());
|
||||
assertEquals(unreadCount, c1.getUnreadCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class CaptureArgumentAction<T> implements Action {
|
||||
|
||||
private final AtomicReference<T> captured;
|
||||
private final Class<T> capturedClass;
|
||||
private final int index;
|
||||
|
||||
public CaptureArgumentAction(AtomicReference<T> captured,
|
||||
Class<T> capturedClass, int index) {
|
||||
this.captured = captured;
|
||||
this.capturedClass = capturedClass;
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
captured.set(capturedClass.cast(invocation.getParameter(index)));
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("captures an argument");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Client;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class TestLifecycleModule {
|
||||
|
||||
@Provides
|
||||
LifecycleManager provideLifecycleManager() {
|
||||
@NotNullByDefault
|
||||
LifecycleManager lifecycleManager = new LifecycleManager() {
|
||||
|
||||
@Override
|
||||
public void registerService(Service s) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerClient(Client c) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerForShutdown(ExecutorService e) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public StartResult startServices(@Nullable String nickname) {
|
||||
return StartResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopServices() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForDatabase() throws InterruptedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForStartup() throws InterruptedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void waitForShutdown() throws InterruptedException {
|
||||
}
|
||||
};
|
||||
return lifecycleManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
ShutdownManager provideShutdownManager() {
|
||||
@NotNullByDefault
|
||||
ShutdownManager shutdownManager = new ShutdownManager() {
|
||||
|
||||
@Override
|
||||
public int addShutdownHook(Runnable hook) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeShutdownHook(int handle) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
return shutdownManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@IoExecutor
|
||||
@Singleton
|
||||
Executor provideIoExecutor() {
|
||||
return Executors.newCachedThreadPool();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.briarproject.briar;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.plugin.PluginConfig;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginCallback;
|
||||
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
@Module
|
||||
public class TestPluginConfigModule {
|
||||
|
||||
public static final TransportId TRANSPORT_ID = new TransportId("id");
|
||||
public static final int MAX_LATENCY = 2 * 60 * 1000; // 2 minutes
|
||||
|
||||
@NotNullByDefault
|
||||
private final SimplexPluginFactory simplex = new SimplexPluginFactory() {
|
||||
|
||||
@Override
|
||||
public TransportId getId() {
|
||||
return TRANSPORT_ID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxLatency() {
|
||||
return MAX_LATENCY;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public SimplexPlugin createPlugin(SimplexPluginCallback callback) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@Provides
|
||||
PluginConfig providePluginConfig() {
|
||||
@NotNullByDefault
|
||||
PluginConfig pluginConfig = new PluginConfig() {
|
||||
|
||||
@Override
|
||||
public Collection<DuplexPluginFactory> getDuplexFactories() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<SimplexPluginFactory> getSimplexFactories() {
|
||||
return Collections.singletonList(simplex);
|
||||
}
|
||||
};
|
||||
return pluginConfig;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package org.briarproject.briar.blog;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
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.contact.ContactManager;
|
||||
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.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
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.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogFactory;
|
||||
import org.briarproject.briar.api.blog.BlogPost;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.blog.BlogPostHeader;
|
||||
import org.briarproject.briar.api.blog.event.BlogPostAddedEvent;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TIME_RECEIVED;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_TYPE;
|
||||
import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.api.blog.MessageType.POST;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BlogManagerImplTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final BlogManagerImpl blogManager;
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ContactManager contactManager =
|
||||
context.mock(ContactManager.class);
|
||||
private final BlogFactory blogFactory = context.mock(BlogFactory.class);
|
||||
|
||||
private final Blog blog1, blog2;
|
||||
private final Message message;
|
||||
private final MessageId messageId;
|
||||
|
||||
public BlogManagerImplTest() {
|
||||
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
||||
BlogPostFactory blogPostFactory = context.mock(BlogPostFactory.class);
|
||||
blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
|
||||
metadataParser, contactManager, blogFactory, blogPostFactory);
|
||||
|
||||
blog1 = createBlog();
|
||||
blog2 = createBlog();
|
||||
messageId = new MessageId(getRandomId());
|
||||
message = new Message(messageId, blog1.getId(), 42, getRandomBytes(42));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLocalState() throws DbException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
final ContactId contactId = new ContactId(0);
|
||||
|
||||
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
||||
blog1.getAuthor().getId(), true, true);
|
||||
final Collection<Contact> contacts = Collections.singletonList(contact);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(blog1.getAuthor()));
|
||||
oneOf(blogFactory).createBlog(blog1.getAuthor());
|
||||
will(returnValue(blog1));
|
||||
oneOf(db).addGroup(txn, blog1.getGroup());
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
||||
will(returnValue(blog2));
|
||||
oneOf(db).addGroup(txn, blog2.getGroup());
|
||||
oneOf(db).setGroupVisibility(txn, contactId, blog2.getId(), SHARED);
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(blog1.getAuthor()));
|
||||
oneOf(blogFactory).createBlog(blog1.getAuthor());
|
||||
will(returnValue(blog1));
|
||||
oneOf(db).setGroupVisibility(txn, contactId, blog1.getId(), SHARED);
|
||||
}});
|
||||
|
||||
blogManager.createLocalState(txn);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingContact() throws DbException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
final ContactId contactId = new ContactId(0);
|
||||
Contact contact = new Contact(contactId, blog2.getAuthor(),
|
||||
blog1.getAuthor().getId(), true, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(blogFactory).createBlog(blog2.getAuthor());
|
||||
will(returnValue(blog2));
|
||||
oneOf(db).removeGroup(txn, blog2.getGroup());
|
||||
}});
|
||||
|
||||
blogManager.removingContact(txn, contact);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessage() throws DbException, FormatException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
BdfList list = new BdfList();
|
||||
BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
|
||||
BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(KEY_TYPE, POST.getInt()),
|
||||
new BdfEntry(KEY_TIMESTAMP, 0),
|
||||
new BdfEntry(KEY_TIME_RECEIVED, 1),
|
||||
new BdfEntry(KEY_AUTHOR, author),
|
||||
new BdfEntry(KEY_READ, false)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager)
|
||||
.getAuthorStatus(txn, blog1.getAuthor().getId());
|
||||
will(returnValue(VERIFIED));
|
||||
}});
|
||||
|
||||
blogManager.incomingMessage(txn, message, list, meta);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getEvents().size());
|
||||
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
|
||||
|
||||
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
|
||||
assertEquals(blog1.getId(), e.getGroupId());
|
||||
|
||||
BlogPostHeader h = e.getHeader();
|
||||
assertEquals(1, h.getTimeReceived());
|
||||
assertEquals(messageId, h.getId());
|
||||
assertEquals(null, h.getParentId());
|
||||
assertEquals(VERIFIED, h.getAuthorStatus());
|
||||
assertEquals(blog1.getAuthor(), h.getAuthor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveBlog() throws Exception {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
|
||||
checkGetBlogExpectations(txn, false, blog1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(blog2.getAuthor()));
|
||||
oneOf(contactManager).contactExists(txn, blog1.getAuthor().getId(),
|
||||
blog2.getAuthor().getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).removeGroup(txn, blog1.getGroup());
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
blogManager.removeBlog(blog1);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddLocalPost() throws DbException, FormatException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final BlogPost post =
|
||||
new BlogPost(message, null, blog1.getAuthor());
|
||||
BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
|
||||
final BdfDictionary meta = BdfDictionary.of(
|
||||
new BdfEntry(KEY_TYPE, POST.getInt()),
|
||||
new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
|
||||
new BdfEntry(KEY_AUTHOR, authorMeta),
|
||||
new BdfEntry(KEY_READ, true)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(clientHelper)
|
||||
.addLocalMessage(txn, message, meta, true);
|
||||
oneOf(identityManager)
|
||||
.getAuthorStatus(txn, blog1.getAuthor().getId());
|
||||
will(returnValue(VERIFIED));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
blogManager.addLocalPost(post);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
assertEquals(1, txn.getEvents().size());
|
||||
assertTrue(txn.getEvents().get(0) instanceof BlogPostAddedEvent);
|
||||
|
||||
BlogPostAddedEvent e = (BlogPostAddedEvent) txn.getEvents().get(0);
|
||||
assertEquals(blog1.getId(), e.getGroupId());
|
||||
|
||||
BlogPostHeader h = e.getHeader();
|
||||
assertEquals(message.getTimestamp(), h.getTimeReceived());
|
||||
assertEquals(messageId, h.getId());
|
||||
assertEquals(null, h.getParentId());
|
||||
assertEquals(VERIFIED, h.getAuthorStatus());
|
||||
assertEquals(blog1.getAuthor(), h.getAuthor());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlogCanBeRemoved() throws Exception {
|
||||
// check that own personal blogs can not be removed
|
||||
final Transaction txn = new Transaction(null, true);
|
||||
checkGetBlogExpectations(txn, true, blog1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(blog1.getAuthor()));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
assertFalse(blogManager.canBeRemoved(blog1.getId()));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
// check that blogs of contacts can not be removed
|
||||
final Transaction txn2 = new Transaction(null, true);
|
||||
checkGetBlogExpectations(txn2, true, blog1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn2);
|
||||
will(returnValue(blog2.getAuthor()));
|
||||
oneOf(contactManager).contactExists(txn2, blog1.getAuthor().getId(),
|
||||
blog2.getAuthor().getId());
|
||||
will(returnValue(true));
|
||||
oneOf(db).commitTransaction(txn2);
|
||||
oneOf(db).endTransaction(txn2);
|
||||
}});
|
||||
assertFalse(blogManager.canBeRemoved(blog1.getId()));
|
||||
context.assertIsSatisfied();
|
||||
|
||||
// check that blogs can be removed if they don't belong to a contact
|
||||
final Transaction txn3 = new Transaction(null, true);
|
||||
checkGetBlogExpectations(txn3, true, blog1);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(identityManager).getLocalAuthor(txn3);
|
||||
will(returnValue(blog2.getAuthor()));
|
||||
oneOf(contactManager).contactExists(txn3, blog1.getAuthor().getId(),
|
||||
blog2.getAuthor().getId());
|
||||
will(returnValue(false));
|
||||
oneOf(db).commitTransaction(txn3);
|
||||
oneOf(db).endTransaction(txn3);
|
||||
}});
|
||||
assertTrue(blogManager.canBeRemoved(blog1.getId()));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private void checkGetBlogExpectations(final Transaction txn,
|
||||
final boolean readOnly, final Blog blog) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(readOnly);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getGroup(txn, blog.getId());
|
||||
will(returnValue(blog.getGroup()));
|
||||
oneOf(blogFactory).parseBlog(blog.getGroup());
|
||||
will(returnValue(blog));
|
||||
}});
|
||||
}
|
||||
|
||||
private Blog createBlog() {
|
||||
final GroupId groupId = new GroupId(getRandomId());
|
||||
final Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
|
||||
final AuthorId authorId = new AuthorId(getRandomId());
|
||||
final byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
final byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
final long created = System.currentTimeMillis();
|
||||
final LocalAuthor localAuthor =
|
||||
new LocalAuthor(authorId, "Author", publicKey, privateKey,
|
||||
created);
|
||||
return new Blog(group, localAuthor);
|
||||
}
|
||||
|
||||
private BdfDictionary authorToBdfDictionary(Author a) {
|
||||
return BdfDictionary.of(
|
||||
new BdfEntry(KEY_AUTHOR_ID, a.getId()),
|
||||
new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
|
||||
new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.blog;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
@@ -21,7 +21,7 @@ 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.bramble.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;
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
package org.briarproject.briar.blog;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
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.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.briar.api.blog.Blog;
|
||||
import org.briarproject.briar.api.blog.BlogFactory;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
|
||||
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_COMMENT;
|
||||
import static org.briarproject.briar.api.blog.BlogPostFactory.SIGNING_LABEL_POST;
|
||||
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.assertFalse;
|
||||
|
||||
public class BlogPostValidatorTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final Blog blog;
|
||||
private final BdfDictionary authorDict;
|
||||
private final ClientId clientId;
|
||||
private final byte[] descriptor;
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final BlogPostValidator validator;
|
||||
private final GroupFactory groupFactory = context.mock(GroupFactory.class);
|
||||
private final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
private final BlogFactory blogFactory = context.mock(BlogFactory.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final Author author;
|
||||
private final String body = TestUtils.getRandomString(42);
|
||||
|
||||
public BlogPostValidatorTest() {
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
clientId = BlogManagerImpl.CLIENT_ID;
|
||||
descriptor = TestUtils.getRandomBytes(42);
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
AuthorId authorId =
|
||||
new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH));
|
||||
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
author = new Author(authorId, "Author", publicKey);
|
||||
authorDict = BdfDictionary.of(
|
||||
new BdfEntry(KEY_AUTHOR_ID, author.getId()),
|
||||
new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
|
||||
new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
|
||||
);
|
||||
blog = new Blog(group, author);
|
||||
|
||||
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] raw = TestUtils.getRandomBytes(123);
|
||||
message = new Message(messageId, group.getId(), timestamp, raw);
|
||||
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
Clock clock = new SystemClock();
|
||||
validator =
|
||||
new BlogPostValidator(groupFactory, messageFactory, blogFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperBlogPost()
|
||||
throws IOException, GeneralSecurityException {
|
||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||
BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
|
||||
|
||||
BdfList signed =
|
||||
BdfList.of(blog.getId(), message.getTimestamp(), body);
|
||||
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
|
||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||
assertFalse(result.getBoolean(KEY_READ));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateBlogPostWithoutAttachments()
|
||||
throws IOException, GeneralSecurityException {
|
||||
BdfList content = BdfList.of(null, null, body);
|
||||
BdfList m = BdfList.of(POST.getInt(), content, null);
|
||||
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateBlogPostWithoutSignature()
|
||||
throws IOException, GeneralSecurityException {
|
||||
BdfList content = BdfList.of(null, null, body, null);
|
||||
BdfList m = BdfList.of(POST.getInt(), content, null);
|
||||
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperBlogComment()
|
||||
throws IOException, GeneralSecurityException {
|
||||
// comment, parent_original_id, parent_id, signature
|
||||
String comment = "This is a blog comment";
|
||||
MessageId pOriginalId = new MessageId(TestUtils.getRandomId());
|
||||
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||
BdfList m =
|
||||
BdfList.of(COMMENT.getInt(), comment, pOriginalId, currentId,
|
||||
sigBytes);
|
||||
|
||||
BdfList signed =
|
||||
BdfList.of(blog.getId(), message.getTimestamp(), comment,
|
||||
pOriginalId, currentId);
|
||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
|
||||
assertEquals(comment, result.getString(KEY_COMMENT));
|
||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||
assertEquals(pOriginalId.getBytes(),
|
||||
result.getRaw(KEY_ORIGINAL_PARENT_MSG_ID));
|
||||
assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
|
||||
assertFalse(result.getBoolean(KEY_READ));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperEmptyBlogComment()
|
||||
throws IOException, GeneralSecurityException {
|
||||
// comment, parent_original_id, signature, parent_current_id
|
||||
MessageId originalId = new MessageId(TestUtils.getRandomId());
|
||||
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||
BdfList m =
|
||||
BdfList.of(COMMENT.getInt(), null, originalId, currentId,
|
||||
sigBytes);
|
||||
|
||||
BdfList signed =
|
||||
BdfList.of(blog.getId(), message.getTimestamp(), null,
|
||||
originalId, currentId);
|
||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
|
||||
assertFalse(result.containsKey(KEY_COMMENT));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperWrappedPost()
|
||||
throws IOException, GeneralSecurityException {
|
||||
// group descriptor, timestamp, content, signature
|
||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||
BdfList m =
|
||||
BdfList.of(WRAPPED_POST.getInt(), descriptor,
|
||||
message.getTimestamp(), body, sigBytes);
|
||||
|
||||
BdfList signed =
|
||||
BdfList.of(blog.getId(), message.getTimestamp(), body);
|
||||
expectCrypto(SIGNING_LABEL_POST, signed, sigBytes);
|
||||
|
||||
final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
|
||||
final byte[] originalBody = TestUtils.getRandomBytes(42);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(groupFactory).createGroup(clientId, descriptor);
|
||||
will(returnValue(blog.getGroup()));
|
||||
oneOf(clientHelper).toByteArray(originalList);
|
||||
will(returnValue(originalBody));
|
||||
oneOf(messageFactory)
|
||||
.createMessage(group.getId(), message.getTimestamp(),
|
||||
originalBody);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
|
||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateProperWrappedComment()
|
||||
throws IOException, GeneralSecurityException {
|
||||
// group descriptor, timestamp, comment, parent_original_id, signature,
|
||||
// parent_current_id
|
||||
String comment = "This is another comment";
|
||||
MessageId originalId = new MessageId(TestUtils.getRandomId());
|
||||
MessageId oldId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] sigBytes = TestUtils.getRandomBytes(42);
|
||||
MessageId currentId = new MessageId(TestUtils.getRandomId());
|
||||
BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
|
||||
message.getTimestamp(), comment, originalId, oldId, sigBytes,
|
||||
currentId);
|
||||
|
||||
BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
|
||||
comment, originalId, oldId);
|
||||
expectCrypto(SIGNING_LABEL_COMMENT, signed, sigBytes);
|
||||
|
||||
final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
|
||||
originalId, oldId, sigBytes);
|
||||
final byte[] originalBody = TestUtils.getRandomBytes(42);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(groupFactory).createGroup(clientId, descriptor);
|
||||
will(returnValue(blog.getGroup()));
|
||||
oneOf(clientHelper).toByteArray(originalList);
|
||||
will(returnValue(originalBody));
|
||||
oneOf(messageFactory)
|
||||
.createMessage(group.getId(), message.getTimestamp(),
|
||||
originalBody);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, m).getDictionary();
|
||||
|
||||
assertEquals(comment, result.getString(KEY_COMMENT));
|
||||
assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
|
||||
assertEquals(
|
||||
message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
|
||||
assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private void expectCrypto(final String label, final BdfList signed,
|
||||
final byte[] sig) throws IOException, GeneralSecurityException {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(blogFactory).parseBlog(group);
|
||||
will(returnValue(blog));
|
||||
oneOf(clientHelper)
|
||||
.verifySignature(label, sig, author.getPublicKey(), signed);
|
||||
}});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,573 @@
|
||||
package org.briarproject.briar.client;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.briar.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Metadata;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageContext;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.MessageValidator;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager.IncomingQueueMessageHook;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager.QueueMessageValidator;
|
||||
import org.briarproject.briar.api.client.QueueMessage;
|
||||
import org.briarproject.briar.api.client.QueueMessageFactory;
|
||||
import org.hamcrest.Description;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.api.Action;
|
||||
import org.jmock.api.Invocation;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
|
||||
import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class MessageQueueManagerImplTest extends BriarTestCase {
|
||||
|
||||
private final GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
private final ClientId clientId =
|
||||
new ClientId(TestUtils.getRandomString(5));
|
||||
private final byte[] descriptor = new byte[0];
|
||||
private final Group group = new Group(groupId, clientId, descriptor);
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
@Test
|
||||
public void testSendingMessages() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final byte[] body = new byte[123];
|
||||
final Metadata groupMetadata = new Metadata();
|
||||
final Metadata messageMetadata = new Metadata();
|
||||
final Metadata groupMetadata1 = new Metadata();
|
||||
final byte[] queueState = new byte[123];
|
||||
groupMetadata1.put(QUEUE_STATE_KEY, queueState);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// First message: queue state does not exist
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
|
||||
will(new EncodeQueueStateAction(1L, 0L, new BdfList()));
|
||||
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
|
||||
with(any(Metadata.class)));
|
||||
oneOf(queueMessageFactory).createMessage(groupId, timestamp, 0L,
|
||||
body);
|
||||
will(new CreateMessageAction());
|
||||
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
|
||||
with(messageMetadata), with(true));
|
||||
// Second message: queue state exists
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata1));
|
||||
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
|
||||
will(new DecodeQueueStateAction(1L, 0L, new BdfList()));
|
||||
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
|
||||
will(new EncodeQueueStateAction(2L, 0L, new BdfList()));
|
||||
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
|
||||
with(any(Metadata.class)));
|
||||
oneOf(queueMessageFactory).createMessage(groupId, timestamp, 1L,
|
||||
body);
|
||||
will(new CreateMessageAction());
|
||||
oneOf(db).addLocalMessage(with(txn), with(any(QueueMessage.class)),
|
||||
with(messageMetadata), with(true));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// First message
|
||||
QueueMessage q = mqm.sendMessage(txn, group, timestamp, body,
|
||||
messageMetadata);
|
||||
assertEquals(groupId, q.getGroupId());
|
||||
assertEquals(timestamp, q.getTimestamp());
|
||||
assertEquals(0L, q.getQueuePosition());
|
||||
assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q.getLength());
|
||||
|
||||
// Second message
|
||||
QueueMessage q1 = mqm.sendMessage(txn, group, timestamp, body,
|
||||
messageMetadata);
|
||||
assertEquals(groupId, q1.getGroupId());
|
||||
assertEquals(timestamp, q1.getTimestamp());
|
||||
assertEquals(1L, q1.getQueuePosition());
|
||||
assertEquals(QUEUE_MESSAGE_HEADER_LENGTH + body.length, q1.getLength());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidatorRejectsShortMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
|
||||
final AtomicReference<MessageValidator> captured =
|
||||
new AtomicReference<MessageValidator>();
|
||||
final QueueMessageValidator queueMessageValidator =
|
||||
context.mock(QueueMessageValidator.class);
|
||||
// The message is too short to be a valid queue message
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH - 1];
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerMessageValidator(with(clientId),
|
||||
with(any(MessageValidator.class)));
|
||||
will(new CaptureArgumentAction<MessageValidator>(captured,
|
||||
MessageValidator.class, 1));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating message validator
|
||||
mqm.registerMessageValidator(clientId, queueMessageValidator);
|
||||
MessageValidator delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// The message should be invalid
|
||||
try {
|
||||
delegate.validateMessage(message, group);
|
||||
fail();
|
||||
} catch (InvalidMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidatorRejectsNegativeQueuePosition() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
|
||||
final AtomicReference<MessageValidator> captured =
|
||||
new AtomicReference<MessageValidator>();
|
||||
final QueueMessageValidator queueMessageValidator =
|
||||
context.mock(QueueMessageValidator.class);
|
||||
// The message has a negative queue position
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
for (int i = 0; i < 8; i++)
|
||||
raw[MESSAGE_HEADER_LENGTH + i] = (byte) 0xFF;
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerMessageValidator(with(clientId),
|
||||
with(any(MessageValidator.class)));
|
||||
will(new CaptureArgumentAction<MessageValidator>(captured,
|
||||
MessageValidator.class, 1));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating message validator
|
||||
mqm.registerMessageValidator(clientId, queueMessageValidator);
|
||||
MessageValidator delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// The message should be invalid
|
||||
try {
|
||||
delegate.validateMessage(message, group);
|
||||
fail();
|
||||
} catch (InvalidMessageException expected) {
|
||||
// Expected
|
||||
}
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidatorDelegatesValidMessage() throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
|
||||
final AtomicReference<MessageValidator> captured =
|
||||
new AtomicReference<MessageValidator>();
|
||||
final QueueMessageValidator queueMessageValidator =
|
||||
context.mock(QueueMessageValidator.class);
|
||||
final Metadata metadata = new Metadata();
|
||||
final MessageContext messageContext =
|
||||
new MessageContext(metadata);
|
||||
// The message is valid, with a queue position of zero
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerMessageValidator(with(clientId),
|
||||
with(any(MessageValidator.class)));
|
||||
will(new CaptureArgumentAction<MessageValidator>(captured,
|
||||
MessageValidator.class, 1));
|
||||
// The message should be delegated
|
||||
oneOf(queueMessageValidator).validateMessage(
|
||||
with(any(QueueMessage.class)), with(group));
|
||||
will(returnValue(messageContext));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating message validator
|
||||
mqm.registerMessageValidator(clientId, queueMessageValidator);
|
||||
MessageValidator delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// The message should be valid and the metadata should be returned
|
||||
assertSame(messageContext, delegate.validateMessage(message, group));
|
||||
assertSame(metadata, messageContext.getMetadata());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessageHookDeletesDuplicateMessage()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
final AtomicReference<IncomingMessageHook> captured =
|
||||
new AtomicReference<IncomingMessageHook>();
|
||||
final IncomingQueueMessageHook incomingQueueMessageHook =
|
||||
context.mock(IncomingQueueMessageHook.class);
|
||||
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final Metadata groupMetadata = new Metadata();
|
||||
final byte[] queueState = new byte[123];
|
||||
groupMetadata.put(QUEUE_STATE_KEY, queueState);
|
||||
// The message has queue position 0
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
|
||||
with(any(IncomingMessageHook.class)));
|
||||
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
|
||||
IncomingMessageHook.class, 1));
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
// Queue position 1 is expected
|
||||
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
|
||||
will(new DecodeQueueStateAction(0L, 1L, new BdfList()));
|
||||
// The message and its metadata should be deleted
|
||||
oneOf(db).deleteMessage(txn, messageId);
|
||||
oneOf(db).deleteMessageMetadata(txn, messageId);
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating incoming message hook
|
||||
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
|
||||
IncomingMessageHook delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// Pass the message to the hook
|
||||
delegate.incomingMessage(txn, message, new Metadata());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessageHookAddsOutOfOrderMessageToPendingList()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
final AtomicReference<IncomingMessageHook> captured =
|
||||
new AtomicReference<IncomingMessageHook>();
|
||||
final IncomingQueueMessageHook incomingQueueMessageHook =
|
||||
context.mock(IncomingQueueMessageHook.class);
|
||||
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final Metadata groupMetadata = new Metadata();
|
||||
final byte[] queueState = new byte[123];
|
||||
groupMetadata.put(QUEUE_STATE_KEY, queueState);
|
||||
// The message has queue position 1
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
ByteUtils.writeUint64(1L, raw, MESSAGE_HEADER_LENGTH);
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
final BdfList pending = BdfList.of(BdfList.of(1L, messageId));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
|
||||
with(any(IncomingMessageHook.class)));
|
||||
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
|
||||
IncomingMessageHook.class, 1));
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
// Queue position 0 is expected
|
||||
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
|
||||
will(new DecodeQueueStateAction(0L, 0L, new BdfList()));
|
||||
// The message should be added to the pending list
|
||||
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
|
||||
will(new EncodeQueueStateAction(0L, 0L, pending));
|
||||
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
|
||||
with(any(Metadata.class)));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating incoming message hook
|
||||
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
|
||||
IncomingMessageHook delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// Pass the message to the hook
|
||||
delegate.incomingMessage(txn, message, new Metadata());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessageHookDelegatesInOrderMessage()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
final AtomicReference<IncomingMessageHook> captured =
|
||||
new AtomicReference<IncomingMessageHook>();
|
||||
final IncomingQueueMessageHook incomingQueueMessageHook =
|
||||
context.mock(IncomingQueueMessageHook.class);
|
||||
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final Metadata groupMetadata = new Metadata();
|
||||
final byte[] queueState = new byte[123];
|
||||
groupMetadata.put(QUEUE_STATE_KEY, queueState);
|
||||
// The message has queue position 0
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
final Metadata messageMetadata = new Metadata();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
|
||||
with(any(IncomingMessageHook.class)));
|
||||
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
|
||||
IncomingMessageHook.class, 1));
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
// Queue position 0 is expected
|
||||
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
|
||||
will(new DecodeQueueStateAction(0L, 0L, new BdfList()));
|
||||
// Queue position 1 should be expected next
|
||||
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
|
||||
will(new EncodeQueueStateAction(0L, 1L, new BdfList()));
|
||||
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
|
||||
with(any(Metadata.class)));
|
||||
// The message should be delegated
|
||||
oneOf(incomingQueueMessageHook).incomingMessage(with(txn),
|
||||
with(any(QueueMessage.class)), with(messageMetadata));
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating incoming message hook
|
||||
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
|
||||
IncomingMessageHook delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// Pass the message to the hook
|
||||
delegate.incomingMessage(txn, message, messageMetadata);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingMessageHookRetrievesPendingMessage()
|
||||
throws Exception {
|
||||
Mockery context = new Mockery();
|
||||
final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
final QueueMessageFactory queueMessageFactory =
|
||||
context.mock(QueueMessageFactory.class);
|
||||
final ValidationManager validationManager =
|
||||
context.mock(ValidationManager.class);
|
||||
final AtomicReference<IncomingMessageHook> captured =
|
||||
new AtomicReference<IncomingMessageHook>();
|
||||
final IncomingQueueMessageHook incomingQueueMessageHook =
|
||||
context.mock(IncomingQueueMessageHook.class);
|
||||
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final Metadata groupMetadata = new Metadata();
|
||||
final byte[] queueState = new byte[123];
|
||||
groupMetadata.put(QUEUE_STATE_KEY, queueState);
|
||||
// The message has queue position 0
|
||||
final MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
final Message message = new Message(messageId, groupId, timestamp, raw);
|
||||
final Metadata messageMetadata = new Metadata();
|
||||
// Queue position 1 is pending
|
||||
final MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
final byte[] raw1 = new byte[QUEUE_MESSAGE_HEADER_LENGTH];
|
||||
final QueueMessage message1 = new QueueMessage(messageId1, groupId,
|
||||
timestamp, 1L, raw1);
|
||||
final Metadata messageMetadata1 = new Metadata();
|
||||
final BdfList pending = BdfList.of(BdfList.of(1L, messageId1));
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(validationManager).registerIncomingMessageHook(with(clientId),
|
||||
with(any(IncomingMessageHook.class)));
|
||||
will(new CaptureArgumentAction<IncomingMessageHook>(captured,
|
||||
IncomingMessageHook.class, 1));
|
||||
oneOf(db).getGroupMetadata(txn, groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
// Queue position 0 is expected, position 1 is pending
|
||||
oneOf(clientHelper).toDictionary(queueState, 0, queueState.length);
|
||||
will(new DecodeQueueStateAction(0L, 0L, pending));
|
||||
// Queue position 2 should be expected next
|
||||
oneOf(clientHelper).toByteArray(with(any(BdfDictionary.class)));
|
||||
will(new EncodeQueueStateAction(0L, 2L, new BdfList()));
|
||||
oneOf(db).mergeGroupMetadata(with(txn), with(groupId),
|
||||
with(any(Metadata.class)));
|
||||
// The new message should be delegated
|
||||
oneOf(incomingQueueMessageHook).incomingMessage(with(txn),
|
||||
with(any(QueueMessage.class)), with(messageMetadata));
|
||||
// The pending message should be retrieved
|
||||
oneOf(db).getRawMessage(txn, messageId1);
|
||||
will(returnValue(raw1));
|
||||
oneOf(db).getMessageMetadata(txn, messageId1);
|
||||
will(returnValue(messageMetadata1));
|
||||
oneOf(queueMessageFactory).createMessage(messageId1, raw1);
|
||||
will(returnValue(message1));
|
||||
// The pending message should be delegated
|
||||
oneOf(incomingQueueMessageHook).incomingMessage(txn, message1,
|
||||
messageMetadata1);
|
||||
}});
|
||||
|
||||
MessageQueueManagerImpl mqm = new MessageQueueManagerImpl(db,
|
||||
clientHelper, queueMessageFactory, validationManager);
|
||||
|
||||
// Capture the delegating incoming message hook
|
||||
mqm.registerIncomingMessageHook(clientId, incomingQueueMessageHook);
|
||||
IncomingMessageHook delegate = captured.get();
|
||||
assertNotNull(delegate);
|
||||
// Pass the message to the hook
|
||||
delegate.incomingMessage(txn, message, messageMetadata);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private class EncodeQueueStateAction implements Action {
|
||||
|
||||
private final long outgoingPosition, incomingPosition;
|
||||
private final BdfList pending;
|
||||
|
||||
private EncodeQueueStateAction(long outgoingPosition,
|
||||
long incomingPosition, BdfList pending) {
|
||||
this.outgoingPosition = outgoingPosition;
|
||||
this.incomingPosition = incomingPosition;
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
BdfDictionary d = (BdfDictionary) invocation.getParameter(0);
|
||||
assertEquals(outgoingPosition, d.getLong("nextOut").longValue());
|
||||
assertEquals(incomingPosition, d.getLong("nextIn").longValue());
|
||||
assertEquals(pending, d.getList("pending"));
|
||||
return new byte[123];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("encodes a queue state");
|
||||
}
|
||||
}
|
||||
|
||||
private class DecodeQueueStateAction implements Action {
|
||||
|
||||
private final long outgoingPosition, incomingPosition;
|
||||
private final BdfList pending;
|
||||
|
||||
private DecodeQueueStateAction(long outgoingPosition,
|
||||
long incomingPosition, BdfList pending) {
|
||||
this.outgoingPosition = outgoingPosition;
|
||||
this.incomingPosition = incomingPosition;
|
||||
this.pending = pending;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
BdfDictionary d = new BdfDictionary();
|
||||
d.put("nextOut", outgoingPosition);
|
||||
d.put("nextIn", incomingPosition);
|
||||
d.put("pending", pending);
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("decodes a queue state");
|
||||
}
|
||||
}
|
||||
|
||||
private class CreateMessageAction implements Action {
|
||||
|
||||
@Override
|
||||
public Object invoke(Invocation invocation) throws Throwable {
|
||||
GroupId groupId = (GroupId) invocation.getParameter(0);
|
||||
long timestamp = (Long) invocation.getParameter(1);
|
||||
long queuePosition = (Long) invocation.getParameter(2);
|
||||
byte[] body = (byte[]) invocation.getParameter(3);
|
||||
byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH + body.length];
|
||||
MessageId id = new MessageId(TestUtils.getRandomId());
|
||||
return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("creates a message");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.briarproject.briar.client;
|
||||
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTree;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MessageTreeImplTest {
|
||||
|
||||
private MessageTree<TestNode> tree;
|
||||
|
||||
@Test
|
||||
public void testMessageTree() {
|
||||
tree = new MessageTreeImpl<TestNode>();
|
||||
testSimpleTree();
|
||||
tree.clear();
|
||||
testSimpleTree();
|
||||
}
|
||||
|
||||
private void testSimpleTree() {
|
||||
TestNode[] nodes = new TestNode[5];
|
||||
for (int i = 0; i < nodes.length; i++) {
|
||||
nodes[i] = new TestNode();
|
||||
}
|
||||
/*
|
||||
Construct the following tree:
|
||||
4
|
||||
1 ->
|
||||
0 ->
|
||||
2
|
||||
3
|
||||
*/
|
||||
nodes[0].setParentId(nodes[1].getId());
|
||||
nodes[2].setParentId(nodes[0].getId());
|
||||
nodes[3].setParentId(nodes[1].getId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
nodes[4].setTimestamp(timestamp - 5);
|
||||
nodes[1].setTimestamp(timestamp - 4);
|
||||
nodes[0].setTimestamp(timestamp - 3);
|
||||
nodes[3].setTimestamp(timestamp - 2);
|
||||
nodes[2].setTimestamp(timestamp - 1);
|
||||
// add all nodes except the last one
|
||||
tree.add(Arrays.asList(Arrays.copyOf(nodes, nodes.length - 1)));
|
||||
tree.add(Collections.singletonList(nodes[nodes.length - 1]));
|
||||
TestNode[] sortedNodes =
|
||||
tree.depthFirstOrder().toArray(new TestNode[5]);
|
||||
assertEquals(nodes[4], sortedNodes[0]);
|
||||
assertEquals(nodes[1], sortedNodes[1]);
|
||||
assertEquals(nodes[0], sortedNodes[2]);
|
||||
assertEquals(nodes[2], sortedNodes[3]);
|
||||
assertEquals(nodes[3], sortedNodes[4]);
|
||||
}
|
||||
|
||||
@NotNullByDefault
|
||||
private class TestNode implements MessageTree.MessageNode {
|
||||
|
||||
private final MessageId id = new MessageId(TestUtils.getRandomId());
|
||||
@Nullable
|
||||
private MessageId parentId;
|
||||
private long timestamp;
|
||||
|
||||
@Override
|
||||
public MessageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public MessageId getParentId() {
|
||||
return parentId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLevel(int level) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescendantCount(int descendantCount) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private void setParentId(MessageId parentId) {
|
||||
this.parentId = parentId;
|
||||
}
|
||||
|
||||
private void setTimestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.briar.forum;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ForumManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.forum;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
import org.briarproject.briar.BriarIntegrationTestComponent;
|
||||
@@ -17,7 +17,7 @@ import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
package org.briarproject.briar.forum;
|
||||
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.ValidatorTestCase;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Collection;
|
||||
|
||||
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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
||||
import static org.briarproject.briar.api.forum.ForumPostFactory.SIGNING_LABEL_POST;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ForumPostValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final MessageId parentId = new MessageId(TestUtils.getRandomId());
|
||||
private final String authorName =
|
||||
TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final byte[] authorPublicKey =
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
private final BdfList authorList = BdfList.of(authorName, authorPublicKey);
|
||||
private final String content =
|
||||
TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
|
||||
private final byte[] signature =
|
||||
TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
private final AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
private final Author author =
|
||||
new Author(authorId, authorName, authorPublicKey);
|
||||
private final BdfList signedWithParent = BdfList.of(groupId, timestamp,
|
||||
parentId.getBytes(), authorList, content);
|
||||
private final BdfList signedWithoutParent = BdfList.of(groupId, timestamp,
|
||||
null, authorList, content);
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBody() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBody() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, signature, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsNullParentId() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
authorPublicKey, signedWithoutParent);
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(null, authorList, content, signature));
|
||||
assertExpectedContext(messageContext, false, authorName);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonRawParentId() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(123, authorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortParentId() throws Exception {
|
||||
byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(invalidParentId, authorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongParentId() throws Exception {
|
||||
byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(invalidParentId, authorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullAuthorList() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, null, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonListAuthorList() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, 123, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortAuthorList() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, new BdfList(), content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongAuthorList() throws Exception {
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, BdfList.of(1, 2, 3), content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullAuthorName() throws Exception {
|
||||
BdfList invalidAuthorList = BdfList.of(null, authorPublicKey);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringAuthorName() throws Exception {
|
||||
BdfList invalidAuthorList = BdfList.of(123, authorPublicKey);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortAuthorName() throws Exception {
|
||||
BdfList invalidAuthorList = BdfList.of("", authorPublicKey);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthAuthorName() throws Exception {
|
||||
final String shortAuthorName = TestUtils.getRandomString(1);
|
||||
BdfList shortNameAuthorList =
|
||||
BdfList.of(shortAuthorName, authorPublicKey);
|
||||
final Author shortNameAuthor =
|
||||
new Author(authorId, shortAuthorName, authorPublicKey);
|
||||
final BdfList signedWithShortNameAuthor = BdfList.of(groupId, timestamp,
|
||||
parentId.getBytes(), shortNameAuthorList, content);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(shortAuthorName, authorPublicKey);
|
||||
will(returnValue(shortNameAuthor));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
authorPublicKey, signedWithShortNameAuthor);
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(parentId, shortNameAuthorList, content, signature));
|
||||
assertExpectedContext(messageContext, true, shortAuthorName);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongAuthorName() throws Exception {
|
||||
String invalidAuthorName =
|
||||
TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH + 1);
|
||||
BdfList invalidAuthorList =
|
||||
BdfList.of(invalidAuthorName, authorPublicKey);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullAuthorPublicKey() throws Exception {
|
||||
BdfList invalidAuthorList = BdfList.of(authorName, null);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonRawAuthorPublicKey() throws Exception {
|
||||
BdfList invalidAuthorList = BdfList.of(authorName, 123);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongAuthorPublicKey() throws Exception {
|
||||
byte[] invalidAuthorPublicKey =
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
|
||||
BdfList invalidAuthorList =
|
||||
BdfList.of(authorName, invalidAuthorPublicKey);
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, invalidAuthorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullContent() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, null, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringContent() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, 123, signature));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthContent() throws Exception {
|
||||
String shortContent = "";
|
||||
final BdfList signedWithShortContent = BdfList.of(groupId, timestamp,
|
||||
parentId.getBytes(), authorList, shortContent);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
authorPublicKey, signedWithShortContent);
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, shortContent, signature));
|
||||
assertExpectedContext(messageContext, true, authorName);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongContent() throws Exception {
|
||||
String invalidContent =
|
||||
TestUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH + 1);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, invalidContent, signature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullSignature() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, null));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonRawSignature() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, 123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongSignature() throws Exception {
|
||||
byte[] invalidSignature =
|
||||
TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH + 1);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, invalidSignature));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsIfVerifyingSignatureThrowsFormatException()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
authorPublicKey, signedWithParent);
|
||||
will(throwException(new FormatException()));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, signature));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsIfVerifyingSignatureThrowsGeneralSecurityException()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
|
||||
will(returnValue(author));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
authorPublicKey, signedWithParent);
|
||||
will(throwException(new GeneralSecurityException()));
|
||||
}});
|
||||
|
||||
ForumPostValidator v = new ForumPostValidator(authorFactory,
|
||||
clientHelper, metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(parentId, authorList, content, signature));
|
||||
}
|
||||
|
||||
private void assertExpectedContext(BdfMessageContext messageContext,
|
||||
boolean hasParent, String authorName) throws FormatException {
|
||||
BdfDictionary meta = messageContext.getDictionary();
|
||||
Collection<MessageId> dependencies = messageContext.getDependencies();
|
||||
if (hasParent) {
|
||||
assertEquals(4, meta.size());
|
||||
assertArrayEquals(parentId.getBytes(), meta.getRaw("parent"));
|
||||
assertEquals(1, dependencies.size());
|
||||
assertEquals(parentId, dependencies.iterator().next());
|
||||
} else {
|
||||
assertEquals(3, meta.size());
|
||||
assertEquals(0, dependencies.size());
|
||||
}
|
||||
assertEquals(timestamp, meta.getLong("timestamp").longValue());
|
||||
assertFalse(meta.getBoolean("read"));
|
||||
BdfDictionary authorMeta = meta.getDictionary("author");
|
||||
assertEquals(3, authorMeta.size());
|
||||
assertArrayEquals(authorId.getBytes(), authorMeta.getRaw("id"));
|
||||
assertEquals(authorName, authorMeta.getString("name"));
|
||||
assertArrayEquals(authorPublicKey, authorMeta.getRaw("publicKey"));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
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.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
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.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorFactory;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
import org.briarproject.bramble.api.properties.TransportPropertyManager;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ANSWERED;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.EXISTS;
|
||||
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.INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.LOCAL_AUTHOR_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.MAC_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
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.NOT_OUR_RESPONSE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
|
||||
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.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
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_ACK;
|
||||
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.hamcrest.Matchers.array;
|
||||
import static org.hamcrest.Matchers.samePropertyValuesAs;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IntroduceeManagerTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroduceeManager introduceeManager;
|
||||
private final DatabaseComponent db;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final AuthorFactory authorFactory;
|
||||
private final ContactManager contactManager;
|
||||
private final Clock clock;
|
||||
private final Contact introducer;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group localGroup1;
|
||||
private final Group introductionGroup1;
|
||||
private final Transaction txn;
|
||||
private final long time = 42L;
|
||||
private final Message localStateMessage;
|
||||
private final SessionId sessionId;
|
||||
private final Message message1;
|
||||
|
||||
public IntroduceeManagerTest() {
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
MessageSender messageSender = context.mock(MessageSender.class);
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
cryptoComponent = context.mock(CryptoComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
clock = context.mock(Clock.class);
|
||||
introductionGroupFactory =
|
||||
context.mock(IntroductionGroupFactory.class);
|
||||
TransportPropertyManager transportPropertyManager =
|
||||
context.mock(TransportPropertyManager.class);
|
||||
authorFactory = context.mock(AuthorFactory.class);
|
||||
contactManager = context.mock(ContactManager.class);
|
||||
IdentityManager identityManager = context.mock(IdentityManager.class);
|
||||
|
||||
introduceeManager = new IntroduceeManager(messageSender, db,
|
||||
clientHelper, clock, cryptoComponent, transportPropertyManager,
|
||||
authorFactory, contactManager, identityManager,
|
||||
introductionGroupFactory);
|
||||
|
||||
AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author0 = new Author(authorId0, "Introducer",
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
AuthorId localAuthorId = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId0 = new ContactId(234);
|
||||
introducer =
|
||||
new Contact(contactId0, author0, localAuthorId, true, true);
|
||||
|
||||
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author1 = new Author(authorId1, "Introducee1",
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author2 = new Author(authorId2, "Introducee2",
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId, true, true);
|
||||
|
||||
ClientId clientId = IntroductionManagerImpl.CLIENT_ID;
|
||||
localGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
clientId, new byte[0]);
|
||||
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
clientId, new byte[0]);
|
||||
|
||||
sessionId = new SessionId(TestUtils.getRandomId());
|
||||
localStateMessage = new Message(
|
||||
new MessageId(TestUtils.getRandomId()),
|
||||
localGroup1.getId(),
|
||||
time,
|
||||
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
message1 = new Message(
|
||||
new MessageId(TestUtils.getRandomId()),
|
||||
introductionGroup1.getId(),
|
||||
time,
|
||||
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
|
||||
txn = new Transaction(null, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingRequestMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
final BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
final BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn,
|
||||
localStateMessage.getId(), state);
|
||||
}});
|
||||
|
||||
introduceeManager.incomingMessage(txn, state, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingResponseMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
final BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
final BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
state.put(STATE, IntroduceeProtocolState.AWAIT_RESPONSES.ordinal());
|
||||
|
||||
// turn request message into a response
|
||||
msg.put(ACCEPT, true);
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
msg.put(TRANSPORT, new BdfDictionary());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn,
|
||||
localStateMessage.getId(), state);
|
||||
}});
|
||||
|
||||
introduceeManager.incomingMessage(txn, state, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDetectReplacedEphemeralPublicKey()
|
||||
throws DbException, FormatException, GeneralSecurityException {
|
||||
|
||||
// TODO MR !237 should use its new default initialization method here
|
||||
final BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, introductionGroup1.getId());
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(MESSAGE_ID, message1.getId());
|
||||
msg.put(MESSAGE_TIME, time);
|
||||
msg.put(NAME, introducee2.getAuthor().getName());
|
||||
msg.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
final BdfDictionary state =
|
||||
initializeSessionState(txn, introductionGroup1.getId(), msg);
|
||||
|
||||
// prepare state for incoming ACK
|
||||
state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal());
|
||||
state.put(ADDED_CONTACT_ID, 2);
|
||||
final byte[] nonce = TestUtils.getRandomBytes(42);
|
||||
state.put(NONCE, nonce);
|
||||
state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
|
||||
// create incoming ACK message
|
||||
final byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
final byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
BdfDictionary ack = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(GROUP_ID, introductionGroup1.getId()),
|
||||
new BdfEntry(MAC, mac),
|
||||
new BdfEntry(SIGNATURE, sig)
|
||||
);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce,
|
||||
introducee2.getAuthor().getPublicKey(), sig);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
|
||||
try {
|
||||
introduceeManager.incomingMessage(txn, state, ack);
|
||||
fail();
|
||||
} catch (DbException e) {
|
||||
// expected
|
||||
assertTrue(e.getCause() instanceof GeneralSecurityException);
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignatureVerification()
|
||||
throws FormatException, DbException, GeneralSecurityException {
|
||||
|
||||
final byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
|
||||
final byte[] nonce = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
final byte[] sig = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, publicKeyBytes);
|
||||
state.put(NONCE, nonce);
|
||||
state.put(SIGNATURE, sig);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(cryptoComponent).verify(SIGNING_LABEL_RESPONSE, nonce,
|
||||
publicKeyBytes, sig);
|
||||
will(returnValue(true));
|
||||
}});
|
||||
introduceeManager.verifySignature(state);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMacVerification()
|
||||
throws FormatException, DbException, GeneralSecurityException {
|
||||
|
||||
final byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
|
||||
final BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake"));
|
||||
final byte[] ePublicKeyBytes =
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
final byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
final SecretKey macKey = TestUtils.getSecretKey();
|
||||
|
||||
// move state to where it would be after an ACK arrived
|
||||
BdfDictionary state = new BdfDictionary();
|
||||
state.put(PUBLIC_KEY, publicKeyBytes);
|
||||
state.put(TRANSPORT, tp);
|
||||
state.put(TIME, time);
|
||||
state.put(E_PUBLIC_KEY, ePublicKeyBytes);
|
||||
state.put(MAC, mac);
|
||||
state.put(MAC_KEY, macKey.getBytes());
|
||||
|
||||
final byte[] signBytes = TestUtils.getRandomBytes(42);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(
|
||||
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
|
||||
will(returnValue(signBytes));
|
||||
//noinspection unchecked
|
||||
oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)),
|
||||
with(array(equal(signBytes))));
|
||||
will(returnValue(mac));
|
||||
}});
|
||||
introduceeManager.verifyMac(state);
|
||||
context.assertIsSatisfied();
|
||||
|
||||
// now produce wrong MAC
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(
|
||||
BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
|
||||
will(returnValue(signBytes));
|
||||
//noinspection unchecked
|
||||
oneOf(cryptoComponent).mac(with(samePropertyValuesAs(macKey)),
|
||||
with(array(equal(signBytes))));
|
||||
will(returnValue(TestUtils.getRandomBytes(MAC_LENGTH)));
|
||||
}});
|
||||
try {
|
||||
introduceeManager.verifyMac(state);
|
||||
fail();
|
||||
} catch(GeneralSecurityException e) {
|
||||
// expected
|
||||
}
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
private BdfDictionary initializeSessionState(final Transaction txn,
|
||||
final GroupId groupId, final BdfDictionary msg)
|
||||
throws DbException, FormatException {
|
||||
|
||||
final SecureRandom secureRandom = context.mock(SecureRandom.class);
|
||||
final Bytes salt = new Bytes(new byte[64]);
|
||||
final BdfDictionary groupMetadata = BdfDictionary.of(
|
||||
new BdfEntry(CONTACT, introducee1.getId().getInt())
|
||||
);
|
||||
final boolean contactExists = false;
|
||||
final BdfDictionary state = new BdfDictionary();
|
||||
state.put(STORAGE_ID, localStateMessage.getId());
|
||||
state.put(STATE, AWAIT_REQUEST.getValue());
|
||||
state.put(ROLE, ROLE_INTRODUCEE);
|
||||
state.put(GROUP_ID, groupId);
|
||||
state.put(INTRODUCER, introducer.getAuthor().getName());
|
||||
state.put(CONTACT_ID_1, introducer.getId().getInt());
|
||||
state.put(LOCAL_AUTHOR_ID, introducer.getLocalAuthorId().getBytes());
|
||||
state.put(NOT_OUR_RESPONSE, localStateMessage.getId());
|
||||
state.put(ANSWERED, false);
|
||||
state.put(EXISTS, contactExists);
|
||||
state.put(REMOTE_AUTHOR_ID, introducee2.getAuthor().getId());
|
||||
state.put(REMOTE_AUTHOR_IS_US, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(cryptoComponent).getSecureRandom();
|
||||
will(returnValue(secureRandom));
|
||||
oneOf(secureRandom).nextBytes(salt.getBytes());
|
||||
oneOf(introductionGroupFactory).createLocalGroup();
|
||||
will(returnValue(localGroup1));
|
||||
oneOf(clientHelper)
|
||||
.createMessage(localGroup1.getId(), time, BdfList.of(salt));
|
||||
will(returnValue(localStateMessage));
|
||||
|
||||
// who is making the introduction? who is the introducer?
|
||||
oneOf(clientHelper).getGroupMetadataAsDictionary(txn,
|
||||
groupId);
|
||||
will(returnValue(groupMetadata));
|
||||
oneOf(db).getContact(txn, introducer.getId());
|
||||
will(returnValue(introducer));
|
||||
|
||||
// create remote author to check if contact exists
|
||||
oneOf(authorFactory).createAuthor(introducee2.getAuthor().getName(),
|
||||
introducee2.getAuthor().getPublicKey());
|
||||
will(returnValue(introducee2.getAuthor()));
|
||||
oneOf(contactManager)
|
||||
.contactExists(txn, introducee2.getAuthor().getId(),
|
||||
introducer.getLocalAuthorId());
|
||||
will(returnValue(contactExists));
|
||||
|
||||
// checks if remote author is one of our identities
|
||||
oneOf(db).containsLocalAuthor(txn, introducee2.getAuthor().getId());
|
||||
will(returnValue(false));
|
||||
|
||||
// store session state
|
||||
oneOf(clientHelper)
|
||||
.addLocalMessage(txn, localStateMessage, state, false);
|
||||
}});
|
||||
|
||||
BdfDictionary result = introduceeManager.initialize(txn, groupId, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
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.CryptoComponent;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
|
||||
import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.CONTACT_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MESSAGE_TIME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.PUBLIC_KEY;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STATE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.STORAGE_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroducerManagerTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroducerManager introducerManager;
|
||||
private final CryptoComponent cryptoComponent;
|
||||
private final ClientHelper clientHelper;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final MessageSender messageSender;
|
||||
private final Clock clock;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group localGroup0;
|
||||
private final Group introductionGroup1;
|
||||
private final Group introductionGroup2;
|
||||
|
||||
public IntroducerManagerTest() {
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
messageSender = context.mock(MessageSender.class);
|
||||
cryptoComponent = context.mock(CryptoComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
clock = context.mock(Clock.class);
|
||||
introductionGroupFactory =
|
||||
context.mock(IntroductionGroupFactory.class);
|
||||
|
||||
introducerManager =
|
||||
new IntroducerManager(messageSender, clientHelper, clock,
|
||||
cryptoComponent, introductionGroupFactory);
|
||||
|
||||
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author1 = new Author(authorId1, "Introducee1",
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author2 = new Author(authorId2, "Introducee2",
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
|
||||
AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId2, true, true);
|
||||
|
||||
localGroup0 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
getClientId(), new byte[0]);
|
||||
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
getClientId(), new byte[0]);
|
||||
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
getClientId(), new byte[0]);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMakeIntroduction() throws DbException, FormatException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final long time = 42L;
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
final SecureRandom secureRandom = context.mock(SecureRandom.class);
|
||||
final Bytes salt = new Bytes(new byte[64]);
|
||||
final Message msg = new Message(new MessageId(TestUtils.getRandomId()),
|
||||
localGroup0.getId(), time, TestUtils.getRandomBytes(64));
|
||||
final BdfDictionary state = new BdfDictionary();
|
||||
state.put(SESSION_ID, msg.getId());
|
||||
state.put(STORAGE_ID, msg.getId());
|
||||
state.put(STATE, PREPARE_REQUESTS.getValue());
|
||||
state.put(ROLE, ROLE_INTRODUCER);
|
||||
state.put(GROUP_ID_1, introductionGroup1.getId());
|
||||
state.put(GROUP_ID_2, introductionGroup2.getId());
|
||||
state.put(CONTACT_1, introducee1.getAuthor().getName());
|
||||
state.put(CONTACT_2, introducee2.getAuthor().getName());
|
||||
state.put(CONTACT_ID_1, introducee1.getId().getInt());
|
||||
state.put(CONTACT_ID_2, introducee2.getId().getInt());
|
||||
state.put(AUTHOR_ID_1, introducee1.getAuthor().getId());
|
||||
state.put(AUTHOR_ID_2, introducee2.getAuthor().getId());
|
||||
final BdfDictionary state2 = (BdfDictionary) state.clone();
|
||||
state2.put(STATE, AWAIT_RESPONSES.getValue());
|
||||
|
||||
final BdfDictionary msg1 = new BdfDictionary();
|
||||
msg1.put(TYPE, TYPE_REQUEST);
|
||||
msg1.put(SESSION_ID, state.getRaw(SESSION_ID));
|
||||
msg1.put(GROUP_ID, state.getRaw(GROUP_ID_1));
|
||||
msg1.put(NAME, state.getString(CONTACT_2));
|
||||
msg1.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
|
||||
final BdfDictionary msg1send = (BdfDictionary) msg1.clone();
|
||||
msg1send.put(MESSAGE_TIME, time);
|
||||
|
||||
final BdfDictionary msg2 = new BdfDictionary();
|
||||
msg2.put(TYPE, TYPE_REQUEST);
|
||||
msg2.put(SESSION_ID, state.getRaw(SESSION_ID));
|
||||
msg2.put(GROUP_ID, state.getRaw(GROUP_ID_2));
|
||||
msg2.put(NAME, state.getString(CONTACT_1));
|
||||
msg2.put(PUBLIC_KEY, introducee1.getAuthor().getPublicKey());
|
||||
final BdfDictionary msg2send = (BdfDictionary) msg2.clone();
|
||||
msg2send.put(MESSAGE_TIME, time);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
// initialize and store session state
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(cryptoComponent).getSecureRandom();
|
||||
will(returnValue(secureRandom));
|
||||
oneOf(secureRandom).nextBytes(salt.getBytes());
|
||||
oneOf(introductionGroupFactory).createLocalGroup();
|
||||
will(returnValue(localGroup0));
|
||||
oneOf(clientHelper).createMessage(localGroup0.getId(), time,
|
||||
BdfList.of(salt));
|
||||
will(returnValue(msg));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee2);
|
||||
will(returnValue(introductionGroup2));
|
||||
oneOf(clientHelper).addLocalMessage(txn, msg, state, false);
|
||||
|
||||
// send message
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn, msg.getId(), state2);
|
||||
oneOf(messageSender).sendMessage(txn, msg1send);
|
||||
oneOf(messageSender).sendMessage(txn, msg2send);
|
||||
}});
|
||||
|
||||
introducerManager
|
||||
.makeIntroduction(txn, introducee1, introducee2, null, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
private ClientId getClientId() {
|
||||
return IntroductionManagerImpl.CLIENT_ID;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package org.briarproject.briar.introduction;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
@@ -51,8 +51,8 @@ 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.briar.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.briarproject.briar.BriarTestUtils.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;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.briar.TestPluginConfigModule;
|
||||
import org.briarproject.bramble.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.contact.ContactModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.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.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.MessageStatus;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE_INTRODUCER;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SESSION_ID;
|
||||
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.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroductionManagerImplTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final IntroductionManagerImpl introductionManager;
|
||||
private final IntroducerManager introducerManager;
|
||||
private final IntroduceeManager introduceeManager;
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final MessageTracker messageTracker;
|
||||
private final IntroductionGroupFactory introductionGroupFactory;
|
||||
private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
|
||||
private final MessageId storageId = new MessageId(sessionId.getBytes());
|
||||
private final long time = 42L;
|
||||
private final Contact introducee1;
|
||||
private final Contact introducee2;
|
||||
private final Group introductionGroup1;
|
||||
private final Group introductionGroup2;
|
||||
private final Message message1;
|
||||
private Transaction txn;
|
||||
|
||||
public IntroductionManagerImplTest() {
|
||||
AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author1 = new Author(authorId1, "Introducee1",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId1 = new ContactId(234);
|
||||
introducee1 =
|
||||
new Contact(contactId1, author1, localAuthorId1, true, true);
|
||||
|
||||
AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
|
||||
Author author2 = new Author(authorId2, "Introducee2",
|
||||
new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
|
||||
ContactId contactId2 = new ContactId(235);
|
||||
introducee2 =
|
||||
new Contact(contactId2, author2, localAuthorId2, true, true);
|
||||
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
clientId, new byte[0]);
|
||||
introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
|
||||
clientId, new byte[0]);
|
||||
|
||||
message1 = new Message(
|
||||
new MessageId(TestUtils.getRandomId()),
|
||||
introductionGroup1.getId(),
|
||||
time,
|
||||
TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
|
||||
);
|
||||
|
||||
// mock ALL THE THINGS!!!
|
||||
context = new Mockery();
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
introducerManager = context.mock(IntroducerManager.class);
|
||||
introduceeManager = context.mock(IntroduceeManager.class);
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
||||
messageTracker = context.mock(MessageTracker.class);
|
||||
introductionGroupFactory = context.mock(IntroductionGroupFactory.class);
|
||||
|
||||
introductionManager = new IntroductionManagerImpl(db, clientHelper,
|
||||
metadataParser, messageTracker, introducerManager,
|
||||
introduceeManager, introductionGroupFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMakeIntroduction() throws DbException, FormatException {
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(introducerManager)
|
||||
.makeIntroduction(txn, introducee1, introducee2, null,
|
||||
time);
|
||||
// get both introduction groups
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(introductionGroupFactory)
|
||||
.createIntroductionGroup(introducee2);
|
||||
will(returnValue(introductionGroup2));
|
||||
// track message for group 1
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
// track message for group 2
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup2.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.makeIntroduction(introducee1, introducee2, null, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptIntroduction() throws DbException, FormatException {
|
||||
final BdfDictionary state = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
|
||||
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
|
||||
);
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager).acceptIntroduction(txn, state, time);
|
||||
// track message
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.acceptIntroduction(introducee1.getId(), sessionId, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclineIntroduction() throws DbException, FormatException {
|
||||
final BdfDictionary state = BdfDictionary.of(
|
||||
new BdfEntry(GROUP_ID_1, introductionGroup1.getId()),
|
||||
new BdfEntry(GROUP_ID_2, introductionGroup2.getId())
|
||||
);
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager).declineIntroduction(txn, state, time);
|
||||
// track message
|
||||
oneOf(messageTracker).trackMessage(txn,
|
||||
introductionGroup1.getId(), time, true);
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.declineIntroduction(introducee1.getId(), sessionId, time);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetIntroductionMessages()
|
||||
throws DbException, FormatException {
|
||||
|
||||
final Map<MessageId, BdfDictionary> metadata = Collections.emptyMap();
|
||||
final Collection<MessageStatus> statuses = Collections.emptyList();
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, introducee1.getId());
|
||||
will(returnValue(introducee1));
|
||||
oneOf(introductionGroupFactory).createIntroductionGroup(introducee1);
|
||||
will(returnValue(introductionGroup1));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
introductionGroup1.getId());
|
||||
will(returnValue(metadata));
|
||||
oneOf(db).getMessageStatus(txn, introducee1.getId(),
|
||||
introductionGroup1.getId());
|
||||
will(returnValue(statuses));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
introductionManager.getIntroductionMessages(introducee1.getId());
|
||||
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingRequestMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
final BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
|
||||
final BdfDictionary state = new BdfDictionary();
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(introduceeManager)
|
||||
.initialize(txn, introductionGroup1.getId(), msg);
|
||||
will(returnValue(state));
|
||||
oneOf(introduceeManager)
|
||||
.incomingMessage(txn, state, msg);
|
||||
// track message
|
||||
oneOf(messageTracker).trackIncomingMessage(txn, message1);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.incomingMessage(txn, message1, new BdfList(), msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingResponseMessage()
|
||||
throws DbException, FormatException {
|
||||
|
||||
final BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_RESPONSE),
|
||||
new BdfEntry(SESSION_ID, sessionId)
|
||||
);
|
||||
|
||||
final BdfDictionary state = new BdfDictionary();
|
||||
state.put(ROLE, ROLE_INTRODUCER);
|
||||
state.put(GROUP_ID_1, introductionGroup1.getId());
|
||||
state.put(GROUP_ID_2, introductionGroup2.getId());
|
||||
|
||||
txn = new Transaction(null, false);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn, storageId);
|
||||
will(returnValue(state));
|
||||
oneOf(introducerManager).incomingMessage(txn, state, msg);
|
||||
// track message
|
||||
oneOf(messageTracker).trackIncomingMessage(txn, message1);
|
||||
}});
|
||||
|
||||
introductionManager
|
||||
.incomingMessage(txn, message1, new BdfList(), msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
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.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.system.SystemClock;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
|
||||
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_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_INTRODUCTION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.MSG;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.NAME;
|
||||
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_ABORT;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_REQUEST;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_RESPONSE;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class IntroductionValidatorTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context = new Mockery();
|
||||
private final Group group;
|
||||
private final Message message;
|
||||
private final IntroductionValidator validator;
|
||||
private final Clock clock = new SystemClock();
|
||||
|
||||
public IntroductionValidatorTest() {
|
||||
GroupId groupId = new GroupId(TestUtils.getRandomId());
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
byte[] descriptor = TestUtils.getRandomBytes(12);
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
|
||||
MessageId messageId = new MessageId(TestUtils.getRandomId());
|
||||
long timestamp = System.currentTimeMillis();
|
||||
byte[] raw = TestUtils.getRandomBytes(123);
|
||||
message = new Message(messageId, group.getId(), timestamp, raw);
|
||||
|
||||
|
||||
ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
|
||||
validator = new IntroductionValidator(clientHelper, metadataEncoder,
|
||||
clock);
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Requests
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionRequest() throws IOException {
|
||||
final byte[] sessionId = TestUtils.getRandomId();
|
||||
final String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
final byte[] publicKey =
|
||||
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
final String text =
|
||||
TestUtils.getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
|
||||
|
||||
BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
|
||||
name, publicKey, text);
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertEquals(name, result.getString(NAME));
|
||||
assertEquals(publicKey, result.getRaw(PUBLIC_KEY));
|
||||
assertEquals(text, result.getString(MSG));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithNoName() throws IOException {
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
|
||||
// no NAME is message
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithLongName()
|
||||
throws IOException {
|
||||
// too long NAME in message
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
msg.put(NAME, msg.get(NAME) + "x");
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionRequestWithWrongType()
|
||||
throws IOException {
|
||||
// wrong message type
|
||||
BdfDictionary msg = getValidIntroductionRequest();
|
||||
msg.put(TYPE, 324234);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString(NAME), msg.getRaw(PUBLIC_KEY));
|
||||
if (msg.containsKey(MSG)) body.add(msg.getString(MSG));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
private BdfDictionary getValidIntroductionRequest() throws FormatException {
|
||||
byte[] sessionId = TestUtils.getRandomId();
|
||||
String name = TestUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
String text = TestUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH);
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_REQUEST);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(NAME, name);
|
||||
msg.put(PUBLIC_KEY, publicKey);
|
||||
msg.put(MSG, text);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Responses
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateIntroductionAcceptResponse() throws IOException {
|
||||
byte[] groupId = TestUtils.getRandomId();
|
||||
byte[] sessionId = TestUtils.getRandomId();
|
||||
long time = clock.currentTimeMillis();
|
||||
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
String transportId = TestUtils
|
||||
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = BdfDictionary.of(
|
||||
new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH),
|
||||
TestUtils.getRandomString(MAX_PROPERTY_LENGTH))
|
||||
);
|
||||
BdfDictionary tp = BdfDictionary.of(
|
||||
new BdfEntry(transportId, tProps)
|
||||
);
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, groupId);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(ACCEPT, true);
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, publicKey);
|
||||
msg.put(TRANSPORT, tp);
|
||||
|
||||
BdfList body = BdfList.of(TYPE_RESPONSE, msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
|
||||
|
||||
final BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_RESPONSE), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertEquals(true, result.getBoolean(ACCEPT));
|
||||
assertEquals(publicKey, result.getRaw(E_PUBLIC_KEY));
|
||||
assertEquals(tp, result.getDictionary(TRANSPORT));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateIntroductionDeclineResponse()
|
||||
throws IOException {
|
||||
BdfDictionary msg = getValidIntroductionResponse(false);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT));
|
||||
|
||||
BdfDictionary result = validator.validateMessage(message, group, body)
|
||||
.getDictionary();
|
||||
|
||||
assertFalse(result.getBoolean(ACCEPT));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithoutAccept()
|
||||
throws IOException {
|
||||
BdfDictionary msg = getValidIntroductionResponse(false);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithBrokenTp()
|
||||
throws IOException {
|
||||
BdfDictionary msg = getValidIntroductionResponse(true);
|
||||
BdfDictionary tp = msg.getDictionary(TRANSPORT);
|
||||
tp.put(TestUtils
|
||||
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
|
||||
msg.put(TRANSPORT, tp);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getRaw(E_PUBLIC_KEY), msg.getDictionary(TRANSPORT));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionResponseWithoutPublicKey()
|
||||
throws IOException {
|
||||
BdfDictionary msg = getValidIntroductionResponse(true);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getBoolean(ACCEPT), msg.getLong(TIME),
|
||||
msg.getDictionary(TRANSPORT));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
private BdfDictionary getValidIntroductionResponse(boolean accept)
|
||||
throws FormatException {
|
||||
|
||||
byte[] groupId = TestUtils.getRandomId();
|
||||
byte[] sessionId = TestUtils.getRandomId();
|
||||
long time = clock.currentTimeMillis();
|
||||
byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
String transportId = TestUtils
|
||||
.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
|
||||
BdfDictionary tProps = BdfDictionary.of(
|
||||
new BdfEntry(TestUtils.getRandomString(MAX_PROPERTY_LENGTH),
|
||||
TestUtils.getRandomString(MAX_PROPERTY_LENGTH))
|
||||
);
|
||||
BdfDictionary tp = BdfDictionary.of(
|
||||
new BdfEntry(transportId, tProps)
|
||||
);
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_RESPONSE);
|
||||
msg.put(GROUP_ID, groupId);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
msg.put(ACCEPT, accept);
|
||||
if (accept) {
|
||||
msg.put(TIME, time);
|
||||
msg.put(E_PUBLIC_KEY, publicKey);
|
||||
msg.put(TRANSPORT, tp);
|
||||
}
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction ACK
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionAck() throws IOException {
|
||||
byte[] sessionId = TestUtils.getRandomId();
|
||||
byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
|
||||
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
|
||||
assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
assertArrayEquals(mac, result.getRaw(MAC));
|
||||
assertArrayEquals(sig, result.getRaw(SIGNATURE));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateTooLongIntroductionAck() throws IOException {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
|
||||
new BdfEntry("garbage", TestUtils.getRandomString(255))
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString("garbage"));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateIntroductionAckWithLongSessionId()
|
||||
throws IOException {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
//
|
||||
// Introduction Abort
|
||||
//
|
||||
|
||||
@Test
|
||||
public void testValidateProperIntroductionAbort() throws IOException {
|
||||
byte[] sessionId = TestUtils.getRandomId();
|
||||
|
||||
BdfDictionary msg = new BdfDictionary();
|
||||
msg.put(TYPE, TYPE_ABORT);
|
||||
msg.put(SESSION_ID, sessionId);
|
||||
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
|
||||
|
||||
BdfDictionary result =
|
||||
validator.validateMessage(message, group, body).getDictionary();
|
||||
|
||||
assertEquals(Long.valueOf(TYPE_ABORT), result.getLong(TYPE));
|
||||
assertEquals(sessionId, result.getRaw(SESSION_ID));
|
||||
context.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testValidateTooLongIntroductionAbort() throws IOException {
|
||||
BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ABORT),
|
||||
new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
|
||||
new BdfEntry("garbage", TestUtils.getRandomString(255))
|
||||
);
|
||||
BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
|
||||
msg.getString("garbage"));
|
||||
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package org.briarproject.briar.introduction;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
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.data.MetadataEncoder;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.sync.ClientId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageQueueManager;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
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.SESSION_ID;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.SIGNATURE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE;
|
||||
import static org.briarproject.briar.api.introduction.IntroductionConstants.TYPE_ACK;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class MessageSenderTest extends BriarTestCase {
|
||||
|
||||
private final Mockery context;
|
||||
private final MessageSender messageSender;
|
||||
private final DatabaseComponent db;
|
||||
private final ClientHelper clientHelper;
|
||||
private final MetadataEncoder metadataEncoder;
|
||||
private final MessageQueueManager messageQueueManager;
|
||||
private final Clock clock;
|
||||
|
||||
public MessageSenderTest() {
|
||||
context = new Mockery();
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
clientHelper = context.mock(ClientHelper.class);
|
||||
metadataEncoder =
|
||||
context.mock(MetadataEncoder.class);
|
||||
messageQueueManager =
|
||||
context.mock(MessageQueueManager.class);
|
||||
clock = context.mock(Clock.class);
|
||||
|
||||
messageSender = new MessageSender(db, clientHelper, clock,
|
||||
metadataEncoder, messageQueueManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendMessage() throws DbException, FormatException {
|
||||
final Transaction txn = new Transaction(null, false);
|
||||
final Group privateGroup =
|
||||
new Group(new GroupId(TestUtils.getRandomId()),
|
||||
new ClientId(TestUtils.getRandomString(5)),
|
||||
new byte[0]);
|
||||
final SessionId sessionId = new SessionId(TestUtils.getRandomId());
|
||||
byte[] mac = TestUtils.getRandomBytes(42);
|
||||
byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
final long time = 42L;
|
||||
final BdfDictionary msg = BdfDictionary.of(
|
||||
new BdfEntry(TYPE, TYPE_ACK),
|
||||
new BdfEntry(GROUP_ID, privateGroup.getId()),
|
||||
new BdfEntry(SESSION_ID, sessionId),
|
||||
new BdfEntry(MAC, mac),
|
||||
new BdfEntry(SIGNATURE, sig)
|
||||
);
|
||||
final BdfList bodyList =
|
||||
BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
|
||||
final byte[] body = TestUtils.getRandomBytes(8);
|
||||
final Metadata metadata = new Metadata();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).toByteArray(bodyList);
|
||||
will(returnValue(body));
|
||||
oneOf(db).getGroup(txn, privateGroup.getId());
|
||||
will(returnValue(privateGroup));
|
||||
oneOf(metadataEncoder).encode(msg);
|
||||
will(returnValue(metadata));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
oneOf(messageQueueManager)
|
||||
.sendMessage(txn, privateGroup, time, body, metadata);
|
||||
}});
|
||||
|
||||
messageSender.sendMessage(txn, msg);
|
||||
|
||||
context.assertIsSatisfied();
|
||||
assertFalse(txn.isCommitted());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.crypto.PrivateKey;
|
||||
@@ -11,6 +10,7 @@ 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.BriarTestCase;
|
||||
import org.briarproject.briar.api.forum.ForumConstants;
|
||||
import org.briarproject.briar.api.forum.ForumPost;
|
||||
import org.briarproject.briar.api.forum.ForumPostFactory;
|
||||
@@ -20,7 +20,7 @@ import org.junit.Test;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.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;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestLifecycleModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.briar.TestLifecycleModule;
|
||||
import org.briarproject.bramble.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.client.ClientModule;
|
||||
import org.briarproject.bramble.crypto.CryptoModule;
|
||||
import org.briarproject.bramble.data.DataModule;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class MessagingManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.ValidatorTestCase;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
|
||||
import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class PrivateMessageValidatorTest extends ValidatorTestCase {
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBody() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, new BdfList());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBody() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of("", 123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullContent() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of((String) null));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringContent() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of(123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongContent() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
String invalidContent =
|
||||
TestUtils.getRandomString(MAX_PRIVATE_MESSAGE_BODY_LENGTH + 1);
|
||||
v.validateMessage(message, group, BdfList.of(invalidContent));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMaxLengthContent() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
String content =
|
||||
TestUtils.getRandomString(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
|
||||
BdfMessageContext messageContext =
|
||||
v.validateMessage(message, group, BdfList.of(content));
|
||||
assertExpectedContext(messageContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthContent() throws Exception {
|
||||
PrivateMessageValidator v = new PrivateMessageValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext =
|
||||
v.validateMessage(message, group, BdfList.of(""));
|
||||
assertExpectedContext(messageContext);
|
||||
}
|
||||
|
||||
private void assertExpectedContext(BdfMessageContext messageContext)
|
||||
throws FormatException {
|
||||
BdfDictionary meta = messageContext.getDictionary();
|
||||
assertEquals(3, meta.size());
|
||||
assertEquals(timestamp, meta.getLong("timestamp").longValue());
|
||||
assertFalse(meta.getBoolean("local"));
|
||||
assertFalse(meta.getBoolean(MSG_KEY_READ));
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.BriarTestCase;
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestUtils;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.crypto.SecretKey;
|
||||
@@ -23,6 +22,7 @@ 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.BriarTestCase;
|
||||
import org.briarproject.briar.api.messaging.MessagingManager;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessage;
|
||||
import org.briarproject.briar.api.messaging.PrivateMessageFactory;
|
||||
@@ -36,10 +36,10 @@ 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.briarproject.briar.TestPluginConfigModule.MAX_LATENCY;
|
||||
import static org.briarproject.briar.TestPluginConfigModule.TRANSPORT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package org.briarproject.briar.messaging;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.TestPluginConfigModule;
|
||||
import org.briarproject.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.briar.TestPluginConfigModule;
|
||||
import org.briarproject.bramble.TestSeedProviderModule;
|
||||
import org.briarproject.bramble.api.contact.ContactManager;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.identity.IdentityManager;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.briar.BriarIntegrationTest;
|
||||
@@ -22,7 +22,7 @@ import java.util.Collection;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@@ -0,0 +1,648 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.bramble.ValidatorTestCase;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.InvalidMessageException;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.privategroup.MessageType;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomString;
|
||||
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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.GroupMessageFactory.SIGNING_LABEL_JOIN;
|
||||
import static org.briarproject.briar.api.privategroup.GroupMessageFactory.SIGNING_LABEL_POST;
|
||||
import static org.briarproject.briar.api.privategroup.MessageType.JOIN;
|
||||
import static org.briarproject.briar.api.privategroup.MessageType.POST;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_ID;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_NAME;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_PARENT_MSG_ID;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_PREVIOUS_MSG_ID;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_READ;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_TIMESTAMP;
|
||||
import static org.briarproject.briar.privategroup.GroupConstants.KEY_TYPE;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupMessageValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final PrivateGroupFactory privateGroupFactory =
|
||||
context.mock(PrivateGroupFactory.class);
|
||||
private final GroupInvitationFactory groupInvitationFactory =
|
||||
context.mock(GroupInvitationFactory.class);
|
||||
|
||||
private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final String memberName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
private final byte[] memberKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
private final byte[] creatorSignature =
|
||||
getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
private final Author member =
|
||||
new Author(new AuthorId(getRandomId()), memberName, memberKey);
|
||||
private final Author creator =
|
||||
new Author(new AuthorId(getRandomId()), creatorName, creatorKey);
|
||||
private final long inviteTimestamp = 42L;
|
||||
private final PrivateGroup privateGroup = new PrivateGroup(group,
|
||||
getRandomString(MAX_GROUP_NAME_LENGTH), creator,
|
||||
getRandomBytes(GROUP_SALT_LENGTH));
|
||||
private final BdfList token = BdfList.of("token");
|
||||
private final MessageId parentId = new MessageId(getRandomId());
|
||||
private final MessageId previousMsgId = new MessageId(getRandomId());
|
||||
private final String postContent =
|
||||
getRandomString(MAX_GROUP_POST_BODY_LENGTH);
|
||||
|
||||
private final GroupMessageValidator validator =
|
||||
new GroupMessageValidator(privateGroupFactory, clientHelper,
|
||||
metadataEncoder, clock, authorFactory,
|
||||
groupInvitationFactory);
|
||||
|
||||
// JOIN message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortJoinMessage() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongJoinMessage() throws Exception {
|
||||
expectCreateAuthor(creator);
|
||||
BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null,
|
||||
signature, "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithTooShortMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), "", memberKey, null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithTooLongMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(),
|
||||
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey, null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithNullMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), null, memberKey, null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithNonStringMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), getRandomBytes(5), memberKey,
|
||||
null, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithTooShortMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, new byte[0], null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithTooLongMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName,
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), null, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithNoullMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, null, null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithNonRawMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, "not raw", null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithNonListInvitation() throws Exception {
|
||||
expectCreateAuthor(creator);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey,
|
||||
"not a list", signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsCreatorJoin() throws Exception {
|
||||
expectJoinMessage(creator, null, true, true);
|
||||
BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey,
|
||||
null, signature);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertExpectedMessageContext(messageContext, JOIN, creator,
|
||||
Collections.<MessageId>emptyList());
|
||||
assertTrue(messageContext.getDictionary()
|
||||
.getBoolean(KEY_INITIAL_JOIN_MSG));
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMemberJoinWithNullInvitation() throws Exception {
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, null,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithTooShortInvitation() throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithTooLongInvitation() throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, creatorSignature, "");
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMemberJoinWithEqualInvitationTime()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(message.getTimestamp(), creatorSignature);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMemberJoinWithLaterInvitationTime()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(message.getTimestamp() + 1,
|
||||
creatorSignature);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithNullInvitationTime()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(null, creatorSignature);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithNonLongInvitationTime()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of("not long", creatorSignature);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithTooShortCreatorSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, new byte[0]);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinWithTooLongCreatorSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp,
|
||||
getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithNullCreatorSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, null);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMemberJoinWithNonRawCreatorSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, "not raw");
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMemberJoinWithInvalidCreatorSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
|
||||
expectJoinMessage(member, invite, false, true);
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMemberJoinWithInvalidMemberSignature()
|
||||
throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
|
||||
expectJoinMessage(member, invite, true, false);
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMemberJoin() throws Exception {
|
||||
BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
|
||||
expectJoinMessage(member, invite, true, true);
|
||||
BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
|
||||
signature);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertExpectedMessageContext(messageContext, JOIN, member,
|
||||
Collections.<MessageId>emptyList());
|
||||
assertFalse(messageContext.getDictionary()
|
||||
.getBoolean(KEY_INITIAL_JOIN_MSG));
|
||||
}
|
||||
|
||||
private void expectCreateAuthor(final Author member) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(member.getName(),
|
||||
member.getPublicKey());
|
||||
will(returnValue(member));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectParsePrivateGroup() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(privateGroupFactory).parsePrivateGroup(group);
|
||||
will(returnValue(privateGroup));
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectJoinMessage(final Author member, final BdfList invite,
|
||||
final boolean creatorSigValid, final boolean memberSigValid)
|
||||
throws Exception {
|
||||
final BdfList signed = BdfList.of(group.getId(), message.getTimestamp(),
|
||||
JOIN.getInt(), member.getName(), member.getPublicKey(), invite);
|
||||
expectCreateAuthor(member);
|
||||
expectParsePrivateGroup();
|
||||
context.checking(new Expectations() {{
|
||||
if (invite != null) {
|
||||
oneOf(groupInvitationFactory).createInviteToken(creator.getId(),
|
||||
member.getId(), privateGroup.getId(), inviteTimestamp);
|
||||
will(returnValue(token));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE,
|
||||
creatorSignature, creatorKey, token);
|
||||
if (!memberSigValid)
|
||||
will(throwException(new GeneralSecurityException()));
|
||||
}
|
||||
if (memberSigValid) {
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_JOIN,
|
||||
signature, member.getPublicKey(), signed);
|
||||
if (!creatorSigValid)
|
||||
will(throwException(new GeneralSecurityException()));
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
// POST Message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortPost() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongPost() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, signature, "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), "", memberKey, parentId,
|
||||
previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(),
|
||||
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNullMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), null, memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonStringMemberName() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), getRandomBytes(5), memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, new byte[0],
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName,
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), parentId,
|
||||
previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNullMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, null,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonRawMemberKey() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, "not raw",
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortParentId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
getRandomBytes(MessageId.LENGTH - 1), previousMsgId,
|
||||
postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongParentId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
getRandomBytes(MessageId.LENGTH + 1), previousMsgId,
|
||||
postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonRawParentId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
"not raw", previousMsgId, postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortPreviousMsgId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, getRandomBytes(MessageId.LENGTH - 1), postContent,
|
||||
signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongPreviousMsgId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, getRandomBytes(MessageId.LENGTH + 1), postContent,
|
||||
signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNullPreviousMsgId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, null, postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonRawPreviousMsgId() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, "not raw", postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortContent() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, "", signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongContent() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId,
|
||||
getRandomString(MAX_GROUP_POST_BODY_LENGTH + 1), signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNullContent() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, null, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonStringContent() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, getRandomBytes(5), signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooShortSignature() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, new byte[0]);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithTooLongSignature() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent,
|
||||
getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNullSignature() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent,null);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsPostWithNonRawSignature() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, "not raw");
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsPostWithInvalidSignature() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
expectPostMessage(member, parentId, false);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsPost() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
expectPostMessage(member, parentId, true);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertExpectedMessageContext(messageContext, POST, member,
|
||||
Arrays.asList(parentId, previousMsgId));
|
||||
assertArrayEquals(previousMsgId.getBytes(),
|
||||
messageContext.getDictionary().getRaw(KEY_PREVIOUS_MSG_ID));
|
||||
assertArrayEquals(parentId.getBytes(),
|
||||
messageContext.getDictionary().getRaw(KEY_PARENT_MSG_ID));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsTopLevelPost() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, null,
|
||||
previousMsgId, postContent, signature);
|
||||
expectPostMessage(member, null, true);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertExpectedMessageContext(messageContext, POST, member,
|
||||
Collections.singletonList(previousMsgId));
|
||||
assertArrayEquals(previousMsgId.getBytes(),
|
||||
messageContext.getDictionary().getRaw(KEY_PREVIOUS_MSG_ID));
|
||||
assertFalse(
|
||||
messageContext.getDictionary().containsKey(KEY_PARENT_MSG_ID));
|
||||
}
|
||||
|
||||
private void expectPostMessage(final Author member,
|
||||
final MessageId parentId, final boolean sigValid) throws Exception {
|
||||
final BdfList signed = BdfList.of(group.getId(), message.getTimestamp(),
|
||||
POST.getInt(), member.getName(), member.getPublicKey(),
|
||||
parentId == null ? null : parentId.getBytes(),
|
||||
previousMsgId.getBytes(), postContent);
|
||||
expectCreateAuthor(member);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
|
||||
member.getPublicKey(), signed);
|
||||
if (!sigValid) will(throwException(new GeneralSecurityException()));
|
||||
}});
|
||||
}
|
||||
|
||||
private void assertExpectedMessageContext(BdfMessageContext c,
|
||||
MessageType type, Author member,
|
||||
Collection<MessageId> dependencies) throws FormatException {
|
||||
BdfDictionary d = c.getDictionary();
|
||||
assertEquals(type.getInt(), d.getLong(KEY_TYPE).intValue());
|
||||
assertEquals(message.getTimestamp(),
|
||||
d.getLong(KEY_TIMESTAMP).longValue());
|
||||
assertFalse(d.getBoolean(KEY_READ));
|
||||
assertEquals(member.getId().getBytes(), d.getRaw(KEY_MEMBER_ID));
|
||||
assertEquals(member.getName(), d.getString(KEY_MEMBER_NAME));
|
||||
assertEquals(member.getPublicKey(), d.getRaw(KEY_MEMBER_PUBLIC_KEY));
|
||||
assertEquals(dependencies, c.getDependencies());
|
||||
}
|
||||
|
||||
@Test(expected = InvalidMessageException.class)
|
||||
public void testRejectsMessageWithUnknownType() throws Exception {
|
||||
BdfList body = BdfList.of(POST.getInt() + 1, memberName, memberKey,
|
||||
parentId, previousMsgId, postContent, signature);
|
||||
expectCreateAuthor(member);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.privategroup;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
@@ -21,8 +21,8 @@ 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.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.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;
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.BrambleMockTestCase;
|
||||
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.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
import org.briarproject.bramble.api.db.Transaction;
|
||||
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.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessageFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.jmock.Expectations;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupManager.*;
|
||||
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
|
||||
|
||||
protected final DatabaseComponent db =
|
||||
context.mock(DatabaseComponent.class);
|
||||
protected final ClientHelper clientHelper =
|
||||
context.mock(ClientHelper.class);
|
||||
protected final PrivateGroupFactory privateGroupFactory =
|
||||
context.mock(PrivateGroupFactory.class);
|
||||
protected final PrivateGroupManager privateGroupManager =
|
||||
context.mock(PrivateGroupManager.class);
|
||||
protected final MessageParser messageParser =
|
||||
context.mock(MessageParser.class);
|
||||
protected final GroupMessageFactory groupMessageFactory =
|
||||
context.mock(GroupMessageFactory.class);
|
||||
protected final IdentityManager identityManager =
|
||||
context.mock(IdentityManager.class);
|
||||
protected final MessageEncoder messageEncoder =
|
||||
context.mock(MessageEncoder.class);
|
||||
protected final MessageTracker messageTracker =
|
||||
context.mock(MessageTracker.class);
|
||||
protected final Clock clock = context.mock(Clock.class);
|
||||
|
||||
protected final Transaction txn = new Transaction(null, false);
|
||||
protected final GroupId contactGroupId = new GroupId(getRandomId());
|
||||
protected final GroupId privateGroupId = new GroupId(getRandomId());
|
||||
protected final Group privateGroupGroup =
|
||||
new Group(privateGroupId, CLIENT_ID, getRandomBytes(5));
|
||||
private final AuthorId authorId = new AuthorId(getRandomId());
|
||||
protected final Author author =
|
||||
new Author(authorId, "Author", getRandomBytes(12));
|
||||
protected final PrivateGroup privateGroup =
|
||||
new PrivateGroup(privateGroupGroup, "Private Group", author,
|
||||
getRandomBytes(8));
|
||||
protected final byte[] signature = getRandomBytes(42);
|
||||
protected final MessageId lastLocalMessageId = new MessageId(getRandomId());
|
||||
protected final MessageId lastRemoteMessageId =
|
||||
new MessageId(getRandomId());
|
||||
protected final long localTimestamp = 3L;
|
||||
protected final long inviteTimestamp = 6L;
|
||||
protected final long messageTimestamp = inviteTimestamp + 1;
|
||||
protected final MessageId messageId = new MessageId(getRandomId());
|
||||
protected final Message message =
|
||||
new Message(messageId, contactGroupId, messageTimestamp,
|
||||
getRandomBytes(42));
|
||||
private final BdfDictionary meta =
|
||||
BdfDictionary.of(new BdfEntry("me", "ta"));
|
||||
protected final ContactId contactId = new ContactId(5);
|
||||
protected final Contact contact =
|
||||
new Contact(contactId, author, new AuthorId(getRandomId()), true,
|
||||
true);
|
||||
|
||||
protected final InviteMessage inviteMessage =
|
||||
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, privateGroup.getName(),
|
||||
privateGroup.getCreator(), privateGroup.getSalt(), "msg",
|
||||
signature);
|
||||
protected final JoinMessage joinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastRemoteMessageId);
|
||||
protected final LeaveMessage leaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastRemoteMessageId);
|
||||
protected final AbortMessage abortMessage =
|
||||
new AbortMessage(messageId, contactGroupId, privateGroupId,
|
||||
inviteTimestamp + 1);
|
||||
|
||||
protected void assertSessionConstantsUnchanged(Session s1, Session s2) {
|
||||
assertEquals(s1.getRole(), s2.getRole());
|
||||
assertEquals(s1.getContactGroupId(), s2.getContactGroupId());
|
||||
assertEquals(s1.getPrivateGroupId(), s2.getPrivateGroupId());
|
||||
}
|
||||
|
||||
protected void assertSessionRecordedSentMessage(Session s) {
|
||||
assertEquals(messageId, s.getLastLocalMessageId());
|
||||
assertEquals(lastRemoteMessageId, s.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, s.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, s.getInviteTimestamp());
|
||||
}
|
||||
|
||||
protected void expectGetLocalTimestamp(final long time) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will(returnValue(time));
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectSendInviteMessage(final String msg)
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeInviteMessage(contactGroupId, privateGroupId,
|
||||
inviteTimestamp, privateGroup.getName(), author,
|
||||
privateGroup.getSalt(), msg, signature);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
expectSendMessage(INVITE, true);
|
||||
}
|
||||
|
||||
protected void expectSendJoinMessage(final JoinMessage m, boolean visible)
|
||||
throws Exception {
|
||||
expectGetLocalTimestamp(messageTimestamp);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeJoinMessage(m.getContactGroupId(),
|
||||
m.getPrivateGroupId(), m.getTimestamp(),
|
||||
lastLocalMessageId);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
expectSendMessage(JOIN, visible);
|
||||
}
|
||||
|
||||
protected void expectSendLeaveMessage(boolean visible) throws Exception {
|
||||
expectGetLocalTimestamp(messageTimestamp);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeLeaveMessage(contactGroupId, privateGroupId,
|
||||
messageTimestamp, lastLocalMessageId);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
expectSendMessage(LEAVE, visible);
|
||||
}
|
||||
|
||||
protected void expectSendAbortMessage() throws Exception {
|
||||
expectGetLocalTimestamp(messageTimestamp);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.encodeAbortMessage(contactGroupId, privateGroupId,
|
||||
messageTimestamp);
|
||||
will(returnValue(message));
|
||||
}});
|
||||
expectSendMessage(ABORT, false);
|
||||
}
|
||||
|
||||
private void expectSendMessage(final MessageType type,
|
||||
final boolean visible) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(type, privateGroupId,
|
||||
message.getTimestamp(), true, true, visible, false);
|
||||
will(returnValue(meta));
|
||||
oneOf(clientHelper).addLocalMessage(txn, message, meta, true);
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectSetPrivateGroupVisibility(final Group.Visibility v)
|
||||
throws Exception {
|
||||
expectGetContactId();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).setGroupVisibility(txn, contactId, privateGroupId, v);
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectGetContactId() throws Exception {
|
||||
final BdfDictionary groupMeta = BdfDictionary
|
||||
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, contactId.getInt()));
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper)
|
||||
.getGroupMetadataAsDictionary(txn, contactGroupId);
|
||||
will(returnValue(groupMeta));
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectIsSubscribedPrivateGroup()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, privateGroupId);
|
||||
will(returnValue(true));
|
||||
oneOf(db).getGroup(txn, privateGroupId);
|
||||
will(returnValue(privateGroupGroup));
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectIsNotSubscribedPrivateGroup()
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).containsGroup(txn, privateGroupId);
|
||||
will(returnValue(false));
|
||||
}});
|
||||
}
|
||||
|
||||
protected void expectMarkMessageVisibleInUi(final MessageId m,
|
||||
final boolean visible)
|
||||
throws Exception {
|
||||
final BdfDictionary d = new BdfDictionary();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).setVisibleInUi(d, visible);
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn, m, d);
|
||||
}});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.DISSOLVED;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.ERROR;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.INVITED;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.JOINED;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.LEFT;
|
||||
import static org.briarproject.briar.privategroup.invitation.CreatorState.START;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CreatorProtocolEngineTest extends AbstractProtocolEngineTest {
|
||||
|
||||
private final CreatorProtocolEngine engine =
|
||||
new CreatorProtocolEngine(db, clientHelper, privateGroupManager,
|
||||
privateGroupFactory, groupMessageFactory, identityManager,
|
||||
messageParser, messageEncoder, messageTracker, clock);
|
||||
|
||||
private CreatorSession getDefaultSession(CreatorState state) {
|
||||
return new CreatorSession(contactGroupId, privateGroupId,
|
||||
lastLocalMessageId, lastRemoteMessageId, localTimestamp,
|
||||
inviteTimestamp, state);
|
||||
}
|
||||
|
||||
// onInviteAction
|
||||
|
||||
@Test
|
||||
public void testOnInviteActionFromStart() throws Exception {
|
||||
CreatorSession session =
|
||||
new CreatorSession(contactGroupId, privateGroupId);
|
||||
String message = "Invitation Message";
|
||||
|
||||
expectOnLocalInvite(message);
|
||||
CreatorSession newSession =
|
||||
engine.onInviteAction(txn, session, message, inviteTimestamp,
|
||||
signature);
|
||||
assertEquals(INVITED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(null, newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteActionFromStartWithNullMessage() throws Exception {
|
||||
CreatorSession session =
|
||||
new CreatorSession(contactGroupId, privateGroupId);
|
||||
|
||||
expectOnLocalInvite(null);
|
||||
CreatorSession newSession =
|
||||
engine.onInviteAction(txn, session, null, inviteTimestamp,
|
||||
signature);
|
||||
assertEquals(INVITED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(null, newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
private void expectOnLocalInvite(final String msg) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getGroup(txn, privateGroupId);
|
||||
will(returnValue(privateGroupGroup));
|
||||
oneOf(privateGroupFactory).parsePrivateGroup(privateGroupGroup);
|
||||
will(returnValue(privateGroup));
|
||||
oneOf(messageTracker).trackOutgoingMessage(txn, message);
|
||||
}});
|
||||
expectSendInviteMessage(msg);
|
||||
expectGetLocalTimestamp(messageTimestamp);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnInviteActionFromInvited() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(INVITED), null,
|
||||
inviteTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnInviteActionFromJoined() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(JOINED), null,
|
||||
inviteTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnInviteActionFromLeft() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(LEFT), null,
|
||||
inviteTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnInviteActionFromDissolved() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(DISSOLVED), null,
|
||||
inviteTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnInviteActionFromError() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
|
||||
inviteTimestamp, signature);
|
||||
}
|
||||
|
||||
// onJoinAction
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnJoinActionFails() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(START));
|
||||
}
|
||||
|
||||
// onLeaveAction
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromStart() throws Exception {
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromDissolved() throws Exception {
|
||||
CreatorSession session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromError() throws Exception {
|
||||
CreatorSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromInvited() throws Exception {
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
|
||||
expectOnLocalLeave();
|
||||
CreatorSession newSession = engine.onLeaveAction(txn, session);
|
||||
assertEquals(DISSOLVED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromJoined() throws Exception {
|
||||
CreatorSession session = getDefaultSession(JOINED);
|
||||
|
||||
expectOnLocalLeave();
|
||||
CreatorSession newSession = engine.onLeaveAction(txn, session);
|
||||
assertEquals(DISSOLVED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromLeft() throws Exception {
|
||||
CreatorSession session = getDefaultSession(LEFT);
|
||||
|
||||
expectOnLocalLeave();
|
||||
CreatorSession newSession = engine.onLeaveAction(txn, session);
|
||||
assertEquals(DISSOLVED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(lastRemoteMessageId, newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
private void expectOnLocalLeave() throws Exception {
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
expectSendLeaveMessage(false);
|
||||
}
|
||||
|
||||
// onMemberAddedAction
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedAction() throws Exception {
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
|
||||
session = getDefaultSession(INVITED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
|
||||
session = getDefaultSession(JOINED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
|
||||
session = getDefaultSession(LEFT);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
|
||||
session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
|
||||
session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
// onInviteMessage
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageInAnyStateWhenSubscribed() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession session = getDefaultSession(LEFT);
|
||||
CreatorSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertEquals(ERROR, newSession.getState());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
session = getDefaultSession(START);
|
||||
newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageInAnyStateWhenNotSubscribed()
|
||||
throws Exception {
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
CreatorSession session = getDefaultSession(LEFT);
|
||||
CreatorSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertEquals(ERROR, newSession.getState());
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
session = getDefaultSession(START);
|
||||
newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
// onJoinMessage
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromStart() throws Exception {
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromJoined() throws Exception {
|
||||
CreatorSession session = getDefaultSession(JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromLeft() throws Exception {
|
||||
CreatorSession session = getDefaultSession(LEFT);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromInvitedWithWrongTimestamp()
|
||||
throws Exception {
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromInvitedWithInvalidDependency()
|
||||
throws Exception {
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
JoinMessage invalidJoinMessage =
|
||||
new JoinMessage(messageId, contactGroupId, privateGroupId,
|
||||
inviteTimestamp + 1, messageId);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, invalidJoinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromInvited() throws Exception {
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
JoinMessage properJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, inviteTimestamp + 1,
|
||||
lastRemoteMessageId);
|
||||
|
||||
expectSendJoinMessage(properJoinMessage, false);
|
||||
expectMarkMessageVisibleInUi(properJoinMessage.getId(), true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageTracker)
|
||||
.trackMessage(txn, contactGroupId, inviteTimestamp + 1,
|
||||
false);
|
||||
}});
|
||||
expectGetContactId();
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
CreatorSession newSession =
|
||||
engine.onJoinMessage(txn, session, properJoinMessage);
|
||||
assertEquals(JOINED, newSession.getState());
|
||||
assertEquals(messageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(properJoinMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromDissolved() throws Exception {
|
||||
CreatorSession session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromError() throws Exception {
|
||||
CreatorSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
|
||||
}
|
||||
|
||||
// onLeaveMessage
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromStart() throws Exception {
|
||||
LeaveMessage leaveMessage =
|
||||
new LeaveMessage(messageId, contactGroupId, privateGroupId,
|
||||
inviteTimestamp, lastLocalMessageId);
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLeft() throws Exception {
|
||||
CreatorSession session = getDefaultSession(LEFT);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvitedWithWrongTime() throws Exception {
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvitedWithWrongDependency()
|
||||
throws Exception {
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(messageId, contactGroupId, privateGroupId,
|
||||
inviteTimestamp + 1, lastLocalMessageId);
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvited()
|
||||
throws Exception {
|
||||
LeaveMessage properLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, inviteTimestamp + 1,
|
||||
lastRemoteMessageId);
|
||||
CreatorSession session = getDefaultSession(INVITED);
|
||||
|
||||
expectMarkMessageVisibleInUi(properLeaveMessage.getId(), true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageTracker)
|
||||
.trackMessage(txn, contactGroupId, inviteTimestamp + 1,
|
||||
false);
|
||||
}});
|
||||
expectGetContactId();
|
||||
CreatorSession newSession =
|
||||
engine.onLeaveMessage(txn, session, properLeaveMessage);
|
||||
assertEquals(START, newSession.getState());
|
||||
assertEquals(lastLocalMessageId, newSession.getLastLocalMessageId());
|
||||
assertEquals(properLeaveMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(localTimestamp, newSession.getLocalTimestamp());
|
||||
assertEquals(inviteTimestamp, newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromDissolved() throws Exception {
|
||||
CreatorSession session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session,
|
||||
engine.onLeaveMessage(txn, session, leaveMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromError() throws Exception {
|
||||
CreatorSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session,
|
||||
engine.onLeaveMessage(txn, session, leaveMessage));
|
||||
}
|
||||
|
||||
// onAbortMessage
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
|
||||
expectIsNotSubscribedPrivateGroup();
|
||||
expectSendAbortMessage();
|
||||
CreatorSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenSubscribed() throws Exception {
|
||||
CreatorSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
CreatorSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private void expectAbortWhenSubscribedToGroup() throws Exception {
|
||||
expectIsSubscribedPrivateGroup();
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
expectSendAbortMessage();
|
||||
}
|
||||
|
||||
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
|
||||
expectIsNotSubscribedPrivateGroup();
|
||||
expectSendAbortMessage();
|
||||
}
|
||||
|
||||
private void assertSessionAborted(CreatorSession oldSession,
|
||||
CreatorSession newSession) throws Exception {
|
||||
assertEquals(ERROR, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(oldSession, newSession);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,900 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.client.ClientHelper;
|
||||
import org.briarproject.bramble.api.client.ContactGroupFactory;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfEntry;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.bramble.api.data.MetadataParser;
|
||||
import org.briarproject.bramble.api.db.DatabaseComponent;
|
||||
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.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.sync.Group;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.MessageTracker;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupManager;
|
||||
import org.briarproject.briar.api.privategroup.invitation.GroupInvitationItem;
|
||||
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.jmock.AbstractExpectations;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static junit.framework.TestCase.fail;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomString;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
|
||||
import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupInvitationManagerImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
|
||||
private final ClientHelper clientHelper = context.mock(ClientHelper.class);
|
||||
private final ContactGroupFactory contactGroupFactory =
|
||||
context.mock(ContactGroupFactory.class);
|
||||
private final PrivateGroupFactory privateGroupFactory =
|
||||
context.mock(PrivateGroupFactory.class);
|
||||
private final PrivateGroupManager privateGroupManager =
|
||||
context.mock(PrivateGroupManager.class);
|
||||
private final MessageParser messageParser =
|
||||
context.mock(MessageParser.class);
|
||||
private final SessionParser sessionParser =
|
||||
context.mock(SessionParser.class);
|
||||
private final SessionEncoder sessionEncoder =
|
||||
context.mock(SessionEncoder.class);
|
||||
private final ProtocolEngineFactory engineFactory =
|
||||
context.mock(ProtocolEngineFactory.class);
|
||||
|
||||
private final CreatorProtocolEngine creatorEngine;
|
||||
private final InviteeProtocolEngine inviteeEngine;
|
||||
private final PeerProtocolEngine peerEngine;
|
||||
private final CreatorSession creatorSession;
|
||||
private final InviteeSession inviteeSession;
|
||||
private final PeerSession peerSession;
|
||||
private final MessageMetadata messageMetadata;
|
||||
|
||||
private final GroupInvitationManagerImpl groupInvitationManager;
|
||||
|
||||
private final Group localGroup =
|
||||
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
|
||||
private final Transaction txn = new Transaction(null, false);
|
||||
private final ContactId contactId = new ContactId(0);
|
||||
private final Author author =
|
||||
new Author(new AuthorId(getRandomId()), getRandomString(5),
|
||||
getRandomBytes(5));
|
||||
private final Contact contact =
|
||||
new Contact(contactId, author, new AuthorId(getRandomId()), true,
|
||||
true);
|
||||
private final Group contactGroup =
|
||||
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
|
||||
private final Group privateGroup =
|
||||
new Group(new GroupId(getRandomId()), CLIENT_ID, getRandomBytes(5));
|
||||
private final BdfDictionary meta = BdfDictionary.of(new BdfEntry("m", "e"));
|
||||
private final Message message =
|
||||
new Message(new MessageId(getRandomId()), contactGroup.getId(),
|
||||
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
|
||||
private final BdfList body = BdfList.of("body");
|
||||
private final SessionId sessionId =
|
||||
new SessionId(privateGroup.getId().getBytes());
|
||||
private final Message storageMessage =
|
||||
new Message(new MessageId(getRandomId()), contactGroup.getId(),
|
||||
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
|
||||
private final BdfDictionary bdfSession =
|
||||
BdfDictionary.of(new BdfEntry("f", "o"));
|
||||
private final Map<MessageId, BdfDictionary> oneResult =
|
||||
Collections.singletonMap(storageMessage.getId(), bdfSession);
|
||||
private final Map<MessageId, BdfDictionary> noResults =
|
||||
Collections.emptyMap();
|
||||
|
||||
|
||||
public GroupInvitationManagerImplTest() {
|
||||
context.setImposteriser(ClassImposteriser.INSTANCE);
|
||||
creatorEngine = context.mock(CreatorProtocolEngine.class);
|
||||
inviteeEngine = context.mock(InviteeProtocolEngine.class);
|
||||
peerEngine = context.mock(PeerProtocolEngine.class);
|
||||
|
||||
creatorSession = context.mock(CreatorSession.class);
|
||||
inviteeSession = context.mock(InviteeSession.class);
|
||||
peerSession = context.mock(PeerSession.class);
|
||||
|
||||
messageMetadata = context.mock(MessageMetadata.class);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(engineFactory).createCreatorEngine();
|
||||
will(returnValue(creatorEngine));
|
||||
oneOf(engineFactory).createInviteeEngine();
|
||||
will(returnValue(inviteeEngine));
|
||||
oneOf(engineFactory).createPeerEngine();
|
||||
will(returnValue(peerEngine));
|
||||
oneOf(contactGroupFactory).createLocalGroup(CLIENT_ID);
|
||||
will(returnValue(localGroup));
|
||||
}});
|
||||
MetadataParser metadataParser = context.mock(MetadataParser.class);
|
||||
MessageTracker messageTracker = context.mock(MessageTracker.class);
|
||||
groupInvitationManager =
|
||||
new GroupInvitationManagerImpl(db, clientHelper, metadataParser,
|
||||
messageTracker, contactGroupFactory,
|
||||
privateGroupFactory, privateGroupManager, messageParser,
|
||||
sessionParser, sessionEncoder, engineFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLocalState() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).addGroup(txn, localGroup);
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
}});
|
||||
expectAddingContact(contact, true);
|
||||
groupInvitationManager.createLocalState(txn);
|
||||
}
|
||||
|
||||
private void expectAddingContact(final Contact c,
|
||||
final boolean contactExists) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).containsGroup(txn, contactGroup.getId());
|
||||
will(returnValue(contactExists));
|
||||
}});
|
||||
if (contactExists) return;
|
||||
|
||||
final BdfDictionary meta = BdfDictionary
|
||||
.of(new BdfEntry(GROUP_KEY_CONTACT_ID, c.getId().getInt()));
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).addGroup(txn, contactGroup);
|
||||
oneOf(db).setGroupVisibility(txn, c.getId(), contactGroup.getId(),
|
||||
SHARED);
|
||||
oneOf(clientHelper)
|
||||
.mergeGroupMetadata(txn, contactGroup.getId(), meta);
|
||||
oneOf(db).getGroups(txn, PrivateGroupManager.CLIENT_ID);
|
||||
will(returnValue(Collections.singletonList(privateGroup)));
|
||||
oneOf(privateGroupManager)
|
||||
.isMember(txn, privateGroup.getId(), c.getAuthor());
|
||||
will(returnValue(true));
|
||||
}});
|
||||
expectAddingMember(privateGroup.getId(), c);
|
||||
}
|
||||
|
||||
private void expectAddingMember(final GroupId g, final Contact c)
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, c);
|
||||
will(returnValue(contactGroup));
|
||||
}});
|
||||
expectGetSession(noResults, new SessionId(g.getBytes()),
|
||||
contactGroup.getId());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(peerEngine).onMemberAddedAction(with(txn),
|
||||
with(any(PeerSession.class)));
|
||||
will(returnValue(peerSession));
|
||||
}});
|
||||
expectStoreSession(peerSession, storageMessage.getId());
|
||||
expectCreateStorageId();
|
||||
}
|
||||
|
||||
private void expectCreateStorageId() throws DbException {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(clientHelper)
|
||||
.createMessageForStoringMetadata(contactGroup.getId());
|
||||
will(returnValue(storageMessage));
|
||||
oneOf(db).addLocalMessage(txn, storageMessage, new Metadata(),
|
||||
false);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectStoreSession(final Session session,
|
||||
final MessageId storageId) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(sessionEncoder).encodeSession(session);
|
||||
will(returnValue(meta));
|
||||
oneOf(clientHelper).mergeMessageMetadata(txn, storageId, meta);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectGetSession(final Map<MessageId, BdfDictionary> results,
|
||||
final SessionId sessionId, final GroupId contactGroupId)
|
||||
throws Exception {
|
||||
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(sessionParser).getSessionQuery(sessionId);
|
||||
will(returnValue(query));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroupId, query);
|
||||
will(returnValue(results));
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingContact() throws Exception {
|
||||
expectAddingContact(contact, false);
|
||||
groupInvitationManager.addingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingContact() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).removeGroup(txn, contactGroup);
|
||||
}});
|
||||
groupInvitationManager.removingContact(txn, contact);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testIncomingUnknownMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.INVITEE, ABORT);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingFirstInviteMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingFirstJoinMessage() throws Exception {
|
||||
expectFirstIncomingMessage(Role.PEER, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingInviteMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, INVITE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingJoinMessageForCreator() throws Exception {
|
||||
expectIncomingMessage(Role.CREATOR, JOIN);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingLeaveMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, LEAVE);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIncomingAbortMessage() throws Exception {
|
||||
expectIncomingMessage(Role.INVITEE, ABORT);
|
||||
groupInvitationManager.incomingMessage(txn, message, body, meta);
|
||||
}
|
||||
|
||||
private void expectFirstIncomingMessage(Role role, MessageType type)
|
||||
throws Exception {
|
||||
expectParseMessageMetadata();
|
||||
expectGetSession(noResults, sessionId, contactGroup.getId());
|
||||
Session session = expectHandleFirstMessage(role, messageMetadata, type);
|
||||
if (session != null) {
|
||||
expectCreateStorageId();
|
||||
expectStoreSession(session, storageMessage.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void expectParseMessageMetadata() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).parseMetadata(meta);
|
||||
will(returnValue(messageMetadata));
|
||||
oneOf(messageMetadata).getPrivateGroupId();
|
||||
will(returnValue(privateGroup.getId()));
|
||||
}});
|
||||
|
||||
}
|
||||
|
||||
private void expectIncomingMessage(Role role, MessageType type)
|
||||
throws Exception {
|
||||
BdfDictionary bdfSession = BdfDictionary.of(new BdfEntry("f", "o"));
|
||||
expectIncomingMessageWithSession(role, type, bdfSession);
|
||||
}
|
||||
|
||||
private void expectIncomingMessageWithSession(final Role role,
|
||||
final MessageType type, final BdfDictionary bdfSession)
|
||||
throws Exception {
|
||||
expectParseMessageMetadata();
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
Session session = expectHandleMessage(role, messageMetadata, bdfSession,
|
||||
type);
|
||||
expectStoreSession(session, storageMessage.getId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Session expectHandleFirstMessage(Role role,
|
||||
final MessageMetadata messageMetadata, final MessageType type)
|
||||
throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageMetadata).getPrivateGroupId();
|
||||
will(returnValue(privateGroup.getId()));
|
||||
oneOf(messageMetadata).getMessageType();
|
||||
will(returnValue(type));
|
||||
}});
|
||||
if (type == ABORT || type == LEAVE) return null;
|
||||
|
||||
AbstractProtocolEngine engine;
|
||||
Session session;
|
||||
if (type == INVITE) {
|
||||
assertEquals(Role.INVITEE, role);
|
||||
engine = inviteeEngine;
|
||||
session = inviteeSession;
|
||||
} else if (type == JOIN) {
|
||||
assertEquals(Role.PEER, role);
|
||||
engine = peerEngine;
|
||||
session = peerSession;
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
expectIndividualMessage(type, engine, session);
|
||||
return session;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Session expectHandleMessage(final Role role,
|
||||
final MessageMetadata messageMetadata, final BdfDictionary state,
|
||||
final MessageType type) throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageMetadata).getMessageType();
|
||||
will(returnValue(type));
|
||||
oneOf(sessionParser).getRole(state);
|
||||
will(returnValue(role));
|
||||
}});
|
||||
if (role == Role.CREATOR) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(sessionParser)
|
||||
.parseCreatorSession(contactGroup.getId(), state);
|
||||
will(returnValue(creatorSession));
|
||||
}});
|
||||
expectIndividualMessage(type, creatorEngine, creatorSession);
|
||||
return creatorSession;
|
||||
} else if (role == Role.INVITEE) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(sessionParser)
|
||||
.parseInviteeSession(contactGroup.getId(), state);
|
||||
will(returnValue(inviteeSession));
|
||||
}});
|
||||
expectIndividualMessage(type, inviteeEngine, inviteeSession);
|
||||
return inviteeSession;
|
||||
} else if (role == Role.PEER) {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(sessionParser)
|
||||
.parsePeerSession(contactGroup.getId(), state);
|
||||
will(returnValue(peerSession));
|
||||
}});
|
||||
expectIndividualMessage(type, peerEngine, peerSession);
|
||||
return peerSession;
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
private <S extends Session> void expectIndividualMessage(
|
||||
final MessageType type, final ProtocolEngine<S> engine,
|
||||
final S session) throws Exception {
|
||||
if (type == INVITE) {
|
||||
final InviteMessage msg = context.mock(InviteMessage.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).parseInviteMessage(message, body);
|
||||
will(returnValue(msg));
|
||||
oneOf(engine).onInviteMessage(with(txn),
|
||||
with(AbstractExpectations.<S>anything()), with(msg));
|
||||
will(returnValue(session));
|
||||
}});
|
||||
} else if (type == JOIN) {
|
||||
final JoinMessage msg = context.mock(JoinMessage.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).parseJoinMessage(message, body);
|
||||
will(returnValue(msg));
|
||||
oneOf(engine).onJoinMessage(with(txn),
|
||||
with(AbstractExpectations.<S>anything()), with(msg));
|
||||
will(returnValue(session));
|
||||
}});
|
||||
} else if (type == LEAVE) {
|
||||
final LeaveMessage msg = context.mock(LeaveMessage.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).parseLeaveMessage(message, body);
|
||||
will(returnValue(msg));
|
||||
oneOf(engine).onLeaveMessage(with(txn),
|
||||
with(AbstractExpectations.<S>anything()), with(msg));
|
||||
will(returnValue(session));
|
||||
}});
|
||||
} else if (type == ABORT) {
|
||||
final AbortMessage msg = context.mock(AbortMessage.class);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).parseAbortMessage(message, body);
|
||||
will(returnValue(msg));
|
||||
oneOf(engine).onAbortMessage(with(txn),
|
||||
with(AbstractExpectations.<S>anything()), with(msg));
|
||||
will(returnValue(session));
|
||||
}});
|
||||
} else {
|
||||
fail();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendFirstInvitation() throws Exception {
|
||||
final String msg = "Invitation text for first invitation";
|
||||
final long time = 42L;
|
||||
final byte[] signature = getRandomBytes(42);
|
||||
|
||||
expectGetSession(noResults, sessionId, contactGroup.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
}});
|
||||
expectCreateStorageId();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(creatorEngine).onInviteAction(with(txn),
|
||||
with(any(CreatorSession.class)), with(msg), with(time),
|
||||
with(signature));
|
||||
will(returnValue(creatorSession));
|
||||
}});
|
||||
expectStoreSession(creatorSession, storageMessage.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
groupInvitationManager.sendInvitation(privateGroup.getId(), contactId,
|
||||
msg, time, signature);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendSubsequentInvitation() throws Exception {
|
||||
final String msg = "Invitation text for subsequent invitation";
|
||||
final long time = 43L;
|
||||
final byte[] signature = getRandomBytes(43);
|
||||
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(sessionParser)
|
||||
.parseCreatorSession(contactGroup.getId(), bdfSession);
|
||||
will(returnValue(creatorSession));
|
||||
oneOf(creatorEngine).onInviteAction(with(txn),
|
||||
with(any(CreatorSession.class)), with(msg), with(time),
|
||||
with(signature));
|
||||
will(returnValue(creatorSession));
|
||||
}});
|
||||
expectStoreSession(creatorSession, storageMessage.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
groupInvitationManager.sendInvitation(privateGroup.getId(), contactId,
|
||||
msg, time, signature);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRespondToInvitationWithoutSession() throws Exception {
|
||||
final SessionId sessionId = new SessionId(getRandomId());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
expectGetSession(noResults, sessionId, contactGroup.getId());
|
||||
|
||||
groupInvitationManager.respondToInvitation(contactId, sessionId, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptInvitationWithSession() throws Exception {
|
||||
expectRespondToInvitation(sessionId, true);
|
||||
groupInvitationManager
|
||||
.respondToInvitation(contactId, sessionId, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclineInvitationWithSession() throws Exception {
|
||||
expectRespondToInvitation(sessionId, false);
|
||||
groupInvitationManager
|
||||
.respondToInvitation(contactId, sessionId, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptInvitationWithGroupId() throws Exception {
|
||||
PrivateGroup pg = new PrivateGroup(privateGroup,
|
||||
getRandomString(MAX_GROUP_NAME_LENGTH), author,
|
||||
getRandomBytes(GROUP_SALT_LENGTH));
|
||||
|
||||
expectRespondToInvitation(sessionId, true);
|
||||
groupInvitationManager.respondToInvitation(contactId, pg, true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeclineInvitationWithGroupId() throws Exception {
|
||||
PrivateGroup pg = new PrivateGroup(privateGroup,
|
||||
getRandomString(MAX_GROUP_NAME_LENGTH), author,
|
||||
getRandomBytes(GROUP_SALT_LENGTH));
|
||||
|
||||
expectRespondToInvitation(sessionId, false);
|
||||
groupInvitationManager.respondToInvitation(contactId, pg, false);
|
||||
}
|
||||
|
||||
private void expectRespondToInvitation(final SessionId sessionId,
|
||||
final boolean accept) throws Exception {
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(sessionParser)
|
||||
.parseInviteeSession(contactGroup.getId(), bdfSession);
|
||||
will(returnValue(inviteeSession));
|
||||
if (accept) oneOf(inviteeEngine).onJoinAction(txn, inviteeSession);
|
||||
else oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession);
|
||||
will(returnValue(inviteeSession));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
expectStoreSession(inviteeSession, storageMessage.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRevealRelationship() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(sessionParser)
|
||||
.parsePeerSession(contactGroup.getId(), bdfSession);
|
||||
will(returnValue(peerSession));
|
||||
oneOf(peerEngine).onJoinAction(txn, peerSession);
|
||||
will(returnValue(peerSession));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
expectStoreSession(peerSession, storageMessage.getId());
|
||||
|
||||
groupInvitationManager
|
||||
.revealRelationship(contactId, privateGroup.getId());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testRevealRelationshipWithoutSession() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
expectGetSession(noResults, sessionId, contactGroup.getId());
|
||||
|
||||
groupInvitationManager
|
||||
.revealRelationship(contactId, privateGroup.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInvitationMessages() throws Exception {
|
||||
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
|
||||
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
final BdfDictionary meta2 = BdfDictionary.of(new BdfEntry("m2", "e"));
|
||||
final Map<MessageId, BdfDictionary> results =
|
||||
new HashMap<MessageId, BdfDictionary>();
|
||||
results.put(message.getId(), meta);
|
||||
results.put(messageId2, meta2);
|
||||
long time1 = 1L, time2 = 2L;
|
||||
final MessageMetadata messageMetadata1 =
|
||||
new MessageMetadata(INVITE, privateGroup.getId(), time1, true,
|
||||
true, true, true);
|
||||
final MessageMetadata messageMetadata2 =
|
||||
new MessageMetadata(JOIN, privateGroup.getId(), time2, true,
|
||||
true, true, true);
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(messageParser).getMessagesVisibleInUiQuery();
|
||||
will(returnValue(query));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId(), query);
|
||||
will(returnValue(results));
|
||||
// first message
|
||||
oneOf(messageParser).parseMetadata(meta);
|
||||
will(returnValue(messageMetadata1));
|
||||
oneOf(db).getMessageStatus(txn, contactId, message.getId());
|
||||
oneOf(clientHelper).getMessage(txn, message.getId());
|
||||
will(returnValue(message));
|
||||
oneOf(clientHelper).toList(message);
|
||||
will(returnValue(body));
|
||||
oneOf(messageParser).parseInviteMessage(message, body);
|
||||
// second message
|
||||
oneOf(messageParser).parseMetadata(meta2);
|
||||
will(returnValue(messageMetadata2));
|
||||
oneOf(db).getMessageStatus(txn, contactId, messageId2);
|
||||
// end transaction
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
Collection<InvitationMessage> messages =
|
||||
groupInvitationManager.getInvitationMessages(contactId);
|
||||
assertEquals(2, messages.size());
|
||||
for (InvitationMessage m : messages) {
|
||||
assertEquals(contactGroup.getId(), m.getGroupId());
|
||||
assertEquals(contactId, m.getContactId());
|
||||
if (m.getId().equals(message.getId())) {
|
||||
assertTrue(m instanceof GroupInvitationRequest);
|
||||
assertEquals(time1, m.getTimestamp());
|
||||
} else if (m.getId().equals(messageId2)) {
|
||||
assertTrue(m instanceof GroupInvitationResponse);
|
||||
assertEquals(time2, m.getTimestamp());
|
||||
} else {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInvitations() throws Exception {
|
||||
final BdfDictionary query = BdfDictionary.of(new BdfEntry("q", "u"));
|
||||
final MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
final BdfDictionary meta2 = BdfDictionary.of(new BdfEntry("m2", "e"));
|
||||
final Map<MessageId, BdfDictionary> results =
|
||||
new HashMap<MessageId, BdfDictionary>();
|
||||
results.put(message.getId(), meta);
|
||||
results.put(messageId2, meta2);
|
||||
final Message message2 = new Message(messageId2, contactGroup.getId(),
|
||||
0L, getRandomBytes(MESSAGE_HEADER_LENGTH + 1));
|
||||
long time1 = 1L, time2 = 2L;
|
||||
final String groupName = getRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
final byte[] salt = getRandomBytes(GROUP_SALT_LENGTH);
|
||||
final InviteMessage inviteMessage1 =
|
||||
new InviteMessage(message.getId(), contactGroup.getId(),
|
||||
privateGroup.getId(), time1, groupName, author, salt,
|
||||
null, getRandomBytes(5));
|
||||
final InviteMessage inviteMessage2 =
|
||||
new InviteMessage(message.getId(), contactGroup.getId(),
|
||||
privateGroup.getId(), time2, groupName, author, salt,
|
||||
null, getRandomBytes(5));
|
||||
final PrivateGroup pg = new PrivateGroup(privateGroup, groupName,
|
||||
author, salt);
|
||||
final BdfList body2 = BdfList.of("body2");
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser).getInvitesAvailableToAnswerQuery();
|
||||
will(returnValue(query));
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
|
||||
contactGroup.getId(), query);
|
||||
will(returnValue(results));
|
||||
// message 1
|
||||
oneOf(clientHelper).getMessage(txn, message.getId());
|
||||
will(returnValue(message));
|
||||
oneOf(clientHelper).toList(message);
|
||||
will(returnValue(body));
|
||||
oneOf(messageParser).parseInviteMessage(message, body);
|
||||
will(returnValue(inviteMessage1));
|
||||
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
|
||||
salt);
|
||||
will(returnValue(pg));
|
||||
// message 2
|
||||
oneOf(clientHelper).getMessage(txn, messageId2);
|
||||
will(returnValue(message2));
|
||||
oneOf(clientHelper).toList(message2);
|
||||
will(returnValue(body2));
|
||||
oneOf(messageParser).parseInviteMessage(message2, body2);
|
||||
will(returnValue(inviteMessage2));
|
||||
oneOf(privateGroupFactory).createPrivateGroup(groupName, author,
|
||||
salt);
|
||||
will(returnValue(pg));
|
||||
// end transaction
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
|
||||
Collection<GroupInvitationItem> items =
|
||||
groupInvitationManager.getInvitations();
|
||||
assertEquals(2, items.size());
|
||||
for (GroupInvitationItem i : items) {
|
||||
assertEquals(contact, i.getCreator());
|
||||
assertEquals(author, i.getCreator().getAuthor());
|
||||
assertEquals(privateGroup.getId(), i.getId());
|
||||
assertEquals(groupName, i.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsInvitationAllowed() throws Exception {
|
||||
expectIsInvitationAllowed(CreatorState.START);
|
||||
assertTrue(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsNotInvitationAllowed() throws Exception {
|
||||
expectIsInvitationAllowed(CreatorState.DISSOLVED);
|
||||
assertFalse(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
|
||||
expectIsInvitationAllowed(CreatorState.ERROR);
|
||||
assertFalse(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
|
||||
expectIsInvitationAllowed(CreatorState.INVITED);
|
||||
assertFalse(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
|
||||
expectIsInvitationAllowed(CreatorState.JOINED);
|
||||
assertFalse(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
|
||||
expectIsInvitationAllowed(CreatorState.LEFT);
|
||||
assertFalse(groupInvitationManager
|
||||
.isInvitationAllowed(contact, privateGroup.getId()));
|
||||
}
|
||||
|
||||
private void expectIsInvitationAllowed(final CreatorState state)
|
||||
throws Exception {
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(db).startTransaction(true);
|
||||
will(returnValue(txn));
|
||||
oneOf(sessionParser)
|
||||
.parseCreatorSession(contactGroup.getId(), bdfSession);
|
||||
will(returnValue(creatorSession));
|
||||
oneOf(creatorSession).getState();
|
||||
will(returnValue(state));
|
||||
oneOf(db).commitTransaction(txn);
|
||||
oneOf(db).endTransaction(txn);
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddingMember() throws Exception {
|
||||
expectAddingMember(privateGroup.getId(), contact);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContactsByAuthorId(txn, author.getId());
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
}});
|
||||
groupInvitationManager.addingMember(txn, privateGroup.getId(), author);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovingGroupEndsSessions() throws Exception {
|
||||
final Contact contact2 = new Contact(new ContactId(2), author,
|
||||
author.getId(), true, true);
|
||||
final Contact contact3 = new Contact(new ContactId(3), author,
|
||||
author.getId(), true, true);
|
||||
final Collection<Contact> contacts =
|
||||
Arrays.asList(contact, contact2, contact3);
|
||||
|
||||
final Group contactGroup2 = new Group(new GroupId(getRandomId()),
|
||||
CLIENT_ID, getRandomBytes(5));
|
||||
final Group contactGroup3 = new Group(new GroupId(getRandomId()),
|
||||
CLIENT_ID, getRandomBytes(5));
|
||||
|
||||
final MessageId storageId2 = new MessageId(getRandomId());
|
||||
final MessageId storageId3 = new MessageId(getRandomId());
|
||||
final BdfDictionary bdfSession2 =
|
||||
BdfDictionary.of(new BdfEntry("f2", "o"));
|
||||
final BdfDictionary bdfSession3 =
|
||||
BdfDictionary.of(new BdfEntry("f3", "o"));
|
||||
|
||||
expectGetSession(oneResult, sessionId, contactGroup.getId());
|
||||
expectGetSession(Collections.singletonMap(storageId2, bdfSession2),
|
||||
sessionId, contactGroup2.getId());
|
||||
expectGetSession(Collections.singletonMap(storageId3, bdfSession3),
|
||||
sessionId, contactGroup3.getId());
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContacts(txn);
|
||||
will(returnValue(contacts));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact);
|
||||
will(returnValue(contactGroup));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact2);
|
||||
will(returnValue(contactGroup2));
|
||||
oneOf(contactGroupFactory).createContactGroup(CLIENT_ID, contact3);
|
||||
will(returnValue(contactGroup3));
|
||||
// session 1
|
||||
oneOf(sessionParser).getRole(bdfSession);
|
||||
will(returnValue(Role.CREATOR));
|
||||
oneOf(sessionParser)
|
||||
.parseCreatorSession(contactGroup.getId(), bdfSession);
|
||||
will(returnValue(creatorSession));
|
||||
oneOf(creatorEngine).onLeaveAction(txn, creatorSession);
|
||||
will(returnValue(creatorSession));
|
||||
// session 2
|
||||
oneOf(sessionParser).getRole(bdfSession2);
|
||||
will(returnValue(Role.INVITEE));
|
||||
oneOf(sessionParser)
|
||||
.parseInviteeSession(contactGroup2.getId(), bdfSession2);
|
||||
will(returnValue(inviteeSession));
|
||||
oneOf(inviteeEngine).onLeaveAction(txn, inviteeSession);
|
||||
will(returnValue(inviteeSession));
|
||||
// session 3
|
||||
oneOf(sessionParser).getRole(bdfSession3);
|
||||
will(returnValue(Role.PEER));
|
||||
oneOf(sessionParser)
|
||||
.parsePeerSession(contactGroup3.getId(), bdfSession3);
|
||||
will(returnValue(peerSession));
|
||||
oneOf(peerEngine).onLeaveAction(txn, peerSession);
|
||||
will(returnValue(peerSession));
|
||||
}});
|
||||
|
||||
expectStoreSession(creatorSession, storageMessage.getId());
|
||||
expectStoreSession(inviteeSession, storageId2);
|
||||
expectStoreSession(peerSession, storageId3);
|
||||
|
||||
groupInvitationManager.removingGroup(txn, privateGroup.getId());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,582 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.ValidatorTestCase;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
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.identity.Author;
|
||||
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.api.privategroup.PrivateGroup;
|
||||
import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomString;
|
||||
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.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
|
||||
import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.INVITE;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.JOIN;
|
||||
import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class GroupInvitationValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final PrivateGroupFactory privateGroupFactory =
|
||||
context.mock(PrivateGroupFactory.class);
|
||||
private final MessageEncoder messageEncoder =
|
||||
context.mock(MessageEncoder.class);
|
||||
|
||||
private final String groupName = getRandomString(MAX_GROUP_NAME_LENGTH);
|
||||
private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
|
||||
private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
|
||||
private final Author creator =
|
||||
new Author(new AuthorId(getRandomId()), creatorName, creatorKey);
|
||||
private final byte[] salt = getRandomBytes(GROUP_SALT_LENGTH);
|
||||
private final PrivateGroup privateGroup =
|
||||
new PrivateGroup(group, groupName, creator, salt);
|
||||
private final String inviteText =
|
||||
getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH);
|
||||
private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
|
||||
private final BdfDictionary meta =
|
||||
BdfDictionary.of(new BdfEntry("meta", "data"));
|
||||
private final MessageId previousMessageId = new MessageId(getRandomId());
|
||||
|
||||
private final GroupInvitationValidator validator =
|
||||
new GroupInvitationValidator(clientHelper, metadataEncoder,
|
||||
clock, authorFactory, privateGroupFactory, messageEncoder);
|
||||
|
||||
// INVITE Message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortInviteMessage() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongInviteMessage() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText, signature, "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortGroupName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), "", creatorName,
|
||||
creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongGroupName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(),
|
||||
getRandomString(MAX_GROUP_NAME_LENGTH + 1), creatorName,
|
||||
creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNullGroupName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), null, creatorName,
|
||||
creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonStringGroupName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), getRandomBytes(5),
|
||||
creatorName, creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortCreatorName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, "", creatorKey,
|
||||
salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongCreatorName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName,
|
||||
getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), creatorKey, salt,
|
||||
inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNullCreatorName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, null,
|
||||
creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonStringCreatorName()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName,
|
||||
getRandomBytes(5), creatorKey, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortCreatorKey()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
new byte[0], salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongCreatorKey()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), salt, inviteText,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNullCreatorKey()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
null, salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonRawCreatorKey()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
"not raw", salt, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortGroupSalt()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, getRandomBytes(GROUP_SALT_LENGTH - 1), inviteText,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongGroupSalt()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, getRandomBytes(GROUP_SALT_LENGTH + 1), inviteText,
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNullGroupSalt()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, null, inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonRawGroupSalt()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, "not raw", inviteText, signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortContent() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, "", signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongContent() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt,
|
||||
getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH + 1),
|
||||
signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsInviteMessageWithNullContent() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, null, signature);
|
||||
expectInviteMessage(false);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonStringContent()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, getRandomBytes(5), signature);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooShortSignature()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText, new byte[0]);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithTooLongSignature()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText,
|
||||
getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNullSignature()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText, null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithNonRawSignature()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText, "not raw");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInviteMessageWithInvalidSignature()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, null, signature);
|
||||
expectInviteMessage(true);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidInviteMessage() throws Exception {
|
||||
BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
|
||||
creatorKey, salt, inviteText, signature);
|
||||
expectInviteMessage(false);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertTrue(messageContext.getDependencies().isEmpty());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
private void expectInviteMessage(final boolean exception) throws Exception {
|
||||
final BdfList signed = BdfList.of(message.getTimestamp(),
|
||||
message.getGroupId(), privateGroup.getId());
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(authorFactory).createAuthor(creatorName, creatorKey);
|
||||
will(returnValue(creator));
|
||||
oneOf(privateGroupFactory).createPrivateGroup(groupName, creator,
|
||||
salt);
|
||||
will(returnValue(privateGroup));
|
||||
oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE, signature,
|
||||
creatorKey, signed);
|
||||
if (exception) {
|
||||
will(throwException(new GeneralSecurityException()));
|
||||
} else {
|
||||
oneOf(messageEncoder).encodeMetadata(INVITE,
|
||||
message.getGroupId(), message.getTimestamp(), false,
|
||||
false, false, false);
|
||||
will(returnValue(meta));
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
// JOIN Message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortJoinMessage() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongJoinMessage() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(),
|
||||
previousMessageId, "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithTooShortGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH - 1), previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithTooLongGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH + 1), previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithNullGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), null, previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithNonRawGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), "not raw",
|
||||
previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithTooShortPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(),
|
||||
getRandomBytes(UniqueId.LENGTH - 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithTooLongPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(),
|
||||
getRandomBytes(UniqueId.LENGTH + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsJoinMessageWithNonRawPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(),
|
||||
"not raw");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsJoinMessageWithNullPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(), null);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(JOIN, message.getGroupId(),
|
||||
message.getTimestamp(), false, false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidJoinMessage() throws Exception {
|
||||
BdfList body = BdfList.of(JOIN.getValue(), privateGroup.getId(),
|
||||
previousMessageId);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(JOIN, message.getGroupId(),
|
||||
message.getTimestamp(), false, false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(1, messageContext.getDependencies().size());
|
||||
assertEquals(previousMessageId,
|
||||
messageContext.getDependencies().iterator().next());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
// LEAVE message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortLeaveMessage() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongLeaveMessage() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(),
|
||||
previousMessageId, "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithTooShortGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH - 1), previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithTooLongGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH + 1), previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithNullGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), null, previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithNonRawGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), "not raw",
|
||||
previousMessageId);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithTooShortPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(),
|
||||
getRandomBytes(UniqueId.LENGTH - 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithTooLongPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(),
|
||||
getRandomBytes(UniqueId.LENGTH + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsLeaveMessageWithNonRawPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(),
|
||||
"not raw");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsLeaveMessageWithNullPreviousMessageId()
|
||||
throws Exception {
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(), null);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(LEAVE, message.getGroupId(),
|
||||
message.getTimestamp(), false, false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidLeaveMessage() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(LEAVE, message.getGroupId(),
|
||||
message.getTimestamp(), false, false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
BdfList body = BdfList.of(LEAVE.getValue(), privateGroup.getId(),
|
||||
previousMessageId);
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(1, messageContext.getDependencies().size());
|
||||
assertEquals(previousMessageId,
|
||||
messageContext.getDependencies().iterator().next());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
// ABORT message
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortAbortMessage() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue());
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongAbortMessage() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), privateGroup.getId(), "");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsAbortMessageWithTooShortGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH - 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsAbortMessageWithTooLongGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(),
|
||||
getRandomBytes(GroupId.LENGTH + 1));
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsAbortMessageWithNullGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), null);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsAbortMessageWithNonRawGroupId() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue(), "not raw");
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsValidAbortMessage() throws Exception {
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder).encodeMetadata(ABORT, message.getGroupId(),
|
||||
message.getTimestamp(), false, false, false, false);
|
||||
will(returnValue(meta));
|
||||
}});
|
||||
BdfList body = BdfList.of(ABORT.getValue(), privateGroup.getId());
|
||||
BdfMessageContext messageContext =
|
||||
validator.validateMessage(message, group, body);
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
assertEquals(meta, messageContext.getDictionary());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsMessageWithUnknownType() throws Exception {
|
||||
BdfList body = BdfList.of(ABORT.getValue() + 1);
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsEmptyMessage() throws Exception {
|
||||
BdfList body = new BdfList();
|
||||
validator.validateMessage(message, group, body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,771 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
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.identity.Author;
|
||||
import org.briarproject.bramble.api.identity.AuthorId;
|
||||
import org.briarproject.bramble.api.identity.LocalAuthor;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.briarproject.briar.api.privategroup.GroupMessage;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.ACCEPTED;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.DISSOLVED;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.ERROR;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.INVITED;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.JOINED;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.LEFT;
|
||||
import static org.briarproject.briar.privategroup.invitation.InviteeState.START;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
|
||||
|
||||
private final InviteeProtocolEngine engine =
|
||||
new InviteeProtocolEngine(db, clientHelper, privateGroupManager,
|
||||
privateGroupFactory, groupMessageFactory, identityManager,
|
||||
messageParser, messageEncoder, messageTracker, clock);
|
||||
private final LocalAuthor localAuthor =
|
||||
new LocalAuthor(new AuthorId(getRandomId()), "Local Author",
|
||||
getRandomBytes(12), getRandomBytes(12), 42L);
|
||||
|
||||
private InviteeSession getDefaultSession(InviteeState state) {
|
||||
return new InviteeSession(contactGroupId, privateGroupId,
|
||||
lastLocalMessageId, lastRemoteMessageId, localTimestamp,
|
||||
inviteTimestamp, state);
|
||||
}
|
||||
|
||||
// onInviteAction
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromStart() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(START), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromLeft() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(ACCEPTED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromInvited() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(INVITED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromDissolved() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(DISSOLVED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromAccepted() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(ACCEPTED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromJoined() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(JOINED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromError() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
// onJoinAction
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromStart() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(START));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromAccepted() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(ACCEPTED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromJoined() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromLeft() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(LEFT));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromDissolved() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(DISSOLVED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromError() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(ERROR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinActionFromInvited() throws Exception {
|
||||
final JoinMessage properJoinMessage =
|
||||
new JoinMessage(messageId, contactGroupId, privateGroupId,
|
||||
messageTimestamp, lastRemoteMessageId);
|
||||
final Message inviteMsg =
|
||||
new Message(lastRemoteMessageId, contactGroupId, 1337L,
|
||||
getRandomBytes(42));
|
||||
final BdfList inviteList = BdfList.of("inviteMessage");
|
||||
final long timestamp = 0L;
|
||||
final GroupMessage joinGroupMessage =
|
||||
new GroupMessage(message, null, localAuthor);
|
||||
|
||||
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
|
||||
expectSendJoinMessage(properJoinMessage, true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageTracker).trackOutgoingMessage(txn, message);
|
||||
oneOf(clientHelper).getMessage(txn, lastRemoteMessageId);
|
||||
will(returnValue(inviteMsg));
|
||||
oneOf(clientHelper).toList(inviteMsg);
|
||||
will(returnValue(inviteList));
|
||||
oneOf(messageParser).parseInviteMessage(inviteMsg, inviteList);
|
||||
will(returnValue(inviteMessage));
|
||||
oneOf(privateGroupFactory)
|
||||
.createPrivateGroup(inviteMessage.getGroupName(),
|
||||
inviteMessage.getCreator(),
|
||||
inviteMessage.getSalt());
|
||||
will(returnValue(privateGroup));
|
||||
oneOf(clock).currentTimeMillis();
|
||||
will((returnValue(timestamp)));
|
||||
oneOf(identityManager).getLocalAuthor(txn);
|
||||
will(returnValue(localAuthor));
|
||||
oneOf(groupMessageFactory).createJoinMessage(privateGroupId,
|
||||
inviteMessage.getTimestamp() + 1, localAuthor,
|
||||
inviteMessage.getTimestamp(), inviteMessage.getSignature());
|
||||
will(returnValue(joinGroupMessage));
|
||||
oneOf(privateGroupManager)
|
||||
.addPrivateGroup(txn, privateGroup, joinGroupMessage,
|
||||
false);
|
||||
}});
|
||||
expectSetPrivateGroupVisibility(VISIBLE);
|
||||
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
InviteeSession newSession = engine.onJoinAction(txn, session);
|
||||
|
||||
assertEquals(ACCEPTED, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOnJoinActionFromInvitedWithoutInvitationId()
|
||||
throws Exception {
|
||||
InviteeSession session =
|
||||
new InviteeSession(contactGroupId, privateGroupId,
|
||||
lastLocalMessageId, null, localTimestamp,
|
||||
inviteTimestamp, INVITED);
|
||||
engine.onJoinAction(txn, session);
|
||||
}
|
||||
|
||||
// onLeaveAction
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromStart() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromLeft() throws Exception {
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromDissolved() throws Exception {
|
||||
InviteeSession session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromError() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromInvited() throws Exception {
|
||||
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
|
||||
expectSendLeaveMessage(true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageTracker).trackOutgoingMessage(txn, message);
|
||||
}});
|
||||
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
InviteeSession newSession = engine.onLeaveAction(txn, session);
|
||||
|
||||
assertEquals(START, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testOnLeaveActionFromInvitedWithoutInvitationId()
|
||||
throws Exception {
|
||||
InviteeSession session =
|
||||
new InviteeSession(contactGroupId, privateGroupId,
|
||||
lastLocalMessageId, null, localTimestamp,
|
||||
inviteTimestamp, INVITED);
|
||||
engine.onJoinAction(txn, session);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromAccepted() throws Exception {
|
||||
expectSendLeaveMessage(false);
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
InviteeSession newSession = engine.onLeaveAction(txn, session);
|
||||
|
||||
assertEquals(LEFT, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromJoined() throws Exception {
|
||||
expectSendLeaveMessage(false);
|
||||
InviteeSession session = getDefaultSession(JOINED);
|
||||
InviteeSession newSession = engine.onLeaveAction(txn, session);
|
||||
|
||||
assertEquals(LEFT, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
// onMemberAddedAction
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromStart() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromInvited() throws Exception {
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromAccepted() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromJoined() throws Exception {
|
||||
InviteeSession session = getDefaultSession(JOINED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromLeft() throws Exception {
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromDissolved() throws Exception {
|
||||
InviteeSession session = getDefaultSession(DISSOLVED);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromError() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
// onInviteMessage
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromStartWithLowerTimestamp()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
assertTrue(
|
||||
inviteMessage.getTimestamp() <= session.getInviteTimestamp());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromStartButNotCreator() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
InviteMessage properInviteMessage =
|
||||
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
privateGroup.getName(), privateGroup.getCreator(),
|
||||
privateGroup.getSalt(), "msg", signature);
|
||||
Author notCreator =
|
||||
new Author(new AuthorId(getRandomId()), "Not Creator",
|
||||
getRandomBytes(5));
|
||||
final Contact notCreatorContact =
|
||||
new Contact(contactId, notCreator, localAuthor.getId(), true,
|
||||
true);
|
||||
|
||||
expectGetContactId();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(notCreatorContact));
|
||||
}});
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, properInviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromStart() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
final InviteMessage properInviteMessage =
|
||||
new InviteMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
privateGroup.getName(), privateGroup.getCreator(),
|
||||
privateGroup.getSalt(), "msg", signature);
|
||||
assertEquals(contact.getAuthor(), privateGroup.getCreator());
|
||||
|
||||
expectGetContactId();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
}});
|
||||
expectMarkMessageVisibleInUi(properInviteMessage.getId(), true);
|
||||
expectMarkMessageAvailableToAnswer(properInviteMessage.getId(), true);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageTracker).trackMessage(txn, contactGroupId,
|
||||
properInviteMessage.getTimestamp(), false);
|
||||
oneOf(privateGroupFactory)
|
||||
.createPrivateGroup(properInviteMessage.getGroupName(),
|
||||
properInviteMessage.getCreator(),
|
||||
properInviteMessage.getSalt());
|
||||
will(returnValue(privateGroup));
|
||||
}});
|
||||
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, properInviteMessage);
|
||||
|
||||
assertEquals(INVITED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(properInviteMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertEquals(properInviteMessage.getTimestamp(),
|
||||
newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromInvited() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromAccepted() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromJoined() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(JOINED);
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromLeft() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromDissolved() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(DISSOLVED);
|
||||
InviteeSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromError() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session,
|
||||
engine.onInviteMessage(txn, session, inviteMessage));
|
||||
}
|
||||
|
||||
// onJoinMessage
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromStart() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromInvited() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromJoined() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(JOINED);
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromLeft() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromDissolved() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(DISSOLVED);
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromAcceptedWithWrongTimestamp()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
assertTrue(joinMessage.getTimestamp() <= session.getInviteTimestamp());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromAcceptedWithInvalidDependency()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
JoinMessage invalidJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
lastLocalMessageId);
|
||||
assertFalse(invalidJoinMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertNotNull(invalidJoinMessage.getPreviousMessageId());
|
||||
assertFalse(session.getLastRemoteMessageId()
|
||||
.equals(invalidJoinMessage.getPreviousMessageId()));
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, invalidJoinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromAccepted() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ACCEPTED);
|
||||
JoinMessage properJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
lastRemoteMessageId);
|
||||
assertFalse(properJoinMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertNotNull(properJoinMessage.getPreviousMessageId());
|
||||
assertTrue(session.getLastRemoteMessageId()
|
||||
.equals(properJoinMessage.getPreviousMessageId()));
|
||||
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
|
||||
|
||||
InviteeSession newSession =
|
||||
engine.onJoinMessage(txn, session, properJoinMessage);
|
||||
|
||||
assertEquals(JOINED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(properJoinMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertEquals(session.getInviteTimestamp(),
|
||||
newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromFromError() throws Exception {
|
||||
InviteeSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onJoinMessage(txn, session, joinMessage));
|
||||
}
|
||||
|
||||
// onLeaveMessage
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromStart() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromDissolved() throws Exception {
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession session = getDefaultSession(DISSOLVED);
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvitedWithWrongTimestamp()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
assertTrue(leaveMessage.getTimestamp() <= session.getInviteTimestamp());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLeftWithWrongTimestamp()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
assertTrue(leaveMessage.getTimestamp() <= session.getInviteTimestamp());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvitedWithInvalidDependency()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1, null);
|
||||
assertFalse(invalidLeaveMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNull(invalidLeaveMessage.getPreviousMessageId());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLeftWithInvalidDependency()
|
||||
throws Exception {
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1, null);
|
||||
assertFalse(invalidLeaveMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNull(invalidLeaveMessage.getPreviousMessageId());
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromInvited() throws Exception {
|
||||
InviteeSession session = getDefaultSession(INVITED);
|
||||
LeaveMessage properLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
lastRemoteMessageId);
|
||||
assertFalse(properLeaveMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertNotNull(properLeaveMessage.getPreviousMessageId());
|
||||
assertTrue(session.getLastRemoteMessageId()
|
||||
.equals(properLeaveMessage.getPreviousMessageId()));
|
||||
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, properLeaveMessage);
|
||||
|
||||
assertEquals(DISSOLVED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(properLeaveMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertEquals(session.getInviteTimestamp(),
|
||||
newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLeft() throws Exception {
|
||||
InviteeSession session = getDefaultSession(LEFT);
|
||||
LeaveMessage properLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, session.getInviteTimestamp() + 1,
|
||||
lastRemoteMessageId);
|
||||
assertFalse(properLeaveMessage.getTimestamp() <=
|
||||
session.getInviteTimestamp());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertNotNull(properLeaveMessage.getPreviousMessageId());
|
||||
assertTrue(session.getLastRemoteMessageId()
|
||||
.equals(properLeaveMessage.getPreviousMessageId()));
|
||||
|
||||
InviteeSession newSession =
|
||||
engine.onLeaveMessage(txn, session, properLeaveMessage);
|
||||
|
||||
assertEquals(DISSOLVED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(properLeaveMessage.getId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertEquals(session.getInviteTimestamp(),
|
||||
newSession.getInviteTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
// onAbortMessage
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenSubscribed() throws Exception {
|
||||
InviteeSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
InviteeSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private void expectMarkMessageAvailableToAnswer(final MessageId id,
|
||||
final boolean available) throws Exception {
|
||||
final BdfDictionary meta = new BdfDictionary();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageEncoder)
|
||||
.setAvailableToAnswer(meta, available);
|
||||
oneOf(clientHelper)
|
||||
.mergeMessageMetadata(txn, id, meta);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectAbortWhenSubscribedToGroup() throws Exception {
|
||||
expectAbort(true);
|
||||
}
|
||||
|
||||
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
|
||||
expectAbort(false);
|
||||
}
|
||||
|
||||
private void expectAbort(boolean subscribed) throws Exception {
|
||||
final BdfDictionary query = BdfDictionary.of(new BdfEntry("query", ""));
|
||||
final BdfDictionary meta = BdfDictionary.of(new BdfEntry("meta", ""));
|
||||
final Map<MessageId, BdfDictionary> invites =
|
||||
Collections.singletonMap(lastRemoteMessageId, meta);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(messageParser)
|
||||
.getInvitesAvailableToAnswerQuery(privateGroupId);
|
||||
will(returnValue(query));
|
||||
oneOf(clientHelper)
|
||||
.getMessageMetadataAsDictionary(txn, contactGroupId, query);
|
||||
will(returnValue(invites));
|
||||
}});
|
||||
expectMarkMessageAvailableToAnswer(lastRemoteMessageId, false);
|
||||
if (subscribed) {
|
||||
expectIsSubscribedPrivateGroup();
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
} else {
|
||||
expectIsNotSubscribedPrivateGroup();
|
||||
}
|
||||
expectSendAbortMessage();
|
||||
}
|
||||
|
||||
private void assertSessionAborted(InviteeSession oldSession,
|
||||
InviteeSession newSession) throws Exception {
|
||||
assertEquals(ERROR, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(oldSession, newSession);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,701 @@
|
||||
package org.briarproject.briar.privategroup.invitation;
|
||||
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.api.client.ProtocolStateException;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.briarproject.bramble.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.AWAIT_MEMBER;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.BOTH_JOINED;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.ERROR;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.LOCAL_JOINED;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.LOCAL_LEFT;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.NEITHER_JOINED;
|
||||
import static org.briarproject.briar.privategroup.invitation.PeerState.START;
|
||||
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 PeerProtocolEngineTest extends AbstractProtocolEngineTest {
|
||||
|
||||
private final PeerProtocolEngine engine =
|
||||
new PeerProtocolEngine(db, clientHelper, privateGroupManager,
|
||||
privateGroupFactory, groupMessageFactory, identityManager,
|
||||
messageParser, messageEncoder, messageTracker, clock);
|
||||
|
||||
private PeerSession getDefaultSession(PeerState state) {
|
||||
return new PeerSession(contactGroupId, privateGroupId,
|
||||
lastLocalMessageId, lastRemoteMessageId, localTimestamp, state);
|
||||
}
|
||||
|
||||
// onInviteAction
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromStart() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(START), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromAwaitMember() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(AWAIT_MEMBER), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromNeitherJoined() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(NEITHER_JOINED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromLocalJoined() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(LOCAL_JOINED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromBothJoined() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(BOTH_JOINED), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromLocalLeft() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(LOCAL_LEFT), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
@Test(expected = UnsupportedOperationException.class)
|
||||
public void testOnInviteActionFromError() throws Exception {
|
||||
engine.onInviteAction(txn, getDefaultSession(ERROR), null,
|
||||
messageTimestamp, signature);
|
||||
}
|
||||
|
||||
// onJoinAction
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromStart() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(START));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromAwaitMember() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(AWAIT_MEMBER));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromLocalJoined() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(LOCAL_JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromBothJoined() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(BOTH_JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnJoinActionFromError() throws Exception {
|
||||
engine.onJoinAction(txn, getDefaultSession(ERROR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinActionFromNeitherJoined() throws Exception {
|
||||
JoinMessage joinMessage =
|
||||
new JoinMessage(messageId, contactGroupId,
|
||||
privateGroupId, messageTimestamp, lastRemoteMessageId);
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
|
||||
expectSendJoinMessage(joinMessage, false);
|
||||
expectSetPrivateGroupVisibility(VISIBLE);
|
||||
PeerSession newSession = engine.onJoinAction(txn, session);
|
||||
|
||||
assertEquals(LOCAL_JOINED, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinActionFromLocalLeft() throws Exception {
|
||||
JoinMessage joinMessage =
|
||||
new JoinMessage(messageId, contactGroupId,
|
||||
privateGroupId, messageTimestamp, lastRemoteMessageId);
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
|
||||
expectSendJoinMessage(joinMessage, false);
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
PeerSession newSession = engine.onJoinAction(txn, session);
|
||||
|
||||
assertEquals(BOTH_JOINED, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
// onLeaveAction
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromStart() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromAwaitMember() throws Exception {
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromNeitherJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromLocalLeft() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromError() throws Exception {
|
||||
PeerSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onLeaveAction(txn, session));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromLocalJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_JOINED);
|
||||
|
||||
expectSendLeaveMessage(false);
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
PeerSession newSession = engine.onLeaveAction(txn, session);
|
||||
|
||||
assertEquals(NEITHER_JOINED, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveActionFromBothJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(BOTH_JOINED);
|
||||
|
||||
expectSendLeaveMessage(false);
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
PeerSession newSession = engine.onLeaveAction(txn, session);
|
||||
|
||||
assertEquals(LOCAL_LEFT, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
// onMemberAddedAction
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromStart() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
PeerSession newSession = engine.onMemberAddedAction(txn, session);
|
||||
|
||||
assertEquals(NEITHER_JOINED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(session.getLastRemoteMessageId(),
|
||||
newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromAwaitMember() throws Exception {
|
||||
JoinMessage joinMessage =
|
||||
new JoinMessage(messageId, contactGroupId,
|
||||
privateGroupId, messageTimestamp, lastRemoteMessageId);
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
|
||||
expectSendJoinMessage(joinMessage, false);
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
expectRelationshipRevealed(true);
|
||||
PeerSession newSession = engine.onMemberAddedAction(txn, session);
|
||||
|
||||
assertEquals(BOTH_JOINED, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnMemberAddedFromNeitherJoined() throws Exception {
|
||||
engine.onMemberAddedAction(txn, getDefaultSession(NEITHER_JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnMemberAddedFromLocalJoined() throws Exception {
|
||||
engine.onMemberAddedAction(txn, getDefaultSession(LOCAL_JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnMemberAddedFromBothJoined() throws Exception {
|
||||
engine.onMemberAddedAction(txn, getDefaultSession(BOTH_JOINED));
|
||||
}
|
||||
|
||||
@Test(expected = ProtocolStateException.class)
|
||||
public void testOnMemberAddedFromLocalLeft() throws Exception {
|
||||
engine.onMemberAddedAction(txn, getDefaultSession(LOCAL_LEFT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnMemberAddedFromError() throws Exception {
|
||||
PeerSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session, engine.onMemberAddedAction(txn, session));
|
||||
}
|
||||
|
||||
// onInviteMessage
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromStart() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromAwaitMember() throws Exception {
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromNeitherJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromLocalJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromBothJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(BOTH_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromLocalLeft() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onInviteMessage(txn, session, inviteMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnInviteMessageFromError() throws Exception {
|
||||
PeerSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session,
|
||||
engine.onInviteMessage(txn, session, inviteMessage));
|
||||
}
|
||||
|
||||
// onJoinMessage
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromAwaitMember() throws Exception {
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromBothJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(BOTH_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromLocalLeft() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromStartWithInvalidDependency()
|
||||
throws Exception {
|
||||
JoinMessage invalidJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(START);
|
||||
assertNotNull(invalidJoinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidJoinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, invalidJoinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromStart() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
assertNotNull(joinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(joinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
|
||||
assertEquals(AWAIT_MEMBER, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromNeitherJoinedWithInvalidDependency()
|
||||
throws Exception {
|
||||
JoinMessage invalidJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
assertNotNull(invalidJoinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidJoinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, invalidJoinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromNeitherJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
assertNotNull(joinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(joinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
JoinMessage myJoinMessage = new JoinMessage(messageId, contactGroupId,
|
||||
privateGroupId, messageTimestamp, lastRemoteMessageId);
|
||||
|
||||
expectSendJoinMessage(myJoinMessage, false);
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
expectRelationshipRevealed(true);
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
|
||||
assertEquals(BOTH_JOINED, newSession.getState());
|
||||
assertEquals(myJoinMessage.getId(), newSession.getLastLocalMessageId());
|
||||
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(myJoinMessage.getTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromLocalJoinedWithInvalidDependency()
|
||||
throws Exception {
|
||||
JoinMessage invalidJoinMessage =
|
||||
new JoinMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(LOCAL_JOINED);
|
||||
assertNotNull(invalidJoinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidJoinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, invalidJoinMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromLocalJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_JOINED);
|
||||
assertNotNull(joinMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(joinMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectSetPrivateGroupVisibility(SHARED);
|
||||
expectRelationshipRevealed(false);
|
||||
PeerSession newSession =
|
||||
engine.onJoinMessage(txn, session, joinMessage);
|
||||
|
||||
assertEquals(BOTH_JOINED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(joinMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnJoinMessageFromError() throws Exception {
|
||||
PeerSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session,
|
||||
engine.onJoinMessage(txn, session, joinMessage));
|
||||
}
|
||||
|
||||
// onLeaveMessage
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromStart() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromNeitherJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(NEITHER_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLocalJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_JOINED);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromAwaitMemberWithInvalidDependency()
|
||||
throws Exception {
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidLeaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromAwaitMember() throws Exception {
|
||||
PeerSession session = getDefaultSession(AWAIT_MEMBER);
|
||||
assertNotNull(leaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(leaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
|
||||
assertEquals(START, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLocalLeftWithInvalidDependency()
|
||||
throws Exception {
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidLeaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromLocalLeft() throws Exception {
|
||||
PeerSession session = getDefaultSession(LOCAL_LEFT);
|
||||
assertNotNull(leaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(leaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
|
||||
assertEquals(NEITHER_JOINED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromBothJoinedWithInvalidDependency()
|
||||
throws Exception {
|
||||
LeaveMessage invalidLeaveMessage =
|
||||
new LeaveMessage(new MessageId(getRandomId()), contactGroupId,
|
||||
privateGroupId, 0L, lastLocalMessageId);
|
||||
PeerSession session = getDefaultSession(BOTH_JOINED);
|
||||
assertNotNull(invalidLeaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertFalse(invalidLeaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, invalidLeaveMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromBothJoined() throws Exception {
|
||||
PeerSession session = getDefaultSession(BOTH_JOINED);
|
||||
assertNotNull(leaveMessage.getPreviousMessageId());
|
||||
assertNotNull(session.getLastRemoteMessageId());
|
||||
assertTrue(leaveMessage.getPreviousMessageId()
|
||||
.equals(session.getLastRemoteMessageId()));
|
||||
|
||||
expectSetPrivateGroupVisibility(VISIBLE); // FIXME correct?
|
||||
PeerSession newSession =
|
||||
engine.onLeaveMessage(txn, session, leaveMessage);
|
||||
|
||||
assertEquals(LOCAL_JOINED, newSession.getState());
|
||||
assertEquals(session.getLastLocalMessageId(),
|
||||
newSession.getLastLocalMessageId());
|
||||
assertEquals(leaveMessage.getId(), newSession.getLastRemoteMessageId());
|
||||
assertEquals(session.getLocalTimestamp(),
|
||||
newSession.getLocalTimestamp());
|
||||
assertSessionConstantsUnchanged(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLeaveMessageFromError() throws Exception {
|
||||
PeerSession session = getDefaultSession(ERROR);
|
||||
assertEquals(session,
|
||||
engine.onLeaveMessage(txn, session, leaveMessage));
|
||||
}
|
||||
|
||||
// onAbortMessage
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenNotSubscribed() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnAbortMessageWhenSubscribed() throws Exception {
|
||||
PeerSession session = getDefaultSession(START);
|
||||
|
||||
expectAbortWhenNotSubscribedToGroup();
|
||||
PeerSession newSession =
|
||||
engine.onAbortMessage(txn, session, abortMessage);
|
||||
assertSessionAborted(session, newSession);
|
||||
}
|
||||
|
||||
// helper methods
|
||||
|
||||
private void expectRelationshipRevealed(final boolean byContact)
|
||||
throws Exception {
|
||||
expectGetContactId();
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(db).getContact(txn, contactId);
|
||||
will(returnValue(contact));
|
||||
oneOf(privateGroupManager)
|
||||
.relationshipRevealed(txn, privateGroupId, author.getId(),
|
||||
byContact);
|
||||
}});
|
||||
}
|
||||
|
||||
private void expectAbortWhenSubscribedToGroup() throws Exception {
|
||||
expectIsSubscribedPrivateGroup();
|
||||
expectSetPrivateGroupVisibility(INVISIBLE);
|
||||
expectSendAbortMessage();
|
||||
}
|
||||
|
||||
private void expectAbortWhenNotSubscribedToGroup() throws Exception {
|
||||
expectIsNotSubscribedPrivateGroup();
|
||||
expectSendAbortMessage();
|
||||
}
|
||||
|
||||
private void assertSessionAborted(PeerSession oldSession,
|
||||
PeerSession newSession) throws Exception {
|
||||
assertEquals(ERROR, newSession.getState());
|
||||
assertSessionRecordedSentMessage(newSession);
|
||||
assertSessionConstantsUnchanged(oldSession, newSession);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertSessionRecordedSentMessage(Session s) {
|
||||
assertEquals(messageId, s.getLastLocalMessageId());
|
||||
assertEquals(lastRemoteMessageId, s.getLastRemoteMessageId());
|
||||
assertEquals(messageTimestamp, s.getLocalTimestamp());
|
||||
// invitation timestamp is untouched for peers
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.sharing;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
@@ -30,7 +30,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static org.briarproject.TestUtils.assertGroupCount;
|
||||
import static org.briarproject.briar.BriarTestUtils.assertGroupCount;
|
||||
import static org.briarproject.briar.api.blog.BlogSharingManager.CLIENT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
@@ -2,7 +2,7 @@ package org.briarproject.briar.sharing;
|
||||
|
||||
import net.jodah.concurrentunit.Waiter;
|
||||
|
||||
import org.briarproject.TestDatabaseModule;
|
||||
import org.briarproject.bramble.TestDatabaseModule;
|
||||
import org.briarproject.bramble.api.Bytes;
|
||||
import org.briarproject.bramble.api.contact.Contact;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
@@ -39,8 +39,8 @@ 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.bramble.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.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;
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
package org.briarproject.briar.sharing;
|
||||
|
||||
import org.briarproject.bramble.TestUtils;
|
||||
import org.briarproject.bramble.ValidatorTestCase;
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.client.BdfMessageContext;
|
||||
import org.briarproject.bramble.api.data.BdfDictionary;
|
||||
import org.briarproject.bramble.api.data.BdfList;
|
||||
import org.briarproject.briar.api.client.SessionId;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_NAME;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.INVITATION_MSG;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.LOCAL;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SESSION_ID;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ABORT;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_ACCEPT;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_DECLINE;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_INVITATION;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.SHARE_MSG_TYPE_LEAVE;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.TIME;
|
||||
import static org.briarproject.briar.api.sharing.SharingConstants.TYPE;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ForumSharingValidatorTest extends ValidatorTestCase {
|
||||
|
||||
private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
|
||||
private final String forumName =
|
||||
TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH);
|
||||
private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH);
|
||||
private final String content =
|
||||
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
|
||||
|
||||
@Test
|
||||
public void testAcceptsInvitationWithContent() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, content));
|
||||
assertExpectedContextForInvitation(messageContext, forumName, content);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsInvitationWithoutContent() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt));
|
||||
assertExpectedContextForInvitation(messageContext, forumName, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsAccept() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ACCEPT, sessionId));
|
||||
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ACCEPT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsDecline() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_DECLINE, sessionId));
|
||||
assertExpectedContext(messageContext, SHARE_MSG_TYPE_DECLINE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsLeave() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_LEAVE, sessionId));
|
||||
assertExpectedContext(messageContext, SHARE_MSG_TYPE_LEAVE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsAbort() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId));
|
||||
assertExpectedContext(messageContext, SHARE_MSG_TYPE_ABORT);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullMessageType() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of(null, sessionId));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonLongMessageType() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of("", sessionId));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsInvalidMessageType() throws Exception {
|
||||
int invalidMessageType = SHARE_MSG_TYPE_ABORT + 1;
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(invalidMessageType, sessionId));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullSessionId() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, null));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonRawSessionId() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, 123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortSessionId() throws Exception {
|
||||
byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongSessionId() throws Exception {
|
||||
byte[] invalidSessionId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, invalidSessionId));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForAbort() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group, BdfList.of(SHARE_MSG_TYPE_ABORT));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForAbort() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_ABORT, sessionId, 123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortBodyForInvitation() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongBodyForInvitation() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, content, 123));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullForumName() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, null,
|
||||
salt, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringForumName() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, 123,
|
||||
salt, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortForumName() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, "",
|
||||
salt, content));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthForumName() throws Exception {
|
||||
String shortForumName = TestUtils.getRandomString(1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, shortForumName,
|
||||
salt, content));
|
||||
assertExpectedContextForInvitation(messageContext, shortForumName,
|
||||
content);
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongForumName() throws Exception {
|
||||
String invalidForumName =
|
||||
TestUtils.getRandomString(MAX_FORUM_NAME_LENGTH + 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId,
|
||||
invalidForumName, salt, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullSalt() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
null, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonRawSalt() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
123, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooShortSalt() throws Exception {
|
||||
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH - 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
invalidSalt, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongSalt() throws Exception {
|
||||
byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH + 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
invalidSalt, content));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNullContent() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, null));
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsNonStringContent() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, 123));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceptsMinLengthContent() throws Exception {
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
BdfMessageContext messageContext = v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, ""));
|
||||
assertExpectedContextForInvitation(messageContext, forumName, "");
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testRejectsTooLongContent() throws Exception {
|
||||
String invalidContent =
|
||||
TestUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
|
||||
ForumSharingValidator v = new ForumSharingValidator(clientHelper,
|
||||
metadataEncoder, clock);
|
||||
v.validateMessage(message, group,
|
||||
BdfList.of(SHARE_MSG_TYPE_INVITATION, sessionId, forumName,
|
||||
salt, invalidContent));
|
||||
}
|
||||
|
||||
private void assertExpectedContextForInvitation(
|
||||
BdfMessageContext messageContext, String forumName,
|
||||
@Nullable String content) throws FormatException {
|
||||
BdfDictionary meta = messageContext.getDictionary();
|
||||
if (content == null) {
|
||||
assertEquals(6, meta.size());
|
||||
} else {
|
||||
assertEquals(7, meta.size());
|
||||
assertEquals(content, meta.getString(INVITATION_MSG));
|
||||
}
|
||||
assertEquals(forumName, meta.getString(FORUM_NAME));
|
||||
assertEquals(salt, meta.getRaw(FORUM_SALT));
|
||||
assertEquals(SHARE_MSG_TYPE_INVITATION, meta.getLong(TYPE).intValue());
|
||||
assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID));
|
||||
assertFalse(meta.getBoolean(LOCAL));
|
||||
assertEquals(timestamp, meta.getLong(TIME).longValue());
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
}
|
||||
|
||||
private void assertExpectedContext(BdfMessageContext messageContext,
|
||||
int type) throws FormatException {
|
||||
BdfDictionary meta = messageContext.getDictionary();
|
||||
assertEquals(4, meta.size());
|
||||
assertEquals(type, meta.getLong(TYPE).intValue());
|
||||
assertEquals(sessionId.getBytes(), meta.getRaw(SESSION_ID));
|
||||
assertFalse(meta.getBoolean(LOCAL));
|
||||
assertEquals(timestamp, meta.getLong(TIME).longValue());
|
||||
assertEquals(0, messageContext.getDependencies().size());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.briar.sharing;
|
||||
|
||||
import org.briarproject.briar.BriarTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class SharingManagerImplTest extends BriarTestCase {
|
||||
|
||||
@Test
|
||||
public void testUnitTestsExist() {
|
||||
fail(); // FIXME: Write tests
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user