Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
4 package org.mozilla.gecko.background.db;
6 import java.util.ArrayList;
8 import org.json.simple.JSONArray;
9 import org.mozilla.gecko.background.sync.helpers.BookmarkHelpers;
10 import org.mozilla.gecko.background.sync.helpers.ExpectFetchDelegate;
11 import org.mozilla.gecko.background.sync.helpers.ExpectFetchSinceDelegate;
12 import org.mozilla.gecko.background.sync.helpers.ExpectFinishDelegate;
13 import org.mozilla.gecko.background.sync.helpers.ExpectGuidsSinceDelegate;
14 import org.mozilla.gecko.background.sync.helpers.ExpectInvalidTypeStoreDelegate;
15 import org.mozilla.gecko.db.BrowserContract;
16 import org.mozilla.gecko.sync.Utils;
17 import org.mozilla.gecko.sync.repositories.InactiveSessionException;
18 import org.mozilla.gecko.sync.repositories.NullCursorException;
19 import org.mozilla.gecko.sync.repositories.RepositorySession;
20 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksDataAccessor;
21 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepository;
22 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserBookmarksRepositorySession;
23 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepository;
24 import org.mozilla.gecko.sync.repositories.android.AndroidBrowserRepositoryDataAccessor;
25 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
26 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
27 import org.mozilla.gecko.sync.repositories.delegates.RepositorySessionCreationDelegate;
28 import org.mozilla.gecko.sync.repositories.domain.BookmarkRecord;
29 import org.mozilla.gecko.sync.repositories.domain.Record;
31 import android.content.ContentValues;
32 import android.content.Context;
33 import android.database.Cursor;
35 public class TestAndroidBrowserBookmarksRepository extends AndroidBrowserRepositoryTestCase {
37 @Override
38 protected AndroidBrowserRepository getRepository() {
40 /**
41 * Override this chain in order to avoid our test code having to create two
42 * sessions all the time.
43 */
44 return new AndroidBrowserBookmarksRepository() {
45 @Override
46 protected void sessionCreator(RepositorySessionCreationDelegate delegate, Context context) {
47 AndroidBrowserBookmarksRepositorySession session;
48 session = new AndroidBrowserBookmarksRepositorySession(this, context) {
49 @Override
50 protected synchronized void trackGUID(String guid) {
51 System.out.println("Ignoring trackGUID call: this is a test!");
52 }
53 };
54 delegate.deferredCreationDelegate().onSessionCreated(session);
55 }
56 };
57 }
59 @Override
60 protected AndroidBrowserRepositoryDataAccessor getDataAccessor() {
61 return new AndroidBrowserBookmarksDataAccessor(getApplicationContext());
62 }
64 /**
65 * Hook to return an ExpectFetchDelegate, possibly with special GUIDs ignored.
66 */
67 @Override
68 public ExpectFetchDelegate preparedExpectFetchDelegate(Record[] expected) {
69 ExpectFetchDelegate delegate = new ExpectFetchDelegate(expected);
70 delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
71 return delegate;
72 }
74 /**
75 * Hook to return an ExpectGuidsSinceDelegate expecting only special GUIDs (if there are any).
76 */
77 public ExpectGuidsSinceDelegate preparedExpectOnlySpecialGuidsSinceDelegate() {
78 ExpectGuidsSinceDelegate delegate = new ExpectGuidsSinceDelegate(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet().toArray(new String[] {}));
79 return delegate;
80 }
82 /**
83 * Hook to return an ExpectGuidsSinceDelegate, possibly with special GUIDs ignored.
84 */
85 @Override
86 public ExpectGuidsSinceDelegate preparedExpectGuidsSinceDelegate(String[] expected) {
87 ExpectGuidsSinceDelegate delegate = new ExpectGuidsSinceDelegate(expected);
88 delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
89 return delegate;
90 }
92 /**
93 * Hook to return an ExpectFetchSinceDelegate, possibly with special GUIDs ignored.
94 */
95 public ExpectFetchSinceDelegate preparedExpectFetchSinceDelegate(long timestamp, String[] expected) {
96 ExpectFetchSinceDelegate delegate = new ExpectFetchSinceDelegate(timestamp, expected);
97 delegate.ignore.addAll(AndroidBrowserBookmarksRepositorySession.SPECIAL_GUIDS_MAP.keySet());
98 return delegate;
99 }
101 // NOTE NOTE NOTE
102 // Must store folder before records if we we are checking that the
103 // records returned are the same as those sent in. If the folder isn't stored
104 // first, the returned records won't be identical to those stored because we
105 // aren't able to find the parent name/guid when we do a fetch. If you don't want
106 // to store a folder first, store your record in "mobile" or one of the folders
107 // that always exists.
109 public void testFetchOneWithChildren() {
110 BookmarkRecord folder = BookmarkHelpers.createFolder1();
111 BookmarkRecord bookmark1 = BookmarkHelpers.createBookmark1();
112 BookmarkRecord bookmark2 = BookmarkHelpers.createBookmark2();
114 RepositorySession session = createAndBeginSession();
116 Record[] records = new Record[] { folder, bookmark1, bookmark2 };
117 performWait(storeManyRunnable(session, records));
119 AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
120 helper.dumpDB();
121 closeDataAccessor(helper);
123 String[] guids = new String[] { folder.guid };
124 Record[] expected = new Record[] { folder };
125 performWait(fetchRunnable(session, guids, expected));
126 dispose(session);
127 }
129 @Override
130 public void testFetchAll() {
131 Record[] expected = new Record[3];
132 expected[0] = BookmarkHelpers.createFolder1();
133 expected[1] = BookmarkHelpers.createBookmark1();
134 expected[2] = BookmarkHelpers.createBookmark2();
135 basicFetchAllTest(expected);
136 }
138 @Override
139 public void testGuidsSinceReturnMultipleRecords() {
140 BookmarkRecord record0 = BookmarkHelpers.createBookmark1();
141 BookmarkRecord record1 = BookmarkHelpers.createBookmark2();
142 guidsSinceReturnMultipleRecords(record0, record1);
143 }
145 @Override
146 public void testGuidsSinceReturnNoRecords() {
147 guidsSinceReturnNoRecords(BookmarkHelpers.createBookmarkInMobileFolder1());
148 }
150 @Override
151 public void testFetchSinceOneRecord() {
152 fetchSinceOneRecord(BookmarkHelpers.createBookmarkInMobileFolder1(),
153 BookmarkHelpers.createBookmarkInMobileFolder2());
154 }
156 @Override
157 public void testFetchSinceReturnNoRecords() {
158 fetchSinceReturnNoRecords(BookmarkHelpers.createBookmark1());
159 }
161 @Override
162 public void testFetchOneRecordByGuid() {
163 fetchOneRecordByGuid(BookmarkHelpers.createBookmarkInMobileFolder1(),
164 BookmarkHelpers.createBookmarkInMobileFolder2());
165 }
167 @Override
168 public void testFetchMultipleRecordsByGuids() {
169 BookmarkRecord record0 = BookmarkHelpers.createFolder1();
170 BookmarkRecord record1 = BookmarkHelpers.createBookmark1();
171 BookmarkRecord record2 = BookmarkHelpers.createBookmark2();
172 fetchMultipleRecordsByGuids(record0, record1, record2);
173 }
175 @Override
176 public void testFetchNoRecordByGuid() {
177 fetchNoRecordByGuid(BookmarkHelpers.createBookmark1());
178 }
181 @Override
182 public void testWipe() {
183 doWipe(BookmarkHelpers.createBookmarkInMobileFolder1(),
184 BookmarkHelpers.createBookmarkInMobileFolder2());
185 }
187 @Override
188 public void testStore() {
189 basicStoreTest(BookmarkHelpers.createBookmark1());
190 }
193 public void testStoreFolder() {
194 basicStoreTest(BookmarkHelpers.createFolder1());
195 }
197 /**
198 * TODO: 2011-12-24, tests disabled because we no longer fail
199 * a store call if we get an unknown record type.
200 */
201 /*
202 * Test storing each different type of Bookmark record.
203 * We expect any records with type other than "bookmark"
204 * or "folder" to fail. For now we throw these away.
205 */
206 /*
207 public void testStoreMicrosummary() {
208 basicStoreFailTest(BookmarkHelpers.createMicrosummary());
209 }
211 public void testStoreQuery() {
212 basicStoreFailTest(BookmarkHelpers.createQuery());
213 }
215 public void testStoreLivemark() {
216 basicStoreFailTest(BookmarkHelpers.createLivemark());
217 }
219 public void testStoreSeparator() {
220 basicStoreFailTest(BookmarkHelpers.createSeparator());
221 }
222 */
224 protected void basicStoreFailTest(Record record) {
225 final RepositorySession session = createAndBeginSession();
226 performWait(storeRunnable(session, record, new ExpectInvalidTypeStoreDelegate()));
227 dispose(session);
228 }
230 /*
231 * Re-parenting tests
232 */
233 // Insert two records missing parent, then insert their parent.
234 // Make sure they end up with the correct parent on fetch.
235 public void testBasicReparenting() throws InactiveSessionException {
236 Record[] expected = new Record[] {
237 BookmarkHelpers.createBookmark1(),
238 BookmarkHelpers.createBookmark2(),
239 BookmarkHelpers.createFolder1()
240 };
241 doMultipleFolderReparentingTest(expected);
242 }
244 // Insert 3 folders and 4 bookmarks in different orders
245 // and make sure they come out parented correctly
246 public void testMultipleFolderReparenting1() throws InactiveSessionException {
247 Record[] expected = new Record[] {
248 BookmarkHelpers.createBookmark1(),
249 BookmarkHelpers.createBookmark2(),
250 BookmarkHelpers.createBookmark3(),
251 BookmarkHelpers.createFolder1(),
252 BookmarkHelpers.createBookmark4(),
253 BookmarkHelpers.createFolder3(),
254 BookmarkHelpers.createFolder2(),
255 };
256 doMultipleFolderReparentingTest(expected);
257 }
259 public void testMultipleFolderReparenting2() throws InactiveSessionException {
260 Record[] expected = new Record[] {
261 BookmarkHelpers.createBookmark1(),
262 BookmarkHelpers.createBookmark2(),
263 BookmarkHelpers.createBookmark3(),
264 BookmarkHelpers.createFolder1(),
265 BookmarkHelpers.createBookmark4(),
266 BookmarkHelpers.createFolder3(),
267 BookmarkHelpers.createFolder2(),
268 };
269 doMultipleFolderReparentingTest(expected);
270 }
272 public void testMultipleFolderReparenting3() throws InactiveSessionException {
273 Record[] expected = new Record[] {
274 BookmarkHelpers.createBookmark1(),
275 BookmarkHelpers.createBookmark2(),
276 BookmarkHelpers.createBookmark3(),
277 BookmarkHelpers.createFolder1(),
278 BookmarkHelpers.createBookmark4(),
279 BookmarkHelpers.createFolder3(),
280 BookmarkHelpers.createFolder2(),
281 };
282 doMultipleFolderReparentingTest(expected);
283 }
285 private void doMultipleFolderReparentingTest(Record[] expected) throws InactiveSessionException {
286 final RepositorySession session = createAndBeginSession();
287 doStore(session, expected);
288 ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
289 performWait(fetchAllRunnable(session, delegate));
290 performWait(finishRunnable(session, new ExpectFinishDelegate()));
291 }
293 /*
294 * Test storing identical records with different guids.
295 * For bookmarks identical is defined by the following fields
296 * being the same: title, uri, type, parentName
297 */
298 @Override
299 public void testStoreIdenticalExceptGuid() {
300 storeIdenticalExceptGuid(BookmarkHelpers.createBookmarkInMobileFolder1());
301 }
303 /*
304 * More complicated situation in which we insert a folder
305 * followed by a couple of its children. We then insert
306 * the folder again but with a different guid. Children
307 * must still get correct parent when they are fetched.
308 * Store a record after with the new guid as the parent
309 * and make sure it works as well.
310 */
311 public void testStoreIdenticalFoldersWithChildren() {
312 final RepositorySession session = createAndBeginSession();
313 Record record0 = BookmarkHelpers.createFolder1();
315 // Get timestamp so that the conflicting folder that we store below is newer.
316 // Children won't come back on this fetch since they haven't been stored, so remove them
317 // before our delegate throws a failure.
318 BookmarkRecord rec0 = (BookmarkRecord) record0;
319 rec0.children = new JSONArray();
320 performWait(storeRunnable(session, record0));
322 ExpectFetchDelegate timestampDelegate = preparedExpectFetchDelegate(new Record[] { rec0 });
323 performWait(fetchRunnable(session, new String[] { record0.guid }, timestampDelegate));
325 AndroidBrowserRepositoryDataAccessor helper = getDataAccessor();
326 helper.dumpDB();
327 closeDataAccessor(helper);
329 Record record1 = BookmarkHelpers.createBookmark1();
330 Record record2 = BookmarkHelpers.createBookmark2();
331 Record record3 = BookmarkHelpers.createFolder1();
332 BookmarkRecord bmk3 = (BookmarkRecord) record3;
333 record3.guid = Utils.generateGuid();
334 record3.lastModified = timestampDelegate.records.get(0).lastModified + 3000;
335 assert(!record0.guid.equals(record3.guid));
337 // Store an additional record after inserting the duplicate folder
338 // with new GUID. Make sure it comes back as well.
339 Record record4 = BookmarkHelpers.createBookmark3();
340 BookmarkRecord bmk4 = (BookmarkRecord) record4;
341 bmk4.parentID = bmk3.guid;
342 bmk4.parentName = bmk3.parentName;
344 doStore(session, new Record[] {
345 record1, record2, record3, bmk4
346 });
347 BookmarkRecord bmk1 = (BookmarkRecord) record1;
348 bmk1.parentID = record3.guid;
349 BookmarkRecord bmk2 = (BookmarkRecord) record2;
350 bmk2.parentID = record3.guid;
351 Record[] expect = new Record[] {
352 bmk1, bmk2, record3
353 };
354 fetchAllRunnable(session, preparedExpectFetchDelegate(expect));
355 dispose(session);
356 }
358 @Override
359 public void testRemoteNewerTimeStamp() {
360 BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
361 BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
362 remoteNewerTimeStamp(local, remote);
363 }
365 @Override
366 public void testLocalNewerTimeStamp() {
367 BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
368 BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
369 localNewerTimeStamp(local, remote);
370 }
372 @Override
373 public void testDeleteRemoteNewer() {
374 BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
375 BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
376 deleteRemoteNewer(local, remote);
377 }
379 @Override
380 public void testDeleteLocalNewer() {
381 BookmarkRecord local = BookmarkHelpers.createBookmarkInMobileFolder1();
382 BookmarkRecord remote = BookmarkHelpers.createBookmarkInMobileFolder2();
383 deleteLocalNewer(local, remote);
384 }
386 @Override
387 public void testDeleteRemoteLocalNonexistent() {
388 BookmarkRecord remote = BookmarkHelpers.createBookmark2();
389 deleteRemoteLocalNonexistent(remote);
390 }
392 @Override
393 public void testCleanMultipleRecords() {
394 cleanMultipleRecords(
395 BookmarkHelpers.createBookmarkInMobileFolder1(),
396 BookmarkHelpers.createBookmarkInMobileFolder2(),
397 BookmarkHelpers.createBookmark1(),
398 BookmarkHelpers.createBookmark2(),
399 BookmarkHelpers.createFolder1());
400 }
402 public void testBasicPositioning() {
403 final RepositorySession session = createAndBeginSession();
404 Record[] expected = new Record[] {
405 BookmarkHelpers.createBookmark1(),
406 BookmarkHelpers.createFolder1(),
407 BookmarkHelpers.createBookmark2()
408 };
409 System.out.println("TEST: Inserting " + expected[0].guid + ", "
410 + expected[1].guid + ", "
411 + expected[2].guid);
412 doStore(session, expected);
414 ExpectFetchDelegate delegate = preparedExpectFetchDelegate(expected);
415 performWait(fetchAllRunnable(session, delegate));
417 int found = 0;
418 boolean foundFolder = false;
419 for (int i = 0; i < delegate.records.size(); i++) {
420 BookmarkRecord rec = (BookmarkRecord) delegate.records.get(i);
421 if (rec.guid.equals(expected[0].guid)) {
422 assertEquals(0, ((BookmarkRecord) delegate.records.get(i)).androidPosition);
423 found++;
424 } else if (rec.guid.equals(expected[2].guid)) {
425 assertEquals(1, ((BookmarkRecord) delegate.records.get(i)).androidPosition);
426 found++;
427 } else if (rec.guid.equals(expected[1].guid)) {
428 foundFolder = true;
429 } else {
430 System.out.println("TEST: found " + rec.guid);
431 }
432 }
433 assertTrue(foundFolder);
434 assertEquals(2, found);
435 dispose(session);
436 }
438 public void testSqlInjectPurgeDeleteAndUpdateByGuid() {
439 // Some setup.
440 RepositorySession session = createAndBeginSession();
441 AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
443 ContentValues cv = new ContentValues();
444 cv.put(BrowserContract.SyncColumns.IS_DELETED, 1);
446 // Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
447 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
448 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
449 bmk2.guid = "' or '1'='1";
451 db.insert(bmk1);
452 db.insert(bmk2);
454 // Test 1 - updateByGuid() handles evil bookmarks correctly.
455 db.updateByGuid(bmk2.guid, cv);
457 // Query bookmarks table.
458 Cursor cur = getAllBookmarks();
459 int numBookmarks = cur.getCount();
461 // Ensure only the evil bookmark is marked for deletion.
462 try {
463 cur.moveToFirst();
464 while (!cur.isAfterLast()) {
465 String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
466 boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
468 if (guid.equals(bmk2.guid)) {
469 assertTrue(deleted);
470 } else {
471 assertFalse(deleted);
472 }
473 cur.moveToNext();
474 }
475 } finally {
476 cur.close();
477 }
479 // Test 2 - Ensure purgeDelete()'s call to delete() deletes only 1 record.
480 try {
481 db.purgeDeleted();
482 } catch (NullCursorException e) {
483 e.printStackTrace();
484 }
486 cur = getAllBookmarks();
487 int numBookmarksAfterDeletion = cur.getCount();
489 // Ensure we have only 1 deleted row.
490 assertEquals(numBookmarksAfterDeletion, numBookmarks - 1);
492 // Ensure only the evil bookmark is deleted.
493 try {
494 cur.moveToFirst();
495 while (!cur.isAfterLast()) {
496 String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
497 boolean deleted = RepoUtils.getLongFromCursor(cur, BrowserContract.SyncColumns.IS_DELETED) == 1;
499 if (guid.equals(bmk2.guid)) {
500 fail("Evil guid was not deleted!");
501 } else {
502 assertFalse(deleted);
503 }
504 cur.moveToNext();
505 }
506 } finally {
507 cur.close();
508 }
509 dispose(session);
510 }
512 protected Cursor getAllBookmarks() {
513 Context context = getApplicationContext();
514 Cursor cur = context.getContentResolver().query(BrowserContractHelpers.BOOKMARKS_CONTENT_URI,
515 BrowserContractHelpers.BookmarkColumns, null, null, null);
516 return cur;
517 }
519 public void testSqlInjectFetch() {
520 // Some setup.
521 RepositorySession session = createAndBeginSession();
522 AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
524 // Create and insert 4 bookmarks, last one is evil (attempts injection).
525 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
526 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
527 BookmarkRecord bmk3 = BookmarkHelpers.createBookmark3();
528 BookmarkRecord bmk4 = BookmarkHelpers.createBookmark4();
529 bmk4.guid = "' or '1'='1";
531 db.insert(bmk1);
532 db.insert(bmk2);
533 db.insert(bmk3);
534 db.insert(bmk4);
536 // Perform a fetch.
537 Cursor cur = null;
538 try {
539 cur = db.fetch(new String[] { bmk3.guid, bmk4.guid });
540 } catch (NullCursorException e1) {
541 e1.printStackTrace();
542 }
544 // Ensure the correct number (2) of records were fetched and with the correct guids.
545 if (cur == null) {
546 fail("No records were fetched.");
547 }
549 try {
550 if (cur.getCount() != 2) {
551 fail("Wrong number of guids fetched!");
552 }
553 cur.moveToFirst();
554 while (!cur.isAfterLast()) {
555 String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
556 if (!guid.equals(bmk3.guid) && !guid.equals(bmk4.guid)) {
557 fail("Wrong guids were fetched!");
558 }
559 cur.moveToNext();
560 }
561 } finally {
562 cur.close();
563 }
564 dispose(session);
565 }
567 public void testSqlInjectDelete() {
568 // Some setup.
569 RepositorySession session = createAndBeginSession();
570 AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
572 // Create and insert 2 bookmarks, 2nd one is evil (attempts injection).
573 BookmarkRecord bmk1 = BookmarkHelpers.createBookmark1();
574 BookmarkRecord bmk2 = BookmarkHelpers.createBookmark2();
575 bmk2.guid = "' or '1'='1";
577 db.insert(bmk1);
578 db.insert(bmk2);
580 // Note size of table before delete.
581 Cursor cur = getAllBookmarks();
582 int numBookmarks = cur.getCount();
584 db.purgeGuid(bmk2.guid);
586 // Note size of table after delete.
587 cur = getAllBookmarks();
588 int numBookmarksAfterDelete = cur.getCount();
590 // Ensure size of table after delete is *only* 1 less.
591 assertEquals(numBookmarksAfterDelete, numBookmarks - 1);
593 try {
594 cur.moveToFirst();
595 while (!cur.isAfterLast()) {
596 String guid = RepoUtils.getStringFromCursor(cur, BrowserContract.SyncColumns.GUID);
597 if (guid.equals(bmk2.guid)) {
598 fail("Guid was not deleted!");
599 }
600 cur.moveToNext();
601 }
602 } finally {
603 cur.close();
604 }
605 dispose(session);
606 }
608 /**
609 * Verify that data accessor's bulkInsert actually inserts.
610 * @throws NullCursorException
611 */
612 public void testBulkInsert() throws NullCursorException {
613 RepositorySession session = createAndBeginSession();
614 AndroidBrowserRepositoryDataAccessor db = getDataAccessor();
616 // Have to set androidID of parent manually.
617 Cursor cur = db.fetch(new String[] { "mobile" } );
618 assertEquals(1, cur.getCount());
619 cur.moveToFirst();
620 int mobileAndroidID = RepoUtils.getIntFromCursor(cur, BrowserContract.Bookmarks._ID);
622 BookmarkRecord bookmark1 = BookmarkHelpers.createBookmarkInMobileFolder1();
623 BookmarkRecord bookmark2 = BookmarkHelpers.createBookmarkInMobileFolder2();
624 bookmark1.androidParentID = mobileAndroidID;
625 bookmark2.androidParentID = mobileAndroidID;
626 ArrayList<Record> recordList = new ArrayList<Record>();
627 recordList.add(bookmark1);
628 recordList.add(bookmark2);
629 db.bulkInsert(recordList);
631 String[] guids = new String[] { bookmark1.guid, bookmark2.guid };
632 Record[] expected = new Record[] { bookmark1, bookmark2 };
633 performWait(fetchRunnable(session, guids, expected));
634 dispose(session);
635 }
636 }