mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-22 15:49:53 +01:00
Unit tests for H2Database.
This commit is contained in:
@@ -14,10 +14,8 @@ import net.sf.briar.api.protocol.Message;
|
|||||||
public interface DatabaseComponent {
|
public interface DatabaseComponent {
|
||||||
|
|
||||||
static final long MEGABYTES = 1024L * 1024L;
|
static final long MEGABYTES = 1024L * 1024L;
|
||||||
static final long GIGABYTES = 1024L * MEGABYTES;
|
|
||||||
|
|
||||||
// FIXME: Some of these should be configurable
|
// FIXME: Some of these should be configurable
|
||||||
static final long MAX_DB_SIZE = 2L * GIGABYTES;
|
|
||||||
static final long MIN_FREE_SPACE = 300L * MEGABYTES;
|
static final long MIN_FREE_SPACE = 300L * MEGABYTES;
|
||||||
static final long CRITICAL_FREE_SPACE = 100L * MEGABYTES;
|
static final long CRITICAL_FREE_SPACE = 100L * MEGABYTES;
|
||||||
static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES;
|
static final long MAX_BYTES_BETWEEN_SPACE_CHECKS = 5L * MEGABYTES;
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ import net.sf.briar.api.protocol.Message;
|
|||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A low-level interface to the database that is managed by a
|
* A low-level interface to the database (DatabaseComponent provides a
|
||||||
* DatabaseComponent. Most operations take a transaction argument, which is
|
* high-level interface). Most operations take a transaction argument, which is
|
||||||
* obtained by calling startTransaction(). Every transaction must be
|
* obtained by calling startTransaction(). Every transaction must be
|
||||||
* terminated by calling either abortTransaction() or commitTransaction(),
|
* terminated by calling either abortTransaction() or commitTransaction(),
|
||||||
* even if an exception is thrown.
|
* even if an exception is thrown.
|
||||||
|
|||||||
@@ -10,31 +10,32 @@ import java.util.logging.Level;
|
|||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sf.briar.api.crypto.Password;
|
import net.sf.briar.api.crypto.Password;
|
||||||
import net.sf.briar.api.db.DatabaseComponent;
|
|
||||||
import net.sf.briar.api.db.DatabasePassword;
|
import net.sf.briar.api.db.DatabasePassword;
|
||||||
import net.sf.briar.api.db.DbException;
|
import net.sf.briar.api.db.DbException;
|
||||||
import net.sf.briar.api.protocol.MessageFactory;
|
import net.sf.briar.api.protocol.MessageFactory;
|
||||||
import net.sf.briar.util.FileUtils;
|
|
||||||
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
/** Contains all the H2-specific code for the database. */
|
||||||
class H2Database extends JdbcDatabase {
|
class H2Database extends JdbcDatabase {
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(H2Database.class.getName());
|
Logger.getLogger(H2Database.class.getName());
|
||||||
|
|
||||||
private final Password password;
|
|
||||||
private final File home;
|
private final File home;
|
||||||
|
private final Password password;
|
||||||
private final String url;
|
private final String url;
|
||||||
|
private final long maxSize;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
H2Database(MessageFactory messageFactory,
|
H2Database(File dir, MessageFactory messageFactory,
|
||||||
@DatabasePassword Password password) {
|
@DatabasePassword Password password, long maxSize) {
|
||||||
super(messageFactory, "BINARY(32)");
|
super(messageFactory, "BINARY(32)");
|
||||||
|
home = new File(dir, "db");
|
||||||
this.password = password;
|
this.password = password;
|
||||||
home = new File(FileUtils.getBriarDirectory(), "Data/db/db");
|
|
||||||
url = "jdbc:h2:split:" + home.getPath()
|
url = "jdbc:h2:split:" + home.getPath()
|
||||||
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
|
+ ";CIPHER=AES;DB_CLOSE_ON_EXIT=false";
|
||||||
|
this.maxSize = maxSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void open(boolean resume) throws DbException {
|
public void open(boolean resume) throws DbException {
|
||||||
@@ -54,7 +55,7 @@ class H2Database extends JdbcDatabase {
|
|||||||
File dir = home.getParentFile();
|
File dir = home.getParentFile();
|
||||||
long free = dir.getFreeSpace();
|
long free = dir.getFreeSpace();
|
||||||
long used = getDiskSpace(dir);
|
long used = getDiskSpace(dir);
|
||||||
long quota = DatabaseComponent.MAX_DB_SIZE - used;
|
long quota = maxSize - used;
|
||||||
long min = Math.min(free, quota);
|
long min = Math.min(free, quota);
|
||||||
if(LOG.isLoggable(Level.FINE)) LOG.fine("Free space: " + min);
|
if(LOG.isLoggable(Level.FINE)) LOG.fine("Free space: " + min);
|
||||||
return min;
|
return min;
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ import net.sf.briar.api.protocol.MessageFactory;
|
|||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
import net.sf.briar.util.FileUtils;
|
import net.sf.briar.util.FileUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic database implementation that can be used with any JDBC-compatible
|
||||||
|
* database library. (Tested with H2, Derby and HSQLDB.)
|
||||||
|
*/
|
||||||
abstract class JdbcDatabase implements Database<Connection> {
|
abstract class JdbcDatabase implements Database<Connection> {
|
||||||
|
|
||||||
private static final String CREATE_LOCAL_SUBSCRIPTIONS =
|
private static final String CREATE_LOCAL_SUBSCRIPTIONS =
|
||||||
@@ -945,7 +949,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
+ " ON messages.groupId = contactSubscriptions.groupId"
|
+ " ON messages.groupId = contactSubscriptions.groupId"
|
||||||
+ " JOIN statuses ON messages.messageId = statuses.messageId"
|
+ " JOIN statuses ON messages.messageId = statuses.messageId"
|
||||||
+ " WHERE contactSubscriptions.contactId = ?"
|
+ " WHERE contactSubscriptions.contactId = ?"
|
||||||
+ " AND statuses.contactId = ? AND status = ?";
|
+ " AND statuses.contactId = ? AND status = ?"
|
||||||
|
+ " AND sendability > ZERO()";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.setInt(2, c.getInt());
|
ps.setInt(2, c.getInt());
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import net.sf.briar.api.protocol.GroupId;
|
|||||||
import net.sf.briar.api.protocol.Message;
|
import net.sf.briar.api.protocol.Message;
|
||||||
import net.sf.briar.api.protocol.MessageId;
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
|
|
||||||
class MessageImpl implements Message {
|
public class MessageImpl implements Message {
|
||||||
|
|
||||||
private final MessageId id, parent;
|
private final MessageId id, parent;
|
||||||
private final GroupId group;
|
private final GroupId group;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
<path refid='test-classes'/>
|
<path refid='test-classes'/>
|
||||||
<path refid='util-classes'/>
|
<path refid='util-classes'/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
<test name='net.sf.briar.db.H2DatabaseTest'/>
|
||||||
<test name='net.sf.briar.i18n.FontManagerTest'/>
|
<test name='net.sf.briar.i18n.FontManagerTest'/>
|
||||||
<test name='net.sf.briar.i18n.I18nTest'/>
|
<test name='net.sf.briar.i18n.I18nTest'/>
|
||||||
<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
|
<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ public class TestUtils {
|
|||||||
return testDir;
|
return testDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void deleteTestDirectories() {
|
public static void deleteTestDirectory(File testDir) {
|
||||||
delete(new File("test.tmp"));
|
delete(testDir);
|
||||||
|
testDir.getParentFile().delete(); // Delete if empty
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getBuildDirectory() {
|
public static File getBuildDirectory() {
|
||||||
|
|||||||
620
test/net/sf/briar/db/H2DatabaseTest.java
Normal file
620
test/net/sf/briar/db/H2DatabaseTest.java
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
package net.sf.briar.db;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.sql.Connection;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import net.sf.briar.TestUtils;
|
||||||
|
import net.sf.briar.api.crypto.Password;
|
||||||
|
import net.sf.briar.api.db.ContactId;
|
||||||
|
import net.sf.briar.api.db.DbException;
|
||||||
|
import net.sf.briar.api.db.Rating;
|
||||||
|
import net.sf.briar.api.db.Status;
|
||||||
|
import net.sf.briar.api.protocol.AuthorId;
|
||||||
|
import net.sf.briar.api.protocol.BatchId;
|
||||||
|
import net.sf.briar.api.protocol.GroupId;
|
||||||
|
import net.sf.briar.api.protocol.Message;
|
||||||
|
import net.sf.briar.api.protocol.MessageFactory;
|
||||||
|
import net.sf.briar.api.protocol.MessageId;
|
||||||
|
import net.sf.briar.protocol.MessageImpl;
|
||||||
|
|
||||||
|
import org.jmock.Expectations;
|
||||||
|
import org.jmock.Mockery;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class H2DatabaseTest extends TestCase {
|
||||||
|
|
||||||
|
private static final int ONE_MEGABYTE = 1024 * 1024;
|
||||||
|
private static final int MAX_SIZE = 5 * ONE_MEGABYTE;
|
||||||
|
|
||||||
|
private final File testDir = TestUtils.getTestDirectory();
|
||||||
|
// The password has the format <file password> <space> <user password>
|
||||||
|
private final String passwordString = "foo bar";
|
||||||
|
// Some bytes for test IDs
|
||||||
|
private final byte[] idBytes = new byte[32], idBytes1 = new byte[32];
|
||||||
|
private final BatchId batchId;
|
||||||
|
private final ContactId contactId;
|
||||||
|
private final MessageId messageId;
|
||||||
|
private final GroupId groupId;
|
||||||
|
private final AuthorId authorId;
|
||||||
|
private final long timestamp = System.currentTimeMillis();
|
||||||
|
private final int size = 1234;
|
||||||
|
private final byte[] body = new byte[size];
|
||||||
|
private final Message message;
|
||||||
|
|
||||||
|
public H2DatabaseTest() {
|
||||||
|
super();
|
||||||
|
for(int i = 0; i < idBytes.length; i++) idBytes[i] = (byte) i;
|
||||||
|
for(int i = 0; i < idBytes1.length; i++) idBytes1[i] = (byte) (i + 1);
|
||||||
|
for(int i = 0; i < body.length; i++) body[i] = (byte) i;
|
||||||
|
batchId = new BatchId(idBytes);
|
||||||
|
contactId = new ContactId(123);
|
||||||
|
messageId = new MessageId(idBytes);
|
||||||
|
groupId = new GroupId(idBytes);
|
||||||
|
authorId = new AuthorId(idBytes);
|
||||||
|
message = new MessageImpl(messageId, MessageId.NONE, groupId, authorId,
|
||||||
|
timestamp, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
testDir.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPersistence() throws DbException {
|
||||||
|
MessageFactory messageFactory = new TestMessageFactory();
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Store some records
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
assertFalse(db.containsContact(txn, contactId));
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
assertTrue(db.containsContact(txn, contactId));
|
||||||
|
assertFalse(db.containsSubscription(txn, groupId));
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
assertTrue(db.containsSubscription(txn, groupId));
|
||||||
|
assertFalse(db.containsMessage(txn, messageId));
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
// Reopen the database
|
||||||
|
db = open(true, messageFactory);
|
||||||
|
// Check that the records are still there
|
||||||
|
txn = db.startTransaction();
|
||||||
|
assertTrue(db.containsContact(txn, contactId));
|
||||||
|
assertTrue(db.containsSubscription(txn, groupId));
|
||||||
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
|
Message m1 = db.getMessage(txn, messageId);
|
||||||
|
assertEquals(messageId, m1.getId());
|
||||||
|
assertEquals(MessageId.NONE, m1.getParent());
|
||||||
|
assertEquals(groupId, m1.getGroup());
|
||||||
|
assertEquals(authorId, m1.getAuthor());
|
||||||
|
assertEquals(timestamp, m1.getTimestamp());
|
||||||
|
assertEquals(size, m1.getSize());
|
||||||
|
assertTrue(Arrays.equals(body, m1.getBody()));
|
||||||
|
// Delete the records
|
||||||
|
db.removeContact(txn, contactId);
|
||||||
|
db.removeMessage(txn, messageId);
|
||||||
|
db.removeSubscription(txn, groupId);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
|
||||||
|
// Repoen the database
|
||||||
|
db = open(true, messageFactory);
|
||||||
|
// Check that the records are gone
|
||||||
|
txn = db.startTransaction();
|
||||||
|
assertFalse(db.containsContact(txn, contactId));
|
||||||
|
assertFalse(db.containsSubscription(txn, groupId));
|
||||||
|
assertFalse(db.containsMessage(txn, messageId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRatings() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
// Unknown authors should be unrated
|
||||||
|
assertEquals(Rating.UNRATED, db.getRating(txn, authorId));
|
||||||
|
// Store a rating
|
||||||
|
db.setRating(txn, authorId, Rating.GOOD);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
// Check that the rating was stored
|
||||||
|
txn = db.startTransaction();
|
||||||
|
assertEquals(Rating.GOOD, db.getRating(txn, authorId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnsubscribingRemovesMessage() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Unsubscribing from the group should delete the message
|
||||||
|
txn = db.startTransaction();
|
||||||
|
assertTrue(db.containsMessage(txn, messageId));
|
||||||
|
db.removeSubscription(txn, groupId);
|
||||||
|
assertFalse(db.containsMessage(txn, messageId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendableMessagesMustBeSendable() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message should not be sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
assertEquals(0, db.getSendability(txn, messageId));
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Changing the sendability to > 0 should make the message sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Changing the sendability to 0 should make the message unsendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.setSendability(txn, messageId, 0);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendableMessagesMustBeNew() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message has no status yet, so it should not be sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Changing the status to Status.NEW should make the message sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Changing the status to SENT should make the message unsendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.SENT);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Changing the status to SEEN should also make the message unsendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.SEEN);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendableMessagesMustBeSubscribed() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The contact is not subscribed, so the message should not be sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The contact subscribing should make the message sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The contact unsubscribing should make the message unsendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
db.clearSubscriptions(txn, contactId);
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSendableMessagesMustFitCapacity() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message is too large to send
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, size - 1).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message is just the right size to send
|
||||||
|
txn = db.startTransaction();
|
||||||
|
it = db.getSendableMessages(txn, contactId, size).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBatchesToAck() throws DbException {
|
||||||
|
BatchId batchId1 = new BatchId(idBytes1);
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact and some batches to ack
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addBatchToAck(txn, contactId, batchId);
|
||||||
|
db.addBatchToAck(txn, contactId, batchId1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Both batch IDs should be returned
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Set<BatchId> acks = db.removeBatchesToAck(txn, contactId);
|
||||||
|
assertEquals(2, acks.size());
|
||||||
|
assertTrue(acks.contains(batchId));
|
||||||
|
assertTrue(acks.contains(batchId1));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Both batch IDs should have been removed
|
||||||
|
txn = db.startTransaction();
|
||||||
|
acks = db.removeBatchesToAck(txn, contactId);
|
||||||
|
assertEquals(0, acks.size());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveAckedBatch() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Get the message and mark it as sent
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.SENT);
|
||||||
|
db.addOutstandingBatch(txn, contactId, batchId,
|
||||||
|
Collections.singleton(messageId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message should no longer be sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
// Pretend that the batch was acked
|
||||||
|
db.removeAckedBatch(txn, contactId, batchId);
|
||||||
|
// The message still should not be sendable
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemoveLostBatch() throws DbException {
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Add a contact, subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addContact(txn, contactId);
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addSubscription(txn, contactId, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.setSendability(txn, messageId, 1);
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.NEW);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Get the message and mark it as sent
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.setStatus(txn, contactId, messageId, Status.SENT);
|
||||||
|
db.addOutstandingBatch(txn, contactId, batchId,
|
||||||
|
Collections.singleton(messageId));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// The message should no longer be sendable
|
||||||
|
txn = db.startTransaction();
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
// Pretend that the batch was lost
|
||||||
|
db.removeLostBatch(txn, contactId, batchId);
|
||||||
|
// The message should be sendable again
|
||||||
|
it = db.getSendableMessages(txn, contactId, ONE_MEGABYTE).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMessagesByAuthor() throws DbException {
|
||||||
|
AuthorId authorId1 = new AuthorId(idBytes1);
|
||||||
|
MessageId messageId1 = new MessageId(idBytes1);
|
||||||
|
Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
|
||||||
|
authorId1, timestamp, body);
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Subscribe to a group and store two messages
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.addMessage(txn, message1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Check that each message is retrievable via its author
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getMessagesByAuthor(txn, authorId).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
it = db.getMessagesByAuthor(txn, authorId1).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId1, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetMessagesByParent() throws DbException {
|
||||||
|
MessageId parentId = new MessageId(idBytes1);
|
||||||
|
Message message1 = new MessageImpl(messageId, parentId, groupId,
|
||||||
|
authorId, timestamp, body);
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Subscribe to a group and store a message
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Check that the message is retrievable via its parent
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it =
|
||||||
|
db.getMessagesByParent(txn, parentId).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetOldMessages() throws DbException {
|
||||||
|
MessageId messageId1 = new MessageId(idBytes1);
|
||||||
|
Message message1 = new MessageImpl(messageId1, MessageId.NONE, groupId,
|
||||||
|
authorId, timestamp + 1000, body);
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Subscribe to a group and store two messages
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message);
|
||||||
|
db.addMessage(txn, message1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Allowing enough capacity for one message should return the older one
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Iterator<MessageId> it = db.getOldMessages(txn, size).iterator();
|
||||||
|
assertTrue(it.hasNext());
|
||||||
|
assertEquals(messageId, it.next());
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
// Allowing enough capacity for both messages should return both
|
||||||
|
txn = db.startTransaction();
|
||||||
|
Set<MessageId> ids = new HashSet<MessageId>();
|
||||||
|
for(MessageId id : db.getOldMessages(txn, size * 2)) ids.add(id);
|
||||||
|
assertEquals(2, ids.size());
|
||||||
|
assertTrue(ids.contains(messageId));
|
||||||
|
assertTrue(ids.contains(messageId1));
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetFreeSpace() throws DbException {
|
||||||
|
byte[] largeBody = new byte[ONE_MEGABYTE];
|
||||||
|
for(int i = 0; i < largeBody.length; i++) largeBody[i] = (byte) i;
|
||||||
|
Message message1 = new MessageImpl(messageId, MessageId.NONE, groupId,
|
||||||
|
authorId, timestamp, largeBody);
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
MessageFactory messageFactory = context.mock(MessageFactory.class);
|
||||||
|
|
||||||
|
// Create a new database
|
||||||
|
Database<Connection> db = open(false, messageFactory);
|
||||||
|
// Sanity check: there should be enough space on disk for this test
|
||||||
|
assertTrue(testDir.getFreeSpace() > MAX_SIZE);
|
||||||
|
// The free space should not be more than the allowed maximum size
|
||||||
|
long free = db.getFreeSpace();
|
||||||
|
assertTrue(free <= MAX_SIZE);
|
||||||
|
assertTrue(free > 0);
|
||||||
|
// Storing a message should reduce the free space
|
||||||
|
Connection txn = db.startTransaction();
|
||||||
|
db.addSubscription(txn, groupId);
|
||||||
|
db.addMessage(txn, message1);
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
assertTrue(db.getFreeSpace() < free);
|
||||||
|
|
||||||
|
db.close();
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Database<Connection> open(boolean resume,
|
||||||
|
MessageFactory messageFactory) throws DbException {
|
||||||
|
final char[] passwordArray = passwordString.toCharArray();
|
||||||
|
Mockery context = new Mockery();
|
||||||
|
final Password password = context.mock(Password.class);
|
||||||
|
context.checking(new Expectations() {{
|
||||||
|
oneOf(password).getPassword();
|
||||||
|
will(returnValue(passwordArray));
|
||||||
|
}});
|
||||||
|
Database<Connection> db =
|
||||||
|
new H2Database(testDir, messageFactory, password, MAX_SIZE);
|
||||||
|
db.open(resume);
|
||||||
|
context.assertIsSatisfied();
|
||||||
|
// The password array should be cleared after use
|
||||||
|
assertTrue(Arrays.equals(new char[passwordString.length()],
|
||||||
|
passwordArray));
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestMessageFactory implements MessageFactory {
|
||||||
|
|
||||||
|
public Message createMessage(MessageId id, MessageId parent,
|
||||||
|
GroupId group, AuthorId author, long timestamp, byte[] body) {
|
||||||
|
return new MessageImpl(id, parent, group, author, timestamp, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -97,6 +97,6 @@ public class I18nTest extends TestCase {
|
|||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.delete(base);
|
TestUtils.delete(base);
|
||||||
TestUtils.delete(french);
|
TestUtils.delete(french);
|
||||||
TestUtils.deleteTestDirectories();
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,6 @@ public class InvitationWorkerTest extends TestCase {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectories();
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,6 @@ public class SetupWorkerTest extends TestCase {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectories();
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,6 @@ public class FileUtilsTest extends TestCase {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectories();
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -196,6 +196,6 @@ public class ZipUtilsTest extends TestCase {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
TestUtils.deleteTestDirectories();
|
TestUtils.deleteTestDirectory(testDir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user