From 47708d489d2a393e68093afc5e94ebdb62986db1 Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Tue, 10 Dec 2013 22:23:37 +0000 Subject: [PATCH] Added the ability to remove pseudonyms from the database. --- .../sf/briar/api/db/DatabaseComponent.java | 15 +- .../api/db/LocalAuthorExistsException.java | 10 ++ .../api/db/NoSuchLocalAuthorException.java | 11 ++ .../api/db/event/LocalAuthorAddedEvent.java | 17 +++ .../api/db/event/LocalAuthorRemovedEvent.java | 17 +++ briar-core/src/net/sf/briar/db/Database.java | 40 ++++- .../sf/briar/db/DatabaseComponentImpl.java | 142 +++++++++++++++--- .../src/net/sf/briar/db/JdbcDatabase.java | 66 +++++++- .../sf/briar/db/DatabaseComponentTest.java | 66 +++++++- .../src/net/sf/briar/db/H2DatabaseTest.java | 25 +++ 10 files changed, 368 insertions(+), 41 deletions(-) create mode 100644 briar-api/src/net/sf/briar/api/db/LocalAuthorExistsException.java create mode 100644 briar-api/src/net/sf/briar/api/db/NoSuchLocalAuthorException.java create mode 100644 briar-api/src/net/sf/briar/api/db/event/LocalAuthorAddedEvent.java create mode 100644 briar-api/src/net/sf/briar/api/db/event/LocalAuthorRemovedEvent.java diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java index 312dfd18ec..4e438d2781 100644 --- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java +++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java @@ -56,7 +56,7 @@ public interface DatabaseComponent { /** Stores an endpoint. */ void addEndpoint(Endpoint ep) throws DbException; - /** Stores a pseudonym that the user can use to sign messages. */ + /** Stores a local pseudonym. */ void addLocalAuthor(LocalAuthor a) throws DbException; /** Stores a locally generated group message. */ @@ -179,10 +179,10 @@ public interface DatabaseComponent { */ Map<ContactId, Long> getLastConnected() throws DbException; - /** Returns the pseudonym with the given ID. */ + /** Returns the local pseudonym with the given ID. */ LocalAuthor getLocalAuthor(AuthorId a) throws DbException; - /** Returns all pseudonyms that the user can use to sign messages. */ + /** Returns all local pseudonyms. */ Collection<LocalAuthor> getLocalAuthors() throws DbException; /** Returns the local transport properties for all transports. */ @@ -295,6 +295,11 @@ public interface DatabaseComponent { /** Removes a contact (and all associated state) from the database. */ void removeContact(ContactId c) throws DbException; + /** + * Removes a local pseudonym (and all associated state) from the database. + */ + void removeLocalAuthor(AuthorId a) throws DbException; + /** * Removes a transport (and any associated configuration and local * properties) from the database. @@ -302,8 +307,8 @@ public interface DatabaseComponent { void removeTransport(TransportId t) throws DbException; /** - * Sets the connection reordering window for the given endoint in the given - * rotation period. + * Sets the connection reordering window for the given endpoint in the + * given rotation period. */ void setConnectionWindow(ContactId c, TransportId t, long period, long centre, byte[] bitmap) throws DbException; diff --git a/briar-api/src/net/sf/briar/api/db/LocalAuthorExistsException.java b/briar-api/src/net/sf/briar/api/db/LocalAuthorExistsException.java new file mode 100644 index 0000000000..52425d7e43 --- /dev/null +++ b/briar-api/src/net/sf/briar/api/db/LocalAuthorExistsException.java @@ -0,0 +1,10 @@ +package net.sf.briar.api.db; + +/** + * Thrown when a duplicate pseudonym is added to the database. This exception + * may occur due to concurrent updates and does not indicate a database error. + */ +public class LocalAuthorExistsException extends DbException { + + private static final long serialVersionUID = -1483877298070151673L; +} diff --git a/briar-api/src/net/sf/briar/api/db/NoSuchLocalAuthorException.java b/briar-api/src/net/sf/briar/api/db/NoSuchLocalAuthorException.java new file mode 100644 index 0000000000..d091fdaeb4 --- /dev/null +++ b/briar-api/src/net/sf/briar/api/db/NoSuchLocalAuthorException.java @@ -0,0 +1,11 @@ +package net.sf.briar.api.db; + +/** + * Thrown when a database operation is attempted for a pseudonym that is not in + * the database. This exception may occur due to concurrent updates and does + * not indicate a database error. + */ +public class NoSuchLocalAuthorException extends DbException { + + private static final long serialVersionUID = 494398665376703860L; +} diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalAuthorAddedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalAuthorAddedEvent.java new file mode 100644 index 0000000000..4b72402fa3 --- /dev/null +++ b/briar-api/src/net/sf/briar/api/db/event/LocalAuthorAddedEvent.java @@ -0,0 +1,17 @@ +package net.sf.briar.api.db.event; + +import net.sf.briar.api.AuthorId; + +/** An event that is broadcast when a pseudonym for the user is added. */ +public class LocalAuthorAddedEvent extends DatabaseEvent { + + private final AuthorId authorId; + + public LocalAuthorAddedEvent(AuthorId authorId) { + this.authorId = authorId; + } + + public AuthorId getAuthorId() { + return authorId; + } +} diff --git a/briar-api/src/net/sf/briar/api/db/event/LocalAuthorRemovedEvent.java b/briar-api/src/net/sf/briar/api/db/event/LocalAuthorRemovedEvent.java new file mode 100644 index 0000000000..0b5bd8849e --- /dev/null +++ b/briar-api/src/net/sf/briar/api/db/event/LocalAuthorRemovedEvent.java @@ -0,0 +1,17 @@ +package net.sf.briar.api.db.event; + +import net.sf.briar.api.AuthorId; + +/** An event that is broadcast when a pseudonym for the user is removed. */ +public class LocalAuthorRemovedEvent extends DatabaseEvent { + + private final AuthorId authorId; + + public LocalAuthorRemovedEvent(AuthorId authorId) { + this.authorId = authorId; + } + + public AuthorId getAuthorId() { + return authorId; + } +} diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java index 9df90f43f4..1a10cc82db 100644 --- a/briar-core/src/net/sf/briar/db/Database.java +++ b/briar-core/src/net/sf/briar/db/Database.java @@ -49,6 +49,8 @@ import net.sf.briar.api.transport.TemporarySecret; * <li> transport * <li> window * </ul> + * If table A has a foreign key pointing to table B, we get a read lock on A to + * read A, a write lock on A to write A, and write locks on A and B to write B. */ interface Database<T> { @@ -80,8 +82,8 @@ interface Database<T> { * Stores a contact with the given pseudonym, associated with the given * local pseudonym, and returns an ID for the contact. * <p> - * Locking: contact write, retention write, subscription write, transport - * write, window write. + * Locking: contact write, message write, retention write, + * subscription write, transport write, window write. */ ContactId addContact(T txn, Author remote, AuthorId local) throws DbException; @@ -103,9 +105,10 @@ interface Database<T> { throws DbException; /** - * Stores a pseudonym that the user can use to sign messages. + * Stores a local pseudonym. * <p> - * Locking: contact write, identity write. + * Locking: contact write, identity write, message write, retention write, + * subscription write, transport write, window write. */ void addLocalAuthor(T txn, LocalAuthor a) throws DbException; @@ -181,6 +184,13 @@ interface Database<T> { */ boolean containsContact(T txn, ContactId c) throws DbException; + /** + * Returns true if the database contains the given local pseudonym. + * <p> + * Locking: identity read. + */ + boolean containsLocalAuthor(T txn, AuthorId a) throws DbException; + /** * Returns true if the database contains the given message. * <p> @@ -246,6 +256,13 @@ interface Database<T> { */ Collection<Contact> getContacts(T txn) throws DbException; + /** + * Returns all contacts associated with the given local pseudonym. + * <p> + * Locking: contact read. + */ + Collection<ContactId> getContacts(T txn, AuthorId a) throws DbException; + /** * Returns all endpoints. * <p> @@ -291,14 +308,14 @@ interface Database<T> { Map<ContactId, Long> getLastConnected(T txn) throws DbException; /** - * Returns the pseudonym with the given ID. + * Returns the local pseudonym with the given ID. * <p> - * Locking: identitiy read. + * Locking: identity read. */ LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; /** - * Returns all pseudonyms that the user can use to sign messages. + * Returns all local pseudonyms. * <p> * Locking: identity read. */ @@ -566,6 +583,15 @@ interface Database<T> { */ void removeContact(T txn, ContactId c) throws DbException; + /** + * Removes the local pseudonym with the given ID (and all associated + * state) from the database. + * <p> + * Locking: contact write, identity write, message write, retention write, + * subscription write, transport write, window write. + */ + void removeLocalAuthor(T txn, AuthorId a) throws DbException; + /** * Removes a message (and all associated state) from the database. * <p> diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java index df375151f6..d7c45f52a4 100644 --- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java +++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java @@ -38,7 +38,9 @@ import net.sf.briar.api.db.ContactExistsException; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.GroupMessageHeader; +import net.sf.briar.api.db.LocalAuthorExistsException; import net.sf.briar.api.db.NoSuchContactException; +import net.sf.briar.api.db.NoSuchLocalAuthorException; import net.sf.briar.api.db.NoSuchMessageException; import net.sf.briar.api.db.NoSuchSubscriptionException; import net.sf.briar.api.db.NoSuchTransportException; @@ -48,6 +50,8 @@ import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.DatabaseEvent; import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.GroupMessageAddedEvent; +import net.sf.briar.api.db.event.LocalAuthorAddedEvent; +import net.sf.briar.api.db.event.LocalAuthorRemovedEvent; import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent; import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent; import net.sf.briar.api.db.event.MessageExpiredEvent; @@ -186,35 +190,47 @@ DatabaseCleaner.Callback { ContactId c; contactLock.writeLock().lock(); try { - retentionLock.writeLock().lock(); + identityLock.readLock().lock(); try { - subscriptionLock.writeLock().lock(); + messageLock.writeLock().lock(); try { - transportLock.writeLock().lock(); + retentionLock.writeLock().lock(); try { - windowLock.writeLock().lock(); + subscriptionLock.writeLock().lock(); try { - T txn = db.startTransaction(); + transportLock.writeLock().lock(); try { - if(db.containsContact(txn, remote.getId())) - throw new ContactExistsException(); - c = db.addContact(txn, remote, local); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(db.containsContact(txn, remote.getId())) + throw new ContactExistsException(); + if(!db.containsLocalAuthor(txn, local)) + throw new NoSuchLocalAuthorException(); + c = db.addContact(txn, remote, local); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.writeLock().unlock(); } } finally { - windowLock.writeLock().unlock(); + subscriptionLock.writeLock().unlock(); } } finally { - transportLock.writeLock().unlock(); + retentionLock.writeLock().unlock(); } } finally { - subscriptionLock.writeLock().unlock(); + messageLock.writeLock().unlock(); } } finally { - retentionLock.writeLock().unlock(); + identityLock.readLock().unlock(); } } finally { contactLock.writeLock().unlock(); @@ -263,13 +279,40 @@ DatabaseCleaner.Callback { try { identityLock.writeLock().lock(); try { - T txn = db.startTransaction(); + messageLock.writeLock().lock(); try { - db.addLocalAuthor(txn, a); - db.commitTransaction(txn); - } catch(DbException e) { - db.abortTransaction(txn); - throw e; + retentionLock.writeLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + transportLock.writeLock().lock(); + try { + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(db.containsLocalAuthor(txn, a.getId())) + throw new LocalAuthorExistsException(); + db.addLocalAuthor(txn, a); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + retentionLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); } } finally { identityLock.writeLock().unlock(); @@ -277,6 +320,7 @@ DatabaseCleaner.Callback { } finally { contactLock.writeLock().unlock(); } + callListeners(new LocalAuthorAddedEvent(a.getId())); } public void addLocalGroupMessage(Message m) throws DbException { @@ -942,6 +986,8 @@ DatabaseCleaner.Callback { try { T txn = db.startTransaction(); try { + if(!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); LocalAuthor localAuthor = db.getLocalAuthor(txn, a); db.commitTransaction(txn); return localAuthor; @@ -1643,6 +1689,58 @@ DatabaseCleaner.Callback { callListeners(new ContactRemovedEvent(c)); } + public void removeLocalAuthor(AuthorId a) throws DbException { + Collection<ContactId> affected; + contactLock.writeLock().lock(); + try { + identityLock.writeLock().lock(); + try { + messageLock.writeLock().lock(); + try { + retentionLock.writeLock().lock(); + try { + subscriptionLock.writeLock().lock(); + try { + transportLock.writeLock().lock(); + try { + windowLock.writeLock().lock(); + try { + T txn = db.startTransaction(); + try { + if(!db.containsLocalAuthor(txn, a)) + throw new NoSuchLocalAuthorException(); + affected = db.getContacts(txn, a); + db.removeLocalAuthor(txn, a); + db.commitTransaction(txn); + } catch(DbException e) { + db.abortTransaction(txn); + throw e; + } + } finally { + windowLock.writeLock().unlock(); + } + } finally { + transportLock.writeLock().unlock(); + } + } finally { + subscriptionLock.writeLock().unlock(); + } + } finally { + retentionLock.writeLock().unlock(); + } + } finally { + messageLock.writeLock().unlock(); + } + } finally { + identityLock.writeLock().unlock(); + } + } finally { + contactLock.writeLock().unlock(); + } + for(ContactId c : affected) callListeners(new ContactRemovedEvent(c)); + callListeners(new LocalAuthorRemovedEvent(a)); + } + public void removeTransport(TransportId t) throws DbException { transportLock.writeLock().lock(); try { diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java index f7de855305..2c432b5c66 100644 --- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java +++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java @@ -60,6 +60,7 @@ import net.sf.briar.api.transport.TemporarySecret; abstract class JdbcDatabase implements Database<Connection> { // Locking: identity + // Dependents: contact, message, retention, subscription, transport, window private static final String CREATE_LOCAL_AUTHORS = "CREATE TABLE localAuthors" + " (authorId HASH NOT NULL," @@ -81,10 +82,7 @@ abstract class JdbcDatabase implements Database<Connection> { + " UNIQUE (authorId)," + " FOREIGN KEY (localAuthorId)" + " REFERENCES localAuthors (authorId)" - + " ON DELETE RESTRICT)"; // Deletion not allowed - - private static final String INDEX_CONTACTS_BY_AUTHOR = - "CREATE INDEX contactsByAuthor ON contacts (authorId)"; + + " ON DELETE CASCADE)"; // Locking: subscription // Dependents: message @@ -376,7 +374,6 @@ abstract class JdbcDatabase implements Database<Connection> { s = txn.createStatement(); s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS)); s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); - s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR); s.executeUpdate(insertTypeNames(CREATE_GROUPS)); s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES)); s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS)); @@ -1000,6 +997,27 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public boolean containsLocalAuthor(Connection txn, AuthorId a) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT NULL FROM localAuthors WHERE authorId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, a.getBytes()); + rs = ps.executeQuery(); + boolean found = rs.next(); + if(rs.next()) throw new DbStateException(); + rs.close(); + ps.close(); + return found; + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public boolean containsMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; @@ -1228,6 +1246,28 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public Collection<ContactId> getContacts(Connection txn, AuthorId a) + throws DbException { + PreparedStatement ps = null; + ResultSet rs = null; + try { + String sql = "SELECT contactId FROM contacts" + + " WHERE localAuthorId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, a.getBytes()); + rs = ps.executeQuery(); + List<ContactId> ids = new ArrayList<ContactId>(); + while(rs.next()) ids.add(new ContactId(rs.getInt(1))); + rs.close(); + ps.close(); + return Collections.unmodifiableList(ids); + } catch(SQLException e) { + tryToClose(rs); + tryToClose(ps); + throw new DbException(e); + } + } + public Collection<Endpoint> getEndpoints(Connection txn) throws DbException { PreparedStatement ps = null; @@ -2543,6 +2583,22 @@ abstract class JdbcDatabase implements Database<Connection> { } } + public void removeLocalAuthor(Connection txn, AuthorId a) + throws DbException { + PreparedStatement ps = null; + try { + String sql = "DELETE FROM localAuthors WHERE authorId = ?"; + ps = txn.prepareStatement(sql); + ps.setBytes(1, a.getBytes()); + int affected = ps.executeUpdate(); + if(affected != 1) throw new DbStateException(); + ps.close(); + } catch(SQLException e) { + tryToClose(ps); + throw new DbException(e); + } + } + public void removeMessage(Connection txn, MessageId m) throws DbException { PreparedStatement ps = null; try { diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java index 6fcac54935..cc6a37ab37 100644 --- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java +++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java @@ -25,12 +25,15 @@ import net.sf.briar.api.TransportProperties; import net.sf.briar.api.db.AckAndRequest; import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.NoSuchContactException; +import net.sf.briar.api.db.NoSuchLocalAuthorException; import net.sf.briar.api.db.NoSuchSubscriptionException; import net.sf.briar.api.db.NoSuchTransportException; import net.sf.briar.api.db.event.ContactAddedEvent; import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.GroupMessageAddedEvent; +import net.sf.briar.api.db.event.LocalAuthorAddedEvent; +import net.sf.briar.api.db.event.LocalAuthorRemovedEvent; import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent; import net.sf.briar.api.db.event.PrivateMessageAddedEvent; import net.sf.briar.api.db.event.SubscriptionAddedEvent; @@ -122,9 +125,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase { final ShutdownManager shutdown = context.mock(ShutdownManager.class); final DatabaseListener listener = context.mock(DatabaseListener.class); context.checking(new Expectations() {{ - exactly(10).of(database).startTransaction(); + exactly(11).of(database).startTransaction(); will(returnValue(txn)); - exactly(10).of(database).commitTransaction(txn); + exactly(11).of(database).commitTransaction(txn); // open() oneOf(database).open(); will(returnValue(false)); @@ -134,10 +137,16 @@ public abstract class DatabaseComponentTest extends BriarTestCase { oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); will(returnValue(shutdownHandle)); // addLocalAuthor(localAuthor) + oneOf(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(false)); oneOf(database).addLocalAuthor(txn, localAuthor); + oneOf(listener).eventOccurred(with(any( + LocalAuthorAddedEvent.class))); // addContact(author, localAuthorId) oneOf(database).containsContact(txn, authorId); will(returnValue(false)); + oneOf(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(true)); oneOf(database).addContact(txn, author, localAuthorId); will(returnValue(contactId)); oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class))); @@ -180,6 +189,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(true)); oneOf(database).removeContact(txn, contactId); oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class))); + // removeLocalAuthor(localAuthorId) + oneOf(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(true)); + oneOf(database).getContacts(txn, localAuthorId); + will(returnValue(Collections.emptyList())); + oneOf(database).removeLocalAuthor(txn, localAuthorId); + oneOf(listener).eventOccurred(with(any( + LocalAuthorRemovedEvent.class))); // close() oneOf(shutdown).removeShutdownHook(shutdownHandle); oneOf(cleaner).stopCleaning(); @@ -202,6 +219,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase { assertEquals(Arrays.asList(groupId), db.getSubscriptions()); db.unsubscribe(group); db.removeContact(contactId); + db.removeLocalAuthor(localAuthorId); db.removeListener(listener); db.close(); @@ -512,6 +530,46 @@ public abstract class DatabaseComponentTest extends BriarTestCase { context.assertIsSatisfied(); } + @Test + public void testVariousMethodsThrowExceptionIfLocalAuthorIsMissing() + throws Exception { + Mockery context = new Mockery(); + @SuppressWarnings("unchecked") + final Database<Object> database = context.mock(Database.class); + final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class); + final ShutdownManager shutdown = context.mock(ShutdownManager.class); + context.checking(new Expectations() {{ + // Check whether the pseudonym is in the DB (which it's not) + exactly(3).of(database).startTransaction(); + will(returnValue(txn)); + exactly(3).of(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(false)); + exactly(3).of(database).abortTransaction(txn); + // This is needed for addContact() to proceed + exactly(1).of(database).containsContact(txn, authorId); + will(returnValue(false)); + }}); + DatabaseComponent db = createDatabaseComponent(database, cleaner, + shutdown); + + try { + db.addContact(author, localAuthorId); + fail(); + } catch(NoSuchLocalAuthorException expected) {} + + try { + db.getLocalAuthor(localAuthorId); + fail(); + } catch(NoSuchLocalAuthorException expected) {} + + try { + db.removeLocalAuthor(localAuthorId); + fail(); + } catch(NoSuchLocalAuthorException expected) {} + + context.assertIsSatisfied(); + } + @Test public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing() throws Exception { @@ -571,6 +629,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { // addLocalAuthor(localAuthor) oneOf(database).startTransaction(); will(returnValue(txn)); + oneOf(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(false)); oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).commitTransaction(txn); // addContact(author, localAuthorId) @@ -578,6 +638,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase { will(returnValue(txn)); oneOf(database).containsContact(txn, authorId); will(returnValue(false)); + oneOf(database).containsLocalAuthor(txn, localAuthorId); + will(returnValue(true)); oneOf(database).addContact(txn, author, localAuthorId); will(returnValue(contactId)); oneOf(database).commitTransaction(txn); diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java index c6719eab26..82e9ef580b 100644 --- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java +++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java @@ -1715,6 +1715,31 @@ public class H2DatabaseTest extends BriarTestCase { db.close(); } + @Test + public void testGetContactsByLocalAuthorId() throws Exception { + Database<Connection> db = open(false); + Connection txn = db.startTransaction(); + + // Add a local author - no contacts should be associated + db.addLocalAuthor(txn, localAuthor); + Collection<ContactId> contacts = db.getContacts(txn, localAuthorId); + assertEquals(Collections.emptyList(), contacts); + + // Add a contact associated with the local author + assertEquals(contactId, db.addContact(txn, author, localAuthorId)); + contacts = db.getContacts(txn, localAuthorId); + assertEquals(Collections.singletonList(contactId), contacts); + + // Remove the local author - the contact should be removed + db.removeLocalAuthor(txn, localAuthorId); + contacts = db.getContacts(txn, localAuthorId); + assertEquals(Collections.emptyList(), contacts); + assertFalse(db.containsContact(txn, contactId)); + + db.commitTransaction(txn); + db.close(); + } + @Test public void testExceptionHandling() throws Exception { Database<Connection> db = open(false); -- GitLab