|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 package org.mozilla.gecko.background.sync; |
|
5 |
|
6 import java.io.IOException; |
|
7 import java.io.UnsupportedEncodingException; |
|
8 |
|
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; |
|
28 |
|
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; |
|
38 |
|
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/"; |
|
52 |
|
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"; |
|
56 |
|
57 private Context context; |
|
58 |
|
59 public static boolean syncsAutomatically(Account a) { |
|
60 return ContentResolver.getSyncAutomatically(a, BrowserContract.AUTHORITY); |
|
61 } |
|
62 |
|
63 public static boolean isSyncable(Account a) { |
|
64 return 1 == ContentResolver.getIsSyncable(a, BrowserContract.AUTHORITY); |
|
65 } |
|
66 |
|
67 public static boolean willEnableOnUpgrade(Account a, AccountManager accountManager) { |
|
68 return "1".equals(accountManager.getUserData(a, Constants.DATA_ENABLE_ON_UPGRADE)); |
|
69 } |
|
70 |
|
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 } |
|
81 |
|
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 } |
|
90 |
|
91 @Override |
|
92 public void setUp() { |
|
93 context = getApplicationContext(); |
|
94 final AccountManager accountManager = AccountManager.get(context); |
|
95 |
|
96 deleteTestAccount(); |
|
97 |
|
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 } |
|
105 |
|
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 } |
|
112 |
|
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); |
|
120 |
|
121 assertNotNull(account); |
|
122 assertTrue(syncsAutomatically(account)); |
|
123 assertTrue(isSyncable(account)); |
|
124 assertFalse(willEnableOnUpgrade(account, accountManager)); |
|
125 |
|
126 LeakySyncAdapter adapter = new LeakySyncAdapter(context, true, account); |
|
127 adapter.informUpgradeRequiredResponse(null); |
|
128 |
|
129 // Oh god. |
|
130 try { |
|
131 Thread.sleep(1000); |
|
132 } catch (InterruptedException e) { |
|
133 e.printStackTrace(); |
|
134 } |
|
135 |
|
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 } |
|
141 |
|
142 private class Result { |
|
143 public boolean called = false; |
|
144 } |
|
145 |
|
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 } |
|
160 |
|
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 }; |
|
174 |
|
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); |
|
179 |
|
180 session.interpretHTTPFailure(simulate400()); |
|
181 assertTrue(calledUpgradeRequired.called); |
|
182 } |
|
183 |
|
184 @Override |
|
185 public void tearDown() { |
|
186 deleteTestAccount(); |
|
187 } |
|
188 } |