|
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.concurrent.ExecutorService; |
|
7 |
|
8 import org.mozilla.gecko.background.common.log.Logger; |
|
9 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; |
|
10 import org.mozilla.gecko.background.sync.helpers.DefaultBeginDelegate; |
|
11 import org.mozilla.gecko.background.sync.helpers.DefaultCleanDelegate; |
|
12 import org.mozilla.gecko.background.sync.helpers.DefaultFetchDelegate; |
|
13 import org.mozilla.gecko.background.sync.helpers.DefaultFinishDelegate; |
|
14 import org.mozilla.gecko.background.sync.helpers.DefaultSessionCreationDelegate; |
|
15 import org.mozilla.gecko.background.sync.helpers.DefaultStoreDelegate; |
|
16 import org.mozilla.gecko.background.sync.helpers.ExpectBeginDelegate; |
|
17 import org.mozilla.gecko.background.sync.helpers.ExpectBeginFailDelegate; |
|
18 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate; |
|
19 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate; |
|
20 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate; |
|
21 import org.mozilla.gecko.background.sync.helpers.ExpectFinishFailDelegate; |
|
22 import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate; |
|
23 import org.mozilla.gecko.background.sync.helpers.ExpectInvalidRequestFetchDelegate; |
|
24 import org.mozilla.gecko.background.sync.helpers.ExpectManyStoredDelegate; |
|
25 import org.mozilla.gecko.background.sync.helpers.ExpectStoreCompletedDelegate; |
|
26 import org.mozilla.gecko.background.sync.helpers.ExpectStoredDelegate; |
|
27 import org.mozilla.gecko.background.sync.helpers.SessionTestHelper; |
|
28 import org.mozilla.gecko.background.testhelpers.WaitHelper; |
|
29 import org.mozilla.gecko.db.BrowserContract; |
|
30 import org.mozilla.gecko.sync.Utils; |
|
31 import org.mozilla.gecko.sync.repositories.InactiveSessionException; |
|
32 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; |
|
33 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; |
|
34 import org.mozilla.gecko.sync.repositories.Repository; |
|
35 import org.mozilla.gecko.sync.repositories.RepositorySession; |
|
36 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor; |
|
37 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionGuidsSinceDelegate; |
|
38 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionWipeDelegate; |
|
39 import org.mozilla.gecko.sync.repositories.domain.Record; |
|
40 |
|
41 import android.content.ContentValues; |
|
42 import android.content.Context; |
|
43 |
|
44 public abstract class AndroidBrowserRepositoryTestCase extends AndroidSyncTestCase { |
|
45 protected static String LOG_TAG = "BrowserRepositoryTest"; |
|
46 |
|
47 protected static void wipe(AndroidBrowserRepositoryDataAccessor helper) { |
|
48 Logger.debug(LOG_TAG, "Wiping."); |
|
49 try { |
|
50 helper.wipe(); |
|
51 } catch (NullPointerException e) { |
|
52 // This will be handled in begin, here we can just ignore |
|
53 // the error if it actually occurs since this is just test |
|
54 // code. We will throw a ProfileDatabaseException. This |
|
55 // error shouldn't occur in the future, but results from |
|
56 // trying to access content providers before Fennec has |
|
57 // been run at least once. |
|
58 Logger.error(LOG_TAG, "ProfileDatabaseException seen in wipe. Begin should fail"); |
|
59 fail("NullPointerException in wipe."); |
|
60 } |
|
61 } |
|
62 |
|
63 @Override |
|
64 public void setUp() { |
|
65 AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); |
|
66 wipe(helper); |
|
67 assertTrue(WaitHelper.getTestWaiter().isIdle()); |
|
68 closeDataAccessor(helper); |
|
69 } |
|
70 |
|
71 public void tearDown() { |
|
72 assertTrue(WaitHelper.getTestWaiter().isIdle()); |
|
73 } |
|
74 |
|
75 protected RepositorySession createSession() { |
|
76 return SessionTestHelper.createSession( |
|
77 getApplicationContext(), |
|
78 getRepository()); |
|
79 } |
|
80 |
|
81 protected RepositorySession createAndBeginSession() { |
|
82 return SessionTestHelper.createAndBeginSession( |
|
83 getApplicationContext(), |
|
84 getRepository()); |
|
85 } |
|
86 |
|
87 protected static void dispose(RepositorySession session) { |
|
88 if (session != null) { |
|
89 session.abort(); |
|
90 } |
|
91 } |
|
92 |
|
93 /** |
|
94 * Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored. |
|
95 */ |
|
96 public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) { |
|
97 return new ExpectFetchDelegate(expected); |
|
98 } |
|
99 |
|
100 /** |
|
101 * Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored. |
|
102 */ |
|
103 public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) { |
|
104 return new ExpectGuidsSinceDelegate(expected); |
|
105 } |
|
106 |
|
107 /** |
|
108 * Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any). |
|
109 */ |
|
110 public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() { |
|
111 return new ExpectGuidsSinceDelegate(new String[] {}); |
|
112 } |
|
113 |
|
114 /** |
|
115 * Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored. |
|
116 */ |
|
117 public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) { |
|
118 return new ExpectFetchSinceDelegate(timestamp, expected); |
|
119 } |
|
120 |
|
121 public static Runnable storeRunnable(final RepositorySession session, final Record record, final DefaultStoreDelegate delegate) { |
|
122 return new Runnable() { |
|
123 @Override |
|
124 public void run() { |
|
125 session.setStoreDelegate(delegate); |
|
126 try { |
|
127 session.store(record); |
|
128 session.storeDone(); |
|
129 } catch (NoStoreDelegateException e) { |
|
130 fail("NoStoreDelegateException should not occur."); |
|
131 } |
|
132 } |
|
133 }; |
|
134 } |
|
135 |
|
136 public static Runnable storeRunnable(final RepositorySession session, final Record record) { |
|
137 return storeRunnable(session, record, new ExpectStoredDelegate(record.guid)); |
|
138 } |
|
139 |
|
140 public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records, final DefaultStoreDelegate delegate) { |
|
141 return new Runnable() { |
|
142 @Override |
|
143 public void run() { |
|
144 session.setStoreDelegate(delegate); |
|
145 try { |
|
146 for (Record record : records) { |
|
147 session.store(record); |
|
148 } |
|
149 session.storeDone(); |
|
150 } catch (NoStoreDelegateException e) { |
|
151 fail("NoStoreDelegateException should not occur."); |
|
152 } |
|
153 } |
|
154 }; |
|
155 } |
|
156 |
|
157 public static Runnable storeManyRunnable(final RepositorySession session, final Record[] records) { |
|
158 return storeManyRunnable(session, records, new ExpectManyStoredDelegate(records)); |
|
159 } |
|
160 |
|
161 /** |
|
162 * Store a record and don't expect a store callback until we're done. |
|
163 * |
|
164 * @param session |
|
165 * @param record |
|
166 * @return Runnable. |
|
167 */ |
|
168 public static Runnable quietStoreRunnable(final RepositorySession session, final Record record) { |
|
169 return storeRunnable(session, record, new ExpectStoreCompletedDelegate()); |
|
170 } |
|
171 |
|
172 public static Runnable beginRunnable(final RepositorySession session, final DefaultBeginDelegate delegate) { |
|
173 return new Runnable() { |
|
174 @Override |
|
175 public void run() { |
|
176 try { |
|
177 session.begin(delegate); |
|
178 } catch (InvalidSessionTransitionException e) { |
|
179 performNotify(e); |
|
180 } |
|
181 } |
|
182 }; |
|
183 } |
|
184 |
|
185 public static Runnable finishRunnable(final RepositorySession session, final DefaultFinishDelegate delegate) { |
|
186 return new Runnable() { |
|
187 @Override |
|
188 public void run() { |
|
189 try { |
|
190 session.finish(delegate); |
|
191 } catch (InactiveSessionException e) { |
|
192 performNotify(e); |
|
193 } |
|
194 } |
|
195 }; |
|
196 } |
|
197 |
|
198 public static Runnable fetchAllRunnable(final RepositorySession session, final ExpectFetchDelegate delegate) { |
|
199 return new Runnable() { |
|
200 @Override |
|
201 public void run() { |
|
202 session.fetchAll(delegate); |
|
203 } |
|
204 }; |
|
205 } |
|
206 |
|
207 public Runnable fetchAllRunnable(final RepositorySession session, final Record[] expectedRecords) { |
|
208 return fetchAllRunnable(session, preparedExpectFetchDelegate(expectedRecords)); |
|
209 } |
|
210 |
|
211 public Runnable guidsSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { |
|
212 return new Runnable() { |
|
213 @Override |
|
214 public void run() { |
|
215 session.guidsSince(timestamp, preparedExpectGuidsSinceDelegate(expected)); |
|
216 } |
|
217 }; |
|
218 } |
|
219 |
|
220 public Runnable fetchSinceRunnable(final RepositorySession session, final long timestamp, final String[] expected) { |
|
221 return new Runnable() { |
|
222 @Override |
|
223 public void run() { |
|
224 session.fetchSince(timestamp, preparedExpectFetchSinceDelegate(timestamp, expected)); |
|
225 } |
|
226 }; |
|
227 } |
|
228 |
|
229 public static Runnable fetchRunnable(final RepositorySession session, final String[] guids, final DefaultFetchDelegate delegate) { |
|
230 return new Runnable() { |
|
231 @Override |
|
232 public void run() { |
|
233 try { |
|
234 session.fetch(guids, delegate); |
|
235 } catch (InactiveSessionException e) { |
|
236 performNotify(e); |
|
237 } |
|
238 } |
|
239 }; |
|
240 } |
|
241 public Runnable fetchRunnable(final RepositorySession session, final String[] guids, final Record[] expected) { |
|
242 return fetchRunnable(session, guids, preparedExpectFetchDelegate(expected)); |
|
243 } |
|
244 |
|
245 public static Runnable cleanRunnable(final Repository repository, final boolean success, final Context context, final DefaultCleanDelegate delegate) { |
|
246 return new Runnable() { |
|
247 @Override |
|
248 public void run() { |
|
249 repository.clean(success, delegate, context); |
|
250 } |
|
251 }; |
|
252 } |
|
253 |
|
254 protected abstract Repository getRepository(); |
|
255 protected abstract AndroidBrowserRepositoryDataAccessor getDataAccessor(); |
|
256 |
|
257 protected static void doStore(RepositorySession session, Record[] records) { |
|
258 performWait(storeManyRunnable(session, records)); |
|
259 } |
|
260 |
|
261 // Tests to implement |
|
262 public abstract void testFetchAll(); |
|
263 public abstract void testGuidsSinceReturnMultipleRecords(); |
|
264 public abstract void testGuidsSinceReturnNoRecords(); |
|
265 public abstract void testFetchSinceOneRecord(); |
|
266 public abstract void testFetchSinceReturnNoRecords(); |
|
267 public abstract void testFetchOneRecordByGuid(); |
|
268 public abstract void testFetchMultipleRecordsByGuids(); |
|
269 public abstract void testFetchNoRecordByGuid(); |
|
270 public abstract void testWipe(); |
|
271 public abstract void testStore(); |
|
272 public abstract void testRemoteNewerTimeStamp(); |
|
273 public abstract void testLocalNewerTimeStamp(); |
|
274 public abstract void testDeleteRemoteNewer(); |
|
275 public abstract void testDeleteLocalNewer(); |
|
276 public abstract void testDeleteRemoteLocalNonexistent(); |
|
277 public abstract void testStoreIdenticalExceptGuid(); |
|
278 public abstract void testCleanMultipleRecords(); |
|
279 |
|
280 |
|
281 /* |
|
282 * Test abstractions |
|
283 */ |
|
284 protected void basicStoreTest(Record record) { |
|
285 final RepositorySession session = createAndBeginSession(); |
|
286 performWait(storeRunnable(session, record)); |
|
287 } |
|
288 |
|
289 protected void basicFetchAllTest(Record[] expected) { |
|
290 Logger.debug("rnewman", "Starting testFetchAll."); |
|
291 RepositorySession session = createAndBeginSession(); |
|
292 Logger.debug("rnewman", "Prepared."); |
|
293 |
|
294 AndroidBrowserRepositoryDataAccessor helper = getDataAccessor(); |
|
295 helper.dumpDB(); |
|
296 performWait(storeManyRunnable(session, expected)); |
|
297 |
|
298 helper.dumpDB(); |
|
299 performWait(fetchAllRunnable(session, expected)); |
|
300 |
|
301 closeDataAccessor(helper); |
|
302 dispose(session); |
|
303 } |
|
304 |
|
305 /* |
|
306 * Tests for clean |
|
307 */ |
|
308 // Input: 4 records; 2 which are to be cleaned, 2 which should remain after the clean |
|
309 protected void cleanMultipleRecords(Record delete0, Record delete1, Record keep0, Record keep1, Record keep2) { |
|
310 RepositorySession session = createAndBeginSession(); |
|
311 doStore(session, new Record[] { |
|
312 delete0, delete1, keep0, keep1, keep2 |
|
313 }); |
|
314 |
|
315 // Force two records to appear deleted. |
|
316 AndroidBrowserRepositoryDataAccessor db = getDataAccessor(); |
|
317 ContentValues cv = new ContentValues(); |
|
318 cv.put(BrowserContract.SyncColumns.IS_DELETED, 1); |
|
319 db.updateByGuid(delete0.guid, cv); |
|
320 db.updateByGuid(delete1.guid, cv); |
|
321 |
|
322 final DefaultCleanDelegate delegate = new DefaultCleanDelegate() { |
|
323 public void onCleaned(Repository repo) { |
|
324 performNotify(); |
|
325 } |
|
326 }; |
|
327 |
|
328 final Runnable cleanRunnable = cleanRunnable( |
|
329 getRepository(), |
|
330 true, |
|
331 getApplicationContext(), |
|
332 delegate); |
|
333 |
|
334 performWait(cleanRunnable); |
|
335 performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[] { keep0, keep1, keep2}))); |
|
336 closeDataAccessor(db); |
|
337 dispose(session); |
|
338 } |
|
339 |
|
340 /* |
|
341 * Tests for guidsSince |
|
342 */ |
|
343 protected void guidsSinceReturnMultipleRecords(Record record0, Record record1) { |
|
344 RepositorySession session = createAndBeginSession(); |
|
345 long timestamp = System.currentTimeMillis(); |
|
346 |
|
347 String[] expected = new String[2]; |
|
348 expected[0] = record0.guid; |
|
349 expected[1] = record1.guid; |
|
350 |
|
351 Logger.debug(getName(), "Storing two records..."); |
|
352 performWait(storeManyRunnable(session, new Record[] { record0, record1 })); |
|
353 Logger.debug(getName(), "Getting guids since " + timestamp + "; expecting " + expected.length); |
|
354 performWait(guidsSinceRunnable(session, timestamp, expected)); |
|
355 dispose(session); |
|
356 } |
|
357 |
|
358 protected void guidsSinceReturnNoRecords(Record record0) { |
|
359 RepositorySession session = createAndBeginSession(); |
|
360 |
|
361 // Store 1 record in the past. |
|
362 performWait(storeRunnable(session, record0)); |
|
363 |
|
364 String[] expected = {}; |
|
365 performWait(guidsSinceRunnable(session, System.currentTimeMillis() + 1000, expected)); |
|
366 dispose(session); |
|
367 } |
|
368 |
|
369 /* |
|
370 * Tests for fetchSince |
|
371 */ |
|
372 protected void fetchSinceOneRecord(Record record0, Record record1) { |
|
373 RepositorySession session = createAndBeginSession(); |
|
374 |
|
375 performWait(storeRunnable(session, record0)); |
|
376 long timestamp = System.currentTimeMillis(); |
|
377 Logger.debug("fetchSinceOneRecord", "Entering synchronized section. Timestamp " + timestamp); |
|
378 synchronized(this) { |
|
379 try { |
|
380 wait(1000); |
|
381 } catch (InterruptedException e) { |
|
382 Logger.warn("fetchSinceOneRecord", "Interrupted.", e); |
|
383 } |
|
384 } |
|
385 Logger.debug("fetchSinceOneRecord", "Storing."); |
|
386 performWait(storeRunnable(session, record1)); |
|
387 |
|
388 Logger.debug("fetchSinceOneRecord", "Fetching record 1."); |
|
389 String[] expectedOne = new String[] { record1.guid }; |
|
390 performWait(fetchSinceRunnable(session, timestamp + 10, expectedOne)); |
|
391 |
|
392 Logger.debug("fetchSinceOneRecord", "Fetching both, relying on inclusiveness."); |
|
393 String[] expectedBoth = new String[] { record0.guid, record1.guid }; |
|
394 performWait(fetchSinceRunnable(session, timestamp - 3000, expectedBoth)); |
|
395 |
|
396 Logger.debug("fetchSinceOneRecord", "Done."); |
|
397 dispose(session); |
|
398 } |
|
399 |
|
400 protected void fetchSinceReturnNoRecords(Record record) { |
|
401 RepositorySession session = createAndBeginSession(); |
|
402 |
|
403 performWait(storeRunnable(session, record)); |
|
404 |
|
405 long timestamp = System.currentTimeMillis(); |
|
406 |
|
407 performWait(fetchSinceRunnable(session, timestamp + 2000, new String[] {})); |
|
408 dispose(session); |
|
409 } |
|
410 |
|
411 protected void fetchOneRecordByGuid(Record record0, Record record1) { |
|
412 RepositorySession session = createAndBeginSession(); |
|
413 |
|
414 Record[] store = new Record[] { record0, record1 }; |
|
415 performWait(storeManyRunnable(session, store)); |
|
416 |
|
417 String[] guids = new String[] { record0.guid }; |
|
418 Record[] expected = new Record[] { record0 }; |
|
419 performWait(fetchRunnable(session, guids, expected)); |
|
420 dispose(session); |
|
421 } |
|
422 |
|
423 protected void fetchMultipleRecordsByGuids(Record record0, |
|
424 Record record1, Record record2) { |
|
425 RepositorySession session = createAndBeginSession(); |
|
426 |
|
427 Record[] store = new Record[] { record0, record1, record2 }; |
|
428 performWait(storeManyRunnable(session, store)); |
|
429 |
|
430 String[] guids = new String[] { record0.guid, record2.guid }; |
|
431 Record[] expected = new Record[] { record0, record2 }; |
|
432 performWait(fetchRunnable(session, guids, expected)); |
|
433 dispose(session); |
|
434 } |
|
435 |
|
436 protected void fetchNoRecordByGuid(Record record) { |
|
437 RepositorySession session = createAndBeginSession(); |
|
438 |
|
439 performWait(storeRunnable(session, record)); |
|
440 performWait(fetchRunnable(session, |
|
441 new String[] { Utils.generateGuid() }, |
|
442 new Record[] {})); |
|
443 dispose(session); |
|
444 } |
|
445 |
|
446 /* |
|
447 * Test wipe |
|
448 */ |
|
449 protected void doWipe(final Record record0, final Record record1) { |
|
450 final RepositorySession session = createAndBeginSession(); |
|
451 final Runnable run = new Runnable() { |
|
452 @Override |
|
453 public void run() { |
|
454 session.wipe(new RepositorySessionWipeDelegate() { |
|
455 public void onWipeSucceeded() { |
|
456 performNotify(); |
|
457 } |
|
458 public void onWipeFailed(Exception ex) { |
|
459 fail("wipe should have succeeded"); |
|
460 performNotify(); |
|
461 } |
|
462 @Override |
|
463 public RepositorySessionWipeDelegate deferredWipeDelegate(final ExecutorService executor) { |
|
464 final RepositorySessionWipeDelegate self = this; |
|
465 return new RepositorySessionWipeDelegate() { |
|
466 |
|
467 @Override |
|
468 public void onWipeSucceeded() { |
|
469 new Thread(new Runnable() { |
|
470 @Override |
|
471 public void run() { |
|
472 self.onWipeSucceeded(); |
|
473 }}).start(); |
|
474 } |
|
475 |
|
476 @Override |
|
477 public void onWipeFailed(final Exception ex) { |
|
478 new Thread(new Runnable() { |
|
479 @Override |
|
480 public void run() { |
|
481 self.onWipeFailed(ex); |
|
482 }}).start(); |
|
483 } |
|
484 |
|
485 @Override |
|
486 public RepositorySessionWipeDelegate deferredWipeDelegate(ExecutorService newExecutor) { |
|
487 if (newExecutor == executor) { |
|
488 return this; |
|
489 } |
|
490 throw new IllegalArgumentException("Can't re-defer this delegate."); |
|
491 } |
|
492 }; |
|
493 } |
|
494 }); |
|
495 } |
|
496 }; |
|
497 |
|
498 // Store 2 records. |
|
499 Record[] records = new Record[] { record0, record1 }; |
|
500 performWait(storeManyRunnable(session, records)); |
|
501 performWait(fetchAllRunnable(session, records)); |
|
502 |
|
503 // Wipe. |
|
504 performWait(run); |
|
505 dispose(session); |
|
506 } |
|
507 |
|
508 /* |
|
509 * TODO adding or subtracting from lastModified timestamps does NOTHING |
|
510 * since it gets overwritten when we store stuff. See other tests |
|
511 * for ways to do this properly. |
|
512 */ |
|
513 |
|
514 /* |
|
515 * Record being stored has newer timestamp than existing local record, local |
|
516 * record has not been modified since last sync. |
|
517 */ |
|
518 protected void remoteNewerTimeStamp(Record local, Record remote) { |
|
519 final RepositorySession session = createAndBeginSession(); |
|
520 |
|
521 // Record existing and hasn't changed since before lastSync. |
|
522 // Automatically will be assigned lastModified = current time. |
|
523 performWait(storeRunnable(session, local)); |
|
524 |
|
525 remote.guid = local.guid; |
|
526 |
|
527 // Get the timestamp and make remote newer than it |
|
528 ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); |
|
529 performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); |
|
530 remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; |
|
531 performWait(storeRunnable(session, remote)); |
|
532 |
|
533 Record[] expected = new Record[] { remote }; |
|
534 ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected); |
|
535 performWait(fetchAllRunnable(session, delegate)); |
|
536 dispose(session); |
|
537 } |
|
538 |
|
539 /* |
|
540 * Local record has a newer timestamp than the record being stored. For now, |
|
541 * we just take newer (local) record) |
|
542 */ |
|
543 protected void localNewerTimeStamp(Record local, Record remote) { |
|
544 final RepositorySession session = createAndBeginSession(); |
|
545 |
|
546 performWait(storeRunnable(session, local)); |
|
547 |
|
548 remote.guid = local.guid; |
|
549 |
|
550 // Get the timestamp and make remote older than it |
|
551 ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); |
|
552 performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); |
|
553 remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; |
|
554 performWait(storeRunnable(session, remote)); |
|
555 |
|
556 // Do a fetch and make sure that we get back the local record. |
|
557 Record[] expected = new Record[] { local }; |
|
558 performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); |
|
559 dispose(session); |
|
560 } |
|
561 |
|
562 /* |
|
563 * Insert a record that is marked as deleted, remote has newer timestamp |
|
564 */ |
|
565 protected void deleteRemoteNewer(Record local, Record remote) { |
|
566 final RepositorySession session = createAndBeginSession(); |
|
567 |
|
568 // Record existing and hasn't changed since before lastSync. |
|
569 // Automatically will be assigned lastModified = current time. |
|
570 performWait(storeRunnable(session, local)); |
|
571 |
|
572 // Pass the same record to store, but mark it deleted and modified |
|
573 // more recently |
|
574 ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { local }); |
|
575 performWait(fetchRunnable(session, new String[] { local.guid }, timestampDelegate)); |
|
576 remote.lastModified = timestampDelegate.records.get(0).lastModified + 1000; |
|
577 remote.deleted = true; |
|
578 remote.guid = local.guid; |
|
579 performWait(storeRunnable(session, remote)); |
|
580 |
|
581 performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(new Record[]{}))); |
|
582 dispose(session); |
|
583 } |
|
584 |
|
585 // Store two records that are identical (this has different meanings based on the |
|
586 // type of record) other than their guids. The record existing locally already |
|
587 // should have its guid replaced (the assumption is that the record existed locally |
|
588 // and then sync was enabled and this record existed on another sync'd device). |
|
589 public void storeIdenticalExceptGuid(Record record0) { |
|
590 Logger.debug("storeIdenticalExceptGuid", "Started."); |
|
591 final RepositorySession session = createAndBeginSession(); |
|
592 Logger.debug("storeIdenticalExceptGuid", "Session is " + session); |
|
593 performWait(storeRunnable(session, record0)); |
|
594 Logger.debug("storeIdenticalExceptGuid", "Stored record0."); |
|
595 DefaultFetchDelegate timestampDelegate = getTimestampDelegate(record0.guid); |
|
596 |
|
597 performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate)); |
|
598 Logger.debug("storeIdenticalExceptGuid", "fetchRunnable done."); |
|
599 record0.lastModified = timestampDelegate.records.get(0).lastModified + 3000; |
|
600 record0.guid = Utils.generateGuid(); |
|
601 Logger.debug("storeIdenticalExceptGuid", "Storing modified..."); |
|
602 performWait(storeRunnable(session, record0)); |
|
603 Logger.debug("storeIdenticalExceptGuid", "Stored modified."); |
|
604 |
|
605 Record[] expected = new Record[] { record0 }; |
|
606 performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); |
|
607 Logger.debug("storeIdenticalExceptGuid", "Fetched all. Returning."); |
|
608 dispose(session); |
|
609 } |
|
610 |
|
611 // Special delegate so that we don't verify parenting is correct since |
|
612 // at some points it won't be since parent folder hasn't been stored. |
|
613 private DefaultFetchDelegate getTimestampDelegate(final String guid) { |
|
614 return new DefaultFetchDelegate() { |
|
615 @Override |
|
616 public void onFetchCompleted(final long fetchEnd) { |
|
617 assertEquals(guid, this.records.get(0).guid); |
|
618 performNotify(); |
|
619 } |
|
620 }; |
|
621 } |
|
622 |
|
623 /* |
|
624 * Insert a record that is marked as deleted, local has newer timestamp |
|
625 * and was not marked deleted (so keep it) |
|
626 */ |
|
627 protected void deleteLocalNewer(Record local, Record remote) { |
|
628 Logger.debug("deleteLocalNewer", "Begin."); |
|
629 final RepositorySession session = createAndBeginSession(); |
|
630 |
|
631 Logger.debug("deleteLocalNewer", "Storing local..."); |
|
632 performWait(storeRunnable(session, local)); |
|
633 |
|
634 // Create an older version of a record with the same GUID. |
|
635 remote.guid = local.guid; |
|
636 |
|
637 Logger.debug("deleteLocalNewer", "Fetching..."); |
|
638 |
|
639 // Get the timestamp and make remote older than it |
|
640 Record[] expected = new Record[] { local }; |
|
641 ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(expected); |
|
642 performWait(fetchRunnable(session, new String[] { remote.guid }, timestampDelegate)); |
|
643 |
|
644 Logger.debug("deleteLocalNewer", "Fetched."); |
|
645 remote.lastModified = timestampDelegate.records.get(0).lastModified - 1000; |
|
646 |
|
647 Logger.debug("deleteLocalNewer", "Last modified is " + remote.lastModified); |
|
648 remote.deleted = true; |
|
649 Logger.debug("deleteLocalNewer", "Storing deleted..."); |
|
650 performWait(quietStoreRunnable(session, remote)); // This appears to do a lot of work...?! |
|
651 |
|
652 // Do a fetch and make sure that we get back the first (local) record. |
|
653 performWait(fetchAllRunnable(session, preparedExpectFetchDelegate(expected))); |
|
654 Logger.debug("deleteLocalNewer", "Fetched and done!"); |
|
655 dispose(session); |
|
656 } |
|
657 |
|
658 /* |
|
659 * Insert a record that is marked as deleted, record never existed locally |
|
660 */ |
|
661 protected void deleteRemoteLocalNonexistent(Record remote) { |
|
662 final RepositorySession session = createAndBeginSession(); |
|
663 |
|
664 long timestamp = 1000000000; |
|
665 |
|
666 // Pass a record marked deleted to store, doesn't exist locally |
|
667 remote.lastModified = timestamp; |
|
668 remote.deleted = true; |
|
669 performWait(quietStoreRunnable(session, remote)); |
|
670 |
|
671 ExpectFetchDelegate delegate = preparedExpectFetchDelegate(new Record[]{}); |
|
672 performWait(fetchAllRunnable(session, delegate)); |
|
673 dispose(session); |
|
674 } |
|
675 |
|
676 /* |
|
677 * Tests that don't require specific records based on type of repository. |
|
678 * These tests don't need to be overriden in subclasses, they will just work. |
|
679 */ |
|
680 public void testCreateSessionNullContext() { |
|
681 Logger.debug(LOG_TAG, "In testCreateSessionNullContext."); |
|
682 Repository repo = getRepository(); |
|
683 try { |
|
684 repo.createSession(new DefaultSessionCreationDelegate(), null); |
|
685 fail("Should throw."); |
|
686 } catch (Exception ex) { |
|
687 assertNotNull(ex); |
|
688 } |
|
689 } |
|
690 |
|
691 public void testStoreNullRecord() { |
|
692 final RepositorySession session = createAndBeginSession(); |
|
693 try { |
|
694 session.setStoreDelegate(new DefaultStoreDelegate()); |
|
695 session.store(null); |
|
696 fail("Should throw."); |
|
697 } catch (Exception ex) { |
|
698 assertNotNull(ex); |
|
699 } |
|
700 dispose(session); |
|
701 } |
|
702 |
|
703 public void testFetchNoGuids() { |
|
704 final RepositorySession session = createAndBeginSession(); |
|
705 performWait(fetchRunnable(session, new String[] {}, new ExpectInvalidRequestFetchDelegate())); |
|
706 dispose(session); |
|
707 } |
|
708 |
|
709 public void testFetchNullGuids() { |
|
710 final RepositorySession session = createAndBeginSession(); |
|
711 performWait(fetchRunnable(session, null, new ExpectInvalidRequestFetchDelegate())); |
|
712 dispose(session); |
|
713 } |
|
714 |
|
715 public void testBeginOnNewSession() { |
|
716 final RepositorySession session = createSession(); |
|
717 performWait(beginRunnable(session, new ExpectBeginDelegate())); |
|
718 dispose(session); |
|
719 } |
|
720 |
|
721 public void testBeginOnRunningSession() { |
|
722 final RepositorySession session = createAndBeginSession(); |
|
723 try { |
|
724 session.begin(new ExpectBeginFailDelegate()); |
|
725 } catch (InvalidSessionTransitionException e) { |
|
726 dispose(session); |
|
727 return; |
|
728 } |
|
729 fail("Should have caught InvalidSessionTransitionException."); |
|
730 } |
|
731 |
|
732 public void testBeginOnFinishedSession() throws InactiveSessionException { |
|
733 final RepositorySession session = createAndBeginSession(); |
|
734 performWait(finishRunnable(session, new ExpectFinishDelegate())); |
|
735 try { |
|
736 session.begin(new ExpectBeginFailDelegate()); |
|
737 } catch (InvalidSessionTransitionException e) { |
|
738 Logger.debug(getName(), "Yay! Got an exception.", e); |
|
739 dispose(session); |
|
740 return; |
|
741 } catch (Exception e) { |
|
742 Logger.debug(getName(), "Yay! Got an exception.", e); |
|
743 dispose(session); |
|
744 return; |
|
745 } |
|
746 fail("Should have caught InvalidSessionTransitionException."); |
|
747 } |
|
748 |
|
749 public void testFinishOnFinishedSession() throws InactiveSessionException { |
|
750 final RepositorySession session = createAndBeginSession(); |
|
751 performWait(finishRunnable(session, new ExpectFinishDelegate())); |
|
752 try { |
|
753 session.finish(new ExpectFinishFailDelegate()); |
|
754 } catch (InactiveSessionException e) { |
|
755 dispose(session); |
|
756 return; |
|
757 } |
|
758 fail("Should have caught InactiveSessionException."); |
|
759 } |
|
760 |
|
761 public void testFetchOnInactiveSession() throws InactiveSessionException { |
|
762 final RepositorySession session = createSession(); |
|
763 try { |
|
764 session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); |
|
765 } catch (InactiveSessionException e) { |
|
766 // Yay. |
|
767 dispose(session); |
|
768 return; |
|
769 }; |
|
770 fail("Should have caught InactiveSessionException."); |
|
771 } |
|
772 |
|
773 public void testFetchOnFinishedSession() { |
|
774 final RepositorySession session = createAndBeginSession(); |
|
775 Logger.debug(getName(), "Finishing..."); |
|
776 performWait(finishRunnable(session, new ExpectFinishDelegate())); |
|
777 try { |
|
778 session.fetch(new String[] { Utils.generateGuid() }, new DefaultFetchDelegate()); |
|
779 } catch (InactiveSessionException e) { |
|
780 // Yay. |
|
781 dispose(session); |
|
782 return; |
|
783 }; |
|
784 fail("Should have caught InactiveSessionException."); |
|
785 } |
|
786 |
|
787 public void testGuidsSinceOnUnstartedSession() { |
|
788 final RepositorySession session = createSession(); |
|
789 Runnable run = new Runnable() { |
|
790 @Override |
|
791 public void run() { |
|
792 session.guidsSince(System.currentTimeMillis(), |
|
793 new RepositorySessionGuidsSinceDelegate() { |
|
794 public void onGuidsSinceSucceeded(String[] guids) { |
|
795 fail("Session inactive, should fail"); |
|
796 performNotify(); |
|
797 } |
|
798 |
|
799 public void onGuidsSinceFailed(Exception ex) { |
|
800 verifyInactiveException(ex); |
|
801 performNotify(); |
|
802 } |
|
803 }); |
|
804 } |
|
805 }; |
|
806 performWait(run); |
|
807 dispose(session); |
|
808 } |
|
809 |
|
810 private static void verifyInactiveException(Exception ex) { |
|
811 if (!(ex instanceof InactiveSessionException)) { |
|
812 fail("Wrong exception type"); |
|
813 } |
|
814 } |
|
815 |
|
816 protected void closeDataAccessor(AndroidBrowserRepositoryDataAccessor dataAccessor) { |
|
817 } |
|
818 } |