/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.android.sync.test;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import org.json.simple.parser.ParseException;
import org.mozilla.android.sync.test.helpers.MockGlobalSession;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.SyncConstants;
import org.mozilla.gecko.sync.GlobalSession;
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.delegates.GlobalSessionCallback;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.setup.SyncAccounts;
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
import org.mozilla.gecko.sync.setup.test.TestSyncAccounts;
import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import ch.boye.httpclientandroidlib.HttpEntity;
import ch.boye.httpclientandroidlib.HttpResponse;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.entity.StringEntity;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
/**
* When syncing and a server responds with a 400 "Upgrade Required," Sync
* accounts should be disabled.
*
* (We are not testing for package updating, because MY_PACKAGE_REPLACED
* broadcasts can only be sent by the system. Testing for package replacement
* needs to be done manually on a device.)
*
* @author liuche
*
*/
public class TestUpgradeRequired extends AndroidSyncTestCase {
private final String TEST_SERVER = "http://test.ser.ver/";
private static final String TEST_USERNAME = "user1";
private static final String TEST_PASSWORD = "pass1";
private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
private Context context;
public static boolean syncsAutomatically(Account a) {
return ContentResolver.getSyncAutomatically(a, BrowserContract.AUTHORITY);
}
public static boolean isSyncable(Account a) {
return 1 == ContentResolver.getIsSyncable(a, BrowserContract.AUTHORITY);
}
public static boolean willEnableOnUpgrade(Account a, AccountManager accountManager) {
return "1".equals(accountManager.getUserData(a, Constants.DATA_ENABLE_ON_UPGRADE));
}
private static Account getTestAccount(AccountManager accountManager) {
final String type = SyncConstants.ACCOUNTTYPE_SYNC;
Account[] existing = accountManager.getAccountsByType(type);
for (Account account : existing) {
if (account.name.equals(TEST_USERNAME)) {
return account;
}
}
return null;
}
private void deleteTestAccount() {
final AccountManager accountManager = AccountManager.get(context);
final Account found = getTestAccount(accountManager);
if (found == null) {
return;
}
TestSyncAccounts.deleteAccount(this, accountManager, found);
}
@Override
public void setUp() {
context = getApplicationContext();
final AccountManager accountManager = AccountManager.get(context);
deleteTestAccount();
// Set up and enable Sync accounts.
SyncAccountParameters syncAccountParams = new SyncAccountParameters(context, accountManager, TEST_USERNAME, TEST_PASSWORD, TEST_SYNC_KEY, TEST_SERVER, null, null, null);
final Account account = SyncAccounts.createSyncAccount(syncAccountParams, true);
assertNotNull(account);
assertTrue(syncsAutomatically(account));
assertTrue(isSyncable(account));
}
/**
* Verify that when SyncAdapter is informed of an Upgrade Required
* response, that it disables the account it's syncing.
*/
public void testInformUpgradeRequired() {
final AccountManager accountManager = AccountManager.get(context);
final Account account = getTestAccount(accountManager);
assertNotNull(account);
assertTrue(syncsAutomatically(account));
assertTrue(isSyncable(account));
assertFalse(willEnableOnUpgrade(account, accountManager));
SyncAdapter adapter = new SyncAdapter(context, true);
adapter.localAccount = account;
adapter.informUpgradeRequiredResponse(null);
// Oh god.
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// We have disabled the account, but it's still syncable.
assertFalse(syncsAutomatically(account));
assertTrue(isSyncable(account));
assertTrue(willEnableOnUpgrade(account, accountManager));
}
private class Result {
public boolean called = false;
}
public static HttpResponse simulate400() {
HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 400, "Bad Request") {
@Override
public HttpEntity getEntity() {
try {
return new StringEntity("16");
} catch (UnsupportedEncodingException e) {
// Never happens.
return null;
}
}
};
return response;
}
/**
* Verify that when a 400 response is received with an
* "Upgrade Required" response code body, we call
* informUpgradeRequiredResponse on the delegate.
*/
public void testUpgradeResponse() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
final Result calledUpgradeRequired = new Result();
final GlobalSessionCallback callback = new BlankGlobalSessionCallback() {
@Override
public void informUpgradeRequiredResponse(final GlobalSession session) {
calledUpgradeRequired.called = true;
}
};
final GlobalSession session = new MockGlobalSession(
TEST_SERVER, TEST_USERNAME, TEST_PASSWORD,
new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY), callback);
session.interpretHTTPFailure(simulate400());
assertTrue(calledUpgradeRequired.called);
}
@Override
public void tearDown() {
deleteTestAccount();
}
public abstract class BlankGlobalSessionCallback implements GlobalSessionCallback {
public BlankGlobalSessionCallback() {
}
@Override
public void requestBackoff(long backoff) {
}
@Override
public boolean wantNodeAssignment() {
return false;
}
@Override
public void informUnauthorizedResponse(GlobalSession globalSession,
URI oldClusterURL) {
}
@Override
public void informNodeAssigned(GlobalSession globalSession,
URI oldClusterURL, URI newClusterURL) {
}
@Override
public void informNodeAuthenticationFailed(GlobalSession globalSession,
URI failedClusterURL) {
}
@Override
public void handleAborted(GlobalSession globalSession, String reason) {
}
@Override
public void handleError(GlobalSession globalSession, Exception ex) {
}
@Override
public void handleSuccess(GlobalSession globalSession) {
}
@Override
public void handleStageCompleted(Stage currentState,
GlobalSession globalSession) {
}
@Override
public boolean shouldBackOff() {
return false;
}
}
}