mobile/android/tests/background/junit3/src/db/TestBookmarks.java

changeset 0
6474c204b198
equal deleted inserted replaced
-1:000000000000 0:6c9ebd764706
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.ArrayList;
7 import java.util.Collection;
8 import java.util.Collections;
9 import java.util.HashSet;
10 import java.util.Iterator;
11
12 import org.json.simple.JSONArray;
13 import org.mozilla.gecko.R;
14 import org.mozilla.gecko.background.common.log.Logger;
15 import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
16 import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers;
17 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate;
18 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate;
19 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate;
20 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate;
21 import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate;
22 import org.mozilla.gecko.db.BrowserContract;
23 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
24 import org.mozilla.gecko.sync.Utils;
25 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
26 import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException;
27 import org.mozilla.gecko.sync.repositories.NoStoreDelegateException;
28 import org.mozilla.gecko.sync.repositories.NullCursorException;
29 import org.mozilla.gecko.sync.repositories.RepositorySession;
30 import org.mozilla.gecko.sync.repositories.RepositorySessionBundle;
31 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor;
32 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository;
33 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession;
34 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
35 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate;
36 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
37 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate;
38 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate;
39 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
40 import org.mozilla.gecko.sync.repositories.domain.Record;
41
42 import android.content.ContentResolver;
43 import android.content.ContentUris;
44 import android.content.ContentValues;
45 import android.database.Cursor;
46 import android.net.Uri;
47
48 public class TestBookmarks extends AndroidSyncTestCase {
49
50 protected static final String LOG_TAG = "BookmarksTest";
51
52 /**
53 * Trivial test that forbidden records (reading list prior to Bug 762109, pinned items…)
54 * will be ignored if processed.
55 */
56 public void testForbiddenItemsAreIgnored() {
57 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
58 final long now = System.currentTimeMillis();
59 final String bookmarksCollection = "bookmarks";
60
61 final BookmarkRecord toRead = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false);
62 final BookmarkRecord pinned = new BookmarkRecord("pinpinpinpin", "bookmarks", now - 1, false);
63 final BookmarkRecord normal = new BookmarkRecord("baaaaaaaaaaa", "bookmarks", now - 2, false);
64
65 final BookmarkRecord readingList = new BookmarkRecord(Bookmarks.READING_LIST_FOLDER_GUID,
66 bookmarksCollection, now - 3, false);
67 final BookmarkRecord pinnedItems = new BookmarkRecord(Bookmarks.PINNED_FOLDER_GUID,
68 bookmarksCollection, now - 4, false);
69
70 toRead.type = normal.type = pinned.type = "bookmark";
71 readingList.type = "folder";
72 pinnedItems.type = "folder";
73
74 toRead.parentID = Bookmarks.READING_LIST_FOLDER_GUID;
75 pinned.parentID = Bookmarks.PINNED_FOLDER_GUID;
76 normal.parentID = Bookmarks.TOOLBAR_FOLDER_GUID;
77
78 readingList.parentID = Bookmarks.PLACES_FOLDER_GUID;
79 pinnedItems.parentID = Bookmarks.PLACES_FOLDER_GUID;
80
81 inBegunSession(repo, new SimpleSuccessBeginDelegate() {
82 @Override
83 public void onBeginSucceeded(RepositorySession session) {
84 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(toRead));
85 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinned));
86 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(readingList));
87 assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinnedItems));
88 assertFalse(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(normal));
89 finishAndNotify(session);
90 }
91 });
92 }
93
94 /**
95 * Trivial test that pinned items will be skipped if present in the DB.
96 */
97 public void testPinnedItemsAreNotRetrieved() {
98 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
99
100 // Ensure that they exist.
101 setUpFennecPinnedItemsRecord();
102
103 // They're there in the DB…
104 final ArrayList<String> roots = fetchChildrenDirect(Bookmarks.FIXED_ROOT_ID);
105 Logger.info(LOG_TAG, "Roots: " + roots);
106 assertTrue(roots.contains(Bookmarks.PINNED_FOLDER_GUID));
107
108 final ArrayList<String> pinned = fetchChildrenDirect(Bookmarks.FIXED_PINNED_LIST_ID);
109 Logger.info(LOG_TAG, "Pinned: " + pinned);
110 assertTrue(pinned.contains("dapinneditem"));
111
112 // … but not when we fetch.
113 final ArrayList<String> guids = fetchGUIDs(repo);
114 assertFalse(guids.contains(Bookmarks.PINNED_FOLDER_GUID));
115 assertFalse(guids.contains("dapinneditem"));
116 }
117
118 /**
119 * Trivial test that reading list records will be skipped if present in the DB.
120 */
121 public void testReadingListIsNotRetrieved() {
122 final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
123
124 // Ensure that it exists.
125 setUpFennecReadingListRecord();
126
127 // It's there in the DB…
128 final ArrayList<String> roots = fetchChildrenDirect(BrowserContract.Bookmarks.FIXED_ROOT_ID);
129 Logger.info(LOG_TAG, "Roots: " + roots);
130 assertTrue(roots.contains(Bookmarks.READING_LIST_FOLDER_GUID));
131
132 // … but not when we fetch.
133 assertFalse(fetchGUIDs(repo).contains(Bookmarks.READING_LIST_FOLDER_GUID));
134 }
135
136 public void testRetrieveFolderHasAccurateChildren() {
137 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
138
139 final long now = System.currentTimeMillis();
140
141 final String folderGUID = "eaaaaaaaafff";
142 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now - 5, false);
143 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false);
144 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now - 3, false);
145 BookmarkRecord bookmarkC = new BookmarkRecord("aaaaaaaaaccc", "bookmarks", now - 2, false);
146
147 folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC);
148 folder.sortIndex = 150;
149 folder.title = "Test items";
150 folder.parentID = "toolbar";
151 folder.parentName = "Bookmarks Toolbar";
152 folder.type = "folder";
153
154 bookmarkA.parentID = folderGUID;
155 bookmarkA.bookmarkURI = "http://example.com/A";
156 bookmarkA.title = "Title A";
157 bookmarkA.type = "bookmark";
158
159 bookmarkB.parentID = folderGUID;
160 bookmarkB.bookmarkURI = "http://example.com/B";
161 bookmarkB.title = "Title B";
162 bookmarkB.type = "bookmark";
163
164 bookmarkC.parentID = folderGUID;
165 bookmarkC.bookmarkURI = "http://example.com/C";
166 bookmarkC.title = "Title C";
167 bookmarkC.type = "bookmark";
168
169 BookmarkRecord[] folderOnly = new BookmarkRecord[1];
170 BookmarkRecord[] children = new BookmarkRecord[3];
171
172 folderOnly[0] = folder;
173
174 children[0] = bookmarkA;
175 children[1] = bookmarkB;
176 children[2] = bookmarkC;
177
178 wipe();
179 Logger.debug(getName(), "Storing just folder...");
180 storeRecordsInSession(repo, folderOnly, null);
181
182 // We don't have any children, despite our insistence upon storing.
183 assertChildrenAreOrdered(repo, folderGUID, new Record[] {});
184
185 // Now store the children.
186 Logger.debug(getName(), "Storing children...");
187 storeRecordsInSession(repo, children, null);
188
189 // Now we have children, but their order is not determined, because
190 // they were stored out-of-session with the original folder.
191 assertChildrenAreUnordered(repo, folderGUID, children);
192
193 // Now if we store the folder record again, they'll be put in the
194 // right place.
195 folder.lastModified++;
196 Logger.debug(getName(), "Storing just folder again...");
197 storeRecordsInSession(repo, folderOnly, null);
198 Logger.debug(getName(), "Fetching children yet again...");
199 assertChildrenAreOrdered(repo, folderGUID, children);
200
201 // Now let's see what happens when we see records in the same session.
202 BookmarkRecord[] parentMixed = new BookmarkRecord[4];
203 BookmarkRecord[] parentFirst = new BookmarkRecord[4];
204 BookmarkRecord[] parentLast = new BookmarkRecord[4];
205
206 // None of our records have a position set.
207 assertTrue(bookmarkA.androidPosition <= 0);
208 assertTrue(bookmarkB.androidPosition <= 0);
209 assertTrue(bookmarkC.androidPosition <= 0);
210
211 parentMixed[1] = folder;
212 parentMixed[0] = bookmarkA;
213 parentMixed[2] = bookmarkC;
214 parentMixed[3] = bookmarkB;
215
216 parentFirst[0] = folder;
217 parentFirst[1] = bookmarkC;
218 parentFirst[2] = bookmarkA;
219 parentFirst[3] = bookmarkB;
220
221 parentLast[3] = folder;
222 parentLast[0] = bookmarkB;
223 parentLast[1] = bookmarkA;
224 parentLast[2] = bookmarkC;
225
226 wipe();
227 storeRecordsInSession(repo, parentMixed, null);
228 assertChildrenAreOrdered(repo, folderGUID, children);
229
230 wipe();
231 storeRecordsInSession(repo, parentFirst, null);
232 assertChildrenAreOrdered(repo, folderGUID, children);
233
234 wipe();
235 storeRecordsInSession(repo, parentLast, null);
236 assertChildrenAreOrdered(repo, folderGUID, children);
237
238 // Ensure that records are ordered even if we re-process the folder.
239 wipe();
240 storeRecordsInSession(repo, parentLast, null);
241 folder.lastModified++;
242 storeRecordsInSession(repo, folderOnly, null);
243 assertChildrenAreOrdered(repo, folderGUID, children);
244 }
245
246 public void testMergeFoldersPreservesSaneOrder() {
247 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
248
249 final long now = System.currentTimeMillis();
250 final String folderGUID = "mobile";
251
252 wipe();
253 final long mobile = setUpFennecMobileRecord();
254
255 // No children.
256 assertChildrenAreUnordered(repo, folderGUID, new Record[] {});
257
258 // Add some, as Fennec would.
259 fennecAddBookmark("Bookmark One", "http://example.com/fennec/One");
260 fennecAddBookmark("Bookmark Two", "http://example.com/fennec/Two");
261
262 Logger.debug(getName(), "Fetching children...");
263 JSONArray folderChildren = fetchChildrenForGUID(repo, folderGUID);
264
265 assertTrue(folderChildren != null);
266 Logger.debug(getName(), "Children are " + folderChildren.toJSONString());
267 assertEquals(2, folderChildren.size());
268 String guidOne = (String) folderChildren.get(0);
269 String guidTwo = (String) folderChildren.get(1);
270
271 // Make sure positions were saved.
272 assertChildrenAreDirect(mobile, new String[] {
273 guidOne,
274 guidTwo
275 });
276
277 // Add some through Sync.
278 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now, false);
279 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now, false);
280 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now, false);
281
282 folder.children = childrenFromRecords(bookmarkA, bookmarkB);
283 folder.sortIndex = 150;
284 folder.title = "Mobile Bookmarks";
285 folder.parentID = "places";
286 folder.parentName = "";
287 folder.type = "folder";
288
289 bookmarkA.parentID = folderGUID;
290 bookmarkA.parentName = "Mobile Bookmarks"; // Using this title exercises Bug 748898.
291 bookmarkA.bookmarkURI = "http://example.com/A";
292 bookmarkA.title = "Title A";
293 bookmarkA.type = "bookmark";
294
295 bookmarkB.parentID = folderGUID;
296 bookmarkB.parentName = "mobile";
297 bookmarkB.bookmarkURI = "http://example.com/B";
298 bookmarkB.title = "Title B";
299 bookmarkB.type = "bookmark";
300
301 BookmarkRecord[] parentMixed = new BookmarkRecord[3];
302 parentMixed[0] = bookmarkA;
303 parentMixed[1] = folder;
304 parentMixed[2] = bookmarkB;
305
306 storeRecordsInSession(repo, parentMixed, null);
307
308 BookmarkRecord expectedOne = new BookmarkRecord(guidOne, "bookmarks", now - 10, false);
309 BookmarkRecord expectedTwo = new BookmarkRecord(guidTwo, "bookmarks", now - 10, false);
310
311 // We want the server to win in this case, and otherwise to preserve order.
312 // TODO
313 assertChildrenAreOrdered(repo, folderGUID, new Record[] {
314 bookmarkA,
315 bookmarkB,
316 expectedOne,
317 expectedTwo
318 });
319
320 // Furthermore, the children of that folder should be correct in the DB.
321 ContentResolver cr = getApplicationContext().getContentResolver();
322 final long folderId = fennecGetFolderId(cr, folderGUID);
323 Logger.debug(getName(), "Folder " + folderGUID + " => " + folderId);
324
325 assertChildrenAreDirect(folderId, new String[] {
326 bookmarkA.guid,
327 bookmarkB.guid,
328 expectedOne.guid,
329 expectedTwo.guid
330 });
331 }
332
333 /**
334 * Apply a folder record whose children array is already accurately
335 * stored in the database. Verify that the parent folder is not flagged
336 * for reupload (i.e., that its modified time is *ahem* unmodified).
337 */
338 public void testNoReorderingMeansNoReupload() {
339 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
340
341 final long now = System.currentTimeMillis();
342
343 final String folderGUID = "eaaaaaaaafff";
344 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false);
345 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false);
346 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false);
347
348 folder.children = childrenFromRecords(bookmarkA, bookmarkB);
349 folder.sortIndex = 150;
350 folder.title = "Test items";
351 folder.parentID = "toolbar";
352 folder.parentName = "Bookmarks Toolbar";
353 folder.type = "folder";
354
355 bookmarkA.parentID = folderGUID;
356 bookmarkA.bookmarkURI = "http://example.com/A";
357 bookmarkA.title = "Title A";
358 bookmarkA.type = "bookmark";
359
360 bookmarkB.parentID = folderGUID;
361 bookmarkB.bookmarkURI = "http://example.com/B";
362 bookmarkB.title = "Title B";
363 bookmarkB.type = "bookmark";
364
365 BookmarkRecord[] abf = new BookmarkRecord[3];
366 BookmarkRecord[] justFolder = new BookmarkRecord[1];
367
368 abf[0] = bookmarkA;
369 abf[1] = bookmarkB;
370 abf[2] = folder;
371
372 justFolder[0] = folder;
373
374 final String[] abGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid };
375 final Record[] abRecords = new Record[] { bookmarkA, bookmarkB };
376 final String[] baGUIDs = new String[] { bookmarkB.guid, bookmarkA.guid };
377 final Record[] baRecords = new Record[] { bookmarkB, bookmarkA };
378
379 wipe();
380 Logger.debug(getName(), "Storing A, B, folder...");
381 storeRecordsInSession(repo, abf, null);
382
383 ContentResolver cr = getApplicationContext().getContentResolver();
384 final long folderID = fennecGetFolderId(cr, folderGUID);
385 assertChildrenAreOrdered(repo, folderGUID, abRecords);
386 assertChildrenAreDirect(folderID, abGUIDs);
387
388 // To ensure an interval.
389 try {
390 Thread.sleep(100);
391 } catch (InterruptedException e) {
392 }
393
394 // Store the same folder record again, and check the tracking.
395 // Because the folder array didn't change,
396 // the item is still tracked to not be uploaded.
397 folder.lastModified = System.currentTimeMillis() + 1;
398 HashSet<String> tracked = new HashSet<String>();
399 storeRecordsInSession(repo, justFolder, tracked);
400 assertChildrenAreOrdered(repo, folderGUID, abRecords);
401 assertChildrenAreDirect(folderID, abGUIDs);
402
403 assertTrue(tracked.contains(folderGUID));
404
405 // Store again, but with a different order.
406 tracked = new HashSet<String>();
407 folder.children = childrenFromRecords(bookmarkB, bookmarkA);
408 folder.lastModified = System.currentTimeMillis() + 1;
409 storeRecordsInSession(repo, justFolder, tracked);
410 assertChildrenAreOrdered(repo, folderGUID, baRecords);
411 assertChildrenAreDirect(folderID, baGUIDs);
412
413 // Now it's going to be reuploaded.
414 assertFalse(tracked.contains(folderGUID));
415 }
416
417 /**
418 * Exercise the deletion of folders when their children have not been
419 * marked as deleted. In a database with constraints, this would fail
420 * if we simply deleted the records, so we move them first.
421 */
422 public void testFolderDeletionOrphansChildren() {
423 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
424
425 long now = System.currentTimeMillis();
426
427 // Add a folder and four children.
428 final String folderGUID = "eaaaaaaaafff";
429 BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false);
430 BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false);
431 BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false);
432 BookmarkRecord bookmarkC = new BookmarkRecord("daaaaaaaaccc", "bookmarks", now -7, false);
433 BookmarkRecord bookmarkD = new BookmarkRecord("baaaaaaaaddd", "bookmarks", now -4, false);
434
435 folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC, bookmarkD);
436 folder.sortIndex = 150;
437 folder.title = "Test items";
438 folder.parentID = "toolbar";
439 folder.parentName = "Bookmarks Toolbar";
440 folder.type = "folder";
441
442 bookmarkA.parentID = folderGUID;
443 bookmarkA.bookmarkURI = "http://example.com/A";
444 bookmarkA.title = "Title A";
445 bookmarkA.type = "bookmark";
446
447 bookmarkB.parentID = folderGUID;
448 bookmarkB.bookmarkURI = "http://example.com/B";
449 bookmarkB.title = "Title B";
450 bookmarkB.type = "bookmark";
451
452 bookmarkC.parentID = folderGUID;
453 bookmarkC.bookmarkURI = "http://example.com/C";
454 bookmarkC.title = "Title C";
455 bookmarkC.type = "bookmark";
456
457 bookmarkD.parentID = folderGUID;
458 bookmarkD.bookmarkURI = "http://example.com/D";
459 bookmarkD.title = "Title D";
460 bookmarkD.type = "bookmark";
461
462 BookmarkRecord[] abfcd = new BookmarkRecord[5];
463 BookmarkRecord[] justFolder = new BookmarkRecord[1];
464 abfcd[0] = bookmarkA;
465 abfcd[1] = bookmarkB;
466 abfcd[2] = folder;
467 abfcd[3] = bookmarkC;
468 abfcd[4] = bookmarkD;
469
470 justFolder[0] = folder;
471
472 final String[] abcdGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid, bookmarkC.guid, bookmarkD.guid };
473 final Record[] abcdRecords = new Record[] { bookmarkA, bookmarkB, bookmarkC, bookmarkD };
474
475 wipe();
476 Logger.debug(getName(), "Storing A, B, folder, C, D...");
477 storeRecordsInSession(repo, abfcd, null);
478
479 // Verify that it worked.
480 ContentResolver cr = getApplicationContext().getContentResolver();
481 final long folderID = fennecGetFolderId(cr, folderGUID);
482 assertChildrenAreOrdered(repo, folderGUID, abcdRecords);
483 assertChildrenAreDirect(folderID, abcdGUIDs);
484
485 now = System.currentTimeMillis();
486
487 // Add one child to unsorted bookmarks.
488 BookmarkRecord unsortedA = new BookmarkRecord("yiamunsorted", "bookmarks", now, false);
489 unsortedA.parentID = "unfiled";
490 unsortedA.title = "Unsorted A";
491 unsortedA.type = "bookmark";
492 unsortedA.androidPosition = 0;
493
494 BookmarkRecord[] ua = new BookmarkRecord[1];
495 ua[0] = unsortedA;
496
497 storeRecordsInSession(repo, ua, null);
498
499 // Ensure that the database is in this state.
500 assertChildrenAreOrdered(repo, "unfiled", ua);
501
502 // Delete the second child, the folder, and then the third child.
503 bookmarkB.bookmarkURI = bookmarkC.bookmarkURI = folder.bookmarkURI = null;
504 bookmarkB.deleted = bookmarkC.deleted = folder.deleted = true;
505 bookmarkB.title = bookmarkC.title = folder.title = null;
506
507 // Nulling the type of folder is very important: it verifies
508 // that the session can behave correctly according to local type.
509 bookmarkB.type = bookmarkC.type = folder.type = null;
510
511 bookmarkB.lastModified = bookmarkC.lastModified = folder.lastModified = now = System.currentTimeMillis();
512
513 BookmarkRecord[] deletions = new BookmarkRecord[] { bookmarkB, folder, bookmarkC };
514 storeRecordsInSession(repo, deletions, null);
515
516 // Verify that the unsorted bookmarks folder contains its child and the
517 // first and fourth children of the now-deleted folder.
518 // Also verify that the folder is gone.
519 long unsortedID = fennecGetFolderId(cr, "unfiled");
520 long toolbarID = fennecGetFolderId(cr, "toolbar");
521 String[] expected = new String[] { unsortedA.guid, bookmarkA.guid, bookmarkD.guid };
522
523 // This will trigger positioning.
524 assertChildrenAreUnordered(repo, "unfiled", new Record[] { unsortedA, bookmarkA, bookmarkD });
525 assertChildrenAreDirect(unsortedID, expected);
526 assertChildrenAreDirect(toolbarID, new String[] {});
527 }
528
529 /**
530 * A test where we expect to replace a local folder with a new folder (with a
531 * new GUID), whilst adding children to it. Verifies that replace and insert
532 * co-operate.
533 */
534 public void testInsertAndReplaceGuid() {
535 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
536 wipe();
537
538 BookmarkRecord folder1 = BookmarkHelpers.createFolder1();
539 BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1
540 BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2
541 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1
542 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1
543 BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2
544 BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3
545
546 BookmarkRecord[] records = new BookmarkRecord[] {
547 folder1, folder2, folder3,
548 bmk1, bmk4
549 };
550 storeRecordsInSession(repo, records, null);
551
552 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 });
553 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 });
554 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 });
555
556 // Replace folder3 with a record with a new GUID, and add bmk4 as folder3's child.
557 final long now = System.currentTimeMillis();
558 folder3.guid = Utils.generateGuid();
559 folder3.lastModified = now;
560 bmk4.title = bmk4.title + "/NEW";
561 bmk4.parentID = folder3.guid; // Incoming child knows its parent.
562 bmk4.parentName = folder3.title;
563 bmk4.lastModified = now;
564
565 // Order of store should not matter.
566 ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>();
567 changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(bmk4); changedRecords.add(folder3);
568 Collections.shuffle(changedRecords);
569 storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null);
570
571 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 });
572 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 });
573 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 });
574
575 assertNotNull(fetchGUID(repo, folder3.guid));
576 assertEquals(bmk4.title, fetchGUID(repo, bmk4.guid).title);
577 }
578
579 /**
580 * A test where we expect to replace a local folder with a new folder (with a
581 * new title but the same GUID), whilst adding children to it. Verifies that
582 * replace and insert co-operate.
583 */
584 public void testInsertAndReplaceTitle() {
585 AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository();
586 wipe();
587
588 BookmarkRecord folder1 = BookmarkHelpers.createFolder1();
589 BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1
590 BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2
591 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1
592 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1
593 BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2
594 BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3
595
596 BookmarkRecord[] records = new BookmarkRecord[] {
597 folder1, folder2, folder3,
598 bmk1, bmk4
599 };
600 storeRecordsInSession(repo, records, null);
601
602 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 });
603 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 });
604 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 });
605
606 // Rename folder1, and add bmk2 as folder1's child.
607 final long now = System.currentTimeMillis();
608 folder1.title = folder1.title + "/NEW";
609 folder1.lastModified = now;
610 bmk2.title = bmk2.title + "/NEW";
611 bmk2.parentID = folder1.guid; // Incoming child knows its parent.
612 bmk2.parentName = folder1.title;
613 bmk2.lastModified = now;
614
615 // Order of store should not matter.
616 ArrayList<BookmarkRecord> changedRecords = new ArrayList<BookmarkRecord>();
617 changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(folder1);
618 Collections.shuffle(changedRecords);
619 storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null);
620
621 assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 });
622 assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 });
623 assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 });
624
625 assertEquals(folder1.title, fetchGUID(repo, folder1.guid).title);
626 assertEquals(bmk2.title, fetchGUID(repo, bmk2.guid).title);
627 }
628
629 /**
630 * Create and begin a new session, handing control to the delegate when started.
631 * Returns when the delegate has notified.
632 */
633 public void inBegunSession(final AndroidBrowserBookmarksRepository repo,
634 final RepositorySessionBeginDelegate beginDelegate) {
635 Runnable go = new Runnable() {
636 @Override
637 public void run() {
638 RepositorySessionCreationDelegate delegate = new SimpleSuccessCreationDelegate() {
639 @Override
640 public void onSessionCreated(final RepositorySession session) {
641 try {
642 session.begin(beginDelegate);
643 } catch (InvalidSessionTransitionException e) {
644 performNotify(e);
645 }
646 }
647 };
648 repo.createSession(delegate, getApplicationContext());
649 }
650 };
651 performWait(go);
652 }
653
654 /**
655 * Finish the provided session, notifying on success.
656 *
657 * @param session
658 */
659 public void finishAndNotify(final RepositorySession session) {
660 try {
661 session.finish(new SimpleSuccessFinishDelegate() {
662 @Override
663 public void onFinishSucceeded(RepositorySession session,
664 RepositorySessionBundle bundle) {
665 performNotify();
666 }
667 });
668 } catch (InactiveSessionException e) {
669 performNotify(e);
670 }
671 }
672
673 /**
674 * Simple helper class for fetching all records.
675 * The fetched records' GUIDs are stored in `fetchedGUIDs`.
676 */
677 public class SimpleFetchAllBeginDelegate extends SimpleSuccessBeginDelegate {
678 public final ArrayList<String> fetchedGUIDs = new ArrayList<String>();
679
680 @Override
681 public void onBeginSucceeded(final RepositorySession session) {
682 RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
683
684 @Override
685 public void onFetchedRecord(Record record) {
686 fetchedGUIDs.add(record.guid);
687 }
688
689 @Override
690 public void onFetchCompleted(long end) {
691 finishAndNotify(session);
692 }
693 };
694 session.fetchSince(0, fetchDelegate);
695 }
696 }
697
698 /**
699 * Simple helper class for fetching a single record by GUID.
700 * The fetched record is stored in `fetchedRecord`.
701 */
702 public class SimpleFetchOneBeginDelegate extends SimpleSuccessBeginDelegate {
703 public final String guid;
704 public Record fetchedRecord = null;
705
706 public SimpleFetchOneBeginDelegate(String guid) {
707 this.guid = guid;
708 }
709
710 @Override
711 public void onBeginSucceeded(final RepositorySession session) {
712 RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() {
713
714 @Override
715 public void onFetchedRecord(Record record) {
716 fetchedRecord = record;
717 }
718
719 @Override
720 public void onFetchCompleted(long end) {
721 finishAndNotify(session);
722 }
723 };
724 try {
725 session.fetch(new String[] { guid }, fetchDelegate);
726 } catch (InactiveSessionException e) {
727 performNotify("Session is inactive.", e);
728 }
729 }
730 }
731
732 /**
733 * Create a new session for the given repository, storing each record
734 * from the provided array. Notifies on failure or success.
735 *
736 * Optionally populates a provided Collection with tracked items.
737 * @param repo
738 * @param records
739 * @param tracked
740 */
741 public void storeRecordsInSession(AndroidBrowserBookmarksRepository repo,
742 final BookmarkRecord[] records,
743 final Collection<String> tracked) {
744 SimpleSuccessBeginDelegate beginDelegate = new SimpleSuccessBeginDelegate() {
745 @Override
746 public void onBeginSucceeded(final RepositorySession session) {
747 RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() {
748
749 @Override
750 public void onStoreCompleted(final long storeEnd) {
751 // Pass back whatever we tracked.
752 if (tracked != null) {
753 Iterator<String> iter = session.getTrackedRecordIDs();
754 while (iter.hasNext()) {
755 tracked.add(iter.next());
756 }
757 }
758 finishAndNotify(session);
759 }
760
761 @Override
762 public void onRecordStoreSucceeded(String guid) {
763 }
764 };
765 session.setStoreDelegate(storeDelegate);
766 for (BookmarkRecord record : records) {
767 try {
768 session.store(record);
769 } catch (NoStoreDelegateException e) {
770 // Never happens.
771 }
772 }
773 session.storeDone();
774 }
775 };
776 inBegunSession(repo, beginDelegate);
777 }
778
779 public ArrayList<String> fetchGUIDs(AndroidBrowserBookmarksRepository repo) {
780 SimpleFetchAllBeginDelegate beginDelegate = new SimpleFetchAllBeginDelegate();
781 inBegunSession(repo, beginDelegate);
782 return beginDelegate.fetchedGUIDs;
783 }
784
785 public BookmarkRecord fetchGUID(AndroidBrowserBookmarksRepository repo,
786 final String guid) {
787 Logger.info(LOG_TAG, "Fetching for " + guid);
788 SimpleFetchOneBeginDelegate beginDelegate = new SimpleFetchOneBeginDelegate(guid);
789 inBegunSession(repo, beginDelegate);
790 Logger.info(LOG_TAG, "Fetched " + beginDelegate.fetchedRecord);
791 assertTrue(beginDelegate.fetchedRecord != null);
792 return (BookmarkRecord) beginDelegate.fetchedRecord;
793 }
794
795 public JSONArray fetchChildrenForGUID(AndroidBrowserBookmarksRepository repo,
796 final String guid) {
797 return fetchGUID(repo, guid).children;
798 }
799
800 @SuppressWarnings("unchecked")
801 protected static JSONArray childrenFromRecords(BookmarkRecord... records) {
802 JSONArray children = new JSONArray();
803 for (BookmarkRecord record : records) {
804 children.add(record.guid);
805 }
806 return children;
807 }
808
809
810 protected void updateRow(ContentValues values) {
811 Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI;
812 final String where = BrowserContract.Bookmarks.GUID + " = ?";
813 final String[] args = new String[] { values.getAsString(BrowserContract.Bookmarks.GUID) };
814 getApplicationContext().getContentResolver().update(uri, values, where, args);
815 }
816
817 protected Uri insertRow(ContentValues values) {
818 Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI;
819 return getApplicationContext().getContentResolver().insert(uri, values);
820 }
821
822 protected static ContentValues specialFolder() {
823 ContentValues values = new ContentValues();
824
825 final long now = System.currentTimeMillis();
826 values.put(Bookmarks.DATE_CREATED, now);
827 values.put(Bookmarks.DATE_MODIFIED, now);
828 values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER);
829
830 return values;
831 }
832
833 protected static ContentValues fennecMobileRecordWithoutTitle() {
834 ContentValues values = specialFolder();
835 values.put(BrowserContract.SyncColumns.GUID, "mobile");
836 values.putNull(BrowserContract.Bookmarks.TITLE);
837
838 return values;
839 }
840
841 protected ContentValues fennecPinnedItemsRecord() {
842 final ContentValues values = specialFolder();
843 final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_pinned);
844
845 values.put(BrowserContract.SyncColumns.GUID, Bookmarks.PINNED_FOLDER_GUID);
846 values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID);
847 values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
848 values.put(Bookmarks.TITLE, title);
849 return values;
850 }
851
852 protected static ContentValues fennecPinnedChildItemRecord() {
853 ContentValues values = new ContentValues();
854
855 final long now = System.currentTimeMillis();
856
857 values.put(BrowserContract.SyncColumns.GUID, "dapinneditem");
858 values.put(Bookmarks.DATE_CREATED, now);
859 values.put(Bookmarks.DATE_MODIFIED, now);
860 values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_BOOKMARK);
861 values.put(Bookmarks.URL, "user-entered:foobar");
862 values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID);
863 values.put(Bookmarks.TITLE, "Foobar");
864 return values;
865 }
866
867 protected ContentValues fennecReadingListRecord() {
868 final ContentValues values = specialFolder();
869 final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_reading_list);
870
871 values.put(BrowserContract.SyncColumns.GUID, Bookmarks.READING_LIST_FOLDER_GUID);
872 values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID);
873 values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID);
874 values.put(Bookmarks.TITLE, title);
875 return values;
876 }
877
878 protected long setUpFennecMobileRecordWithoutTitle() {
879 ContentResolver cr = getApplicationContext().getContentResolver();
880 ContentValues values = fennecMobileRecordWithoutTitle();
881 updateRow(values);
882 return fennecGetMobileBookmarksFolderId(cr);
883 }
884
885 protected long setUpFennecMobileRecord() {
886 ContentResolver cr = getApplicationContext().getContentResolver();
887 ContentValues values = fennecMobileRecordWithoutTitle();
888 values.put(BrowserContract.Bookmarks.PARENT, BrowserContract.Bookmarks.FIXED_ROOT_ID);
889 String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_mobile);
890 values.put(BrowserContract.Bookmarks.TITLE, title);
891 updateRow(values);
892 return fennecGetMobileBookmarksFolderId(cr);
893 }
894
895 protected void setUpFennecPinnedItemsRecord() {
896 insertRow(fennecPinnedItemsRecord());
897 insertRow(fennecPinnedChildItemRecord());
898 }
899
900 protected void setUpFennecReadingListRecord() {
901 insertRow(fennecReadingListRecord());
902 }
903
904 //
905 // Fennec fake layer.
906 //
907 private Uri appendProfile(Uri uri) {
908 final String defaultProfile = "default"; // Fennec constant removed in Bug 715307.
909 return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, defaultProfile).build();
910 }
911
912 private long fennecGetFolderId(ContentResolver cr, String guid) {
913 Cursor c = null;
914 try {
915 c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI),
916 new String[] { BrowserContract.Bookmarks._ID },
917 BrowserContract.Bookmarks.GUID + " = ?",
918 new String[] { guid },
919 null);
920
921 if (c.moveToFirst()) {
922 return c.getLong(c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID));
923 }
924 return -1;
925 } finally {
926 if (c != null) {
927 c.close();
928 }
929 }
930 }
931
932 private long fennecGetMobileBookmarksFolderId(ContentResolver cr) {
933 return fennecGetFolderId(cr, BrowserContract.Bookmarks.MOBILE_FOLDER_GUID);
934 }
935
936 public void fennecAddBookmark(String title, String uri) {
937 ContentResolver cr = getApplicationContext().getContentResolver();
938
939 long folderId = fennecGetMobileBookmarksFolderId(cr);
940 if (folderId < 0) {
941 return;
942 }
943
944 ContentValues values = new ContentValues();
945 values.put(BrowserContract.Bookmarks.TITLE, title);
946 values.put(BrowserContract.Bookmarks.URL, uri);
947 values.put(BrowserContract.Bookmarks.PARENT, folderId);
948
949 // Restore deleted record if possible
950 values.put(BrowserContract.Bookmarks.IS_DELETED, 0);
951
952 Logger.debug(getName(), "Adding bookmark " + title + ", " + uri + " in " + folderId);
953 int updated = cr.update(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI),
954 values,
955 BrowserContract.Bookmarks.URL + " = ?",
956 new String[] { uri });
957
958 if (updated == 0) {
959 Uri insert = cr.insert(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), values);
960 long idFromUri = ContentUris.parseId(insert);
961 Logger.debug(getName(), "Inserted " + uri + " as " + idFromUri);
962 Logger.debug(getName(), "Position is " + getPosition(idFromUri));
963 }
964 }
965
966 private long getPosition(long idFromUri) {
967 ContentResolver cr = getApplicationContext().getContentResolver();
968 Cursor c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI),
969 new String[] { BrowserContract.Bookmarks.POSITION },
970 BrowserContract.Bookmarks._ID + " = ?",
971 new String[] { String.valueOf(idFromUri) },
972 null);
973 if (!c.moveToFirst()) {
974 return -2;
975 }
976 return c.getLong(0);
977 }
978
979 protected AndroidBrowserBookmarksDataAccessor dataAccessor = null;
980 protected AndroidBrowserBookmarksDataAccessor getDataAccessor() {
981 if (dataAccessor == null) {
982 dataAccessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext());
983 }
984 return dataAccessor;
985 }
986
987 protected void wipe() {
988 Logger.debug(getName(), "Wiping.");
989 getDataAccessor().wipe();
990 }
991
992 protected void assertChildrenAreOrdered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) {
993 Logger.debug(getName(), "Fetching children...");
994 JSONArray folderChildren = fetchChildrenForGUID(repo, guid);
995
996 assertTrue(folderChildren != null);
997 Logger.debug(getName(), "Children are " + folderChildren.toJSONString());
998 assertEquals(expected.length, folderChildren.size());
999 for (int i = 0; i < expected.length; ++i) {
1000 assertEquals(expected[i].guid, ((String) folderChildren.get(i)));
1001 }
1002 }
1003
1004 protected void assertChildrenAreUnordered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) {
1005 Logger.debug(getName(), "Fetching children...");
1006 JSONArray folderChildren = fetchChildrenForGUID(repo, guid);
1007
1008 assertTrue(folderChildren != null);
1009 Logger.debug(getName(), "Children are " + folderChildren.toJSONString());
1010 assertEquals(expected.length, folderChildren.size());
1011 for (Record record : expected) {
1012 folderChildren.contains(record.guid);
1013 }
1014 }
1015
1016 /**
1017 * Return a sequence of children GUIDs for the provided folder ID.
1018 */
1019 protected ArrayList<String> fetchChildrenDirect(long id) {
1020 Logger.debug(getName(), "Fetching children directly from DB...");
1021 final ArrayList<String> out = new ArrayList<String>();
1022 final AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext());
1023 Cursor cur = null;
1024 try {
1025 cur = accessor.getChildren(id);
1026 } catch (NullCursorException e) {
1027 fail("Got null cursor.");
1028 }
1029 try {
1030 if (!cur.moveToFirst()) {
1031 return out;
1032 }
1033 final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID);
1034 while (!cur.isAfterLast()) {
1035 out.add(cur.getString(guidCol));
1036 cur.moveToNext();
1037 }
1038 } finally {
1039 cur.close();
1040 }
1041 return out;
1042 }
1043
1044 /**
1045 * Assert that the children of the provided ID are correct and positioned in the database.
1046 * @param id
1047 * @param guids
1048 */
1049 protected void assertChildrenAreDirect(long id, String[] guids) {
1050 Logger.debug(getName(), "Fetching children directly from DB...");
1051 AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext());
1052 Cursor cur = null;
1053 try {
1054 cur = accessor.getChildren(id);
1055 } catch (NullCursorException e) {
1056 fail("Got null cursor.");
1057 }
1058 try {
1059 if (guids == null || guids.length == 0) {
1060 assertFalse(cur.moveToFirst());
1061 return;
1062 }
1063
1064 assertTrue(cur.moveToFirst());
1065 int i = 0;
1066 final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID);
1067 final int posCol = cur.getColumnIndex(BrowserContract.Bookmarks.POSITION);
1068 while (!cur.isAfterLast()) {
1069 assertTrue(i < guids.length);
1070 final String guid = cur.getString(guidCol);
1071 final int pos = cur.getInt(posCol);
1072 Logger.debug(getName(), "Fetched child: " + guid + " has position " + pos);
1073 assertEquals(guids[i], guid);
1074 assertEquals(i, pos);
1075
1076 ++i;
1077 cur.moveToNext();
1078 }
1079 assertEquals(guids.length, i);
1080 } finally {
1081 cur.close();
1082 }
1083 }
1084 }
1085
1086 /**
1087 TODO
1088
1089 Test for storing a record that will reconcile to mobile; postcondition is
1090 that there's still a directory called mobile that includes all the items that
1091 it used to.
1092
1093 mobile folder created without title.
1094 Unsorted put in mobile???
1095 Tests for children retrieval
1096 Tests for children merge
1097 Tests for modify retrieve parent when child added, removed, reordered (oh, reorder is hard! Any change, then.)
1098 Safety mode?
1099 Test storing folder first, contents first.
1100 Store folder in next session. Verify order recovery.
1101
1102
1103 */

mercurial