1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/tests/background/junit3/src/sync/TestUpgradeRequired.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,188 @@ 1.4 +/* Any copyright is dedicated to the Public Domain. 1.5 + http://creativecommons.org/publicdomain/zero/1.0/ */ 1.6 + 1.7 +package org.mozilla.gecko.background.sync; 1.8 + 1.9 +import java.io.IOException; 1.10 +import java.io.UnsupportedEncodingException; 1.11 + 1.12 +import org.json.simple.parser.ParseException; 1.13 +import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; 1.14 +import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback; 1.15 +import org.mozilla.gecko.background.testhelpers.MockGlobalSession; 1.16 +import org.mozilla.gecko.background.testhelpers.MockSharedPreferences; 1.17 +import org.mozilla.gecko.db.BrowserContract; 1.18 +import org.mozilla.gecko.sync.GlobalSession; 1.19 +import org.mozilla.gecko.sync.NonObjectJSONException; 1.20 +import org.mozilla.gecko.sync.SyncConfiguration; 1.21 +import org.mozilla.gecko.sync.SyncConfigurationException; 1.22 +import org.mozilla.gecko.sync.SyncConstants; 1.23 +import org.mozilla.gecko.sync.crypto.CryptoException; 1.24 +import org.mozilla.gecko.sync.crypto.KeyBundle; 1.25 +import org.mozilla.gecko.sync.delegates.GlobalSessionCallback; 1.26 +import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider; 1.27 +import org.mozilla.gecko.sync.setup.Constants; 1.28 +import org.mozilla.gecko.sync.setup.SyncAccounts; 1.29 +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; 1.30 +import org.mozilla.gecko.sync.syncadapter.SyncAdapter; 1.31 + 1.32 +import android.accounts.Account; 1.33 +import android.accounts.AccountManager; 1.34 +import android.content.ContentResolver; 1.35 +import android.content.Context; 1.36 +import ch.boye.httpclientandroidlib.HttpEntity; 1.37 +import ch.boye.httpclientandroidlib.HttpResponse; 1.38 +import ch.boye.httpclientandroidlib.ProtocolVersion; 1.39 +import ch.boye.httpclientandroidlib.entity.StringEntity; 1.40 +import ch.boye.httpclientandroidlib.message.BasicHttpResponse; 1.41 + 1.42 +/** 1.43 + * When syncing and a server responds with a 400 "Upgrade Required," Sync 1.44 + * accounts should be disabled. 1.45 + * 1.46 + * (We are not testing for package updating, because MY_PACKAGE_REPLACED 1.47 + * broadcasts can only be sent by the system. Testing for package replacement 1.48 + * needs to be done manually on a device.) 1.49 + * 1.50 + * @author liuche 1.51 + * 1.52 + */ 1.53 +public class TestUpgradeRequired extends AndroidSyncTestCase { 1.54 + private final String TEST_SERVER = "http://test.ser.ver/"; 1.55 + 1.56 + private static final String TEST_USERNAME = "user1"; 1.57 + private static final String TEST_PASSWORD = "pass1"; 1.58 + private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea"; 1.59 + 1.60 + private Context context; 1.61 + 1.62 + public static boolean syncsAutomatically(Account a) { 1.63 + return ContentResolver.getSyncAutomatically(a, BrowserContract.AUTHORITY); 1.64 + } 1.65 + 1.66 + public static boolean isSyncable(Account a) { 1.67 + return 1 == ContentResolver.getIsSyncable(a, BrowserContract.AUTHORITY); 1.68 + } 1.69 + 1.70 + public static boolean willEnableOnUpgrade(Account a, AccountManager accountManager) { 1.71 + return "1".equals(accountManager.getUserData(a, Constants.DATA_ENABLE_ON_UPGRADE)); 1.72 + } 1.73 + 1.74 + private static Account getTestAccount(AccountManager accountManager) { 1.75 + final String type = SyncConstants.ACCOUNTTYPE_SYNC; 1.76 + Account[] existing = accountManager.getAccountsByType(type); 1.77 + for (Account account : existing) { 1.78 + if (account.name.equals(TEST_USERNAME)) { 1.79 + return account; 1.80 + } 1.81 + } 1.82 + return null; 1.83 + } 1.84 + 1.85 + private void deleteTestAccount() { 1.86 + final AccountManager accountManager = AccountManager.get(context); 1.87 + final Account found = getTestAccount(accountManager); 1.88 + if (found == null) { 1.89 + return; 1.90 + } 1.91 + TestSyncAccounts.deleteAccount(this, accountManager, found); 1.92 + } 1.93 + 1.94 + @Override 1.95 + public void setUp() { 1.96 + context = getApplicationContext(); 1.97 + final AccountManager accountManager = AccountManager.get(context); 1.98 + 1.99 + deleteTestAccount(); 1.100 + 1.101 + // Set up and enable Sync accounts. 1.102 + SyncAccountParameters syncAccountParams = new SyncAccountParameters(context, accountManager, TEST_USERNAME, TEST_PASSWORD, TEST_SYNC_KEY, TEST_SERVER, null, null, null); 1.103 + final Account account = SyncAccounts.createSyncAccount(syncAccountParams, true); 1.104 + assertNotNull(account); 1.105 + assertTrue(syncsAutomatically(account)); 1.106 + assertTrue(isSyncable(account)); 1.107 + } 1.108 + 1.109 + private static class LeakySyncAdapter extends SyncAdapter { 1.110 + public LeakySyncAdapter(Context context, boolean autoInitialize, Account account) { 1.111 + super(context, autoInitialize); 1.112 + this.localAccount = account; 1.113 + } 1.114 + } 1.115 + 1.116 + /** 1.117 + * Verify that when SyncAdapter is informed of an Upgrade Required 1.118 + * response, that it disables the account it's syncing. 1.119 + */ 1.120 + public void testInformUpgradeRequired() { 1.121 + final AccountManager accountManager = AccountManager.get(context); 1.122 + final Account account = getTestAccount(accountManager); 1.123 + 1.124 + assertNotNull(account); 1.125 + assertTrue(syncsAutomatically(account)); 1.126 + assertTrue(isSyncable(account)); 1.127 + assertFalse(willEnableOnUpgrade(account, accountManager)); 1.128 + 1.129 + LeakySyncAdapter adapter = new LeakySyncAdapter(context, true, account); 1.130 + adapter.informUpgradeRequiredResponse(null); 1.131 + 1.132 + // Oh god. 1.133 + try { 1.134 + Thread.sleep(1000); 1.135 + } catch (InterruptedException e) { 1.136 + e.printStackTrace(); 1.137 + } 1.138 + 1.139 + // We have disabled the account, but it's still syncable. 1.140 + assertFalse(syncsAutomatically(account)); 1.141 + assertTrue(isSyncable(account)); 1.142 + assertTrue(willEnableOnUpgrade(account, accountManager)); 1.143 + } 1.144 + 1.145 + private class Result { 1.146 + public boolean called = false; 1.147 + } 1.148 + 1.149 + public static HttpResponse simulate400() { 1.150 + HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 400, "Bad Request") { 1.151 + @Override 1.152 + public HttpEntity getEntity() { 1.153 + try { 1.154 + return new StringEntity("16"); 1.155 + } catch (UnsupportedEncodingException e) { 1.156 + // Never happens. 1.157 + return null; 1.158 + } 1.159 + } 1.160 + }; 1.161 + return response; 1.162 + } 1.163 + 1.164 + /** 1.165 + * Verify that when a 400 response is received with an 1.166 + * "Upgrade Required" response code body, we call 1.167 + * informUpgradeRequiredResponse on the delegate. 1.168 + */ 1.169 + public void testUpgradeResponse() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException { 1.170 + final Result calledUpgradeRequired = new Result(); 1.171 + final GlobalSessionCallback callback = new DefaultGlobalSessionCallback() { 1.172 + @Override 1.173 + public void informUpgradeRequiredResponse(final GlobalSession session) { 1.174 + calledUpgradeRequired.called = true; 1.175 + } 1.176 + }; 1.177 + 1.178 + MockSharedPreferences prefs = new MockSharedPreferences(); 1.179 + SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), prefs); 1.180 + config.syncKeyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY); 1.181 + final GlobalSession session = new MockGlobalSession(config, callback); 1.182 + 1.183 + session.interpretHTTPFailure(simulate400()); 1.184 + assertTrue(calledUpgradeRequired.called); 1.185 + } 1.186 + 1.187 + @Override 1.188 + public void tearDown() { 1.189 + deleteTestAccount(); 1.190 + } 1.191 +}