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