Add connectivity checkers for our own mailbox and a contact's mailbox.

This commit is contained in:
akwizgran
2022-05-26 12:53:42 +01:00
parent ef6e3bb2a7
commit 6358518f88
7 changed files with 674 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import javax.annotation.Nonnull;
import static org.briarproject.bramble.mailbox.ConnectivityCheckerImpl.CONNECTIVITY_CHECK_FRESHNESS_MS;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
private final Clock clock = context.mock(Clock.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final ApiCall apiCall = context.mock(ApiCall.class);
private final Cancellable task = context.mock(Cancellable.class);
private final ConnectivityObserver observer1 =
context.mock(ConnectivityObserver.class, "1");
private final ConnectivityObserver observer2 =
context.mock(ConnectivityObserver.class, "2");
private final MailboxProperties properties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
@Test
public void testFirstObserverStartsCheck() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the check succeeds the observer should be called
context.checking(new Expectations() {{
oneOf(observer1).onConnectivityCheckSucceeded();
}});
checker.onConnectivityCheckSucceeded(now);
// The observer should not be called again when subsequent checks
// succeed
checker.onConnectivityCheckSucceeded(now);
}
@Test
public void testObserverIsAddedToExistingCheck() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When checkConnectivity() is called again before the first check
// succeeds, the observer should be added to the existing check
checker.checkConnectivity(properties, observer2);
// When the check succeeds both observers should be called
context.checking(new Expectations() {{
oneOf(observer1).onConnectivityCheckSucceeded();
oneOf(observer2).onConnectivityCheckSucceeded();
}});
checker.onConnectivityCheckSucceeded(now);
// The observers should not be called again when subsequent checks
// succeed
checker.onConnectivityCheckSucceeded(now);
}
@Test
public void testFreshResultIsReused() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the check succeeds the observer should be called
context.checking(new Expectations() {{
oneOf(observer1).onConnectivityCheckSucceeded();
}});
checker.onConnectivityCheckSucceeded(now);
// When checkConnectivity() is called again within
// CONNECTIVITY_CHECK_FRESHNESS_MS the observer should be called with
// the result of the recent check
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS));
oneOf(observer2).onConnectivityCheckSucceeded();
}});
checker.checkConnectivity(properties, observer2);
}
@Test
public void testStaleResultIsNotReused() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the check succeeds the observer should be called
context.checking(new Expectations() {{
oneOf(observer1).onConnectivityCheckSucceeded();
}});
checker.onConnectivityCheckSucceeded(now);
// When checkConnectivity() is called again after more than
// CONNECTIVITY_CHECK_FRESHNESS_MS another check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer2);
// When the check succeeds the observer should be called
context.checking(new Expectations() {{
oneOf(observer2).onConnectivityCheckSucceeded();
}});
checker.onConnectivityCheckSucceeded(
now + CONNECTIVITY_CHECK_FRESHNESS_MS + 1);
}
@Test
public void testCheckIsCancelledWhenCheckerIsDestroyed() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the checker is destroyed the check should be cancelled
context.checking(new Expectations() {{
oneOf(task).cancel();
}});
checker.destroy();
// If the check runs anyway (cancellation came too late) the observer
// should not be called
checker.onConnectivityCheckSucceeded(now);
}
private ConnectivityCheckerImpl createChecker() {
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {
@Override
@Nonnull
protected ApiCall createConnectivityCheckTask(
@Nonnull MailboxProperties properties) {
return apiCall;
}
};
}
}

View File

@@ -0,0 +1,102 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ContactMailboxConnectivityCheckerTest extends BrambleMockTestCase {
private final Clock clock = context.mock(Clock.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final Cancellable task = context.mock(Cancellable.class);
private final ConnectivityObserver observer =
context.mock(ConnectivityObserver.class);
private final MailboxProperties properties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
@Test
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
ContactMailboxConnectivityChecker checker = createChecker();
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
returnValue(task)
));
}});
checker.checkConnectivity(properties, observer);
// When the check succeeds the observer should be called
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(properties,
requireNonNull(properties.getInboxId()));
will(returnValue(emptyList()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(observer).onConnectivityCheckSucceeded();
}});
// The call should not be retried
assertFalse(apiCall.get().callApi());
}
@Test
public void testObserverIsNotCalledWhenCheckFails() throws Exception {
ContactMailboxConnectivityChecker checker = createChecker();
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
returnValue(task)
));
}});
checker.checkConnectivity(properties, observer);
// When the check fails, the observer should not be called
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(properties,
requireNonNull(properties.getInboxId()));
will(throwException(new IOException()));
}});
// The call should be retried
assertTrue(apiCall.get().callApi());
}
private ContactMailboxConnectivityChecker createChecker() {
return new ContactMailboxConnectivityChecker(clock, mailboxApiCaller,
mailboxApi);
}
}

View File

@@ -0,0 +1,118 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.mailbox.MailboxApi.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
private final Clock clock = context.mock(Clock.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final TransactionManager db =
context.mock(TransactionManager.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final Cancellable task = context.mock(Cancellable.class);
private final ConnectivityObserver observer =
context.mock(ConnectivityObserver.class);
private final MailboxProperties properties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
@Test
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
OwnMailboxConnectivityChecker checker = createChecker();
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
Transaction txn = new Transaction(null, false);
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
returnValue(task)
));
}});
checker.checkConnectivity(properties, observer);
// When the check succeeds, the success should be recorded in the DB
// and the observer should be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).getFolders(properties);
will(returnValue(emptyList()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
oneOf(observer).onConnectivityCheckSucceeded();
}});
// The call should not be retried
assertFalse(apiCall.get().callApi());
}
@Test
public void testObserverIsNotCalledWhenCheckFails() throws Exception {
OwnMailboxConnectivityChecker checker = createChecker();
AtomicReference<ApiCall> apiCall = new AtomicReference<>(null);
Transaction txn = new Transaction(null, false);
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(apiCall, ApiCall.class, 0),
returnValue(task)
));
}});
checker.checkConnectivity(properties, observer);
// When the check fails, the failure should be recorded in the DB and
// the observer should not be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).getFolders(properties);
will(throwException(new IOException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager)
.recordFailedConnectionAttempt(txn, now);
}});
// The call should be retried
assertTrue(apiCall.get().callApi());
}
private OwnMailboxConnectivityChecker createChecker() {
return new OwnMailboxConnectivityChecker(clock, mailboxApiCaller,
mailboxApi, db, mailboxSettingsManager);
}
}