Added the ability to remove pseudonyms from the database.

This commit is contained in:
akwizgran
2013-12-10 22:23:37 +00:00
parent 667dbfdd4a
commit 47708d489d
10 changed files with 368 additions and 41 deletions

View File

@@ -56,7 +56,7 @@ public interface DatabaseComponent {
/** Stores an endpoint. */ /** Stores an endpoint. */
void addEndpoint(Endpoint ep) throws DbException; 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; void addLocalAuthor(LocalAuthor a) throws DbException;
/** Stores a locally generated group message. */ /** Stores a locally generated group message. */
@@ -179,10 +179,10 @@ public interface DatabaseComponent {
*/ */
Map<ContactId, Long> getLastConnected() throws DbException; 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; 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; Collection<LocalAuthor> getLocalAuthors() throws DbException;
/** Returns the local transport properties for all transports. */ /** 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. */ /** Removes a contact (and all associated state) from the database. */
void removeContact(ContactId c) throws DbException; 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 * Removes a transport (and any associated configuration and local
* properties) from the database. * properties) from the database.
@@ -302,8 +307,8 @@ public interface DatabaseComponent {
void removeTransport(TransportId t) throws DbException; void removeTransport(TransportId t) throws DbException;
/** /**
* Sets the connection reordering window for the given endoint in the given * Sets the connection reordering window for the given endpoint in the
* rotation period. * given rotation period.
*/ */
void setConnectionWindow(ContactId c, TransportId t, long period, void setConnectionWindow(ContactId c, TransportId t, long period,
long centre, byte[] bitmap) throws DbException; long centre, byte[] bitmap) throws DbException;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -49,6 +49,8 @@ import net.sf.briar.api.transport.TemporarySecret;
* <li> transport * <li> transport
* <li> window * <li> window
* </ul> * </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> { interface Database<T> {
@@ -80,8 +82,8 @@ interface Database<T> {
* Stores a contact with the given pseudonym, associated with the given * Stores a contact with the given pseudonym, associated with the given
* local pseudonym, and returns an ID for the contact. * local pseudonym, and returns an ID for the contact.
* <p> * <p>
* Locking: contact write, retention write, subscription write, transport * Locking: contact write, message write, retention write,
* write, window write. * subscription write, transport write, window write.
*/ */
ContactId addContact(T txn, Author remote, AuthorId local) ContactId addContact(T txn, Author remote, AuthorId local)
throws DbException; throws DbException;
@@ -103,9 +105,10 @@ interface Database<T> {
throws DbException; throws DbException;
/** /**
* Stores a pseudonym that the user can use to sign messages. * Stores a local pseudonym.
* <p> * <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; void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
@@ -181,6 +184,13 @@ interface Database<T> {
*/ */
boolean containsContact(T txn, ContactId c) throws DbException; 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. * Returns true if the database contains the given message.
* <p> * <p>
@@ -246,6 +256,13 @@ interface Database<T> {
*/ */
Collection<Contact> getContacts(T txn) throws DbException; 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. * Returns all endpoints.
* <p> * <p>
@@ -291,14 +308,14 @@ interface Database<T> {
Map<ContactId, Long> getLastConnected(T txn) throws DbException; Map<ContactId, Long> getLastConnected(T txn) throws DbException;
/** /**
* Returns the pseudonym with the given ID. * Returns the local pseudonym with the given ID.
* <p> * <p>
* Locking: identitiy read. * Locking: identity read.
*/ */
LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException; LocalAuthor getLocalAuthor(T txn, AuthorId a) throws DbException;
/** /**
* Returns all pseudonyms that the user can use to sign messages. * Returns all local pseudonyms.
* <p> * <p>
* Locking: identity read. * Locking: identity read.
*/ */
@@ -566,6 +583,15 @@ interface Database<T> {
*/ */
void removeContact(T txn, ContactId c) throws DbException; 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. * Removes a message (and all associated state) from the database.
* <p> * <p>

View File

@@ -38,7 +38,9 @@ import net.sf.briar.api.db.ContactExistsException;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException; import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.GroupMessageHeader; 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.NoSuchContactException;
import net.sf.briar.api.db.NoSuchLocalAuthorException;
import net.sf.briar.api.db.NoSuchMessageException; import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.db.NoSuchSubscriptionException; import net.sf.briar.api.db.NoSuchSubscriptionException;
import net.sf.briar.api.db.NoSuchTransportException; 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.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.GroupMessageAddedEvent; 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.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent; import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent; import net.sf.briar.api.db.event.MessageExpiredEvent;
@@ -186,35 +190,47 @@ DatabaseCleaner.Callback {
ContactId c; ContactId c;
contactLock.writeLock().lock(); contactLock.writeLock().lock();
try { try {
retentionLock.writeLock().lock(); identityLock.readLock().lock();
try { try {
subscriptionLock.writeLock().lock(); messageLock.writeLock().lock();
try { try {
transportLock.writeLock().lock(); retentionLock.writeLock().lock();
try { try {
windowLock.writeLock().lock(); subscriptionLock.writeLock().lock();
try { try {
T txn = db.startTransaction(); transportLock.writeLock().lock();
try { try {
if(db.containsContact(txn, remote.getId())) windowLock.writeLock().lock();
throw new ContactExistsException(); try {
c = db.addContact(txn, remote, local); T txn = db.startTransaction();
db.commitTransaction(txn); try {
} catch(DbException e) { if(db.containsContact(txn, remote.getId()))
db.abortTransaction(txn); throw new ContactExistsException();
throw e; 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 { } finally {
windowLock.writeLock().unlock(); subscriptionLock.writeLock().unlock();
} }
} finally { } finally {
transportLock.writeLock().unlock(); retentionLock.writeLock().unlock();
} }
} finally { } finally {
subscriptionLock.writeLock().unlock(); messageLock.writeLock().unlock();
} }
} finally { } finally {
retentionLock.writeLock().unlock(); identityLock.readLock().unlock();
} }
} finally { } finally {
contactLock.writeLock().unlock(); contactLock.writeLock().unlock();
@@ -263,13 +279,40 @@ DatabaseCleaner.Callback {
try { try {
identityLock.writeLock().lock(); identityLock.writeLock().lock();
try { try {
T txn = db.startTransaction(); messageLock.writeLock().lock();
try { try {
db.addLocalAuthor(txn, a); retentionLock.writeLock().lock();
db.commitTransaction(txn); try {
} catch(DbException e) { subscriptionLock.writeLock().lock();
db.abortTransaction(txn); try {
throw e; 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 { } finally {
identityLock.writeLock().unlock(); identityLock.writeLock().unlock();
@@ -277,6 +320,7 @@ DatabaseCleaner.Callback {
} finally { } finally {
contactLock.writeLock().unlock(); contactLock.writeLock().unlock();
} }
callListeners(new LocalAuthorAddedEvent(a.getId()));
} }
public void addLocalGroupMessage(Message m) throws DbException { public void addLocalGroupMessage(Message m) throws DbException {
@@ -942,6 +986,8 @@ DatabaseCleaner.Callback {
try { try {
T txn = db.startTransaction(); T txn = db.startTransaction();
try { try {
if(!db.containsLocalAuthor(txn, a))
throw new NoSuchLocalAuthorException();
LocalAuthor localAuthor = db.getLocalAuthor(txn, a); LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
db.commitTransaction(txn); db.commitTransaction(txn);
return localAuthor; return localAuthor;
@@ -1643,6 +1689,58 @@ DatabaseCleaner.Callback {
callListeners(new ContactRemovedEvent(c)); 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 { public void removeTransport(TransportId t) throws DbException {
transportLock.writeLock().lock(); transportLock.writeLock().lock();
try { try {

View File

@@ -60,6 +60,7 @@ import net.sf.briar.api.transport.TemporarySecret;
abstract class JdbcDatabase implements Database<Connection> { abstract class JdbcDatabase implements Database<Connection> {
// Locking: identity // Locking: identity
// Dependents: contact, message, retention, subscription, transport, window
private static final String CREATE_LOCAL_AUTHORS = private static final String CREATE_LOCAL_AUTHORS =
"CREATE TABLE localAuthors" "CREATE TABLE localAuthors"
+ " (authorId HASH NOT NULL," + " (authorId HASH NOT NULL,"
@@ -81,10 +82,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " UNIQUE (authorId)," + " UNIQUE (authorId),"
+ " FOREIGN KEY (localAuthorId)" + " FOREIGN KEY (localAuthorId)"
+ " REFERENCES localAuthors (authorId)" + " REFERENCES localAuthors (authorId)"
+ " ON DELETE RESTRICT)"; // Deletion not allowed + " ON DELETE CASCADE)";
private static final String INDEX_CONTACTS_BY_AUTHOR =
"CREATE INDEX contactsByAuthor ON contacts (authorId)";
// Locking: subscription // Locking: subscription
// Dependents: message // Dependents: message
@@ -376,7 +374,6 @@ abstract class JdbcDatabase implements Database<Connection> {
s = txn.createStatement(); s = txn.createStatement();
s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS)); s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS)); s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
s.executeUpdate(insertTypeNames(CREATE_GROUPS)); s.executeUpdate(insertTypeNames(CREATE_GROUPS));
s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES)); s.executeUpdate(insertTypeNames(CREATE_GROUP_VISIBILITIES));
s.executeUpdate(insertTypeNames(CREATE_CONTACT_GROUPS)); 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) public boolean containsMessage(Connection txn, MessageId m)
throws DbException { throws DbException {
PreparedStatement ps = null; 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) public Collection<Endpoint> getEndpoints(Connection txn)
throws DbException { throws DbException {
PreparedStatement ps = null; 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 { public void removeMessage(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null; PreparedStatement ps = null;
try { try {

View File

@@ -25,12 +25,15 @@ import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.AckAndRequest; import net.sf.briar.api.db.AckAndRequest;
import net.sf.briar.api.db.DatabaseComponent; import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.NoSuchContactException; 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.NoSuchSubscriptionException;
import net.sf.briar.api.db.NoSuchTransportException; import net.sf.briar.api.db.NoSuchTransportException;
import net.sf.briar.api.db.event.ContactAddedEvent; import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent; import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseListener; import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.GroupMessageAddedEvent; 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.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.PrivateMessageAddedEvent; import net.sf.briar.api.db.event.PrivateMessageAddedEvent;
import net.sf.briar.api.db.event.SubscriptionAddedEvent; 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 ShutdownManager shutdown = context.mock(ShutdownManager.class);
final DatabaseListener listener = context.mock(DatabaseListener.class); final DatabaseListener listener = context.mock(DatabaseListener.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
exactly(10).of(database).startTransaction(); exactly(11).of(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
exactly(10).of(database).commitTransaction(txn); exactly(11).of(database).commitTransaction(txn);
// open() // open()
oneOf(database).open(); oneOf(database).open();
will(returnValue(false)); will(returnValue(false));
@@ -134,10 +137,16 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
// addLocalAuthor(localAuthor) // addLocalAuthor(localAuthor)
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(listener).eventOccurred(with(any(
LocalAuthorAddedEvent.class)));
// addContact(author, localAuthorId) // addContact(author, localAuthorId)
oneOf(database).containsContact(txn, authorId); oneOf(database).containsContact(txn, authorId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).addContact(txn, author, localAuthorId); oneOf(database).addContact(txn, author, localAuthorId);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class))); oneOf(listener).eventOccurred(with(any(ContactAddedEvent.class)));
@@ -180,6 +189,14 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(true)); will(returnValue(true));
oneOf(database).removeContact(txn, contactId); oneOf(database).removeContact(txn, contactId);
oneOf(listener).eventOccurred(with(any(ContactRemovedEvent.class))); 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() // close()
oneOf(shutdown).removeShutdownHook(shutdownHandle); oneOf(shutdown).removeShutdownHook(shutdownHandle);
oneOf(cleaner).stopCleaning(); oneOf(cleaner).stopCleaning();
@@ -202,6 +219,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
assertEquals(Arrays.asList(groupId), db.getSubscriptions()); assertEquals(Arrays.asList(groupId), db.getSubscriptions());
db.unsubscribe(group); db.unsubscribe(group);
db.removeContact(contactId); db.removeContact(contactId);
db.removeLocalAuthor(localAuthorId);
db.removeListener(listener); db.removeListener(listener);
db.close(); db.close();
@@ -512,6 +530,46 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
context.assertIsSatisfied(); 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 @Test
public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing() public void testVariousMethodsThrowExceptionIfSubscriptionIsMissing()
throws Exception { throws Exception {
@@ -571,6 +629,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
// addLocalAuthor(localAuthor) // addLocalAuthor(localAuthor)
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// addContact(author, localAuthorId) // addContact(author, localAuthorId)
@@ -578,6 +638,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
will(returnValue(txn)); will(returnValue(txn));
oneOf(database).containsContact(txn, authorId); oneOf(database).containsContact(txn, authorId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true));
oneOf(database).addContact(txn, author, localAuthorId); oneOf(database).addContact(txn, author, localAuthorId);
will(returnValue(contactId)); will(returnValue(contactId));
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);

View File

@@ -1715,6 +1715,31 @@ public class H2DatabaseTest extends BriarTestCase {
db.close(); 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 @Test
public void testExceptionHandling() throws Exception { public void testExceptionHandling() throws Exception {
Database<Connection> db = open(false); Database<Connection> db = open(false);