mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Add connectivity checkers for our own mailbox and a contact's mailbox.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
/**
|
||||
* An interface for checking whether a mailbox is reachable.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
interface ConnectivityChecker {
|
||||
|
||||
/**
|
||||
* Destroys the checker. Any current connectivity check is cancelled.
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* Starts a connectivity check if needed and calls the given observer when
|
||||
* the check succeeds. If a check is already running then the observer is
|
||||
* called when the check succeeds. If a connectivity check has recently
|
||||
* succeeded then the observer is called immediately.
|
||||
*/
|
||||
void checkConnectivity(MailboxProperties properties,
|
||||
ConnectivityObserver o);
|
||||
|
||||
interface ConnectivityObserver {
|
||||
void onConnectivityCheckSucceeded();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.Cancellable;
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.GuardedBy;
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
|
||||
|
||||
/**
|
||||
* If no more than this much time has elapsed since the last connectivity
|
||||
* check succeeded, consider the result to be fresh and don't check again.
|
||||
* <p>
|
||||
* Package access for testing.
|
||||
*/
|
||||
static final long CONNECTIVITY_CHECK_FRESHNESS_MS = 10_000;
|
||||
|
||||
private final Object lock = new Object();
|
||||
|
||||
protected final Clock clock;
|
||||
private final MailboxApiCaller mailboxApiCaller;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private boolean destroyed = false;
|
||||
|
||||
@GuardedBy("lock")
|
||||
@Nullable
|
||||
private Cancellable connectivityCheck = null;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private long lastConnectivityCheckSucceeded = 0;
|
||||
|
||||
@GuardedBy("lock")
|
||||
private final List<ConnectivityObserver> connectivityObservers =
|
||||
new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Creates an {@link ApiCall} for checking whether the mailbox is
|
||||
* reachable. The {@link ApiCall} should call
|
||||
* {@link #onConnectivityCheckSucceeded(long)} if the check succeeds.
|
||||
*/
|
||||
abstract ApiCall createConnectivityCheckTask(MailboxProperties properties);
|
||||
|
||||
ConnectivityCheckerImpl(Clock clock, MailboxApiCaller mailboxApiCaller) {
|
||||
this.clock = clock;
|
||||
this.mailboxApiCaller = mailboxApiCaller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
synchronized (lock) {
|
||||
destroyed = true;
|
||||
connectivityObservers.clear();
|
||||
if (connectivityCheck != null) {
|
||||
connectivityCheck.cancel();
|
||||
connectivityCheck = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkConnectivity(MailboxProperties properties,
|
||||
ConnectivityObserver o) {
|
||||
boolean callNow = false;
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
if (connectivityCheck == null) {
|
||||
// No connectivity check is running
|
||||
long now = clock.currentTimeMillis();
|
||||
if (now - lastConnectivityCheckSucceeded
|
||||
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
|
||||
// The last connectivity check is stale, start a new one
|
||||
connectivityObservers.add(o);
|
||||
ApiCall task =
|
||||
createConnectivityCheckTask(properties);
|
||||
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
|
||||
} else {
|
||||
// The last connectivity check is fresh
|
||||
callNow = true;
|
||||
}
|
||||
} else {
|
||||
// A connectivity check is running, wait for it to succeed
|
||||
connectivityObservers.add(o);
|
||||
}
|
||||
}
|
||||
if (callNow) o.onConnectivityCheckSucceeded();
|
||||
}
|
||||
|
||||
protected void onConnectivityCheckSucceeded(long now) {
|
||||
List<ConnectivityObserver> observers;
|
||||
synchronized (lock) {
|
||||
if (destroyed) return;
|
||||
connectivityCheck = null;
|
||||
lastConnectivityCheckSucceeded = now;
|
||||
observers = new ArrayList<>(connectivityObservers);
|
||||
connectivityObservers.clear();
|
||||
}
|
||||
for (ConnectivityObserver o : observers) {
|
||||
o.onConnectivityCheckSucceeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.mailbox.MailboxProperties;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
|
||||
private final MailboxApi mailboxApi;
|
||||
|
||||
ContactMailboxConnectivityChecker(Clock clock,
|
||||
MailboxApiCaller mailboxApiCaller, MailboxApi mailboxApi) {
|
||||
super(clock, mailboxApiCaller);
|
||||
this.mailboxApi = mailboxApi;
|
||||
}
|
||||
|
||||
@Override
|
||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||
if (properties.isOwner()) throw new IllegalArgumentException();
|
||||
return new SimpleApiCall() {
|
||||
@Override
|
||||
void tryToCallApi() throws IOException, ApiException {
|
||||
mailboxApi.getFiles(properties,
|
||||
requireNonNull(properties.getInboxId()));
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(clock.currentTimeMillis());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package org.briarproject.bramble.mailbox;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
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.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.concurrent.ThreadSafe;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static java.util.logging.Logger.getLogger;
|
||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
|
||||
|
||||
private static final Logger LOG =
|
||||
getLogger(OwnMailboxConnectivityChecker.class.getName());
|
||||
|
||||
private final MailboxApi mailboxApi;
|
||||
private final TransactionManager db;
|
||||
private final MailboxSettingsManager mailboxSettingsManager;
|
||||
|
||||
OwnMailboxConnectivityChecker(Clock clock,
|
||||
MailboxApiCaller mailboxApiCaller,
|
||||
MailboxApi mailboxApi,
|
||||
TransactionManager db,
|
||||
MailboxSettingsManager mailboxSettingsManager) {
|
||||
super(clock, mailboxApiCaller);
|
||||
this.mailboxApi = mailboxApi;
|
||||
this.db = db;
|
||||
this.mailboxSettingsManager = mailboxSettingsManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
|
||||
if (!properties.isOwner()) throw new IllegalArgumentException();
|
||||
return () -> {
|
||||
try {
|
||||
try {
|
||||
mailboxApi.getFolders(properties);
|
||||
LOG.info("Own mailbox is reachable");
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> mailboxSettingsManager
|
||||
.recordSuccessfulConnection(txn, now));
|
||||
// Call the observers and cache the result
|
||||
onConnectivityCheckSucceeded(now);
|
||||
return false; // Don't retry
|
||||
} catch (IOException | ApiException e) {
|
||||
LOG.warning("Own mailbox is unreachable");
|
||||
logException(LOG, WARNING, e);
|
||||
long now = clock.currentTimeMillis();
|
||||
db.transaction(false, txn -> mailboxSettingsManager
|
||||
.recordFailedConnectionAttempt(txn, now));
|
||||
}
|
||||
} catch (DbException e) {
|
||||
logException(LOG, WARNING, e);
|
||||
}
|
||||
return true; // Retry
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user