|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 package org.mozilla.gecko.background.db; |
|
5 |
|
6 import java.util.HashSet; |
|
7 import java.util.Set; |
|
8 |
|
9 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; |
|
10 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate; |
|
11 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate; |
|
12 import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate; |
|
13 import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate; |
|
14 import org.mozilla.gecko.background.sync.helpers.PasswordHelpers; |
|
15 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper; |
|
16 import org.mozilla.gecko.background.testhelpers.WaitHelper; |
|
17 import org.mozilla.gecko.db.BrowserContract; |
|
18 import org.mozilla.gecko.sync.Utils; |
|
19 import org.mozilla.gecko.sync.repositories.InactiveSessionException; |
|
20 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; |
|
21 import org.mozilla.gecko.sync.repositories.Repository; |
|
22 import org.mozilla.gecko.sync.repositories.RepositorySession; |
|
23 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; |
|
24 import org.mozilla.gecko.sync.repositories.android.PasswordsRepositorySession; |
|
25 import org.mozilla.gecko.sync.repositories.android.RepoUtils; |
|
26 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; |
|
27 import org.mozilla.gecko.sync.repositories.domain.PasswordRecord; |
|
28 import org.mozilla.gecko.sync.repositories.domain.Record; |
|
29 |
|
30 import android.content.ContentProviderClient; |
|
31 import android.content.Context; |
|
32 import android.database.Cursor; |
|
33 import android.os.RemoteException; |
|
34 |
|
35 public class TestPasswordsRepository extends AndroidSyncTestCase { |
|
36 private final String NEW_PASSWORD1 = "password"; |
|
37 private final String NEW_PASSWORD2 = "drowssap"; |
|
38 |
|
39 @Override |
|
40 public void setUp() { |
|
41 wipe(); |
|
42 assertTrue(WaitHelper.getTestWaiter().isIdle()); |
|
43 } |
|
44 |
|
45 public void testFetchAll() { |
|
46 RepositorySession session = createAndBeginSession(); |
|
47 Record[] expected = new Record[] { PasswordHelpers.createPassword1(), |
|
48 PasswordHelpers.createPassword2() }; |
|
49 |
|
50 performWait(storeRunnable(session, expected[0])); |
|
51 performWait(storeRunnable(session, expected[1])); |
|
52 |
|
53 performWait(fetchAllRunnable(session, expected)); |
|
54 dispose(session); |
|
55 } |
|
56 |
|
57 public void testGuidsSinceReturnMultipleRecords() { |
|
58 RepositorySession session = createAndBeginSession(); |
|
59 |
|
60 PasswordRecord record1 = PasswordHelpers.createPassword1(); |
|
61 PasswordRecord record2 = PasswordHelpers.createPassword2(); |
|
62 |
|
63 updatePassword(NEW_PASSWORD1, record1); |
|
64 long timestamp = updatePassword(NEW_PASSWORD2, record2); |
|
65 |
|
66 String[] expected = new String[] { record1.guid, record2.guid }; |
|
67 |
|
68 performWait(storeRunnable(session, record1)); |
|
69 performWait(storeRunnable(session, record2)); |
|
70 |
|
71 performWait(guidsSinceRunnable(session, timestamp, expected)); |
|
72 dispose(session); |
|
73 } |
|
74 |
|
75 public void testGuidsSinceReturnNoRecords() { |
|
76 RepositorySession session = createAndBeginSession(); |
|
77 |
|
78 // Store 1 record in the past. |
|
79 performWait(storeRunnable(session, PasswordHelpers.createPassword1())); |
|
80 |
|
81 String[] expected = {}; |
|
82 performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected)); |
|
83 dispose(session); |
|
84 } |
|
85 |
|
86 public void testFetchSinceOneRecord() { |
|
87 RepositorySession session = createAndBeginSession(); |
|
88 |
|
89 // Passwords fetchSince checks timePasswordChanged, not insertion time. |
|
90 PasswordRecord record1 = PasswordHelpers.createPassword1(); |
|
91 long timeModified1 = updatePassword(NEW_PASSWORD1, record1); |
|
92 performWait(storeRunnable(session, record1)); |
|
93 |
|
94 PasswordRecord record2 = PasswordHelpers.createPassword2(); |
|
95 long timeModified2 = updatePassword(NEW_PASSWORD2, record2); |
|
96 performWait(storeRunnable(session, record2)); |
|
97 |
|
98 String[] expectedOne = new String[] { record2.guid }; |
|
99 performWait(fetchSinceRunnable(session, timeModified2 - 10, expectedOne)); |
|
100 |
|
101 String[] expectedBoth = new String[] { record1.guid, record2.guid }; |
|
102 performWait(fetchSinceRunnable(session, timeModified1 - 10, expectedBoth)); |
|
103 |
|
104 dispose(session); |
|
105 } |
|
106 |
|
107 public void testFetchSinceReturnNoRecords() { |
|
108 RepositorySession session = createAndBeginSession(); |
|
109 |
|
110 performWait(storeRunnable(session, PasswordHelpers.createPassword2())); |
|
111 |
|
112 long timestamp = System.currentTimeMillis(); |
|
113 |
|
114 performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {})); |
|
115 dispose(session); |
|
116 } |
|
117 |
|
118 public void testFetchOneRecordByGuid() { |
|
119 RepositorySession session = createAndBeginSession(); |
|
120 Record record = PasswordHelpers.createPassword1(); |
|
121 performWait(storeRunnable(session, record)); |
|
122 performWait(storeRunnable(session, PasswordHelpers.createPassword2())); |
|
123 |
|
124 String[] guids = new String[] { record.guid }; |
|
125 Record[] expected = new Record[] { record }; |
|
126 performWait(fetchRunnable(session, guids, expected)); |
|
127 dispose(session); |
|
128 } |
|
129 |
|
130 public void testFetchMultipleRecordsByGuids() { |
|
131 RepositorySession session = createAndBeginSession(); |
|
132 PasswordRecord record1 = PasswordHelpers.createPassword1(); |
|
133 PasswordRecord record2 = PasswordHelpers.createPassword2(); |
|
134 PasswordRecord record3 = PasswordHelpers.createPassword3(); |
|
135 |
|
136 performWait(storeRunnable(session, record1)); |
|
137 performWait(storeRunnable(session, record2)); |
|
138 performWait(storeRunnable(session, record3)); |
|
139 |
|
140 String[] guids = new String[] { record1.guid, record2.guid }; |
|
141 Record[] expected = new Record[] { record1, record2 }; |
|
142 performWait(fetchRunnable(session, guids, expected)); |
|
143 dispose(session); |
|
144 } |
|
145 |
|
146 public void testFetchNoRecordByGuid() { |
|
147 RepositorySession session = createAndBeginSession(); |
|
148 Record record = PasswordHelpers.createPassword1(); |
|
149 |
|
150 performWait(storeRunnable(session, record)); |
|
151 performWait(fetchRunnable(session, |
|
152 new String[] { Utils.generateGuid() }, |
|
153 new Record[] {})); |
|
154 dispose(session); |
|
155 } |
|
156 |
|
157 public void testStore() { |
|
158 final RepositorySession session = createAndBeginSession(); |
|
159 performWait(storeRunnable(session, PasswordHelpers.createPassword1())); |
|
160 dispose(session); |
|
161 } |
|
162 |
|
163 public void testRemoteNewerTimeStamp() { |
|
164 final RepositorySession session = createAndBeginSession(); |
|
165 |
|
166 // Store updated local record. |
|
167 PasswordRecord local = PasswordHelpers.createPassword1(); |
|
168 updatePassword(NEW_PASSWORD1, local, System.currentTimeMillis() - 1000); |
|
169 performWait(storeRunnable(session, local)); |
|
170 |
|
171 // Sync a remote record version that is newer. |
|
172 PasswordRecord remote = PasswordHelpers.createPassword2(); |
|
173 remote.guid = local.guid; |
|
174 updatePassword(NEW_PASSWORD2, remote); |
|
175 performWait(storeRunnable(session, remote)); |
|
176 |
|
177 // Make a fetch, expecting only the newer (remote) record. |
|
178 performWait(fetchAllRunnable(session, new Record[] { remote })); |
|
179 dispose(session); |
|
180 } |
|
181 |
|
182 public void testLocalNewerTimeStamp() { |
|
183 final RepositorySession session = createAndBeginSession(); |
|
184 // Remote record updated before local record. |
|
185 PasswordRecord remote = PasswordHelpers.createPassword1(); |
|
186 updatePassword(NEW_PASSWORD1, remote, System.currentTimeMillis() - 1000); |
|
187 |
|
188 // Store updated local record. |
|
189 PasswordRecord local = PasswordHelpers.createPassword2(); |
|
190 updatePassword(NEW_PASSWORD2, local); |
|
191 performWait(storeRunnable(session, local)); |
|
192 |
|
193 // Sync a remote record version that is older. |
|
194 remote.guid = local.guid; |
|
195 performWait(storeRunnable(session, remote)); |
|
196 |
|
197 // Make a fetch, expecting only the newer (local) record. |
|
198 performWait(fetchAllRunnable(session, new Record[] { local })); |
|
199 dispose(session); |
|
200 } |
|
201 |
|
202 /* |
|
203 * Store two records that are identical except for guid. Expect to find the |
|
204 * remote one after reconciling. |
|
205 */ |
|
206 public void testStoreIdenticalExceptGuid() { |
|
207 RepositorySession session = createAndBeginSession(); |
|
208 PasswordRecord record = PasswordHelpers.createPassword1(); |
|
209 record.guid = "before1"; |
|
210 // Store record. |
|
211 performWait(storeRunnable(session, record)); |
|
212 |
|
213 // Store same record, but with different guid. |
|
214 record.guid = Utils.generateGuid(); |
|
215 performWait(storeRunnable(session, record)); |
|
216 |
|
217 performWait(fetchAllRunnable(session, new Record[] { record })); |
|
218 dispose(session); |
|
219 |
|
220 session = createAndBeginSession(); |
|
221 |
|
222 PasswordRecord record2 = PasswordHelpers.createPassword2(); |
|
223 record2.guid = "before2"; |
|
224 // Store record. |
|
225 performWait(storeRunnable(session, record2)); |
|
226 |
|
227 // Store same record, but with different guid. |
|
228 record2.guid = Utils.generateGuid(); |
|
229 performWait(storeRunnable(session, record2)); |
|
230 |
|
231 performWait(fetchAllRunnable(session, new Record[] { record, record2 })); |
|
232 dispose(session); |
|
233 } |
|
234 |
|
235 /* |
|
236 * Store two records that are identical except for guid when they both point |
|
237 * to the same site and there are multiple records for that site. Expect to |
|
238 * find the remote one after reconciling. |
|
239 */ |
|
240 public void testStoreIdenticalExceptGuidOnSameSite() { |
|
241 RepositorySession session = createAndBeginSession(); |
|
242 PasswordRecord record1 = PasswordHelpers.createPassword1(); |
|
243 record1.encryptedUsername = "original"; |
|
244 record1.guid = "before1"; |
|
245 PasswordRecord record2 = PasswordHelpers.createPassword1(); |
|
246 record2.encryptedUsername = "different"; |
|
247 record1.guid = "before2"; |
|
248 // Store records. |
|
249 performWait(storeRunnable(session, record1)); |
|
250 performWait(storeRunnable(session, record2)); |
|
251 performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); |
|
252 |
|
253 dispose(session); |
|
254 session = createAndBeginSession(); |
|
255 |
|
256 // Store same records, but with different guids. |
|
257 record1.guid = Utils.generateGuid(); |
|
258 performWait(storeRunnable(session, record1)); |
|
259 performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); |
|
260 |
|
261 record2.guid = Utils.generateGuid(); |
|
262 performWait(storeRunnable(session, record2)); |
|
263 performWait(fetchAllRunnable(session, new Record[] { record1, record2 })); |
|
264 |
|
265 dispose(session); |
|
266 } |
|
267 |
|
268 public void testRawFetch() throws RemoteException { |
|
269 RepositorySession session = createAndBeginSession(); |
|
270 Record[] expected = new Record[] { PasswordHelpers.createPassword1(), |
|
271 PasswordHelpers.createPassword2() }; |
|
272 |
|
273 performWait(storeRunnable(session, expected[0])); |
|
274 performWait(storeRunnable(session, expected[1])); |
|
275 |
|
276 ContentProviderClient client = getApplicationContext().getContentResolver().acquireContentProviderClient(BrowserContract.PASSWORDS_AUTHORITY_URI); |
|
277 Cursor cursor = client.query(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null, null, null); |
|
278 assertEquals(2, cursor.getCount()); |
|
279 cursor.moveToFirst(); |
|
280 Set<String> guids = new HashSet<String>(); |
|
281 while (!cursor.isAfterLast()) { |
|
282 String guid = RepoUtils.getStringFromCursor(cursor, BrowserContract.Passwords.GUID); |
|
283 guids.add(guid); |
|
284 cursor.moveToNext(); |
|
285 } |
|
286 cursor.close(); |
|
287 assertEquals(2, guids.size()); |
|
288 assertTrue(guids.contains(expected[0].guid)); |
|
289 assertTrue(guids.contains(expected[1].guid)); |
|
290 dispose(session); |
|
291 } |
|
292 |
|
293 // Helper methods. |
|
294 private RepositorySession createAndBeginSession() { |
|
295 return SessionTestHelper.createAndBeginSession( |
|
296 getApplicationContext(), |
|
297 getRepository()); |
|
298 } |
|
299 |
|
300 private Repository getRepository() { |
|
301 /** |
|
302 * Override this chain in order to avoid our test code having to create two |
|
303 * sessions all the time. Don't track records, so they filtering doesn't happen. |
|
304 */ |
|
305 return new PasswordsRepositorySession.PasswordsRepository() { |
|
306 @Override |
|
307 public void createSession(RepositorySessionCreationDelegate delegate, |
|
308 Context context) { |
|
309 PasswordsRepositorySession session; |
|
310 session = new PasswordsRepositorySession(this, context) { |
|
311 @Override |
|
312 protected synchronized void trackGUID(String guid) { |
|
313 } |
|
314 }; |
|
315 delegate.onSessionCreated(session); |
|
316 } |
|
317 }; |
|
318 } |
|
319 |
|
320 private void wipe() { |
|
321 Context context = getApplicationContext(); |
|
322 context.getContentResolver().delete(BrowserContractHelpers.PASSWORDS_CONTENT_URI, null, null); |
|
323 context.getContentResolver().delete(BrowserContractHelpers.DELETED_PASSWORDS_CONTENT_URI, null, null); |
|
324 } |
|
325 |
|
326 private static void dispose(RepositorySession session) { |
|
327 if (session != null) { |
|
328 session.abort(); |
|
329 } |
|
330 } |
|
331 |
|
332 private static long updatePassword(String password, PasswordRecord record, long timestamp) { |
|
333 record.encryptedPassword = password; |
|
334 long modifiedTime = System.currentTimeMillis(); |
|
335 record.timePasswordChanged = record.lastModified = modifiedTime; |
|
336 return modifiedTime; |
|
337 } |
|
338 |
|
339 private static long updatePassword(String password, PasswordRecord record) { |
|
340 return updatePassword(password, record, System.currentTimeMillis()); |
|
341 } |
|
342 |
|
343 // Runnable Helpers. |
|
344 private static Runnable storeRunnable(final RepositorySession session, final Record record) { |
|
345 return new Runnable() { |
|
346 @Override |
|
347 public void run() { |
|
348 session.setStoreDelegate(new ExpectStoredDelegate(record.guid)); |
|
349 try { |
|
350 session.store(record); |
|
351 session.storeDone(); |
|
352 } catch (NoStoreDelegateException e) { |
|
353 fail("NoStoreDelegateException should not occur."); |
|
354 } |
|
355 } |
|
356 }; |
|
357 } |
|
358 |
|
359 private static Runnable fetchAllRunnable(final RepositorySession session, final Record[] records) { |
|
360 return new Runnable() { |
|
361 @Override |
|
362 public void run() { |
|
363 session.fetchAll(new ExpectFetchDelegate(records)); |
|
364 } |
|
365 }; |
|
366 } |
|
367 |
|
368 private static Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { |
|
369 return new Runnable() { |
|
370 @Override |
|
371 public void run() { |
|
372 session.guidsSince(timestamp, new ExpectGuidsSinceDelegate(expected)); |
|
373 } |
|
374 }; |
|
375 } |
|
376 |
|
377 private static Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { |
|
378 return new Runnable() { |
|
379 @Override |
|
380 public void run() { |
|
381 session.fetchSince(timestamp, new ExpectFetchSinceDelegate(timestamp, expected)); |
|
382 } |
|
383 }; |
|
384 } |
|
385 |
|
386 private static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) { |
|
387 return new Runnable() { |
|
388 @Override |
|
389 public void run() { |
|
390 try { |
|
391 session.fetch(guids, new ExpectFetchDelegate(expected)); |
|
392 } catch (InactiveSessionException e) { |
|
393 performNotify(e); |
|
394 } |
|
395 } |
|
396 }; |
|
397 } |
|
398 } |