/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.sync.stage.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.HashMap;
import org.json.simple.parser.ParseException;
import org.junit.Before;
import org.junit.Test;
import org.mozilla.android.sync.test.helpers.HTTPServerTestHelper;
import org.mozilla.android.sync.test.helpers.MockGlobalSession;
import org.mozilla.android.sync.test.helpers.MockGlobalSessionCallback;
import org.mozilla.android.sync.test.helpers.MockServer;
import org.mozilla.android.sync.test.helpers.WaitHelper;
import org.mozilla.gecko.sync.AlreadySyncingException;
import org.mozilla.gecko.sync.GlobalSession;
import org.mozilla.gecko.sync.NodeAuthenticationException;
import org.mozilla.gecko.sync.NonObjectJSONException;
import org.mozilla.gecko.sync.SyncConfigurationException;
import org.mozilla.gecko.sync.crypto.CryptoException;
import org.mozilla.gecko.sync.crypto.KeyBundle;
import org.mozilla.gecko.sync.net.BaseResource;
import org.mozilla.gecko.sync.stage.EnsureClusterURLStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
import ch.boye.httpclientandroidlib.message.BasicStatusLine;
public class TestEnsureClusterURLStage {
private static final int TEST_PORT = HTTPServerTestHelper.getTestPort();
private static final String TEST_SERVER = "http://localhost:" + TEST_PORT + "/";
private static final String TEST_OLD_CLUSTER_URL = TEST_SERVER + "cluster/old/";
private static final String TEST_NEW_CLUSTER_URL = TEST_SERVER + "cluster/new/";
static String TEST_NW_URL = TEST_SERVER + "/1.0/c6o7dvmr2c4ud2fyv6woz2u4zi22bcyd/node/weave"; // GET https://server/pathname/version/username/node/weave
private HTTPServerTestHelper data = new HTTPServerTestHelper();
private final String TEST_USERNAME = "johndoe";
private final String TEST_PASSWORD = "password";
private final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
@SuppressWarnings("static-method")
@Before
public void setUp() {
// BaseResource rewrites localhost to 10.0.2.2, which works on Android but not on desktop.
BaseResource.rewriteLocalhost = false;
}
public class MockClusterURLFetchDelegate implements EnsureClusterURLStage.ClusterURLFetchDelegate {
public boolean successCalled = false;
public URI successUrl = null;
public boolean throttleCalled = false;
public boolean failureCalled = false;
public HttpResponse failureResponse = null;
public boolean errorCalled = false;
public Exception errorException = null;
@Override
public void handleSuccess(URI url) {
successCalled = true;
successUrl = url;
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void handleThrottled() {
throttleCalled = true;
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void handleFailure(HttpResponse response) {
failureCalled = true;
failureResponse = response;
WaitHelper.getTestWaiter().performNotify();
}
@Override
public void handleError(Exception e) {
errorCalled = true;
errorException = e;
WaitHelper.getTestWaiter().performNotify(e);
}
}
public MockClusterURLFetchDelegate doFetchClusterURL() {
final MockClusterURLFetchDelegate delegate = new MockClusterURLFetchDelegate();
WaitHelper.getTestWaiter().performWait(new Runnable() {
@Override
public void run() {
try {
EnsureClusterURLStage.fetchClusterURL(TEST_NW_URL, delegate);
} catch (URISyntaxException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
});
return delegate;
}
@Test
public void testFetchClusterURLGood() {
data.startHTTPServer(new MockServer(200, TEST_NEW_CLUSTER_URL));
MockClusterURLFetchDelegate delegate = doFetchClusterURL();
data.stopHTTPServer();
assertTrue(delegate.successCalled);
assertEquals(TEST_NEW_CLUSTER_URL, delegate.successUrl.toASCIIString());
}
@Test
public void testFetchClusterURLThrottled() {
data.startHTTPServer(new MockServer(200, "null"));
MockClusterURLFetchDelegate delegate = doFetchClusterURL();
data.stopHTTPServer();
assertTrue(delegate.throttleCalled);
}
@Test
public void testFetchClusterURLFailure() {
data.startHTTPServer(new MockServer(404, ""));
MockClusterURLFetchDelegate delegate = doFetchClusterURL();
data.stopHTTPServer();
assertTrue(delegate.failureCalled);
assertEquals(404, delegate.failureResponse.getStatusLine().getStatusCode());
}
@Test
/**
* Test that we fetch a node/weave cluster URL if there isn't a cluster URL set.
*/
public void testNodeAssignedIfNotSet() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = new MockGlobalSession(TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback) {
@Override
public void prepareStages() {
super.prepareStages();
HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>(this.stages);
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage(this));
this.stages = Collections.unmodifiableMap(stages);
}
};
assertEquals(null, session.config.getClusterURL());
data.startHTTPServer(new MockServer(200, TEST_NEW_CLUSTER_URL));
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
public void run() {
try {
session.start();
} catch (AlreadySyncingException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
}));
data.stopHTTPServer();
assertEquals(true, callback.calledSuccess);
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
assertEquals(false, callback.calledInformUnauthorizedResponse);
assertEquals(true, callback.calledInformNodeAssigned);
assertEquals(false, callback.calledInformNodeAuthenticationFailed);
assertSame(null, callback.calledInformNodeAssignedOldClusterURL);
assertEquals(TEST_NEW_CLUSTER_URL, callback.calledInformNodeAssignedNewClusterURL.toASCIIString());
}
/**
* Test that, when there is no override, we don't fetch a node/weave cluster URL if there is a cluster URL set.
*/
@Test
public void testNodeNotAssignedIfSet() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = new MockGlobalSession(TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback) {
@Override
public void prepareStages() {
super.prepareStages();
HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>(this.stages);
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage(this));
this.stages = Collections.unmodifiableMap(stages);
}
};
session.config.setClusterURL(new URI(TEST_OLD_CLUSTER_URL));
assertEquals(TEST_OLD_CLUSTER_URL, session.config.getClusterURL().toASCIIString());
data.startHTTPServer(new MockServer(200, TEST_NEW_CLUSTER_URL));
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
public void run() {
try {
session.start();
} catch (AlreadySyncingException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
}));
data.stopHTTPServer();
assertEquals(true, callback.calledSuccess);
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
assertEquals(false, callback.calledInformUnauthorizedResponse);
assertEquals(false, callback.calledInformNodeAssigned);
assertEquals(false, callback.calledInformNodeAuthenticationFailed);
}
/**
* Test that, when there is an override, we fetch a node/weave cluster URL and callback if it is new.
*/
@Test
public void testNodeAssignedCalledWhenOverridden() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback() {
@Override
public boolean wantNodeAssignment() {
return true;
}
};
final GlobalSession session = new MockGlobalSession(TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback) {
@Override
public void prepareStages() {
super.prepareStages();
HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>(this.stages);
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage(this));
this.stages = Collections.unmodifiableMap(stages);
}
};
session.config.setClusterURL(new URI(TEST_OLD_CLUSTER_URL));
assertEquals(TEST_OLD_CLUSTER_URL, session.config.getClusterURL().toASCIIString());
data.startHTTPServer(new MockServer(200, TEST_NEW_CLUSTER_URL));
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
public void run() {
try {
session.start();
} catch (AlreadySyncingException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
}));
data.stopHTTPServer();
assertEquals(true, callback.calledSuccess);
assertEquals(false, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
assertEquals(false, callback.calledInformUnauthorizedResponse);
assertEquals(true, callback.calledInformNodeAssigned);
assertEquals(false, callback.calledInformNodeAuthenticationFailed);
assertEquals(TEST_OLD_CLUSTER_URL, callback.calledInformNodeAssignedOldClusterURL.toASCIIString());
assertEquals(TEST_NEW_CLUSTER_URL, callback.calledInformNodeAssignedNewClusterURL.toASCIIString());
}
/**
* Test that, when there is an override, we fetch a node/weave cluster URL and callback correctly if it is not new.
*/
@Test
public void testNodeFailedCalledWhenOverridden() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException, URISyntaxException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback() {
@Override
public boolean wantNodeAssignment() {
return true;
}
};
final GlobalSession session = new MockGlobalSession(TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback) {
@Override
public void prepareStages() {
super.prepareStages();
HashMap<Stage, GlobalSyncStage> stages = new HashMap<Stage, GlobalSyncStage>(this.stages);
stages.put(Stage.ensureClusterURL, new EnsureClusterURLStage(this));
this.stages = Collections.unmodifiableMap(stages);
}
};
session.config.setClusterURL(new URI(TEST_OLD_CLUSTER_URL));
assertEquals(TEST_OLD_CLUSTER_URL, session.config.getClusterURL().toASCIIString());
data.startHTTPServer(new MockServer(200, TEST_OLD_CLUSTER_URL));
WaitHelper.getTestWaiter().performWait(WaitHelper.onThreadRunnable(new Runnable() {
public void run() {
try {
session.start();
} catch (AlreadySyncingException e) {
WaitHelper.getTestWaiter().performNotify(e);
}
}
}));
data.stopHTTPServer();
assertEquals(false, callback.calledSuccess);
assertEquals(true, callback.calledError);
assertEquals(false, callback.calledAborted);
assertEquals(false, callback.calledRequestBackoff);
assertTrue(callback.calledErrorException instanceof NodeAuthenticationException);
assertEquals(false, callback.calledInformUnauthorizedResponse);
assertEquals(false, callback.calledInformNodeAssigned);
assertEquals(true, callback.calledInformNodeAuthenticationFailed);
assertEquals(TEST_OLD_CLUSTER_URL, callback.calledInformNodeAuthenticationFailedClusterURL.toASCIIString());
}
@Test
public void testInterpretHTTPFailureCallsMaybeNodeReassigned() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
final MockGlobalSessionCallback callback = new MockGlobalSessionCallback();
final GlobalSession session = new MockGlobalSession(TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback);
final HttpResponse response = new BasicHttpResponse(
new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 401, "User authentication failed"));
session.interpretHTTPFailure(response); // This is synchronous...
assertEquals(true, callback.calledInformUnauthorizedResponse); // ... so we can test immediately.
}
}