diff --git a/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java index 089cce54d979cdc067eba10510e9dcdcd4494bbd..a6707b526c0365e5b690e29a976bddb319ec8c91 100644 --- a/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java +++ b/briar-tests/src/org/briarproject/forum/ForumPostValidatorTest.java @@ -1,14 +1,399 @@ package org.briarproject.forum; -import org.briarproject.BriarTestCase; +import org.briarproject.TestUtils; +import org.briarproject.ValidatorTestCase; +import org.briarproject.api.FormatException; +import org.briarproject.api.UniqueId; +import org.briarproject.api.clients.BdfMessageContext; +import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.data.BdfDictionary; +import org.briarproject.api.data.BdfList; +import org.briarproject.api.data.MetadataEncoder; +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.AuthorFactory; +import org.briarproject.api.identity.AuthorId; +import org.briarproject.api.sync.InvalidMessageException; +import org.briarproject.api.sync.MessageId; +import org.briarproject.api.system.Clock; +import org.jmock.Expectations; import org.junit.Test; -import static org.junit.Assert.fail; +import java.security.GeneralSecurityException; +import java.util.Collection; -public class ForumPostValidatorTest extends BriarTestCase { +import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; +import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH; +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 AuthorFactory authorFactory = + context.mock(AuthorFactory.class); + private final ClientHelper clientHelper = context.mock(ClientHelper.class); + private final MetadataEncoder metadataEncoder = + context.mock(MetadataEncoder.class); + private final Clock clock = context.mock(Clock.class); + + 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(1, 2, 3)); + } + + @Test(expected = FormatException.class) + public void testRejectsTooLongBody() throws Exception { + ForumPostValidator v = new ForumPostValidator(authorFactory, + clientHelper, metadataEncoder, clock); + v.validateMessage(message, group, BdfList.of(1, 2, 3, 4, 5)); + } + + @Test + public void testAcceptsNullParentId() throws Exception { + context.checking(new Expectations() {{ + oneOf(authorFactory).createAuthor(authorName, authorPublicKey); + will(returnValue(author)); + oneOf(clientHelper).verifySignature(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 = new byte[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 = new byte[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 = "a"; + 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(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 testUnitTestsExist() { - fail(); // FIXME: Write tests + 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(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(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(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(Long.valueOf(timestamp), meta.getLong("timestamp")); + 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")); } }