Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 package org.mozilla.gecko.background.sync;
6 import java.io.IOException;
7 import java.io.UnsupportedEncodingException;
9 import org.json.simple.parser.ParseException;
10 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
11 import org.mozilla.gecko.background.testhelpers.DefaultGlobalSessionCallback;
12 import org.mozilla.gecko.background.testhelpers.MockGlobalSession;
13 import org.mozilla.gecko.background.testhelpers.MockSharedPreferences;
14 import org.mozilla.gecko.db.BrowserContract;
15 import org.mozilla.gecko.sync.GlobalSession;
16 import org.mozilla.gecko.sync.NonObjectJSONException;
17 import org.mozilla.gecko.sync.SyncConfiguration;
18 import org.mozilla.gecko.sync.SyncConfigurationException;
19 import org.mozilla.gecko.sync.SyncConstants;
20 import org.mozilla.gecko.sync.crypto.CryptoException;
21 import org.mozilla.gecko.sync.crypto.KeyBundle;
22 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
23 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
24 import org.mozilla.gecko.sync.setup.Constants;
25 import org.mozilla.gecko.sync.setup.SyncAccounts;
26 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
27 import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
29 import android.accounts.Account;
30 import android.accounts.AccountManager;
31 import android.content.ContentResolver;
32 import android.content.Context;
33 import ch.boye.httpclientandroidlib.HttpEntity;
34 import ch.boye.httpclientandroidlib.HttpResponse;
35 import ch.boye.httpclientandroidlib.ProtocolVersion;
36 import ch.boye.httpclientandroidlib.entity.StringEntity;
37 import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
39 /**
40 * When syncing and a server responds with a 400 "Upgrade Required," Sync
41 * accounts should be disabled.
42 *
43 * (We are not testing for package updating, because MY_PACKAGE_REPLACED
44 * broadcasts can only be sent by the system. Testing for package replacement
45 * needs to be done manually on a device.)
46 *
47 * @author liuche
48 *
49 */
50 public class TestUpgradeRequired extends AndroidSyncTestCase {
51 private final String TEST_SERVER = "http://test.ser.ver/";
53 private static final String TEST_USERNAME = "user1";
54 private static final String TEST_PASSWORD = "pass1";
55 private static final String TEST_SYNC_KEY = "abcdeabcdeabcdeabcdeabcdea";
57 private Context context;
59 public static boolean syncsAutomatically(Account a) {
60 return ContentResolver.getSyncAutomatically(a, BrowserContract.AUTHORITY);
61 }
63 public static boolean isSyncable(Account a) {
64 return 1 == ContentResolver.getIsSyncable(a, BrowserContract.AUTHORITY);
65 }
67 public static boolean willEnableOnUpgrade(Account a, AccountManager accountManager) {
68 return "1".equals(accountManager.getUserData(a, Constants.DATA_ENABLE_ON_UPGRADE));
69 }
71 private static Account getTestAccount(AccountManager accountManager) {
72 final String type = SyncConstants.ACCOUNTTYPE_SYNC;
73 Account[] existing = accountManager.getAccountsByType(type);
74 for (Account account : existing) {
75 if (account.name.equals(TEST_USERNAME)) {
76 return account;
77 }
78 }
79 return null;
80 }
82 private void deleteTestAccount() {
83 final AccountManager accountManager = AccountManager.get(context);
84 final Account found = getTestAccount(accountManager);
85 if (found == null) {
86 return;
87 }
88 TestSyncAccounts.deleteAccount(this, accountManager, found);
89 }
91 @Override
92 public void setUp() {
93 context = getApplicationContext();
94 final AccountManager accountManager = AccountManager.get(context);
96 deleteTestAccount();
98 // Set up and enable Sync accounts.
99 SyncAccountParameters syncAccountParams = new SyncAccountParameters(context, accountManager, TEST_USERNAME, TEST_PASSWORD, TEST_SYNC_KEY, TEST_SERVER, null, null, null);
100 final Account account = SyncAccounts.createSyncAccount(syncAccountParams, true);
101 assertNotNull(account);
102 assertTrue(syncsAutomatically(account));
103 assertTrue(isSyncable(account));
104 }
106 private static class LeakySyncAdapter extends SyncAdapter {
107 public LeakySyncAdapter(Context context, boolean autoInitialize, Account account) {
108 super(context, autoInitialize);
109 this.localAccount = account;
110 }
111 }
113 /**
114 * Verify that when SyncAdapter is informed of an Upgrade Required
115 * response, that it disables the account it's syncing.
116 */
117 public void testInformUpgradeRequired() {
118 final AccountManager accountManager = AccountManager.get(context);
119 final Account account = getTestAccount(accountManager);
121 assertNotNull(account);
122 assertTrue(syncsAutomatically(account));
123 assertTrue(isSyncable(account));
124 assertFalse(willEnableOnUpgrade(account, accountManager));
126 LeakySyncAdapter adapter = new LeakySyncAdapter(context, true, account);
127 adapter.informUpgradeRequiredResponse(null);
129 // Oh god.
130 try {
131 Thread.sleep(1000);
132 } catch (InterruptedException e) {
133 e.printStackTrace();
134 }
136 // We have disabled the account, but it's still syncable.
137 assertFalse(syncsAutomatically(account));
138 assertTrue(isSyncable(account));
139 assertTrue(willEnableOnUpgrade(account, accountManager));
140 }
142 private class Result {
143 public boolean called = false;
144 }
146 public static HttpResponse simulate400() {
147 HttpResponse response = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 400, "Bad Request") {
148 @Override
149 public HttpEntity getEntity() {
150 try {
151 return new StringEntity("16");
152 } catch (UnsupportedEncodingException e) {
153 // Never happens.
154 return null;
155 }
156 }
157 };
158 return response;
159 }
161 /**
162 * Verify that when a 400 response is received with an
163 * "Upgrade Required" response code body, we call
164 * informUpgradeRequiredResponse on the delegate.
165 */
166 public void testUpgradeResponse() throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException, ParseException, CryptoException {
167 final Result calledUpgradeRequired = new Result();
168 final GlobalSessionCallback callback = new DefaultGlobalSessionCallback() {
169 @Override
170 public void informUpgradeRequiredResponse(final GlobalSession session) {
171 calledUpgradeRequired.called = true;
172 }
173 };
175 MockSharedPreferences prefs = new MockSharedPreferences();
176 SyncConfiguration config = new SyncConfiguration(TEST_USERNAME, new BasicAuthHeaderProvider(TEST_USERNAME, TEST_PASSWORD), prefs);
177 config.syncKeyBundle = new KeyBundle(TEST_USERNAME, TEST_SYNC_KEY);
178 final GlobalSession session = new MockGlobalSession(config, callback);
180 session.interpretHTTPFailure(simulate400());
181 assertTrue(calledUpgradeRequired.called);
182 }
184 @Override
185 public void tearDown() {
186 deleteTestAccount();
187 }
188 }