michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: package org.mozilla.gecko.background.db; michael@0: michael@0: import java.util.ArrayList; michael@0: import java.util.Collection; michael@0: import java.util.Collections; michael@0: import java.util.HashSet; michael@0: import java.util.Iterator; michael@0: michael@0: import org.json.simple.JSONArray; michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.background.common.log.Logger; michael@0: import org.mozilla.gecko.background.helpers.AndroidSyncTestCase; michael@0: import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers; michael@0: import org.mozilla.gecko.background.sync.helpers.SimpleSuccessBeginDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.SimpleSuccessCreationDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFetchDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.SimpleSuccessFinishDelegate; michael@0: import org.mozilla.gecko.background.sync.helpers.SimpleSuccessStoreDelegate; michael@0: import org.mozilla.gecko.db.BrowserContract; michael@0: import org.mozilla.gecko.db.BrowserContract.Bookmarks; michael@0: import org.mozilla.gecko.sync.Utils; michael@0: import org.mozilla.gecko.sync.repositories.InactiveSessionException; michael@0: import org.mozilla.gecko.sync.repositories.InvalidSessionTransitionException; michael@0: import org.mozilla.gecko.sync.repositories.NoStoreDelegateException; michael@0: import org.mozilla.gecko.sync.repositories.NullCursorException; michael@0: import org.mozilla.gecko.sync.repositories.RepositorySession; michael@0: import org.mozilla.gecko.sync.repositories.RepositorySessionBundle; michael@0: import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor; michael@0: import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository; michael@0: import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession; michael@0: import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionBeginDelegate; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionFetchRecordsDelegate; michael@0: import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionStoreDelegate; michael@0: import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord; michael@0: import org.mozilla.gecko.sync.repositories.domain.Record; michael@0: michael@0: import android.content.ContentResolver; michael@0: import android.content.ContentUris; michael@0: import android.content.ContentValues; michael@0: import android.database.Cursor; michael@0: import android.net.Uri; michael@0: michael@0: public class TestBookmarks extends AndroidSyncTestCase { michael@0: michael@0: protected static final String LOG_TAG = "BookmarksTest"; michael@0: michael@0: /** michael@0: * Trivial test that forbidden records (reading list prior to Bug 762109, pinned items…) michael@0: * will be ignored if processed. michael@0: */ michael@0: public void testForbiddenItemsAreIgnored() { michael@0: final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: final long now = System.currentTimeMillis(); michael@0: final String bookmarksCollection = "bookmarks"; michael@0: michael@0: final BookmarkRecord toRead = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); michael@0: final BookmarkRecord pinned = new BookmarkRecord("pinpinpinpin", "bookmarks", now - 1, false); michael@0: final BookmarkRecord normal = new BookmarkRecord("baaaaaaaaaaa", "bookmarks", now - 2, false); michael@0: michael@0: final BookmarkRecord readingList = new BookmarkRecord(Bookmarks.READING_LIST_FOLDER_GUID, michael@0: bookmarksCollection, now - 3, false); michael@0: final BookmarkRecord pinnedItems = new BookmarkRecord(Bookmarks.PINNED_FOLDER_GUID, michael@0: bookmarksCollection, now - 4, false); michael@0: michael@0: toRead.type = normal.type = pinned.type = "bookmark"; michael@0: readingList.type = "folder"; michael@0: pinnedItems.type = "folder"; michael@0: michael@0: toRead.parentID = Bookmarks.READING_LIST_FOLDER_GUID; michael@0: pinned.parentID = Bookmarks.PINNED_FOLDER_GUID; michael@0: normal.parentID = Bookmarks.TOOLBAR_FOLDER_GUID; michael@0: michael@0: readingList.parentID = Bookmarks.PLACES_FOLDER_GUID; michael@0: pinnedItems.parentID = Bookmarks.PLACES_FOLDER_GUID; michael@0: michael@0: inBegunSession(repo, new SimpleSuccessBeginDelegate() { michael@0: @Override michael@0: public void onBeginSucceeded(RepositorySession session) { michael@0: assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(toRead)); michael@0: assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinned)); michael@0: assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(readingList)); michael@0: assertTrue(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(pinnedItems)); michael@0: assertFalse(((AndroidBrowserBookmarksRepositorySession) session).shouldIgnore(normal)); michael@0: finishAndNotify(session); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Trivial test that pinned items will be skipped if present in the DB. michael@0: */ michael@0: public void testPinnedItemsAreNotRetrieved() { michael@0: final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: // Ensure that they exist. michael@0: setUpFennecPinnedItemsRecord(); michael@0: michael@0: // They're there in the DB… michael@0: final ArrayList roots = fetchChildrenDirect(Bookmarks.FIXED_ROOT_ID); michael@0: Logger.info(LOG_TAG, "Roots: " + roots); michael@0: assertTrue(roots.contains(Bookmarks.PINNED_FOLDER_GUID)); michael@0: michael@0: final ArrayList pinned = fetchChildrenDirect(Bookmarks.FIXED_PINNED_LIST_ID); michael@0: Logger.info(LOG_TAG, "Pinned: " + pinned); michael@0: assertTrue(pinned.contains("dapinneditem")); michael@0: michael@0: // … but not when we fetch. michael@0: final ArrayList guids = fetchGUIDs(repo); michael@0: assertFalse(guids.contains(Bookmarks.PINNED_FOLDER_GUID)); michael@0: assertFalse(guids.contains("dapinneditem")); michael@0: } michael@0: michael@0: /** michael@0: * Trivial test that reading list records will be skipped if present in the DB. michael@0: */ michael@0: public void testReadingListIsNotRetrieved() { michael@0: final AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: // Ensure that it exists. michael@0: setUpFennecReadingListRecord(); michael@0: michael@0: // It's there in the DB… michael@0: final ArrayList roots = fetchChildrenDirect(BrowserContract.Bookmarks.FIXED_ROOT_ID); michael@0: Logger.info(LOG_TAG, "Roots: " + roots); michael@0: assertTrue(roots.contains(Bookmarks.READING_LIST_FOLDER_GUID)); michael@0: michael@0: // … but not when we fetch. michael@0: assertFalse(fetchGUIDs(repo).contains(Bookmarks.READING_LIST_FOLDER_GUID)); michael@0: } michael@0: michael@0: public void testRetrieveFolderHasAccurateChildren() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: final long now = System.currentTimeMillis(); michael@0: michael@0: final String folderGUID = "eaaaaaaaafff"; michael@0: BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now - 5, false); michael@0: BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now - 1, false); michael@0: BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now - 3, false); michael@0: BookmarkRecord bookmarkC = new BookmarkRecord("aaaaaaaaaccc", "bookmarks", now - 2, false); michael@0: michael@0: folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC); michael@0: folder.sortIndex = 150; michael@0: folder.title = "Test items"; michael@0: folder.parentID = "toolbar"; michael@0: folder.parentName = "Bookmarks Toolbar"; michael@0: folder.type = "folder"; michael@0: michael@0: bookmarkA.parentID = folderGUID; michael@0: bookmarkA.bookmarkURI = "http://example.com/A"; michael@0: bookmarkA.title = "Title A"; michael@0: bookmarkA.type = "bookmark"; michael@0: michael@0: bookmarkB.parentID = folderGUID; michael@0: bookmarkB.bookmarkURI = "http://example.com/B"; michael@0: bookmarkB.title = "Title B"; michael@0: bookmarkB.type = "bookmark"; michael@0: michael@0: bookmarkC.parentID = folderGUID; michael@0: bookmarkC.bookmarkURI = "http://example.com/C"; michael@0: bookmarkC.title = "Title C"; michael@0: bookmarkC.type = "bookmark"; michael@0: michael@0: BookmarkRecord[] folderOnly = new BookmarkRecord[1]; michael@0: BookmarkRecord[] children = new BookmarkRecord[3]; michael@0: michael@0: folderOnly[0] = folder; michael@0: michael@0: children[0] = bookmarkA; michael@0: children[1] = bookmarkB; michael@0: children[2] = bookmarkC; michael@0: michael@0: wipe(); michael@0: Logger.debug(getName(), "Storing just folder..."); michael@0: storeRecordsInSession(repo, folderOnly, null); michael@0: michael@0: // We don't have any children, despite our insistence upon storing. michael@0: assertChildrenAreOrdered(repo, folderGUID, new Record[] {}); michael@0: michael@0: // Now store the children. michael@0: Logger.debug(getName(), "Storing children..."); michael@0: storeRecordsInSession(repo, children, null); michael@0: michael@0: // Now we have children, but their order is not determined, because michael@0: // they were stored out-of-session with the original folder. michael@0: assertChildrenAreUnordered(repo, folderGUID, children); michael@0: michael@0: // Now if we store the folder record again, they'll be put in the michael@0: // right place. michael@0: folder.lastModified++; michael@0: Logger.debug(getName(), "Storing just folder again..."); michael@0: storeRecordsInSession(repo, folderOnly, null); michael@0: Logger.debug(getName(), "Fetching children yet again..."); michael@0: assertChildrenAreOrdered(repo, folderGUID, children); michael@0: michael@0: // Now let's see what happens when we see records in the same session. michael@0: BookmarkRecord[] parentMixed = new BookmarkRecord[4]; michael@0: BookmarkRecord[] parentFirst = new BookmarkRecord[4]; michael@0: BookmarkRecord[] parentLast = new BookmarkRecord[4]; michael@0: michael@0: // None of our records have a position set. michael@0: assertTrue(bookmarkA.androidPosition <= 0); michael@0: assertTrue(bookmarkB.androidPosition <= 0); michael@0: assertTrue(bookmarkC.androidPosition <= 0); michael@0: michael@0: parentMixed[1] = folder; michael@0: parentMixed[0] = bookmarkA; michael@0: parentMixed[2] = bookmarkC; michael@0: parentMixed[3] = bookmarkB; michael@0: michael@0: parentFirst[0] = folder; michael@0: parentFirst[1] = bookmarkC; michael@0: parentFirst[2] = bookmarkA; michael@0: parentFirst[3] = bookmarkB; michael@0: michael@0: parentLast[3] = folder; michael@0: parentLast[0] = bookmarkB; michael@0: parentLast[1] = bookmarkA; michael@0: parentLast[2] = bookmarkC; michael@0: michael@0: wipe(); michael@0: storeRecordsInSession(repo, parentMixed, null); michael@0: assertChildrenAreOrdered(repo, folderGUID, children); michael@0: michael@0: wipe(); michael@0: storeRecordsInSession(repo, parentFirst, null); michael@0: assertChildrenAreOrdered(repo, folderGUID, children); michael@0: michael@0: wipe(); michael@0: storeRecordsInSession(repo, parentLast, null); michael@0: assertChildrenAreOrdered(repo, folderGUID, children); michael@0: michael@0: // Ensure that records are ordered even if we re-process the folder. michael@0: wipe(); michael@0: storeRecordsInSession(repo, parentLast, null); michael@0: folder.lastModified++; michael@0: storeRecordsInSession(repo, folderOnly, null); michael@0: assertChildrenAreOrdered(repo, folderGUID, children); michael@0: } michael@0: michael@0: public void testMergeFoldersPreservesSaneOrder() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: final long now = System.currentTimeMillis(); michael@0: final String folderGUID = "mobile"; michael@0: michael@0: wipe(); michael@0: final long mobile = setUpFennecMobileRecord(); michael@0: michael@0: // No children. michael@0: assertChildrenAreUnordered(repo, folderGUID, new Record[] {}); michael@0: michael@0: // Add some, as Fennec would. michael@0: fennecAddBookmark("Bookmark One", "http://example.com/fennec/One"); michael@0: fennecAddBookmark("Bookmark Two", "http://example.com/fennec/Two"); michael@0: michael@0: Logger.debug(getName(), "Fetching children..."); michael@0: JSONArray folderChildren = fetchChildrenForGUID(repo, folderGUID); michael@0: michael@0: assertTrue(folderChildren != null); michael@0: Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); michael@0: assertEquals(2, folderChildren.size()); michael@0: String guidOne = (String) folderChildren.get(0); michael@0: String guidTwo = (String) folderChildren.get(1); michael@0: michael@0: // Make sure positions were saved. michael@0: assertChildrenAreDirect(mobile, new String[] { michael@0: guidOne, michael@0: guidTwo michael@0: }); michael@0: michael@0: // Add some through Sync. michael@0: BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now, false); michael@0: BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now, false); michael@0: BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now, false); michael@0: michael@0: folder.children = childrenFromRecords(bookmarkA, bookmarkB); michael@0: folder.sortIndex = 150; michael@0: folder.title = "Mobile Bookmarks"; michael@0: folder.parentID = "places"; michael@0: folder.parentName = ""; michael@0: folder.type = "folder"; michael@0: michael@0: bookmarkA.parentID = folderGUID; michael@0: bookmarkA.parentName = "Mobile Bookmarks"; // Using this title exercises Bug 748898. michael@0: bookmarkA.bookmarkURI = "http://example.com/A"; michael@0: bookmarkA.title = "Title A"; michael@0: bookmarkA.type = "bookmark"; michael@0: michael@0: bookmarkB.parentID = folderGUID; michael@0: bookmarkB.parentName = "mobile"; michael@0: bookmarkB.bookmarkURI = "http://example.com/B"; michael@0: bookmarkB.title = "Title B"; michael@0: bookmarkB.type = "bookmark"; michael@0: michael@0: BookmarkRecord[] parentMixed = new BookmarkRecord[3]; michael@0: parentMixed[0] = bookmarkA; michael@0: parentMixed[1] = folder; michael@0: parentMixed[2] = bookmarkB; michael@0: michael@0: storeRecordsInSession(repo, parentMixed, null); michael@0: michael@0: BookmarkRecord expectedOne = new BookmarkRecord(guidOne, "bookmarks", now - 10, false); michael@0: BookmarkRecord expectedTwo = new BookmarkRecord(guidTwo, "bookmarks", now - 10, false); michael@0: michael@0: // We want the server to win in this case, and otherwise to preserve order. michael@0: // TODO michael@0: assertChildrenAreOrdered(repo, folderGUID, new Record[] { michael@0: bookmarkA, michael@0: bookmarkB, michael@0: expectedOne, michael@0: expectedTwo michael@0: }); michael@0: michael@0: // Furthermore, the children of that folder should be correct in the DB. michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: final long folderId = fennecGetFolderId(cr, folderGUID); michael@0: Logger.debug(getName(), "Folder " + folderGUID + " => " + folderId); michael@0: michael@0: assertChildrenAreDirect(folderId, new String[] { michael@0: bookmarkA.guid, michael@0: bookmarkB.guid, michael@0: expectedOne.guid, michael@0: expectedTwo.guid michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Apply a folder record whose children array is already accurately michael@0: * stored in the database. Verify that the parent folder is not flagged michael@0: * for reupload (i.e., that its modified time is *ahem* unmodified). michael@0: */ michael@0: public void testNoReorderingMeansNoReupload() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: final long now = System.currentTimeMillis(); michael@0: michael@0: final String folderGUID = "eaaaaaaaafff"; michael@0: BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); michael@0: BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); michael@0: BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); michael@0: michael@0: folder.children = childrenFromRecords(bookmarkA, bookmarkB); michael@0: folder.sortIndex = 150; michael@0: folder.title = "Test items"; michael@0: folder.parentID = "toolbar"; michael@0: folder.parentName = "Bookmarks Toolbar"; michael@0: folder.type = "folder"; michael@0: michael@0: bookmarkA.parentID = folderGUID; michael@0: bookmarkA.bookmarkURI = "http://example.com/A"; michael@0: bookmarkA.title = "Title A"; michael@0: bookmarkA.type = "bookmark"; michael@0: michael@0: bookmarkB.parentID = folderGUID; michael@0: bookmarkB.bookmarkURI = "http://example.com/B"; michael@0: bookmarkB.title = "Title B"; michael@0: bookmarkB.type = "bookmark"; michael@0: michael@0: BookmarkRecord[] abf = new BookmarkRecord[3]; michael@0: BookmarkRecord[] justFolder = new BookmarkRecord[1]; michael@0: michael@0: abf[0] = bookmarkA; michael@0: abf[1] = bookmarkB; michael@0: abf[2] = folder; michael@0: michael@0: justFolder[0] = folder; michael@0: michael@0: final String[] abGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid }; michael@0: final Record[] abRecords = new Record[] { bookmarkA, bookmarkB }; michael@0: final String[] baGUIDs = new String[] { bookmarkB.guid, bookmarkA.guid }; michael@0: final Record[] baRecords = new Record[] { bookmarkB, bookmarkA }; michael@0: michael@0: wipe(); michael@0: Logger.debug(getName(), "Storing A, B, folder..."); michael@0: storeRecordsInSession(repo, abf, null); michael@0: michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: final long folderID = fennecGetFolderId(cr, folderGUID); michael@0: assertChildrenAreOrdered(repo, folderGUID, abRecords); michael@0: assertChildrenAreDirect(folderID, abGUIDs); michael@0: michael@0: // To ensure an interval. michael@0: try { michael@0: Thread.sleep(100); michael@0: } catch (InterruptedException e) { michael@0: } michael@0: michael@0: // Store the same folder record again, and check the tracking. michael@0: // Because the folder array didn't change, michael@0: // the item is still tracked to not be uploaded. michael@0: folder.lastModified = System.currentTimeMillis() + 1; michael@0: HashSet tracked = new HashSet(); michael@0: storeRecordsInSession(repo, justFolder, tracked); michael@0: assertChildrenAreOrdered(repo, folderGUID, abRecords); michael@0: assertChildrenAreDirect(folderID, abGUIDs); michael@0: michael@0: assertTrue(tracked.contains(folderGUID)); michael@0: michael@0: // Store again, but with a different order. michael@0: tracked = new HashSet(); michael@0: folder.children = childrenFromRecords(bookmarkB, bookmarkA); michael@0: folder.lastModified = System.currentTimeMillis() + 1; michael@0: storeRecordsInSession(repo, justFolder, tracked); michael@0: assertChildrenAreOrdered(repo, folderGUID, baRecords); michael@0: assertChildrenAreDirect(folderID, baGUIDs); michael@0: michael@0: // Now it's going to be reuploaded. michael@0: assertFalse(tracked.contains(folderGUID)); michael@0: } michael@0: michael@0: /** michael@0: * Exercise the deletion of folders when their children have not been michael@0: * marked as deleted. In a database with constraints, this would fail michael@0: * if we simply deleted the records, so we move them first. michael@0: */ michael@0: public void testFolderDeletionOrphansChildren() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: michael@0: long now = System.currentTimeMillis(); michael@0: michael@0: // Add a folder and four children. michael@0: final String folderGUID = "eaaaaaaaafff"; michael@0: BookmarkRecord folder = new BookmarkRecord(folderGUID, "bookmarks", now -5, false); michael@0: BookmarkRecord bookmarkA = new BookmarkRecord("daaaaaaaaaaa", "bookmarks", now -1, false); michael@0: BookmarkRecord bookmarkB = new BookmarkRecord("baaaaaaaabbb", "bookmarks", now -3, false); michael@0: BookmarkRecord bookmarkC = new BookmarkRecord("daaaaaaaaccc", "bookmarks", now -7, false); michael@0: BookmarkRecord bookmarkD = new BookmarkRecord("baaaaaaaaddd", "bookmarks", now -4, false); michael@0: michael@0: folder.children = childrenFromRecords(bookmarkA, bookmarkB, bookmarkC, bookmarkD); michael@0: folder.sortIndex = 150; michael@0: folder.title = "Test items"; michael@0: folder.parentID = "toolbar"; michael@0: folder.parentName = "Bookmarks Toolbar"; michael@0: folder.type = "folder"; michael@0: michael@0: bookmarkA.parentID = folderGUID; michael@0: bookmarkA.bookmarkURI = "http://example.com/A"; michael@0: bookmarkA.title = "Title A"; michael@0: bookmarkA.type = "bookmark"; michael@0: michael@0: bookmarkB.parentID = folderGUID; michael@0: bookmarkB.bookmarkURI = "http://example.com/B"; michael@0: bookmarkB.title = "Title B"; michael@0: bookmarkB.type = "bookmark"; michael@0: michael@0: bookmarkC.parentID = folderGUID; michael@0: bookmarkC.bookmarkURI = "http://example.com/C"; michael@0: bookmarkC.title = "Title C"; michael@0: bookmarkC.type = "bookmark"; michael@0: michael@0: bookmarkD.parentID = folderGUID; michael@0: bookmarkD.bookmarkURI = "http://example.com/D"; michael@0: bookmarkD.title = "Title D"; michael@0: bookmarkD.type = "bookmark"; michael@0: michael@0: BookmarkRecord[] abfcd = new BookmarkRecord[5]; michael@0: BookmarkRecord[] justFolder = new BookmarkRecord[1]; michael@0: abfcd[0] = bookmarkA; michael@0: abfcd[1] = bookmarkB; michael@0: abfcd[2] = folder; michael@0: abfcd[3] = bookmarkC; michael@0: abfcd[4] = bookmarkD; michael@0: michael@0: justFolder[0] = folder; michael@0: michael@0: final String[] abcdGUIDs = new String[] { bookmarkA.guid, bookmarkB.guid, bookmarkC.guid, bookmarkD.guid }; michael@0: final Record[] abcdRecords = new Record[] { bookmarkA, bookmarkB, bookmarkC, bookmarkD }; michael@0: michael@0: wipe(); michael@0: Logger.debug(getName(), "Storing A, B, folder, C, D..."); michael@0: storeRecordsInSession(repo, abfcd, null); michael@0: michael@0: // Verify that it worked. michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: final long folderID = fennecGetFolderId(cr, folderGUID); michael@0: assertChildrenAreOrdered(repo, folderGUID, abcdRecords); michael@0: assertChildrenAreDirect(folderID, abcdGUIDs); michael@0: michael@0: now = System.currentTimeMillis(); michael@0: michael@0: // Add one child to unsorted bookmarks. michael@0: BookmarkRecord unsortedA = new BookmarkRecord("yiamunsorted", "bookmarks", now, false); michael@0: unsortedA.parentID = "unfiled"; michael@0: unsortedA.title = "Unsorted A"; michael@0: unsortedA.type = "bookmark"; michael@0: unsortedA.androidPosition = 0; michael@0: michael@0: BookmarkRecord[] ua = new BookmarkRecord[1]; michael@0: ua[0] = unsortedA; michael@0: michael@0: storeRecordsInSession(repo, ua, null); michael@0: michael@0: // Ensure that the database is in this state. michael@0: assertChildrenAreOrdered(repo, "unfiled", ua); michael@0: michael@0: // Delete the second child, the folder, and then the third child. michael@0: bookmarkB.bookmarkURI = bookmarkC.bookmarkURI = folder.bookmarkURI = null; michael@0: bookmarkB.deleted = bookmarkC.deleted = folder.deleted = true; michael@0: bookmarkB.title = bookmarkC.title = folder.title = null; michael@0: michael@0: // Nulling the type of folder is very important: it verifies michael@0: // that the session can behave correctly according to local type. michael@0: bookmarkB.type = bookmarkC.type = folder.type = null; michael@0: michael@0: bookmarkB.lastModified = bookmarkC.lastModified = folder.lastModified = now = System.currentTimeMillis(); michael@0: michael@0: BookmarkRecord[] deletions = new BookmarkRecord[] { bookmarkB, folder, bookmarkC }; michael@0: storeRecordsInSession(repo, deletions, null); michael@0: michael@0: // Verify that the unsorted bookmarks folder contains its child and the michael@0: // first and fourth children of the now-deleted folder. michael@0: // Also verify that the folder is gone. michael@0: long unsortedID = fennecGetFolderId(cr, "unfiled"); michael@0: long toolbarID = fennecGetFolderId(cr, "toolbar"); michael@0: String[] expected = new String[] { unsortedA.guid, bookmarkA.guid, bookmarkD.guid }; michael@0: michael@0: // This will trigger positioning. michael@0: assertChildrenAreUnordered(repo, "unfiled", new Record[] { unsortedA, bookmarkA, bookmarkD }); michael@0: assertChildrenAreDirect(unsortedID, expected); michael@0: assertChildrenAreDirect(toolbarID, new String[] {}); michael@0: } michael@0: michael@0: /** michael@0: * A test where we expect to replace a local folder with a new folder (with a michael@0: * new GUID), whilst adding children to it. Verifies that replace and insert michael@0: * co-operate. michael@0: */ michael@0: public void testInsertAndReplaceGuid() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: wipe(); michael@0: michael@0: BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); michael@0: BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 michael@0: BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 michael@0: BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 michael@0: BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 michael@0: BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 michael@0: BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 michael@0: michael@0: BookmarkRecord[] records = new BookmarkRecord[] { michael@0: folder1, folder2, folder3, michael@0: bmk1, bmk4 michael@0: }; michael@0: storeRecordsInSession(repo, records, null); michael@0: michael@0: assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); michael@0: assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); michael@0: assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); michael@0: michael@0: // Replace folder3 with a record with a new GUID, and add bmk4 as folder3's child. michael@0: final long now = System.currentTimeMillis(); michael@0: folder3.guid = Utils.generateGuid(); michael@0: folder3.lastModified = now; michael@0: bmk4.title = bmk4.title + "/NEW"; michael@0: bmk4.parentID = folder3.guid; // Incoming child knows its parent. michael@0: bmk4.parentName = folder3.title; michael@0: bmk4.lastModified = now; michael@0: michael@0: // Order of store should not matter. michael@0: ArrayList changedRecords = new ArrayList(); michael@0: changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(bmk4); changedRecords.add(folder3); michael@0: Collections.shuffle(changedRecords); michael@0: storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); michael@0: michael@0: assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); michael@0: assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); michael@0: assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); michael@0: michael@0: assertNotNull(fetchGUID(repo, folder3.guid)); michael@0: assertEquals(bmk4.title, fetchGUID(repo, bmk4.guid).title); michael@0: } michael@0: michael@0: /** michael@0: * A test where we expect to replace a local folder with a new folder (with a michael@0: * new title but the same GUID), whilst adding children to it. Verifies that michael@0: * replace and insert co-operate. michael@0: */ michael@0: public void testInsertAndReplaceTitle() { michael@0: AndroidBrowserBookmarksRepository repo = new AndroidBrowserBookmarksRepository(); michael@0: wipe(); michael@0: michael@0: BookmarkRecord folder1 = BookmarkHelpers.createFolder1(); michael@0: BookmarkRecord folder2 = BookmarkHelpers.createFolder2(); // child of folder1 michael@0: BookmarkRecord folder3 = BookmarkHelpers.createFolder3(); // child of folder2 michael@0: BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1(); // child of folder1 michael@0: BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2(); // child of folder1 michael@0: BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3(); // child of folder2 michael@0: BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4(); // child of folder3 michael@0: michael@0: BookmarkRecord[] records = new BookmarkRecord[] { michael@0: folder1, folder2, folder3, michael@0: bmk1, bmk4 michael@0: }; michael@0: storeRecordsInSession(repo, records, null); michael@0: michael@0: assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, folder2 }); michael@0: assertChildrenAreUnordered(repo, folder2.guid, new Record[] { folder3 }); michael@0: assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); michael@0: michael@0: // Rename folder1, and add bmk2 as folder1's child. michael@0: final long now = System.currentTimeMillis(); michael@0: folder1.title = folder1.title + "/NEW"; michael@0: folder1.lastModified = now; michael@0: bmk2.title = bmk2.title + "/NEW"; michael@0: bmk2.parentID = folder1.guid; // Incoming child knows its parent. michael@0: bmk2.parentName = folder1.title; michael@0: bmk2.lastModified = now; michael@0: michael@0: // Order of store should not matter. michael@0: ArrayList changedRecords = new ArrayList(); michael@0: changedRecords.add(bmk2); changedRecords.add(bmk3); changedRecords.add(folder1); michael@0: Collections.shuffle(changedRecords); michael@0: storeRecordsInSession(repo, changedRecords.toArray(new BookmarkRecord[changedRecords.size()]), null); michael@0: michael@0: assertChildrenAreUnordered(repo, folder1.guid, new Record[] { bmk1, bmk2, folder2 }); michael@0: assertChildrenAreUnordered(repo, folder2.guid, new Record[] { bmk3, folder3 }); michael@0: assertChildrenAreUnordered(repo, folder3.guid, new Record[] { bmk4 }); michael@0: michael@0: assertEquals(folder1.title, fetchGUID(repo, folder1.guid).title); michael@0: assertEquals(bmk2.title, fetchGUID(repo, bmk2.guid).title); michael@0: } michael@0: michael@0: /** michael@0: * Create and begin a new session, handing control to the delegate when started. michael@0: * Returns when the delegate has notified. michael@0: */ michael@0: public void inBegunSession(final AndroidBrowserBookmarksRepository repo, michael@0: final RepositorySessionBeginDelegate beginDelegate) { michael@0: Runnable go = new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: RepositorySessionCreationDelegate delegate = new SimpleSuccessCreationDelegate() { michael@0: @Override michael@0: public void onSessionCreated(final RepositorySession session) { michael@0: try { michael@0: session.begin(beginDelegate); michael@0: } catch (InvalidSessionTransitionException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: }; michael@0: repo.createSession(delegate, getApplicationContext()); michael@0: } michael@0: }; michael@0: performWait(go); michael@0: } michael@0: michael@0: /** michael@0: * Finish the provided session, notifying on success. michael@0: * michael@0: * @param session michael@0: */ michael@0: public void finishAndNotify(final RepositorySession session) { michael@0: try { michael@0: session.finish(new SimpleSuccessFinishDelegate() { michael@0: @Override michael@0: public void onFinishSucceeded(RepositorySession session, michael@0: RepositorySessionBundle bundle) { michael@0: performNotify(); michael@0: } michael@0: }); michael@0: } catch (InactiveSessionException e) { michael@0: performNotify(e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Simple helper class for fetching all records. michael@0: * The fetched records' GUIDs are stored in `fetchedGUIDs`. michael@0: */ michael@0: public class SimpleFetchAllBeginDelegate extends SimpleSuccessBeginDelegate { michael@0: public final ArrayList fetchedGUIDs = new ArrayList(); michael@0: michael@0: @Override michael@0: public void onBeginSucceeded(final RepositorySession session) { michael@0: RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { michael@0: michael@0: @Override michael@0: public void onFetchedRecord(Record record) { michael@0: fetchedGUIDs.add(record.guid); michael@0: } michael@0: michael@0: @Override michael@0: public void onFetchCompleted(long end) { michael@0: finishAndNotify(session); michael@0: } michael@0: }; michael@0: session.fetchSince(0, fetchDelegate); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Simple helper class for fetching a single record by GUID. michael@0: * The fetched record is stored in `fetchedRecord`. michael@0: */ michael@0: public class SimpleFetchOneBeginDelegate extends SimpleSuccessBeginDelegate { michael@0: public final String guid; michael@0: public Record fetchedRecord = null; michael@0: michael@0: public SimpleFetchOneBeginDelegate(String guid) { michael@0: this.guid = guid; michael@0: } michael@0: michael@0: @Override michael@0: public void onBeginSucceeded(final RepositorySession session) { michael@0: RepositorySessionFetchRecordsDelegate fetchDelegate = new SimpleSuccessFetchDelegate() { michael@0: michael@0: @Override michael@0: public void onFetchedRecord(Record record) { michael@0: fetchedRecord = record; michael@0: } michael@0: michael@0: @Override michael@0: public void onFetchCompleted(long end) { michael@0: finishAndNotify(session); michael@0: } michael@0: }; michael@0: try { michael@0: session.fetch(new String[] { guid }, fetchDelegate); michael@0: } catch (InactiveSessionException e) { michael@0: performNotify("Session is inactive.", e); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Create a new session for the given repository, storing each record michael@0: * from the provided array. Notifies on failure or success. michael@0: * michael@0: * Optionally populates a provided Collection with tracked items. michael@0: * @param repo michael@0: * @param records michael@0: * @param tracked michael@0: */ michael@0: public void storeRecordsInSession(AndroidBrowserBookmarksRepository repo, michael@0: final BookmarkRecord[] records, michael@0: final Collection tracked) { michael@0: SimpleSuccessBeginDelegate beginDelegate = new SimpleSuccessBeginDelegate() { michael@0: @Override michael@0: public void onBeginSucceeded(final RepositorySession session) { michael@0: RepositorySessionStoreDelegate storeDelegate = new SimpleSuccessStoreDelegate() { michael@0: michael@0: @Override michael@0: public void onStoreCompleted(final long storeEnd) { michael@0: // Pass back whatever we tracked. michael@0: if (tracked != null) { michael@0: Iterator iter = session.getTrackedRecordIDs(); michael@0: while (iter.hasNext()) { michael@0: tracked.add(iter.next()); michael@0: } michael@0: } michael@0: finishAndNotify(session); michael@0: } michael@0: michael@0: @Override michael@0: public void onRecordStoreSucceeded(String guid) { michael@0: } michael@0: }; michael@0: session.setStoreDelegate(storeDelegate); michael@0: for (BookmarkRecord record : records) { michael@0: try { michael@0: session.store(record); michael@0: } catch (NoStoreDelegateException e) { michael@0: // Never happens. michael@0: } michael@0: } michael@0: session.storeDone(); michael@0: } michael@0: }; michael@0: inBegunSession(repo, beginDelegate); michael@0: } michael@0: michael@0: public ArrayList fetchGUIDs(AndroidBrowserBookmarksRepository repo) { michael@0: SimpleFetchAllBeginDelegate beginDelegate = new SimpleFetchAllBeginDelegate(); michael@0: inBegunSession(repo, beginDelegate); michael@0: return beginDelegate.fetchedGUIDs; michael@0: } michael@0: michael@0: public BookmarkRecord fetchGUID(AndroidBrowserBookmarksRepository repo, michael@0: final String guid) { michael@0: Logger.info(LOG_TAG, "Fetching for " + guid); michael@0: SimpleFetchOneBeginDelegate beginDelegate = new SimpleFetchOneBeginDelegate(guid); michael@0: inBegunSession(repo, beginDelegate); michael@0: Logger.info(LOG_TAG, "Fetched " + beginDelegate.fetchedRecord); michael@0: assertTrue(beginDelegate.fetchedRecord != null); michael@0: return (BookmarkRecord) beginDelegate.fetchedRecord; michael@0: } michael@0: michael@0: public JSONArray fetchChildrenForGUID(AndroidBrowserBookmarksRepository repo, michael@0: final String guid) { michael@0: return fetchGUID(repo, guid).children; michael@0: } michael@0: michael@0: @SuppressWarnings("unchecked") michael@0: protected static JSONArray childrenFromRecords(BookmarkRecord... records) { michael@0: JSONArray children = new JSONArray(); michael@0: for (BookmarkRecord record : records) { michael@0: children.add(record.guid); michael@0: } michael@0: return children; michael@0: } michael@0: michael@0: michael@0: protected void updateRow(ContentValues values) { michael@0: Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; michael@0: final String where = BrowserContract.Bookmarks.GUID + " = ?"; michael@0: final String[] args = new String[] { values.getAsString(BrowserContract.Bookmarks.GUID) }; michael@0: getApplicationContext().getContentResolver().update(uri, values, where, args); michael@0: } michael@0: michael@0: protected Uri insertRow(ContentValues values) { michael@0: Uri uri = BrowserContractHelpers.BOOKMARKS_CONTENT_URI; michael@0: return getApplicationContext().getContentResolver().insert(uri, values); michael@0: } michael@0: michael@0: protected static ContentValues specialFolder() { michael@0: ContentValues values = new ContentValues(); michael@0: michael@0: final long now = System.currentTimeMillis(); michael@0: values.put(Bookmarks.DATE_CREATED, now); michael@0: values.put(Bookmarks.DATE_MODIFIED, now); michael@0: values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_FOLDER); michael@0: michael@0: return values; michael@0: } michael@0: michael@0: protected static ContentValues fennecMobileRecordWithoutTitle() { michael@0: ContentValues values = specialFolder(); michael@0: values.put(BrowserContract.SyncColumns.GUID, "mobile"); michael@0: values.putNull(BrowserContract.Bookmarks.TITLE); michael@0: michael@0: return values; michael@0: } michael@0: michael@0: protected ContentValues fennecPinnedItemsRecord() { michael@0: final ContentValues values = specialFolder(); michael@0: final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_pinned); michael@0: michael@0: values.put(BrowserContract.SyncColumns.GUID, Bookmarks.PINNED_FOLDER_GUID); michael@0: values.put(Bookmarks._ID, Bookmarks.FIXED_PINNED_LIST_ID); michael@0: values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); michael@0: values.put(Bookmarks.TITLE, title); michael@0: return values; michael@0: } michael@0: michael@0: protected static ContentValues fennecPinnedChildItemRecord() { michael@0: ContentValues values = new ContentValues(); michael@0: michael@0: final long now = System.currentTimeMillis(); michael@0: michael@0: values.put(BrowserContract.SyncColumns.GUID, "dapinneditem"); michael@0: values.put(Bookmarks.DATE_CREATED, now); michael@0: values.put(Bookmarks.DATE_MODIFIED, now); michael@0: values.put(Bookmarks.TYPE, BrowserContract.Bookmarks.TYPE_BOOKMARK); michael@0: values.put(Bookmarks.URL, "user-entered:foobar"); michael@0: values.put(Bookmarks.PARENT, Bookmarks.FIXED_PINNED_LIST_ID); michael@0: values.put(Bookmarks.TITLE, "Foobar"); michael@0: return values; michael@0: } michael@0: michael@0: protected ContentValues fennecReadingListRecord() { michael@0: final ContentValues values = specialFolder(); michael@0: final String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_reading_list); michael@0: michael@0: values.put(BrowserContract.SyncColumns.GUID, Bookmarks.READING_LIST_FOLDER_GUID); michael@0: values.put(Bookmarks._ID, Bookmarks.FIXED_READING_LIST_ID); michael@0: values.put(Bookmarks.PARENT, Bookmarks.FIXED_ROOT_ID); michael@0: values.put(Bookmarks.TITLE, title); michael@0: return values; michael@0: } michael@0: michael@0: protected long setUpFennecMobileRecordWithoutTitle() { michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: ContentValues values = fennecMobileRecordWithoutTitle(); michael@0: updateRow(values); michael@0: return fennecGetMobileBookmarksFolderId(cr); michael@0: } michael@0: michael@0: protected long setUpFennecMobileRecord() { michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: ContentValues values = fennecMobileRecordWithoutTitle(); michael@0: values.put(BrowserContract.Bookmarks.PARENT, BrowserContract.Bookmarks.FIXED_ROOT_ID); michael@0: String title = getApplicationContext().getResources().getString(R.string.bookmarks_folder_mobile); michael@0: values.put(BrowserContract.Bookmarks.TITLE, title); michael@0: updateRow(values); michael@0: return fennecGetMobileBookmarksFolderId(cr); michael@0: } michael@0: michael@0: protected void setUpFennecPinnedItemsRecord() { michael@0: insertRow(fennecPinnedItemsRecord()); michael@0: insertRow(fennecPinnedChildItemRecord()); michael@0: } michael@0: michael@0: protected void setUpFennecReadingListRecord() { michael@0: insertRow(fennecReadingListRecord()); michael@0: } michael@0: michael@0: // michael@0: // Fennec fake layer. michael@0: // michael@0: private Uri appendProfile(Uri uri) { michael@0: final String defaultProfile = "default"; // Fennec constant removed in Bug 715307. michael@0: return uri.buildUpon().appendQueryParameter(BrowserContract.PARAM_PROFILE, defaultProfile).build(); michael@0: } michael@0: michael@0: private long fennecGetFolderId(ContentResolver cr, String guid) { michael@0: Cursor c = null; michael@0: try { michael@0: c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), michael@0: new String[] { BrowserContract.Bookmarks._ID }, michael@0: BrowserContract.Bookmarks.GUID + " = ?", michael@0: new String[] { guid }, michael@0: null); michael@0: michael@0: if (c.moveToFirst()) { michael@0: return c.getLong(c.getColumnIndexOrThrow(BrowserContract.Bookmarks._ID)); michael@0: } michael@0: return -1; michael@0: } finally { michael@0: if (c != null) { michael@0: c.close(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: private long fennecGetMobileBookmarksFolderId(ContentResolver cr) { michael@0: return fennecGetFolderId(cr, BrowserContract.Bookmarks.MOBILE_FOLDER_GUID); michael@0: } michael@0: michael@0: public void fennecAddBookmark(String title, String uri) { michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: michael@0: long folderId = fennecGetMobileBookmarksFolderId(cr); michael@0: if (folderId < 0) { michael@0: return; michael@0: } michael@0: michael@0: ContentValues values = new ContentValues(); michael@0: values.put(BrowserContract.Bookmarks.TITLE, title); michael@0: values.put(BrowserContract.Bookmarks.URL, uri); michael@0: values.put(BrowserContract.Bookmarks.PARENT, folderId); michael@0: michael@0: // Restore deleted record if possible michael@0: values.put(BrowserContract.Bookmarks.IS_DELETED, 0); michael@0: michael@0: Logger.debug(getName(), "Adding bookmark " + title + ", " + uri + " in " + folderId); michael@0: int updated = cr.update(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), michael@0: values, michael@0: BrowserContract.Bookmarks.URL + " = ?", michael@0: new String[] { uri }); michael@0: michael@0: if (updated == 0) { michael@0: Uri insert = cr.insert(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), values); michael@0: long idFromUri = ContentUris.parseId(insert); michael@0: Logger.debug(getName(), "Inserted " + uri + " as " + idFromUri); michael@0: Logger.debug(getName(), "Position is " + getPosition(idFromUri)); michael@0: } michael@0: } michael@0: michael@0: private long getPosition(long idFromUri) { michael@0: ContentResolver cr = getApplicationContext().getContentResolver(); michael@0: Cursor c = cr.query(appendProfile(BrowserContractHelpers.BOOKMARKS_CONTENT_URI), michael@0: new String[] { BrowserContract.Bookmarks.POSITION }, michael@0: BrowserContract.Bookmarks._ID + " = ?", michael@0: new String[] { String.valueOf(idFromUri) }, michael@0: null); michael@0: if (!c.moveToFirst()) { michael@0: return -2; michael@0: } michael@0: return c.getLong(0); michael@0: } michael@0: michael@0: protected AndroidBrowserBookmarksDataAccessor dataAccessor = null; michael@0: protected AndroidBrowserBookmarksDataAccessor getDataAccessor() { michael@0: if (dataAccessor == null) { michael@0: dataAccessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); michael@0: } michael@0: return dataAccessor; michael@0: } michael@0: michael@0: protected void wipe() { michael@0: Logger.debug(getName(), "Wiping."); michael@0: getDataAccessor().wipe(); michael@0: } michael@0: michael@0: protected void assertChildrenAreOrdered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { michael@0: Logger.debug(getName(), "Fetching children..."); michael@0: JSONArray folderChildren = fetchChildrenForGUID(repo, guid); michael@0: michael@0: assertTrue(folderChildren != null); michael@0: Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); michael@0: assertEquals(expected.length, folderChildren.size()); michael@0: for (int i = 0; i < expected.length; ++i) { michael@0: assertEquals(expected[i].guid, ((String) folderChildren.get(i))); michael@0: } michael@0: } michael@0: michael@0: protected void assertChildrenAreUnordered(AndroidBrowserBookmarksRepository repo, String guid, Record[] expected) { michael@0: Logger.debug(getName(), "Fetching children..."); michael@0: JSONArray folderChildren = fetchChildrenForGUID(repo, guid); michael@0: michael@0: assertTrue(folderChildren != null); michael@0: Logger.debug(getName(), "Children are " + folderChildren.toJSONString()); michael@0: assertEquals(expected.length, folderChildren.size()); michael@0: for (Record record : expected) { michael@0: folderChildren.contains(record.guid); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Return a sequence of children GUIDs for the provided folder ID. michael@0: */ michael@0: protected ArrayList fetchChildrenDirect(long id) { michael@0: Logger.debug(getName(), "Fetching children directly from DB..."); michael@0: final ArrayList out = new ArrayList(); michael@0: final AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); michael@0: Cursor cur = null; michael@0: try { michael@0: cur = accessor.getChildren(id); michael@0: } catch (NullCursorException e) { michael@0: fail("Got null cursor."); michael@0: } michael@0: try { michael@0: if (!cur.moveToFirst()) { michael@0: return out; michael@0: } michael@0: final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); michael@0: while (!cur.isAfterLast()) { michael@0: out.add(cur.getString(guidCol)); michael@0: cur.moveToNext(); michael@0: } michael@0: } finally { michael@0: cur.close(); michael@0: } michael@0: return out; michael@0: } michael@0: michael@0: /** michael@0: * Assert that the children of the provided ID are correct and positioned in the database. michael@0: * @param id michael@0: * @param guids michael@0: */ michael@0: protected void assertChildrenAreDirect(long id, String[] guids) { michael@0: Logger.debug(getName(), "Fetching children directly from DB..."); michael@0: AndroidBrowserBookmarksDataAccessor accessor = new AndroidBrowserBookmarksDataAccessor(getApplicationContext()); michael@0: Cursor cur = null; michael@0: try { michael@0: cur = accessor.getChildren(id); michael@0: } catch (NullCursorException e) { michael@0: fail("Got null cursor."); michael@0: } michael@0: try { michael@0: if (guids == null || guids.length == 0) { michael@0: assertFalse(cur.moveToFirst()); michael@0: return; michael@0: } michael@0: michael@0: assertTrue(cur.moveToFirst()); michael@0: int i = 0; michael@0: final int guidCol = cur.getColumnIndex(BrowserContract.SyncColumns.GUID); michael@0: final int posCol = cur.getColumnIndex(BrowserContract.Bookmarks.POSITION); michael@0: while (!cur.isAfterLast()) { michael@0: assertTrue(i < guids.length); michael@0: final String guid = cur.getString(guidCol); michael@0: final int pos = cur.getInt(posCol); michael@0: Logger.debug(getName(), "Fetched child: " + guid + " has position " + pos); michael@0: assertEquals(guids[i], guid); michael@0: assertEquals(i, pos); michael@0: michael@0: ++i; michael@0: cur.moveToNext(); michael@0: } michael@0: assertEquals(guids.length, i); michael@0: } finally { michael@0: cur.close(); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: TODO michael@0: michael@0: Test for storing a record that will reconcile to mobile; postcondition is michael@0: that there's still a directory called mobile that includes all the items that michael@0: it used to. michael@0: michael@0: mobile folder created without title. michael@0: Unsorted put in mobile??? michael@0: Tests for children retrieval michael@0: Tests for children merge michael@0: Tests for modify retrieve parent when child added, removed, reordered (oh, reorder is hard! Any change, then.) michael@0: Safety mode? michael@0: Test storing folder first, contents first. michael@0: Store folder in next session. Verify order recovery. michael@0: michael@0: michael@0: */