Revert 168224 - Update V8 to version 3.15.4.
[chromium-blink-merge.git] / chrome / browser / sync / profile_sync_service_bookmark_unittest.cc
blob2f9f00f64828444bdc2b97c678b03fa0e971a013
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // TODO(akalin): This file is basically just a unit test for
6 // BookmarkChangeProcessor. Write unit tests for
7 // BookmarkModelAssociator separately.
9 #include <map>
10 #include <queue>
11 #include <stack>
12 #include <vector>
14 #include "base/command_line.h"
15 #include "base/file_path.h"
16 #include "base/file_util.h"
17 #include "base/location.h"
18 #include "base/memory/scoped_ptr.h"
19 #include "base/message_loop.h"
20 #include "base/string16.h"
21 #include "base/string_number_conversions.h"
22 #include "base/string_util.h"
23 #include "base/time.h"
24 #include "base/utf_string_conversions.h"
25 #include "chrome/browser/bookmarks/base_bookmark_model_observer.h"
26 #include "chrome/browser/bookmarks/bookmark_model.h"
27 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
28 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
29 #include "chrome/browser/sync/glue/bookmark_model_associator.h"
30 #include "chrome/browser/sync/glue/data_type_error_handler.h"
31 #include "chrome/browser/sync/glue/data_type_error_handler_mock.h"
32 #include "chrome/common/chrome_switches.h"
33 #include "chrome/test/base/testing_profile.h"
34 #include "content/public/test/test_browser_thread.h"
35 #include "sync/api/sync_error.h"
36 #include "sync/internal_api/public/change_record.h"
37 #include "sync/internal_api/public/read_node.h"
38 #include "sync/internal_api/public/read_transaction.h"
39 #include "sync/internal_api/public/test/test_user_share.h"
40 #include "sync/internal_api/public/write_node.h"
41 #include "sync/internal_api/public/write_transaction.h"
42 #include "sync/syncable/mutable_entry.h" // TODO(tim): Remove. Bug 131130.
43 #include "testing/gmock/include/gmock/gmock.h"
44 #include "testing/gtest/include/gtest/gtest.h"
46 namespace browser_sync {
48 using content::BrowserThread;
49 using syncer::BaseNode;
50 using testing::_;
51 using testing::InvokeWithoutArgs;
52 using testing::Mock;
53 using testing::StrictMock;
55 #if defined(OS_ANDROID)
56 static const bool kExpectMobileBookmarks = true;
57 #else
58 static const bool kExpectMobileBookmarks = false;
59 #endif // defined(OS_ANDROID)
61 namespace {
63 // FakeServerChange constructs a list of syncer::ChangeRecords while modifying
64 // the sync model, and can pass the ChangeRecord list to a
65 // syncer::SyncObserver (i.e., the ProfileSyncService) to test the client
66 // change-application behavior.
67 // Tests using FakeServerChange should be careful to avoid back-references,
68 // since FakeServerChange will send the edits in the order specified.
69 class FakeServerChange {
70 public:
71 explicit FakeServerChange(syncer::WriteTransaction* trans) : trans_(trans) {
74 // Pretend that the server told the syncer to add a bookmark object.
75 int64 Add(const std::wstring& title,
76 const std::string& url,
77 bool is_folder,
78 int64 parent_id,
79 int64 predecessor_id) {
80 syncer::ReadNode parent(trans_);
81 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id));
82 syncer::WriteNode node(trans_);
83 if (predecessor_id == 0) {
84 EXPECT_TRUE(node.InitByCreation(syncer::BOOKMARKS, parent, NULL));
85 } else {
86 syncer::ReadNode predecessor(trans_);
87 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id));
88 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
89 EXPECT_TRUE(node.InitByCreation(syncer::BOOKMARKS, parent,
90 &predecessor));
92 EXPECT_EQ(node.GetPredecessorId(), predecessor_id);
93 EXPECT_EQ(node.GetParentId(), parent_id);
94 node.SetIsFolder(is_folder);
95 node.SetTitle(title);
96 if (!is_folder)
97 node.SetURL(GURL(url));
98 syncer::ChangeRecord record;
99 record.action = syncer::ChangeRecord::ACTION_ADD;
100 record.id = node.GetId();
101 changes_.push_back(record);
102 return node.GetId();
105 // Add a bookmark folder.
106 int64 AddFolder(const std::wstring& title,
107 int64 parent_id,
108 int64 predecessor_id) {
109 return Add(title, std::string(), true, parent_id, predecessor_id);
112 // Add a bookmark.
113 int64 AddURL(const std::wstring& title,
114 const std::string& url,
115 int64 parent_id,
116 int64 predecessor_id) {
117 return Add(title, url, false, parent_id, predecessor_id);
120 // Pretend that the server told the syncer to delete an object.
121 void Delete(int64 id) {
123 // Delete the sync node.
124 syncer::WriteNode node(trans_);
125 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
126 EXPECT_FALSE(node.GetFirstChildId());
127 node.Remove();
130 // Verify the deletion.
131 syncer::ReadNode node(trans_);
132 EXPECT_EQ(BaseNode::INIT_FAILED_ENTRY_IS_DEL, node.InitByIdLookup(id));
135 syncer::ChangeRecord record;
136 record.action = syncer::ChangeRecord::ACTION_DELETE;
137 record.id = id;
138 // Deletions are always first in the changelist, but we can't actually do
139 // WriteNode::Remove() on the node until its children are moved. So, as
140 // a practical matter, users of FakeServerChange must move or delete
141 // children before parents. Thus, we must insert the deletion record
142 // at the front of the vector.
143 changes_.insert(changes_.begin(), record);
146 // Set a new title value, and return the old value.
147 std::wstring ModifyTitle(int64 id, const std::wstring& new_title) {
148 syncer::WriteNode node(trans_);
149 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
150 std::string old_title = node.GetTitle();
151 node.SetTitle(new_title);
152 SetModified(id);
153 return UTF8ToWide(old_title);
156 // Set a new parent and predecessor value. Return the old parent id.
157 // We could return the old predecessor id, but it turns out not to be
158 // very useful for assertions.
159 int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) {
160 syncer::ReadNode parent(trans_);
161 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id));
162 syncer::WriteNode node(trans_);
163 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
164 int64 old_parent_id = node.GetParentId();
165 if (predecessor_id == 0) {
166 EXPECT_TRUE(node.SetPosition(parent, NULL));
167 } else {
168 syncer::ReadNode predecessor(trans_);
169 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id));
170 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
171 EXPECT_TRUE(node.SetPosition(parent, &predecessor));
173 SetModified(id);
174 return old_parent_id;
177 void ModifyCreationTime(int64 id, int64 creation_time_us) {
178 syncer::WriteNode node(trans_);
179 ASSERT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
180 sync_pb::BookmarkSpecifics specifics = node.GetBookmarkSpecifics();
181 specifics.set_creation_time_us(creation_time_us);
182 node.SetBookmarkSpecifics(specifics);
183 SetModified(id);
186 // Pass the fake change list to |service|.
187 void ApplyPendingChanges(ChangeProcessor* processor) {
188 processor->ApplyChangesFromSyncModel(
189 trans_, 0, syncer::ImmutableChangeRecordList(&changes_));
192 const syncer::ChangeRecordList& changes() {
193 return changes_;
196 private:
197 // Helper function to push an ACTION_UPDATE record onto the back
198 // of the changelist.
199 void SetModified(int64 id) {
200 // Coalesce multi-property edits.
201 if (!changes_.empty() && changes_.back().id == id &&
202 changes_.back().action ==
203 syncer::ChangeRecord::ACTION_UPDATE)
204 return;
205 syncer::ChangeRecord record;
206 record.action = syncer::ChangeRecord::ACTION_UPDATE;
207 record.id = id;
208 changes_.push_back(record);
211 // The transaction on which everything happens.
212 syncer::WriteTransaction *trans_;
214 // The change list we construct.
215 syncer::ChangeRecordList changes_;
218 class ExtensiveChangesBookmarkModelObserver : public BaseBookmarkModelObserver {
219 public:
220 explicit ExtensiveChangesBookmarkModelObserver()
221 : started_count_(0),
222 completed_count_at_started_(0),
223 completed_count_(0) {}
225 virtual void ExtensiveBookmarkChangesBeginning(
226 BookmarkModel* model) OVERRIDE {
227 ++started_count_;
228 completed_count_at_started_ = completed_count_;
231 virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) OVERRIDE {
232 ++completed_count_;
235 void BookmarkModelChanged() {}
237 int get_started() const {
238 return started_count_;
241 int get_completed_count_at_started() const {
242 return completed_count_at_started_;
245 int get_completed() const {
246 return completed_count_;
249 private:
250 int started_count_;
251 int completed_count_at_started_;
252 int completed_count_;
254 DISALLOW_COPY_AND_ASSIGN(ExtensiveChangesBookmarkModelObserver);
258 class ProfileSyncServiceBookmarkTest : public testing::Test {
259 protected:
260 enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE };
261 enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE };
263 ProfileSyncServiceBookmarkTest()
264 : model_(NULL),
265 ui_thread_(BrowserThread::UI, &message_loop_),
266 file_thread_(BrowserThread::FILE, &message_loop_),
267 local_merge_result_(syncer::BOOKMARKS),
268 syncer_merge_result_(syncer::BOOKMARKS) {
271 virtual ~ProfileSyncServiceBookmarkTest() {
272 StopSync();
273 UnloadBookmarkModel();
276 virtual void SetUp() {
277 test_user_share_.SetUp();
280 virtual void TearDown() {
281 test_user_share_.TearDown();
284 // Load (or re-load) the bookmark model. |load| controls use of the
285 // bookmarks file on disk. |save| controls whether the newly loaded
286 // bookmark model will write out a bookmark file as it goes.
287 void LoadBookmarkModel(LoadOption load, SaveOption save) {
288 bool delete_bookmarks = load == DELETE_EXISTING_STORAGE;
289 profile_.CreateBookmarkModel(delete_bookmarks);
290 model_ = BookmarkModelFactory::GetForProfile(&profile_);
291 // Wait for the bookmarks model to load.
292 profile_.BlockUntilBookmarkModelLoaded();
293 // This noticeably speeds up the unit tests that request it.
294 if (save == DONT_SAVE_TO_STORAGE)
295 model_->ClearStore();
296 message_loop_.RunAllPending();
299 int GetSyncBookmarkCount() {
300 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
301 syncer::ReadNode node(&trans);
302 if (node.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::BOOKMARKS)) !=
303 syncer::BaseNode::INIT_OK)
304 return 0;
305 return node.GetTotalNodeCount();
308 // Creates the bookmark root node and the permanent nodes if they don't
309 // already exist.
310 bool CreatePermanentBookmarkNodes() {
311 bool root_exists = false;
312 syncer::ModelType type = syncer::BOOKMARKS;
314 syncer::WriteTransaction trans(FROM_HERE,
315 test_user_share_.user_share());
316 syncer::ReadNode uber_root(&trans);
317 uber_root.InitByRootLookup();
319 syncer::ReadNode root(&trans);
320 root_exists = (root.InitByTagLookup(syncer::ModelTypeToRootTag(type)) ==
321 BaseNode::INIT_OK);
324 if (!root_exists) {
325 if (!syncer::TestUserShare::CreateRoot(type,
326 test_user_share_.user_share()))
327 return false;
330 const int kNumPermanentNodes = 3;
331 const std::string permanent_tags[kNumPermanentNodes] = {
332 "bookmark_bar", "other_bookmarks", "synced_bookmarks"
334 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
335 syncer::ReadNode root(&trans);
336 EXPECT_EQ(BaseNode::INIT_OK, root.InitByTagLookup(
337 syncer::ModelTypeToRootTag(type)));
339 // Loop through creating permanent nodes as necessary.
340 int64 last_child_id = syncer::kInvalidId;
341 for (int i = 0; i < kNumPermanentNodes; ++i) {
342 // First check if the node already exists. This is for tests that involve
343 // persistence and set up sync more than once.
344 syncer::ReadNode lookup(&trans);
345 if (lookup.InitByTagLookup(permanent_tags[i]) ==
346 syncer::ReadNode::INIT_OK) {
347 last_child_id = lookup.GetId();
348 continue;
351 // If it doesn't exist, create the permanent node at the end of the
352 // ordering.
353 syncer::ReadNode predecessor_node(&trans);
354 syncer::ReadNode* predecessor = NULL;
355 if (last_child_id != syncer::kInvalidId) {
356 EXPECT_EQ(BaseNode::INIT_OK,
357 predecessor_node.InitByIdLookup(last_child_id));
358 predecessor = &predecessor_node;
360 syncer::WriteNode node(&trans);
361 if (!node.InitByCreation(type, root, predecessor))
362 return false;
363 node.SetIsFolder(true);
364 node.GetMutableEntryForTest()->Put(
365 syncer::syncable::UNIQUE_SERVER_TAG, permanent_tags[i]);
366 node.SetTitle(UTF8ToWide(permanent_tags[i]));
367 node.SetExternalId(0);
368 last_child_id = node.GetId();
370 return true;
373 void StartSync() {
374 ASSERT_TRUE(CreatePermanentBookmarkNodes());
376 // Set up model associator.
377 model_associator_.reset(new BookmarkModelAssociator(
378 BookmarkModelFactory::GetForProfile(&profile_),
379 test_user_share_.user_share(),
380 &mock_error_handler_,
381 kExpectMobileBookmarks));
383 local_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS);
384 syncer_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS);
385 int local_count_before = model_->root_node()->GetTotalNodeCount();
386 int syncer_count_before = GetSyncBookmarkCount();
388 syncer::SyncError error = model_associator_->AssociateModels(
389 &local_merge_result_,
390 &syncer_merge_result_);
391 EXPECT_FALSE(error.IsSet());
393 // Verify the merge results were calculated properly.
394 EXPECT_EQ(local_count_before,
395 local_merge_result_.num_items_before_association());
396 EXPECT_EQ(syncer_count_before,
397 syncer_merge_result_.num_items_before_association());
398 EXPECT_EQ(local_merge_result_.num_items_after_association(),
399 local_merge_result_.num_items_before_association() +
400 local_merge_result_.num_items_added() -
401 local_merge_result_.num_items_deleted());
402 EXPECT_EQ(syncer_merge_result_.num_items_after_association(),
403 syncer_merge_result_.num_items_before_association() +
404 syncer_merge_result_.num_items_added() -
405 syncer_merge_result_.num_items_deleted());
406 EXPECT_EQ(model_->root_node()->GetTotalNodeCount(),
407 local_merge_result_.num_items_after_association());
408 EXPECT_EQ(GetSyncBookmarkCount(),
409 syncer_merge_result_.num_items_after_association());
411 MessageLoop::current()->RunAllPending();
413 // Set up change processor.
414 change_processor_.reset(
415 new BookmarkChangeProcessor(model_associator_.get(),
416 &mock_error_handler_));
417 change_processor_->Start(&profile_, test_user_share_.user_share());
420 void StopSync() {
421 change_processor_.reset();
422 syncer::SyncError error = model_associator_->DisassociateModels();
423 EXPECT_FALSE(error.IsSet());
424 model_associator_.reset();
426 message_loop_.RunAllPending();
428 // TODO(akalin): Actually close the database and flush it to disk
429 // (and make StartSync reload from disk). This would require
430 // refactoring TestUserShare.
433 void UnloadBookmarkModel() {
434 profile_.CreateBookmarkModel(false /* delete_bookmarks */);
435 model_ = NULL;
436 message_loop_.RunAllPending();
439 bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode,
440 syncer::BaseNode* sync_node) {
441 return model_associator_->InitSyncNodeFromChromeId(bnode->id(),
442 sync_node);
445 void ExpectSyncerNodeMatching(syncer::BaseTransaction* trans,
446 const BookmarkNode* bnode) {
447 syncer::ReadNode gnode(trans);
448 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode));
449 // Non-root node titles and parents must match.
450 if (!model_->is_permanent_node(bnode)) {
451 EXPECT_EQ(bnode->GetTitle(), UTF8ToUTF16(gnode.GetTitle()));
452 EXPECT_EQ(
453 model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()),
454 bnode->parent());
456 EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder());
457 if (bnode->is_url())
458 EXPECT_EQ(bnode->url(), gnode.GetURL());
460 // Check for position matches.
461 int browser_index = bnode->parent()->GetIndexOf(bnode);
462 if (browser_index == 0) {
463 EXPECT_EQ(gnode.GetPredecessorId(), 0);
464 } else {
465 const BookmarkNode* bprev =
466 bnode->parent()->GetChild(browser_index - 1);
467 syncer::ReadNode gprev(trans);
468 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev));
469 EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId());
470 EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId());
472 if (browser_index == bnode->parent()->child_count() - 1) {
473 EXPECT_EQ(gnode.GetSuccessorId(), 0);
474 } else {
475 const BookmarkNode* bnext =
476 bnode->parent()->GetChild(browser_index + 1);
477 syncer::ReadNode gnext(trans);
478 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext));
479 EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId());
480 EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId());
482 if (!bnode->empty())
483 EXPECT_TRUE(gnode.GetFirstChildId());
486 void ExpectSyncerNodeMatching(const BookmarkNode* bnode) {
487 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
488 ExpectSyncerNodeMatching(&trans, bnode);
491 void ExpectBrowserNodeMatching(syncer::BaseTransaction* trans,
492 int64 sync_id) {
493 EXPECT_TRUE(sync_id);
494 const BookmarkNode* bnode =
495 model_associator_->GetChromeNodeFromSyncId(sync_id);
496 ASSERT_TRUE(bnode);
497 int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id());
498 EXPECT_EQ(id, sync_id);
499 ExpectSyncerNodeMatching(trans, bnode);
502 void ExpectBrowserNodeUnknown(int64 sync_id) {
503 EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id));
506 void ExpectBrowserNodeKnown(int64 sync_id) {
507 EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id));
510 void ExpectSyncerNodeKnown(const BookmarkNode* node) {
511 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
512 EXPECT_NE(sync_id, syncer::kInvalidId);
515 void ExpectSyncerNodeUnknown(const BookmarkNode* node) {
516 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
517 EXPECT_EQ(sync_id, syncer::kInvalidId);
520 void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) {
521 const BookmarkNode* bnode =
522 model_associator_->GetChromeNodeFromSyncId(sync_id);
523 ASSERT_TRUE(bnode);
524 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title));
527 void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) {
528 const BookmarkNode* bnode =
529 model_associator_->GetChromeNodeFromSyncId(sync_id);
530 ASSERT_TRUE(bnode);
531 EXPECT_EQ(GURL(url), bnode->url());
534 void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) {
535 const BookmarkNode* node =
536 model_associator_->GetChromeNodeFromSyncId(sync_id);
537 ASSERT_TRUE(node);
538 const BookmarkNode* parent =
539 model_associator_->GetChromeNodeFromSyncId(parent_sync_id);
540 EXPECT_TRUE(parent);
541 EXPECT_EQ(node->parent(), parent);
544 void ExpectModelMatch(syncer::BaseTransaction* trans) {
545 const BookmarkNode* root = model_->root_node();
546 EXPECT_EQ(root->GetIndexOf(model_->bookmark_bar_node()), 0);
547 EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1);
548 EXPECT_EQ(root->GetIndexOf(model_->mobile_node()), 2);
550 std::stack<int64> stack;
551 stack.push(bookmark_bar_id());
552 while (!stack.empty()) {
553 int64 id = stack.top();
554 stack.pop();
555 if (!id) continue;
557 ExpectBrowserNodeMatching(trans, id);
559 syncer::ReadNode gnode(trans);
560 ASSERT_EQ(BaseNode::INIT_OK, gnode.InitByIdLookup(id));
561 stack.push(gnode.GetFirstChildId());
562 stack.push(gnode.GetSuccessorId());
566 void ExpectModelMatch() {
567 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
568 ExpectModelMatch(&trans);
571 int64 mobile_bookmarks_id() {
572 return
573 model_associator_->GetSyncIdFromChromeId(model_->mobile_node()->id());
576 int64 other_bookmarks_id() {
577 return
578 model_associator_->GetSyncIdFromChromeId(model_->other_node()->id());
581 int64 bookmark_bar_id() {
582 return model_associator_->GetSyncIdFromChromeId(
583 model_->bookmark_bar_node()->id());
586 protected:
587 BookmarkModel* model_;
588 syncer::TestUserShare test_user_share_;
589 scoped_ptr<BookmarkChangeProcessor> change_processor_;
590 StrictMock<DataTypeErrorHandlerMock> mock_error_handler_;
591 scoped_ptr<BookmarkModelAssociator> model_associator_;
593 private:
594 // Used by both |ui_thread_| and |file_thread_|.
595 MessageLoop message_loop_;
596 content::TestBrowserThread ui_thread_;
597 // Needed by |model_|.
598 content::TestBrowserThread file_thread_;
600 syncer::SyncMergeResult local_merge_result_;
601 syncer::SyncMergeResult syncer_merge_result_;
603 TestingProfile profile_;
606 TEST_F(ProfileSyncServiceBookmarkTest, InitialState) {
607 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
608 StartSync();
610 EXPECT_TRUE(other_bookmarks_id());
611 EXPECT_TRUE(bookmark_bar_id());
612 EXPECT_TRUE(mobile_bookmarks_id());
614 ExpectModelMatch();
617 TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) {
618 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
619 StartSync();
621 // Test addition.
622 const BookmarkNode* folder =
623 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foobar"));
624 ExpectSyncerNodeMatching(folder);
625 ExpectModelMatch();
626 const BookmarkNode* folder2 =
627 model_->AddFolder(folder, 0, ASCIIToUTF16("nested"));
628 ExpectSyncerNodeMatching(folder2);
629 ExpectModelMatch();
630 const BookmarkNode* url1 = model_->AddURL(
631 folder, 0, ASCIIToUTF16("Internets #1 Pies Site"),
632 GURL("http://www.easypie.com/"));
633 ExpectSyncerNodeMatching(url1);
634 ExpectModelMatch();
635 const BookmarkNode* url2 = model_->AddURL(
636 folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/"));
637 ExpectSyncerNodeMatching(url2);
638 ExpectModelMatch();
639 // Test addition.
640 const BookmarkNode* mobile_folder =
641 model_->AddFolder(model_->mobile_node(), 0, ASCIIToUTF16("pie"));
642 ExpectSyncerNodeMatching(mobile_folder);
643 ExpectModelMatch();
645 // Test modification.
646 model_->SetTitle(url2, ASCIIToUTF16("EasyJet"));
647 ExpectModelMatch();
648 model_->Move(url1, folder2, 0);
649 ExpectModelMatch();
650 model_->Move(folder2, model_->bookmark_bar_node(), 0);
651 ExpectModelMatch();
652 model_->SetTitle(folder2, ASCIIToUTF16("Not Nested"));
653 ExpectModelMatch();
654 model_->Move(folder, folder2, 0);
655 ExpectModelMatch();
656 model_->SetTitle(folder, ASCIIToUTF16("who's nested now?"));
657 ExpectModelMatch();
658 model_->Copy(url2, model_->bookmark_bar_node(), 0);
659 ExpectModelMatch();
660 model_->SetTitle(mobile_folder, ASCIIToUTF16("strawberry"));
661 ExpectModelMatch();
663 // Test deletion.
664 // Delete a single item.
665 model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2));
666 ExpectModelMatch();
667 // Delete an item with several children.
668 model_->Remove(folder2->parent(),
669 folder2->parent()->GetIndexOf(folder2));
670 ExpectModelMatch();
671 model_->Remove(model_->mobile_node(), 0);
672 ExpectModelMatch();
675 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) {
676 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
677 StartSync();
679 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
681 FakeServerChange adds(&trans);
682 int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0);
683 int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1);
684 int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/",
685 bookmark_bar_id(), f2);
686 int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0);
687 // u3 is a duplicate URL
688 int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2);
689 // u4 is a duplicate title, different URL.
690 adds.AddURL(L"Some old site", "http://slog.thestranger.com/",
691 bookmark_bar_id(), u1);
692 // u5 tests an empty-string title.
693 std::string javascript_url(
694 "javascript:(function(){var w=window.open(" \
695 "'about:blank','gnotesWin','location=0,menubar=0," \
696 "scrollbars=0,status=0,toolbar=0,width=300," \
697 "height=300,resizable');});");
698 adds.AddURL(L"", javascript_url, other_bookmarks_id(), 0);
699 int64 u6 = adds.AddURL(L"Sync1", "http://www.syncable.edu/",
700 mobile_bookmarks_id(), 0);
702 syncer::ChangeRecordList::const_iterator it;
703 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
704 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
705 ExpectBrowserNodeUnknown(it->id);
707 adds.ApplyPendingChanges(change_processor_.get());
709 // Make sure the bookmark model received all of the nodes in |adds|.
710 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
711 ExpectBrowserNodeMatching(&trans, it->id);
712 ExpectModelMatch(&trans);
714 // Part two: test modifications.
715 FakeServerChange mods(&trans);
716 // Mess with u2, and move it into empty folder f2
717 // TODO(ncarter): Determine if we allow ModifyURL ops or not.
718 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */
719 std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google");
720 int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0);
722 // Now move f1 after u2.
723 std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C");
724 int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2);
726 // Then add u3 after f1.
727 int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1);
729 std::wstring u6_old_title = mods.ModifyTitle(u6, L"Mobile Folder A");
731 // Test that the property changes have not yet taken effect.
732 ExpectBrowserNodeTitle(u2, u2_old_title);
733 /* ExpectBrowserNodeURL(u2, u2_old_url); */
734 ExpectBrowserNodeParent(u2, u2_old_parent);
736 ExpectBrowserNodeTitle(f1, f1_old_title);
737 ExpectBrowserNodeParent(f1, f1_old_parent);
739 ExpectBrowserNodeParent(u3, u3_old_parent);
741 ExpectBrowserNodeTitle(u6, u6_old_title);
743 // Apply the changes.
744 mods.ApplyPendingChanges(change_processor_.get());
746 // Check for successful application.
747 for (it = mods.changes().begin(); it != mods.changes().end(); ++it)
748 ExpectBrowserNodeMatching(&trans, it->id);
749 ExpectModelMatch(&trans);
751 // Part 3: Test URL deletion.
752 FakeServerChange dels(&trans);
753 dels.Delete(u2);
754 dels.Delete(u3);
755 dels.Delete(u6);
757 ExpectBrowserNodeKnown(u2);
758 ExpectBrowserNodeKnown(u3);
760 dels.ApplyPendingChanges(change_processor_.get());
762 ExpectBrowserNodeUnknown(u2);
763 ExpectBrowserNodeUnknown(u3);
764 ExpectBrowserNodeUnknown(u6);
765 ExpectModelMatch(&trans);
768 // Tests a specific case in ApplyModelChanges where we move the
769 // children out from under a parent, and then delete the parent
770 // in the same changelist. The delete shows up first in the changelist,
771 // requiring the children to be moved to a temporary location.
772 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) {
773 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
774 StartSync();
776 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
778 // Stress the immediate children of other_node because that's where
779 // ApplyModelChanges puts a temporary foster parent node.
780 std::string url("http://dev.chromium.org/");
781 FakeServerChange adds(&trans);
782 int64 f0 = other_bookmarks_id(); // + other_node
783 int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1
784 int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2
785 int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT
786 int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT
787 int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT
788 int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6
789 int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT
791 syncer::ChangeRecordList::const_iterator it;
792 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
793 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
794 ExpectBrowserNodeUnknown(it->id);
796 adds.ApplyPendingChanges(change_processor_.get());
798 // Make sure the bookmark model received all of the nodes in |adds|.
799 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
800 ExpectBrowserNodeMatching(&trans, it->id);
801 ExpectModelMatch(&trans);
803 // We have to do the moves before the deletions, but FakeServerChange will
804 // put the deletion at the front of the changelist.
805 FakeServerChange ops(&trans);
806 ops.ModifyPosition(f6, other_bookmarks_id(), 0);
807 ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here.
808 ops.ModifyPosition(f2, other_bookmarks_id(), u7);
809 ops.ModifyPosition(u7, f2, 0);
810 ops.ModifyPosition(u4, other_bookmarks_id(), f2);
811 ops.ModifyPosition(u5, f6, 0);
812 ops.Delete(f1);
814 ops.ApplyPendingChanges(change_processor_.get());
816 ExpectModelMatch(&trans);
819 // Simulate a server change record containing a valid but non-canonical URL.
820 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) {
821 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
822 StartSync();
825 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
827 FakeServerChange adds(&trans);
828 std::string url("http://dev.chromium.org");
829 EXPECT_NE(GURL(url).spec(), url);
830 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
832 adds.ApplyPendingChanges(change_processor_.get());
834 EXPECT_TRUE(model_->other_node()->child_count() == 1);
835 ExpectModelMatch(&trans);
838 // Now reboot the sync service, forcing a merge step.
839 StopSync();
840 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
841 StartSync();
843 // There should still be just the one bookmark.
844 EXPECT_TRUE(model_->other_node()->child_count() == 1);
845 ExpectModelMatch();
848 // Simulate a server change record containing an invalid URL (per GURL).
849 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563.
850 TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) {
851 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
852 StartSync();
854 int child_count = 0;
856 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
858 FakeServerChange adds(&trans);
859 std::string url("x");
860 EXPECT_FALSE(GURL(url).is_valid());
861 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
863 adds.ApplyPendingChanges(change_processor_.get());
865 // We're lenient about what should happen -- the model could wind up with
866 // the node or without it; but things should be consistent, and we
867 // shouldn't crash.
868 child_count = model_->other_node()->child_count();
869 EXPECT_TRUE(child_count == 0 || child_count == 1);
870 ExpectModelMatch(&trans);
873 // Now reboot the sync service, forcing a merge step.
874 StopSync();
875 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
876 StartSync();
878 // Things ought not to have changed.
879 EXPECT_EQ(model_->other_node()->child_count(), child_count);
880 ExpectModelMatch();
884 // Test strings that might pose a problem if the titles ever became used as
885 // file names in the sync backend.
886 TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) {
887 // TODO(ncarter): Bug 1570238 explains the failure of this test.
888 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
889 StartSync();
891 const char* names[] = {
892 // The empty string.
894 // Illegal Windows filenames.
895 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4",
896 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3",
897 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
898 // Current/parent directory markers.
899 ".", "..", "...",
900 // Files created automatically by the Windows shell.
901 "Thumbs.db", ".DS_Store",
902 // Names including Win32-illegal characters, and path separators.
903 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar",
904 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar",
905 "foo[bar",
907 // Create both folders and bookmarks using each name.
908 GURL url("http://www.doublemint.com");
909 for (size_t i = 0; i < arraysize(names); ++i) {
910 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16(names[i]));
911 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url);
914 // Verify that the browser model matches the sync model.
915 EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names));
916 ExpectModelMatch();
919 // Stress the internal representation of position by sparse numbers. We want
920 // to repeatedly bisect the range of available positions, to force the
921 // syncer code to renumber its ranges. Pick a number big enough so that it
922 // would exhaust 32bits of room between items a couple of times.
923 TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) {
924 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
925 StartSync();
927 static const int kTimesToInsert = 256;
929 // Create two book-end nodes to insert between.
930 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("Alpha"));
931 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("Omega"));
932 int count = 2;
934 // Test insertion in first half of range by repeatedly inserting in second
935 // position.
936 for (int i = 0; i < kTimesToInsert; ++i) {
937 string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i);
938 model_->AddFolder(model_->other_node(), 1, title);
939 count++;
942 // Test insertion in second half of range by repeatedly inserting in
943 // second-to-last position.
944 for (int i = 0; i < kTimesToInsert; ++i) {
945 string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i);
946 model_->AddFolder(model_->other_node(), count - 1, title);
947 count++;
950 // Verify that the browser model matches the sync model.
951 EXPECT_EQ(model_->other_node()->child_count(), count);
952 ExpectModelMatch();
955 // Introduce a consistency violation into the model, and see that it
956 // puts itself into a lame, error state.
957 TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) {
958 EXPECT_CALL(mock_error_handler_,
959 OnSingleDatatypeUnrecoverableError(_, _));
961 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
962 StartSync();
964 // Add a node which will be the target of the consistency violation.
965 const BookmarkNode* node =
966 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("node"));
967 ExpectSyncerNodeMatching(node);
969 // Now destroy the syncer node as if we were the ProfileSyncService without
970 // updating the ProfileSyncService state. This should introduce
971 // inconsistency between the two models.
973 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
974 syncer::WriteNode sync_node(&trans);
975 ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node));
976 sync_node.Remove();
978 // The models don't match at this point, but the ProfileSyncService
979 // doesn't know it yet.
980 ExpectSyncerNodeKnown(node);
982 // Add a child to the inconsistent node. This should cause detection of the
983 // problem and the syncer should stop processing changes.
984 model_->AddFolder(node, 0, ASCIIToUTF16("nested"));
987 // See what happens if we run model association when there are two exact URL
988 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when
989 // this happens.
990 TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) {
991 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
992 StartSync();
994 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"),
995 GURL("http://dup.com/"));
996 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"),
997 GURL("http://dup.com/"));
999 EXPECT_EQ(2, model_->other_node()->child_count());
1001 // Restart the sync service to trigger model association.
1002 StopSync();
1003 StartSync();
1005 EXPECT_EQ(2, model_->other_node()->child_count());
1006 ExpectModelMatch();
1009 struct TestData {
1010 const wchar_t* title;
1011 const char* url;
1014 // Map from bookmark node ID to its version.
1015 typedef std::map<int64, int64> BookmarkNodeVersionMap;
1017 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code
1018 // in the bookmark model unittest, to make it simpler to set up test data
1019 // here (and reduce the amount of duplication among tests), and to reduce the
1020 // duplication.
1021 class ProfileSyncServiceBookmarkTestWithData
1022 : public ProfileSyncServiceBookmarkTest {
1023 public:
1024 ProfileSyncServiceBookmarkTestWithData();
1026 protected:
1027 // Populates or compares children of the given bookmark node from/with the
1028 // given test data array with the given size. |running_count| is updated as
1029 // urls are added. It is used to set the creation date (or test the creation
1030 // date for CompareWithTestData()).
1031 void PopulateFromTestData(const BookmarkNode* node,
1032 const TestData* data,
1033 int size,
1034 int* running_count);
1035 void CompareWithTestData(const BookmarkNode* node,
1036 const TestData* data,
1037 int size,
1038 int* running_count);
1040 void ExpectBookmarkModelMatchesTestData();
1041 void WriteTestDataToBookmarkModel();
1043 // Verify transaction versions of bookmark nodes and sync nodes are equal
1044 // recursively. If node is in |version_expected|, versions should match
1045 // there, too.
1046 void ExpectTransactionVersionMatch(
1047 const BookmarkNode* node,
1048 const BookmarkNodeVersionMap& version_expected);
1050 private:
1051 const base::Time start_time_;
1053 DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceBookmarkTestWithData);
1056 namespace {
1058 // Constants for bookmark model that looks like:
1059 // |-- Bookmark bar
1060 // | |-- u2, http://www.u2.com/
1061 // | |-- f1
1062 // | | |-- f1u4, http://www.f1u4.com/
1063 // | | |-- f1u2, http://www.f1u2.com/
1064 // | | |-- f1u3, http://www.f1u3.com/
1065 // | | +-- f1u1, http://www.f1u1.com/
1066 // | |-- u1, http://www.u1.com/
1067 // | +-- f2
1068 // | |-- f2u2, http://www.f2u2.com/
1069 // | |-- f2u4, http://www.f2u4.com/
1070 // | |-- f2u3, http://www.f2u3.com/
1071 // | +-- f2u1, http://www.f2u1.com/
1072 // +-- Other bookmarks
1073 // | |-- f3
1074 // | | |-- f3u4, http://www.f3u4.com/
1075 // | | |-- f3u2, http://www.f3u2.com/
1076 // | | |-- f3u3, http://www.f3u3.com/
1077 // | | +-- f3u1, http://www.f3u1.com/
1078 // | |-- u4, http://www.u4.com/
1079 // | |-- u3, http://www.u3.com/
1080 // | --- f4
1081 // | | |-- f4u1, http://www.f4u1.com/
1082 // | | |-- f4u2, http://www.f4u2.com/
1083 // | | |-- f4u3, http://www.f4u3.com/
1084 // | | +-- f4u4, http://www.f4u4.com/
1085 // | |-- dup
1086 // | | +-- dupu1, http://www.dupu1.com/
1087 // | +-- dup
1088 // | | +-- dupu2, http://www.dupu1.com/
1089 // | +-- ls , http://www.ls.com/
1090 // |
1091 // +-- Mobile bookmarks
1092 // |-- f5
1093 // | |-- f5u1, http://www.f5u1.com/
1094 // |-- f6
1095 // | |-- f6u1, http://www.f6u1.com/
1096 // | |-- f6u2, http://www.f6u2.com/
1097 // +-- u5, http://www.u5.com/
1099 static TestData kBookmarkBarChildren[] = {
1100 { L"u2", "http://www.u2.com/" },
1101 { L"f1", NULL },
1102 { L"u1", "http://www.u1.com/" },
1103 { L"f2", NULL },
1105 static TestData kF1Children[] = {
1106 { L"f1u4", "http://www.f1u4.com/" },
1107 { L"f1u2", "http://www.f1u2.com/" },
1108 { L"f1u3", "http://www.f1u3.com/" },
1109 { L"f1u1", "http://www.f1u1.com/" },
1111 static TestData kF2Children[] = {
1112 { L"f2u2", "http://www.f2u2.com/" },
1113 { L"f2u4", "http://www.f2u4.com/" },
1114 { L"f2u3", "http://www.f2u3.com/" },
1115 { L"f2u1", "http://www.f2u1.com/" },
1118 static TestData kOtherBookmarkChildren[] = {
1119 { L"f3", NULL },
1120 { L"u4", "http://www.u4.com/" },
1121 { L"u3", "http://www.u3.com/" },
1122 { L"f4", NULL },
1123 { L"dup", NULL },
1124 { L"dup", NULL },
1125 { L" ls ", "http://www.ls.com/" }
1127 static TestData kF3Children[] = {
1128 { L"f3u4", "http://www.f3u4.com/" },
1129 { L"f3u2", "http://www.f3u2.com/" },
1130 { L"f3u3", "http://www.f3u3.com/" },
1131 { L"f3u1", "http://www.f3u1.com/" },
1133 static TestData kF4Children[] = {
1134 { L"f4u1", "http://www.f4u1.com/" },
1135 { L"f4u2", "http://www.f4u2.com/" },
1136 { L"f4u3", "http://www.f4u3.com/" },
1137 { L"f4u4", "http://www.f4u4.com/" },
1139 static TestData kDup1Children[] = {
1140 { L"dupu1", "http://www.dupu1.com/" },
1142 static TestData kDup2Children[] = {
1143 { L"dupu2", "http://www.dupu2.com/" },
1146 static TestData kMobileBookmarkChildren[] = {
1147 { L"f5", NULL },
1148 { L"f6", NULL },
1149 { L"u5", "http://www.u5.com/" },
1151 static TestData kF5Children[] = {
1152 { L"f5u1", "http://www.f5u1.com/" },
1153 { L"f5u2", "http://www.f5u2.com/" },
1155 static TestData kF6Children[] = {
1156 { L"f6u1", "http://www.f6u1.com/" },
1157 { L"f6u2", "http://www.f6u2.com/" },
1160 } // anonymous namespace.
1162 ProfileSyncServiceBookmarkTestWithData::
1163 ProfileSyncServiceBookmarkTestWithData()
1164 : start_time_(base::Time::Now()) {
1167 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData(
1168 const BookmarkNode* node,
1169 const TestData* data,
1170 int size,
1171 int* running_count) {
1172 DCHECK(node);
1173 DCHECK(data);
1174 DCHECK(node->is_folder());
1175 for (int i = 0; i < size; ++i) {
1176 const TestData& item = data[i];
1177 if (item.url) {
1178 const base::Time add_time =
1179 start_time_ + base::TimeDelta::FromMinutes(*running_count);
1180 model_->AddURLWithCreationTime(node, i, WideToUTF16Hack(item.title),
1181 GURL(item.url), add_time);
1182 } else {
1183 model_->AddFolder(node, i, WideToUTF16Hack(item.title));
1185 (*running_count)++;
1189 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData(
1190 const BookmarkNode* node,
1191 const TestData* data,
1192 int size,
1193 int* running_count) {
1194 DCHECK(node);
1195 DCHECK(data);
1196 DCHECK(node->is_folder());
1197 ASSERT_EQ(size, node->child_count());
1198 for (int i = 0; i < size; ++i) {
1199 const BookmarkNode* child_node = node->GetChild(i);
1200 const TestData& item = data[i];
1201 GURL url = GURL(item.url == NULL ? "" : item.url);
1202 BookmarkNode test_node(url);
1203 test_node.SetTitle(WideToUTF16Hack(item.title));
1204 EXPECT_EQ(child_node->GetTitle(), test_node.GetTitle());
1205 if (item.url) {
1206 EXPECT_FALSE(child_node->is_folder());
1207 EXPECT_TRUE(child_node->is_url());
1208 EXPECT_EQ(child_node->url(), test_node.url());
1209 const base::Time expected_time =
1210 start_time_ + base::TimeDelta::FromMinutes(*running_count);
1211 EXPECT_EQ(expected_time.ToInternalValue(),
1212 child_node->date_added().ToInternalValue());
1213 } else {
1214 EXPECT_TRUE(child_node->is_folder());
1215 EXPECT_FALSE(child_node->is_url());
1217 (*running_count)++;
1221 // TODO(munjal): We should implement some way of generating random data and can
1222 // use the same seed to generate the same sequence.
1223 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() {
1224 const BookmarkNode* bookmarks_bar_node = model_->bookmark_bar_node();
1225 int count = 0;
1226 PopulateFromTestData(bookmarks_bar_node,
1227 kBookmarkBarChildren,
1228 arraysize(kBookmarkBarChildren),
1229 &count);
1231 ASSERT_GE(bookmarks_bar_node->child_count(), 4);
1232 const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1);
1233 PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children), &count);
1234 const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3);
1235 PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children), &count);
1237 const BookmarkNode* other_bookmarks_node = model_->other_node();
1238 PopulateFromTestData(other_bookmarks_node,
1239 kOtherBookmarkChildren,
1240 arraysize(kOtherBookmarkChildren),
1241 &count);
1243 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1244 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1245 PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children), &count);
1246 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1247 PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children), &count);
1248 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1249 PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children),
1250 &count);
1251 dup_node = other_bookmarks_node->GetChild(5);
1252 PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children),
1253 &count);
1255 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node();
1256 PopulateFromTestData(mobile_bookmarks_node,
1257 kMobileBookmarkChildren,
1258 arraysize(kMobileBookmarkChildren),
1259 &count);
1261 ASSERT_GE(mobile_bookmarks_node->child_count(), 3);
1262 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0);
1263 PopulateFromTestData(f5_node, kF5Children, arraysize(kF5Children), &count);
1264 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1);
1265 PopulateFromTestData(f6_node, kF6Children, arraysize(kF6Children), &count);
1267 ExpectBookmarkModelMatchesTestData();
1270 void ProfileSyncServiceBookmarkTestWithData::
1271 ExpectBookmarkModelMatchesTestData() {
1272 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1273 int count = 0;
1274 CompareWithTestData(bookmark_bar_node,
1275 kBookmarkBarChildren,
1276 arraysize(kBookmarkBarChildren),
1277 &count);
1279 ASSERT_GE(bookmark_bar_node->child_count(), 4);
1280 const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1);
1281 CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children), &count);
1282 const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3);
1283 CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children), &count);
1285 const BookmarkNode* other_bookmarks_node = model_->other_node();
1286 CompareWithTestData(other_bookmarks_node,
1287 kOtherBookmarkChildren,
1288 arraysize(kOtherBookmarkChildren),
1289 &count);
1291 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1292 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1293 CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children), &count);
1294 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1295 CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children), &count);
1296 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1297 CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children),
1298 &count);
1299 dup_node = other_bookmarks_node->GetChild(5);
1300 CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children),
1301 &count);
1303 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node();
1304 CompareWithTestData(mobile_bookmarks_node,
1305 kMobileBookmarkChildren,
1306 arraysize(kMobileBookmarkChildren),
1307 &count);
1309 ASSERT_GE(mobile_bookmarks_node->child_count(), 3);
1310 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0);
1311 CompareWithTestData(f5_node, kF5Children, arraysize(kF5Children), &count);
1312 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1);
1313 CompareWithTestData(f6_node, kF6Children, arraysize(kF6Children), &count);
1317 // Tests persistence of the profile sync service by unloading the
1318 // database and then reloading it from disk.
1319 TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) {
1320 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1321 StartSync();
1323 WriteTestDataToBookmarkModel();
1325 ExpectModelMatch();
1327 // Force both models to discard their data and reload from disk. This
1328 // simulates what would happen if the browser were to shutdown normally,
1329 // and then relaunch.
1330 StopSync();
1331 UnloadBookmarkModel();
1332 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
1333 StartSync();
1335 ExpectBookmarkModelMatchesTestData();
1337 // With the BookmarkModel contents verified, ExpectModelMatch will
1338 // verify the contents of the sync model.
1339 ExpectModelMatch();
1342 // Tests the merge case when the BookmarkModel is non-empty but the
1343 // sync model is empty. This corresponds to uploading browser
1344 // bookmarks to an initially empty, new account.
1345 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) {
1346 // Don't start the sync service until we've populated the bookmark model.
1347 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1349 WriteTestDataToBookmarkModel();
1351 // Restart sync. This should trigger a merge step during
1352 // initialization -- we expect the browser bookmarks to be written
1353 // to the sync service during this call.
1354 StartSync();
1356 // Verify that the bookmark model hasn't changed, and that the sync model
1357 // matches it exactly.
1358 ExpectBookmarkModelMatchesTestData();
1359 ExpectModelMatch();
1362 // Tests the merge case when the BookmarkModel is empty but the sync model is
1363 // non-empty. This corresponds (somewhat) to a clean install of the browser,
1364 // with no bookmarks, connecting to a sync account that has some bookmarks.
1365 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) {
1366 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1367 StartSync();
1369 WriteTestDataToBookmarkModel();
1371 ExpectModelMatch();
1373 // Force the databse to unload and write itself to disk.
1374 StopSync();
1376 // Blow away the bookmark model -- it should be empty afterwards.
1377 UnloadBookmarkModel();
1378 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1379 EXPECT_EQ(model_->bookmark_bar_node()->child_count(), 0);
1380 EXPECT_EQ(model_->other_node()->child_count(), 0);
1381 EXPECT_EQ(model_->mobile_node()->child_count(), 0);
1383 // Now restart the sync service. Starting it should populate the bookmark
1384 // model -- test for consistency.
1385 StartSync();
1386 ExpectBookmarkModelMatchesTestData();
1387 ExpectModelMatch();
1390 // Tests the merge cases when both the models are expected to be identical
1391 // after the merge.
1392 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) {
1393 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1394 StartSync();
1395 WriteTestDataToBookmarkModel();
1396 ExpectModelMatch();
1397 StopSync();
1398 UnloadBookmarkModel();
1400 // At this point both the bookmark model and the server should have the
1401 // exact same data and it should match the test data.
1402 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1403 StartSync();
1404 ExpectBookmarkModelMatchesTestData();
1405 ExpectModelMatch();
1406 StopSync();
1407 UnloadBookmarkModel();
1409 // Now reorder some bookmarks in the bookmark model and then merge. Make
1410 // sure we get the order of the server after merge.
1411 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1412 ExpectBookmarkModelMatchesTestData();
1413 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node();
1414 ASSERT_TRUE(bookmark_bar);
1415 ASSERT_GT(bookmark_bar->child_count(), 1);
1416 model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1);
1417 StartSync();
1418 ExpectModelMatch();
1419 ExpectBookmarkModelMatchesTestData();
1422 // Tests the merge cases when both the models are expected to be identical
1423 // after the merge.
1424 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) {
1425 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1426 WriteTestDataToBookmarkModel();
1427 ExpectBookmarkModelMatchesTestData();
1429 // Remove some nodes and reorder some nodes.
1430 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1431 int remove_index = 2;
1432 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1433 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index);
1434 ASSERT_TRUE(child_node);
1435 ASSERT_TRUE(child_node->is_url());
1436 model_->Remove(bookmark_bar_node, remove_index);
1437 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1438 child_node = bookmark_bar_node->GetChild(remove_index);
1439 ASSERT_TRUE(child_node);
1440 ASSERT_TRUE(child_node->is_folder());
1441 model_->Remove(bookmark_bar_node, remove_index);
1443 const BookmarkNode* other_node = model_->other_node();
1444 ASSERT_GE(other_node->child_count(), 1);
1445 const BookmarkNode* f3_node = other_node->GetChild(0);
1446 ASSERT_TRUE(f3_node);
1447 ASSERT_TRUE(f3_node->is_folder());
1448 remove_index = 2;
1449 ASSERT_GT(f3_node->child_count(), remove_index);
1450 model_->Remove(f3_node, remove_index);
1451 ASSERT_GT(f3_node->child_count(), remove_index);
1452 model_->Remove(f3_node, remove_index);
1454 StartSync();
1455 ExpectModelMatch();
1456 StopSync();
1458 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1459 WriteTestDataToBookmarkModel();
1460 ExpectBookmarkModelMatchesTestData();
1462 // Remove some nodes and reorder some nodes.
1463 bookmark_bar_node = model_->bookmark_bar_node();
1464 remove_index = 0;
1465 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1466 child_node = bookmark_bar_node->GetChild(remove_index);
1467 ASSERT_TRUE(child_node);
1468 ASSERT_TRUE(child_node->is_url());
1469 model_->Remove(bookmark_bar_node, remove_index);
1470 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1471 child_node = bookmark_bar_node->GetChild(remove_index);
1472 ASSERT_TRUE(child_node);
1473 ASSERT_TRUE(child_node->is_folder());
1474 model_->Remove(bookmark_bar_node, remove_index);
1476 ASSERT_GE(bookmark_bar_node->child_count(), 2);
1477 model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1);
1479 other_node = model_->other_node();
1480 ASSERT_GE(other_node->child_count(), 1);
1481 f3_node = other_node->GetChild(0);
1482 ASSERT_TRUE(f3_node);
1483 ASSERT_TRUE(f3_node->is_folder());
1484 remove_index = 0;
1485 ASSERT_GT(f3_node->child_count(), remove_index);
1486 model_->Remove(f3_node, remove_index);
1487 ASSERT_GT(f3_node->child_count(), remove_index);
1488 model_->Remove(f3_node, remove_index);
1490 ASSERT_GE(other_node->child_count(), 4);
1491 model_->Move(other_node->GetChild(0), other_node, 1);
1492 model_->Move(other_node->GetChild(2), other_node, 3);
1494 StartSync();
1495 ExpectModelMatch();
1497 // After the merge, the model should match the test data.
1498 ExpectBookmarkModelMatchesTestData();
1501 // Tests that when persisted model associations are used, things work fine.
1502 TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) {
1503 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1504 WriteTestDataToBookmarkModel();
1505 StartSync();
1506 ExpectModelMatch();
1507 // Force sync to shut down and write itself to disk.
1508 StopSync();
1509 // Now restart sync. This time it should use the persistent
1510 // associations.
1511 StartSync();
1512 ExpectModelMatch();
1515 // Tests that when persisted model associations are used, things work fine.
1516 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1517 ModelAssociationInvalidPersistence) {
1518 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1519 WriteTestDataToBookmarkModel();
1520 StartSync();
1521 ExpectModelMatch();
1522 // Force sync to shut down and write itself to disk.
1523 StopSync();
1524 // Change the bookmark model before restarting sync service to simulate
1525 // the situation where bookmark model is different from sync model and
1526 // make sure model associator correctly rebuilds associations.
1527 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1528 model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"),
1529 GURL("http://www.xtra.com"));
1530 // Now restart sync. This time it will try to use the persistent
1531 // associations and realize that they are invalid and hence will rebuild
1532 // associations.
1533 StartSync();
1534 ExpectModelMatch();
1537 TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) {
1538 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1539 StartSync();
1541 // Write test data to bookmark model and verify that the models match.
1542 WriteTestDataToBookmarkModel();
1543 const BookmarkNode* folder_added = model_->other_node()->GetChild(0);
1544 ASSERT_TRUE(folder_added);
1545 ASSERT_TRUE(folder_added->is_folder());
1547 ExpectModelMatch();
1549 // Sort the other-bookmarks children and expect that hte models match.
1550 model_->SortChildren(folder_added);
1551 ExpectModelMatch();
1554 // See what happens if we enable sync but then delete the "Sync Data"
1555 // folder.
1556 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1557 RecoverAfterDeletingSyncDataDirectory) {
1558 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1559 StartSync();
1561 WriteTestDataToBookmarkModel();
1563 StopSync();
1565 // Nuke the sync DB and reload.
1566 TearDown();
1567 SetUp();
1569 StartSync();
1571 // Make sure we're back in sync. In real life, the user would need
1572 // to reauthenticate before this happens, but in the test, authentication
1573 // is sidestepped.
1574 ExpectBookmarkModelMatchesTestData();
1575 ExpectModelMatch();
1578 // Verify that the bookmark model is updated about whether the
1579 // associator is currently running.
1580 TEST_F(ProfileSyncServiceBookmarkTest, AssociationState) {
1581 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1583 ExtensiveChangesBookmarkModelObserver observer;
1584 model_->AddObserver(&observer);
1586 StartSync();
1588 EXPECT_EQ(1, observer.get_started());
1589 EXPECT_EQ(0, observer.get_completed_count_at_started());
1590 EXPECT_EQ(1, observer.get_completed());
1592 model_->RemoveObserver(&observer);
1595 // Verify that the creation_time_us changes are applied in the local model at
1596 // association time and update time.
1597 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateDateAdded) {
1598 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1599 WriteTestDataToBookmarkModel();
1601 // Start and stop sync in order to create bookmark nodes in the sync db.
1602 StartSync();
1603 StopSync();
1605 // Modify the date_added field of a bookmark so it doesn't match with
1606 // the sync data.
1607 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1608 int remove_index = 2;
1609 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1610 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index);
1611 ASSERT_TRUE(child_node);
1612 EXPECT_TRUE(child_node->is_url());
1613 model_->SetDateAdded(child_node, base::Time::FromInternalValue(10));
1615 StartSync();
1617 // Everything should be back in sync after model association.
1618 ExpectBookmarkModelMatchesTestData();
1619 ExpectModelMatch();
1621 // Now trigger a change while syncing. We add a new bookmark, sync it, then
1622 // updates it's creation time.
1623 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1624 FakeServerChange adds(&trans);
1625 const std::wstring kTitle = L"Some site";
1626 const std::string kUrl = "http://www.whatwhat.yeah/";
1627 const int kCreationTime = 30;
1628 int64 id = adds.AddURL(kTitle, kUrl,
1629 bookmark_bar_id(), 0);
1630 adds.ApplyPendingChanges(change_processor_.get());
1631 FakeServerChange updates(&trans);
1632 updates.ModifyCreationTime(id, kCreationTime);
1633 updates.ApplyPendingChanges(change_processor_.get());
1635 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(0);
1636 ASSERT_TRUE(node);
1637 EXPECT_TRUE(node->is_url());
1638 EXPECT_EQ(WideToUTF16Hack(kTitle), node->GetTitle());
1639 EXPECT_EQ(kUrl, node->url().possibly_invalid_spec());
1640 EXPECT_EQ(node->date_added(), base::Time::FromInternalValue(30));
1643 // Output transaction versions of |node| and nodes under it to |node_versions|.
1644 void GetTransactionVersions(
1645 const BookmarkNode* root,
1646 BookmarkNodeVersionMap* node_versions) {
1647 node_versions->clear();
1648 std::queue<const BookmarkNode*> nodes;
1649 nodes.push(root);
1650 while (!nodes.empty()) {
1651 const BookmarkNode* n = nodes.front();
1652 nodes.pop();
1654 std::string version_str;
1655 int64 version;
1656 EXPECT_TRUE(n->GetMetaInfo(kBookmarkTransactionVersionKey, &version_str));
1657 EXPECT_TRUE(base::StringToInt64(version_str, &version));
1659 (*node_versions)[n->id()] = version;
1660 for (int i = 0; i < n->child_count(); ++i)
1661 nodes.push(n->GetChild(i));
1665 void ProfileSyncServiceBookmarkTestWithData::ExpectTransactionVersionMatch(
1666 const BookmarkNode* node,
1667 const BookmarkNodeVersionMap& version_expected) {
1668 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
1670 BookmarkNodeVersionMap bnodes_versions;
1671 GetTransactionVersions(node, &bnodes_versions);
1672 for (BookmarkNodeVersionMap::const_iterator it = bnodes_versions.begin();
1673 it != bnodes_versions.end(); ++it) {
1674 syncer::ReadNode sync_node(&trans);
1675 ASSERT_TRUE(model_associator_->InitSyncNodeFromChromeId(it->first,
1676 &sync_node));
1677 EXPECT_EQ(sync_node.GetEntry()->Get(syncer::syncable::TRANSACTION_VERSION),
1678 it->second);
1679 BookmarkNodeVersionMap::const_iterator expected_ver_it =
1680 version_expected.find(it->first);
1681 if (expected_ver_it != version_expected.end())
1682 EXPECT_EQ(expected_ver_it->second, it->second);
1686 // Test transaction versions of model and nodes are incremented after changes
1687 // are applied.
1688 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateTransactionVersion) {
1689 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1690 StartSync();
1691 WriteTestDataToBookmarkModel();
1692 MessageLoop::current()->RunUntilIdle();
1694 BookmarkNodeVersionMap initial_versions;
1696 // Verify transaction versions in sync model and bookmark model (saved as
1697 // transaction version of root node) are equal after
1698 // WriteTestDataToBookmarkModel() created bookmarks.
1700 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
1701 EXPECT_GT(trans.GetModelVersion(syncer::BOOKMARKS), 0);
1702 GetTransactionVersions(model_->root_node(), &initial_versions);
1703 EXPECT_EQ(trans.GetModelVersion(syncer::BOOKMARKS),
1704 initial_versions[model_->root_node()->id()]);
1706 ExpectTransactionVersionMatch(model_->bookmark_bar_node(),
1707 BookmarkNodeVersionMap());
1708 ExpectTransactionVersionMatch(model_->other_node(),
1709 BookmarkNodeVersionMap());
1710 ExpectTransactionVersionMatch(model_->mobile_node(),
1711 BookmarkNodeVersionMap());
1713 // Verify model version is incremented and bookmark node versions remain
1714 // the same.
1715 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node();
1716 model_->Remove(bookmark_bar, 0);
1717 MessageLoop::current()->RunUntilIdle();
1718 BookmarkNodeVersionMap new_versions;
1719 GetTransactionVersions(model_->root_node(), &new_versions);
1720 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 1,
1721 new_versions[model_->root_node()->id()]);
1722 // HACK(haitaol): siblings of removed node are actually updated in sync model
1723 // because of NEXT_ID/PREV_ID. After switching to ordinal,
1724 // siblings will not get updated and the hack below can be
1725 // removed.
1726 model_->SetNodeMetaInfo(bookmark_bar->GetChild(0),
1727 kBookmarkTransactionVersionKey, "41");
1728 initial_versions[bookmark_bar->GetChild(0)->id()] = 41;
1729 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions);
1730 ExpectTransactionVersionMatch(model_->other_node(), initial_versions);
1731 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions);
1733 // Verify model version and version of changed bookmark are incremented and
1734 // versions of others remain same.
1735 const BookmarkNode* changed_bookmark =
1736 model_->bookmark_bar_node()->GetChild(0);
1737 model_->SetTitle(changed_bookmark, WideToUTF16Hack(L"test"));
1738 MessageLoop::current()->RunUntilIdle();
1739 GetTransactionVersions(model_->root_node(), &new_versions);
1740 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 2,
1741 new_versions[model_->root_node()->id()]);
1742 EXPECT_EQ(initial_versions[changed_bookmark->id()] + 1,
1743 new_versions[changed_bookmark->id()]);
1744 initial_versions.erase(changed_bookmark->id());
1745 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions);
1746 ExpectTransactionVersionMatch(model_->other_node(), initial_versions);
1747 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions);
1750 } // namespace
1752 } // namespace browser_sync