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.db;
6 import java.util.HashSet;
7 import java.util.Set;
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;
30 import android.content.ContentProviderClient;
31 import android.content.Context;
32 import android.database.Cursor;
33 import android.os.RemoteException;
35 public class TestPasswordsRepository extends AndroidSyncTestCase {
36 private final String NEW_PASSWORD1 = "password";
37 private final String NEW_PASSWORD2 = "drowssap";
39 @Override
40 public void setUp() {
41 wipe();
42 assertTrue(WaitHelper.getTestWaiter().isIdle());
43 }
45 public void testFetchAll() {
46 RepositorySession session = createAndBeginSession();
47 Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
48 PasswordHelpers.createPassword2() };
50 performWait(storeRunnable(session, expected[0]));
51 performWait(storeRunnable(session, expected[1]));
53 performWait(fetchAllRunnable(session, expected));
54 dispose(session);
55 }
57 public void testGuidsSinceReturnMultipleRecords() {
58 RepositorySession session = createAndBeginSession();
60 PasswordRecord record1 = PasswordHelpers.createPassword1();
61 PasswordRecord record2 = PasswordHelpers.createPassword2();
63 updatePassword(NEW_PASSWORD1, record1);
64 long timestamp = updatePassword(NEW_PASSWORD2, record2);
66 String[] expected = new String[] { record1.guid, record2.guid };
68 performWait(storeRunnable(session, record1));
69 performWait(storeRunnable(session, record2));
71 performWait(guidsSinceRunnable(session, timestamp, expected));
72 dispose(session);
73 }
75 public void testGuidsSinceReturnNoRecords() {
76 RepositorySession session = createAndBeginSession();
78 // Store 1 record in the past.
79 performWait(storeRunnable(session, PasswordHelpers.createPassword1()));
81 String[] expected = {};
82 performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected));
83 dispose(session);
84 }
86 public void testFetchSinceOneRecord() {
87 RepositorySession session = createAndBeginSession();
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));
94 PasswordRecord record2 = PasswordHelpers.createPassword2();
95 long timeModified2 = updatePassword(NEW_PASSWORD2, record2);
96 performWait(storeRunnable(session, record2));
98 String[] expectedOne = new String[] { record2.guid };
99 performWait(fetchSinceRunnable(session, timeModified2 - 10, expectedOne));
101 String[] expectedBoth = new String[] { record1.guid, record2.guid };
102 performWait(fetchSinceRunnable(session, timeModified1 - 10, expectedBoth));
104 dispose(session);
105 }
107 public void testFetchSinceReturnNoRecords() {
108 RepositorySession session = createAndBeginSession();
110 performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
112 long timestamp = System.currentTimeMillis();
114 performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {}));
115 dispose(session);
116 }
118 public void testFetchOneRecordByGuid() {
119 RepositorySession session = createAndBeginSession();
120 Record record = PasswordHelpers.createPassword1();
121 performWait(storeRunnable(session, record));
122 performWait(storeRunnable(session, PasswordHelpers.createPassword2()));
124 String[] guids = new String[] { record.guid };
125 Record[] expected = new Record[] { record };
126 performWait(fetchRunnable(session, guids, expected));
127 dispose(session);
128 }
130 public void testFetchMultipleRecordsByGuids() {
131 RepositorySession session = createAndBeginSession();
132 PasswordRecord record1 = PasswordHelpers.createPassword1();
133 PasswordRecord record2 = PasswordHelpers.createPassword2();
134 PasswordRecord record3 = PasswordHelpers.createPassword3();
136 performWait(storeRunnable(session, record1));
137 performWait(storeRunnable(session, record2));
138 performWait(storeRunnable(session, record3));
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 }
146 public void testFetchNoRecordByGuid() {
147 RepositorySession session = createAndBeginSession();
148 Record record = PasswordHelpers.createPassword1();
150 performWait(storeRunnable(session, record));
151 performWait(fetchRunnable(session,
152 new String[] { Utils.generateGuid() },
153 new Record[] {}));
154 dispose(session);
155 }
157 public void testStore() {
158 final RepositorySession session = createAndBeginSession();
159 performWait(storeRunnable(session, PasswordHelpers.createPassword1()));
160 dispose(session);
161 }
163 public void testRemoteNewerTimeStamp() {
164 final RepositorySession session = createAndBeginSession();
166 // Store updated local record.
167 PasswordRecord local = PasswordHelpers.createPassword1();
168 updatePassword(NEW_PASSWORD1, local, System.currentTimeMillis() - 1000);
169 performWait(storeRunnable(session, local));
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));
177 // Make a fetch, expecting only the newer (remote) record.
178 performWait(fetchAllRunnable(session, new Record[] { remote }));
179 dispose(session);
180 }
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);
188 // Store updated local record.
189 PasswordRecord local = PasswordHelpers.createPassword2();
190 updatePassword(NEW_PASSWORD2, local);
191 performWait(storeRunnable(session, local));
193 // Sync a remote record version that is older.
194 remote.guid = local.guid;
195 performWait(storeRunnable(session, remote));
197 // Make a fetch, expecting only the newer (local) record.
198 performWait(fetchAllRunnable(session, new Record[] { local }));
199 dispose(session);
200 }
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));
213 // Store same record, but with different guid.
214 record.guid = Utils.generateGuid();
215 performWait(storeRunnable(session, record));
217 performWait(fetchAllRunnable(session, new Record[] { record }));
218 dispose(session);
220 session = createAndBeginSession();
222 PasswordRecord record2 = PasswordHelpers.createPassword2();
223 record2.guid = "before2";
224 // Store record.
225 performWait(storeRunnable(session, record2));
227 // Store same record, but with different guid.
228 record2.guid = Utils.generateGuid();
229 performWait(storeRunnable(session, record2));
231 performWait(fetchAllRunnable(session, new Record[] { record, record2 }));
232 dispose(session);
233 }
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 }));
253 dispose(session);
254 session = createAndBeginSession();
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 }));
261 record2.guid = Utils.generateGuid();
262 performWait(storeRunnable(session, record2));
263 performWait(fetchAllRunnable(session, new Record[] { record1, record2 }));
265 dispose(session);
266 }
268 public void testRawFetch() throws RemoteException {
269 RepositorySession session = createAndBeginSession();
270 Record[] expected = new Record[] { PasswordHelpers.createPassword1(),
271 PasswordHelpers.createPassword2() };
273 performWait(storeRunnable(session, expected[0]));
274 performWait(storeRunnable(session, expected[1]));
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 }
293 // Helper methods.
294 private RepositorySession createAndBeginSession() {
295 return SessionTestHelper.createAndBeginSession(
296 getApplicationContext(),
297 getRepository());
298 }
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 }
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 }
326 private static void dispose(RepositorySession session) {
327 if (session != null) {
328 session.abort();
329 }
330 }
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 }
339 private static long updatePassword(String password, PasswordRecord record) {
340 return updatePassword(password, record, System.currentTimeMillis());
341 }
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 }
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 }
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 }
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 }
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 }