Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / sync / profile_sync_service_bookmark_unittest.cc
blob79c6b6487a5ef8ca2f862105bec4afa4f558e87a
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/files/file_path.h"
16 #include "base/location.h"
17 #include "base/memory/scoped_ptr.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/strings/string16.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/time/time.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/bookmarks/bookmark_test_helpers.h"
29 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
30 #include "chrome/browser/sync/glue/bookmark_model_associator.h"
31 #include "chrome/browser/sync/glue/data_type_error_handler.h"
32 #include "chrome/browser/sync/glue/data_type_error_handler_mock.h"
33 #include "chrome/common/chrome_switches.h"
34 #include "chrome/test/base/testing_profile.h"
35 #include "content/public/test/test_browser_thread.h"
36 #include "sync/api/sync_error.h"
37 #include "sync/internal_api/public/change_record.h"
38 #include "sync/internal_api/public/read_node.h"
39 #include "sync/internal_api/public/read_transaction.h"
40 #include "sync/internal_api/public/test/test_user_share.h"
41 #include "sync/internal_api/public/write_node.h"
42 #include "sync/internal_api/public/write_transaction.h"
43 #include "sync/syncable/mutable_entry.h" // TODO(tim): Remove. Bug 131130.
44 #include "testing/gmock/include/gmock/gmock.h"
45 #include "testing/gtest/include/gtest/gtest.h"
47 namespace browser_sync {
49 using content::BrowserThread;
50 using syncer::BaseNode;
51 using testing::_;
52 using testing::InvokeWithoutArgs;
53 using testing::Mock;
54 using testing::StrictMock;
56 #if defined(OS_ANDROID)
57 static const bool kExpectMobileBookmarks = true;
58 #else
59 static const bool kExpectMobileBookmarks = false;
60 #endif // defined(OS_ANDROID)
62 namespace {
64 // FakeServerChange constructs a list of syncer::ChangeRecords while modifying
65 // the sync model, and can pass the ChangeRecord list to a
66 // syncer::SyncObserver (i.e., the ProfileSyncService) to test the client
67 // change-application behavior.
68 // Tests using FakeServerChange should be careful to avoid back-references,
69 // since FakeServerChange will send the edits in the order specified.
70 class FakeServerChange {
71 public:
72 explicit FakeServerChange(syncer::WriteTransaction* trans) : trans_(trans) {
75 // Pretend that the server told the syncer to add a bookmark object.
76 int64 AddWithMetaInfo(const std::wstring& title,
77 const std::string& url,
78 const BookmarkNode::MetaInfoMap* meta_info_map,
79 bool is_folder,
80 int64 parent_id,
81 int64 predecessor_id) {
82 syncer::ReadNode parent(trans_);
83 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id));
84 syncer::WriteNode node(trans_);
85 if (predecessor_id == 0) {
86 EXPECT_TRUE(node.InitBookmarkByCreation(parent, NULL));
87 } else {
88 syncer::ReadNode predecessor(trans_);
89 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id));
90 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
91 EXPECT_TRUE(node.InitBookmarkByCreation(parent, &predecessor));
93 EXPECT_EQ(node.GetPredecessorId(), predecessor_id);
94 EXPECT_EQ(node.GetParentId(), parent_id);
95 node.SetIsFolder(is_folder);
96 node.SetTitle(title);
98 sync_pb::BookmarkSpecifics specifics(node.GetBookmarkSpecifics());
99 if (!is_folder)
100 specifics.set_url(url);
101 if (meta_info_map)
102 SetNodeMetaInfo(*meta_info_map, &specifics);
103 node.SetBookmarkSpecifics(specifics);
105 syncer::ChangeRecord record;
106 record.action = syncer::ChangeRecord::ACTION_ADD;
107 record.id = node.GetId();
108 changes_.push_back(record);
109 return node.GetId();
112 int64 Add(const std::wstring& title,
113 const std::string& url,
114 bool is_folder,
115 int64 parent_id,
116 int64 predecessor_id) {
117 return AddWithMetaInfo(title, url, NULL, is_folder, parent_id,
118 predecessor_id);
121 // Add a bookmark folder.
122 int64 AddFolder(const std::wstring& title,
123 int64 parent_id,
124 int64 predecessor_id) {
125 return Add(title, std::string(), true, parent_id, predecessor_id);
127 int64 AddFolderWithMetaInfo(const std::wstring& title,
128 const BookmarkNode::MetaInfoMap* meta_info_map,
129 int64 parent_id,
130 int64 predecessor_id) {
131 return AddWithMetaInfo(title, std::string(), meta_info_map, true, parent_id,
132 predecessor_id);
135 // Add a bookmark.
136 int64 AddURL(const std::wstring& title,
137 const std::string& url,
138 int64 parent_id,
139 int64 predecessor_id) {
140 return Add(title, url, false, parent_id, predecessor_id);
142 int64 AddURLWithMetaInfo(const std::wstring& title,
143 const std::string& url,
144 const BookmarkNode::MetaInfoMap* meta_info_map,
145 int64 parent_id,
146 int64 predecessor_id) {
147 return AddWithMetaInfo(title, url, meta_info_map, false, parent_id,
148 predecessor_id);
151 // Pretend that the server told the syncer to delete an object.
152 void Delete(int64 id) {
154 // Delete the sync node.
155 syncer::WriteNode node(trans_);
156 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
157 if (node.GetIsFolder())
158 EXPECT_FALSE(node.GetFirstChildId());
159 node.GetMutableEntryForTest()->PutServerIsDel(true);
160 node.Tombstone();
163 // Verify the deletion.
164 syncer::ReadNode node(trans_);
165 EXPECT_EQ(BaseNode::INIT_FAILED_ENTRY_IS_DEL, node.InitByIdLookup(id));
168 syncer::ChangeRecord record;
169 record.action = syncer::ChangeRecord::ACTION_DELETE;
170 record.id = id;
171 // Deletions are always first in the changelist, but we can't actually do
172 // WriteNode::Remove() on the node until its children are moved. So, as
173 // a practical matter, users of FakeServerChange must move or delete
174 // children before parents. Thus, we must insert the deletion record
175 // at the front of the vector.
176 changes_.insert(changes_.begin(), record);
179 // Set a new title value, and return the old value.
180 std::wstring ModifyTitle(int64 id, const std::wstring& new_title) {
181 syncer::WriteNode node(trans_);
182 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
183 std::string old_title = node.GetTitle();
184 node.SetTitle(new_title);
185 SetModified(id);
186 return base::UTF8ToWide(old_title);
189 // Set a new parent and predecessor value. Return the old parent id.
190 // We could return the old predecessor id, but it turns out not to be
191 // very useful for assertions.
192 int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) {
193 syncer::ReadNode parent(trans_);
194 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id));
195 syncer::WriteNode node(trans_);
196 EXPECT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
197 int64 old_parent_id = node.GetParentId();
198 if (predecessor_id == 0) {
199 EXPECT_TRUE(node.SetPosition(parent, NULL));
200 } else {
201 syncer::ReadNode predecessor(trans_);
202 EXPECT_EQ(BaseNode::INIT_OK, predecessor.InitByIdLookup(predecessor_id));
203 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
204 EXPECT_TRUE(node.SetPosition(parent, &predecessor));
206 SetModified(id);
207 return old_parent_id;
210 void ModifyCreationTime(int64 id, int64 creation_time_us) {
211 syncer::WriteNode node(trans_);
212 ASSERT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
213 sync_pb::BookmarkSpecifics specifics = node.GetBookmarkSpecifics();
214 specifics.set_creation_time_us(creation_time_us);
215 node.SetBookmarkSpecifics(specifics);
216 SetModified(id);
219 void ModifyMetaInfo(int64 id,
220 const BookmarkNode::MetaInfoMap& meta_info_map) {
221 syncer::WriteNode node(trans_);
222 ASSERT_EQ(BaseNode::INIT_OK, node.InitByIdLookup(id));
223 sync_pb::BookmarkSpecifics specifics = node.GetBookmarkSpecifics();
224 SetNodeMetaInfo(meta_info_map, &specifics);
225 node.SetBookmarkSpecifics(specifics);
226 SetModified(id);
229 // Pass the fake change list to |service|.
230 void ApplyPendingChanges(ChangeProcessor* processor) {
231 processor->ApplyChangesFromSyncModel(
232 trans_, 0, syncer::ImmutableChangeRecordList(&changes_));
235 const syncer::ChangeRecordList& changes() {
236 return changes_;
239 private:
240 // Helper function to push an ACTION_UPDATE record onto the back
241 // of the changelist.
242 void SetModified(int64 id) {
243 // Coalesce multi-property edits.
244 if (!changes_.empty() && changes_.back().id == id &&
245 changes_.back().action ==
246 syncer::ChangeRecord::ACTION_UPDATE)
247 return;
248 syncer::ChangeRecord record;
249 record.action = syncer::ChangeRecord::ACTION_UPDATE;
250 record.id = id;
251 changes_.push_back(record);
254 void SetNodeMetaInfo(const BookmarkNode::MetaInfoMap& meta_info_map,
255 sync_pb::BookmarkSpecifics* specifics) {
256 specifics->clear_meta_info();
257 for (BookmarkNode::MetaInfoMap::const_iterator it =
258 meta_info_map.begin(); it != meta_info_map.end(); ++it) {
259 sync_pb::MetaInfo* meta_info = specifics->add_meta_info();
260 meta_info->set_key(it->first);
261 meta_info->set_value(it->second);
266 // The transaction on which everything happens.
267 syncer::WriteTransaction *trans_;
269 // The change list we construct.
270 syncer::ChangeRecordList changes_;
273 class ExtensiveChangesBookmarkModelObserver : public BaseBookmarkModelObserver {
274 public:
275 explicit ExtensiveChangesBookmarkModelObserver()
276 : started_count_(0),
277 completed_count_at_started_(0),
278 completed_count_(0) {}
280 virtual void ExtensiveBookmarkChangesBeginning(
281 BookmarkModel* model) OVERRIDE {
282 ++started_count_;
283 completed_count_at_started_ = completed_count_;
286 virtual void ExtensiveBookmarkChangesEnded(BookmarkModel* model) OVERRIDE {
287 ++completed_count_;
290 virtual void BookmarkModelChanged() OVERRIDE {}
292 int get_started() const {
293 return started_count_;
296 int get_completed_count_at_started() const {
297 return completed_count_at_started_;
300 int get_completed() const {
301 return completed_count_;
304 private:
305 int started_count_;
306 int completed_count_at_started_;
307 int completed_count_;
309 DISALLOW_COPY_AND_ASSIGN(ExtensiveChangesBookmarkModelObserver);
313 class ProfileSyncServiceBookmarkTest : public testing::Test {
314 protected:
315 enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE };
316 enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE };
318 ProfileSyncServiceBookmarkTest()
319 : model_(NULL),
320 ui_thread_(BrowserThread::UI, &message_loop_),
321 file_thread_(BrowserThread::FILE, &message_loop_),
322 local_merge_result_(syncer::BOOKMARKS),
323 syncer_merge_result_(syncer::BOOKMARKS) {
326 virtual ~ProfileSyncServiceBookmarkTest() {
327 StopSync();
328 UnloadBookmarkModel();
331 virtual void SetUp() {
332 test_user_share_.SetUp();
335 virtual void TearDown() {
336 test_user_share_.TearDown();
339 // Inserts a folder directly to the share.
340 // Do not use this after model association is complete.
342 // This function differs from the AddFolder() function declared elsewhere in
343 // this file in that it only affects the sync model. It would be invalid to
344 // change the sync model directly after ModelAssociation. This function can
345 // be invoked prior to model association to set up first-time sync model
346 // association scenarios.
347 int64 AddFolderToShare(syncer::WriteTransaction* trans, std::string title) {
348 EXPECT_FALSE(model_associator_);
350 // Be sure to call CreatePermanentBookmarkNodes(), otherwise this will fail.
351 syncer::ReadNode bookmark_bar(trans);
352 EXPECT_EQ(BaseNode::INIT_OK, bookmark_bar.InitByTagLookup("bookmark_bar"));
354 syncer::WriteNode node(trans);
355 EXPECT_TRUE(node.InitBookmarkByCreation(bookmark_bar, NULL));
356 node.SetIsFolder(true);
357 node.SetTitle(base::ASCIIToWide(title));
359 return node.GetId();
362 // Inserts a bookmark directly to the share.
363 // Do not use this after model association is complete.
365 // This function differs from the AddURL() function declared elsewhere in this
366 // file in that it only affects the sync model. It would be invalid to change
367 // the sync model directly after ModelAssociation. This function can be
368 // invoked prior to model association to set up first-time sync model
369 // association scenarios.
370 int64 AddBookmarkToShare(syncer::WriteTransaction *trans,
371 int64 parent_id,
372 std::string title) {
373 EXPECT_FALSE(model_associator_);
375 syncer::ReadNode parent(trans);
376 EXPECT_EQ(BaseNode::INIT_OK, parent.InitByIdLookup(parent_id));
378 sync_pb::BookmarkSpecifics specifics;
379 specifics.set_url("http://www.google.com/search?q=" + title);
380 specifics.set_title(title);
382 syncer::WriteNode node(trans);
383 EXPECT_TRUE(node.InitBookmarkByCreation(parent, NULL));
384 node.SetIsFolder(false);
385 node.SetTitle(base::ASCIIToWide(title));
386 node.SetBookmarkSpecifics(specifics);
388 return node.GetId();
391 // Load (or re-load) the bookmark model. |load| controls use of the
392 // bookmarks file on disk. |save| controls whether the newly loaded
393 // bookmark model will write out a bookmark file as it goes.
394 void LoadBookmarkModel(LoadOption load, SaveOption save) {
395 bool delete_bookmarks = load == DELETE_EXISTING_STORAGE;
396 profile_.CreateBookmarkModel(delete_bookmarks);
397 model_ = BookmarkModelFactory::GetForProfile(&profile_);
398 test::WaitForBookmarkModelToLoad(model_);
399 // This noticeably speeds up the unit tests that request it.
400 if (save == DONT_SAVE_TO_STORAGE)
401 model_->ClearStore();
402 message_loop_.RunUntilIdle();
405 int GetSyncBookmarkCount() {
406 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
407 syncer::ReadNode node(&trans);
408 if (node.InitByTagLookup(syncer::ModelTypeToRootTag(syncer::BOOKMARKS)) !=
409 syncer::BaseNode::INIT_OK)
410 return 0;
411 return node.GetTotalNodeCount();
414 // Creates the bookmark root node and the permanent nodes if they don't
415 // already exist.
416 bool CreatePermanentBookmarkNodes() {
417 bool root_exists = false;
418 syncer::ModelType type = syncer::BOOKMARKS;
420 syncer::WriteTransaction trans(FROM_HERE,
421 test_user_share_.user_share());
422 syncer::ReadNode uber_root(&trans);
423 uber_root.InitByRootLookup();
425 syncer::ReadNode root(&trans);
426 root_exists = (root.InitByTagLookup(syncer::ModelTypeToRootTag(type)) ==
427 BaseNode::INIT_OK);
430 if (!root_exists) {
431 if (!syncer::TestUserShare::CreateRoot(type,
432 test_user_share_.user_share()))
433 return false;
436 const int kNumPermanentNodes = 3;
437 const std::string permanent_tags[kNumPermanentNodes] = {
438 "bookmark_bar", "other_bookmarks", "synced_bookmarks"
440 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
441 syncer::ReadNode root(&trans);
442 EXPECT_EQ(BaseNode::INIT_OK, root.InitByTagLookup(
443 syncer::ModelTypeToRootTag(type)));
445 // Loop through creating permanent nodes as necessary.
446 int64 last_child_id = syncer::kInvalidId;
447 for (int i = 0; i < kNumPermanentNodes; ++i) {
448 // First check if the node already exists. This is for tests that involve
449 // persistence and set up sync more than once.
450 syncer::ReadNode lookup(&trans);
451 if (lookup.InitByTagLookup(permanent_tags[i]) ==
452 syncer::ReadNode::INIT_OK) {
453 last_child_id = lookup.GetId();
454 continue;
457 // If it doesn't exist, create the permanent node at the end of the
458 // ordering.
459 syncer::ReadNode predecessor_node(&trans);
460 syncer::ReadNode* predecessor = NULL;
461 if (last_child_id != syncer::kInvalidId) {
462 EXPECT_EQ(BaseNode::INIT_OK,
463 predecessor_node.InitByIdLookup(last_child_id));
464 predecessor = &predecessor_node;
466 syncer::WriteNode node(&trans);
467 if (!node.InitBookmarkByCreation(root, predecessor))
468 return false;
469 node.SetIsFolder(true);
470 node.GetMutableEntryForTest()->PutUniqueServerTag(permanent_tags[i]);
471 node.SetTitle(base::UTF8ToWide(permanent_tags[i]));
472 node.SetExternalId(0);
473 last_child_id = node.GetId();
475 return true;
478 bool AssociateModels() {
479 DCHECK(!model_associator_);
481 // Set up model associator.
482 model_associator_.reset(new BookmarkModelAssociator(
483 BookmarkModelFactory::GetForProfile(&profile_),
484 &profile_,
485 test_user_share_.user_share(),
486 &mock_error_handler_,
487 kExpectMobileBookmarks));
489 local_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS);
490 syncer_merge_result_ = syncer::SyncMergeResult(syncer::BOOKMARKS);
491 int local_count_before = model_->root_node()->GetTotalNodeCount();
492 int syncer_count_before = GetSyncBookmarkCount();
494 syncer::SyncError error = model_associator_->AssociateModels(
495 &local_merge_result_,
496 &syncer_merge_result_);
497 if (error.IsSet())
498 return false;
500 base::MessageLoop::current()->RunUntilIdle();
502 // Verify the merge results were calculated properly.
503 EXPECT_EQ(local_count_before,
504 local_merge_result_.num_items_before_association());
505 EXPECT_EQ(syncer_count_before,
506 syncer_merge_result_.num_items_before_association());
507 EXPECT_EQ(local_merge_result_.num_items_after_association(),
508 local_merge_result_.num_items_before_association() +
509 local_merge_result_.num_items_added() -
510 local_merge_result_.num_items_deleted());
511 EXPECT_EQ(syncer_merge_result_.num_items_after_association(),
512 syncer_merge_result_.num_items_before_association() +
513 syncer_merge_result_.num_items_added() -
514 syncer_merge_result_.num_items_deleted());
515 EXPECT_EQ(model_->root_node()->GetTotalNodeCount(),
516 local_merge_result_.num_items_after_association());
517 EXPECT_EQ(GetSyncBookmarkCount(),
518 syncer_merge_result_.num_items_after_association());
519 return true;
522 void StartSync() {
523 test_user_share_.Reload();
525 ASSERT_TRUE(CreatePermanentBookmarkNodes());
526 ASSERT_TRUE(AssociateModels());
528 // Set up change processor.
529 change_processor_.reset(
530 new BookmarkChangeProcessor(model_associator_.get(),
531 &mock_error_handler_));
532 change_processor_->Start(&profile_, test_user_share_.user_share());
535 void StopSync() {
536 change_processor_.reset();
537 if (model_associator_) {
538 syncer::SyncError error = model_associator_->DisassociateModels();
539 EXPECT_FALSE(error.IsSet());
541 model_associator_.reset();
543 message_loop_.RunUntilIdle();
545 // TODO(akalin): Actually close the database and flush it to disk
546 // (and make StartSync reload from disk). This would require
547 // refactoring TestUserShare.
550 void UnloadBookmarkModel() {
551 profile_.CreateBookmarkModel(false /* delete_bookmarks */);
552 model_ = NULL;
553 message_loop_.RunUntilIdle();
556 bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode,
557 syncer::BaseNode* sync_node) {
558 return model_associator_->InitSyncNodeFromChromeId(bnode->id(),
559 sync_node);
562 void ExpectSyncerNodeMatching(syncer::BaseTransaction* trans,
563 const BookmarkNode* bnode) {
564 syncer::ReadNode gnode(trans);
565 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode));
566 // Non-root node titles and parents must match.
567 if (!model_->is_permanent_node(bnode)) {
568 EXPECT_EQ(bnode->GetTitle(), base::UTF8ToUTF16(gnode.GetTitle()));
569 EXPECT_EQ(
570 model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()),
571 bnode->parent());
573 EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder());
574 if (bnode->is_url())
575 EXPECT_EQ(bnode->url(), GURL(gnode.GetBookmarkSpecifics().url()));
577 // Check that meta info matches.
578 const BookmarkNode::MetaInfoMap* meta_info_map = bnode->GetMetaInfoMap();
579 sync_pb::BookmarkSpecifics specifics = gnode.GetBookmarkSpecifics();
580 if (!meta_info_map) {
581 EXPECT_EQ(0, specifics.meta_info_size());
582 } else {
583 EXPECT_EQ(meta_info_map->size(),
584 static_cast<size_t>(specifics.meta_info_size()));
585 for (int i = 0; i < specifics.meta_info_size(); i++) {
586 BookmarkNode::MetaInfoMap::const_iterator it =
587 meta_info_map->find(specifics.meta_info(i).key());
588 EXPECT_TRUE(it != meta_info_map->end());
589 EXPECT_EQ(it->second, specifics.meta_info(i).value());
593 // Check for position matches.
594 int browser_index = bnode->parent()->GetIndexOf(bnode);
595 if (browser_index == 0) {
596 EXPECT_EQ(gnode.GetPredecessorId(), 0);
597 } else {
598 const BookmarkNode* bprev =
599 bnode->parent()->GetChild(browser_index - 1);
600 syncer::ReadNode gprev(trans);
601 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev));
602 EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId());
603 EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId());
605 if (browser_index == bnode->parent()->child_count() - 1) {
606 EXPECT_EQ(gnode.GetSuccessorId(), 0);
607 } else {
608 const BookmarkNode* bnext =
609 bnode->parent()->GetChild(browser_index + 1);
610 syncer::ReadNode gnext(trans);
611 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext));
612 EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId());
613 EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId());
615 if (!bnode->empty())
616 EXPECT_TRUE(gnode.GetFirstChildId());
619 void ExpectSyncerNodeMatching(const BookmarkNode* bnode) {
620 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
621 ExpectSyncerNodeMatching(&trans, bnode);
624 void ExpectBrowserNodeMatching(syncer::BaseTransaction* trans,
625 int64 sync_id) {
626 EXPECT_TRUE(sync_id);
627 const BookmarkNode* bnode =
628 model_associator_->GetChromeNodeFromSyncId(sync_id);
629 ASSERT_TRUE(bnode);
630 int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id());
631 EXPECT_EQ(id, sync_id);
632 ExpectSyncerNodeMatching(trans, bnode);
635 void ExpectBrowserNodeUnknown(int64 sync_id) {
636 EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id));
639 void ExpectBrowserNodeKnown(int64 sync_id) {
640 EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id));
643 void ExpectSyncerNodeKnown(const BookmarkNode* node) {
644 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
645 EXPECT_NE(sync_id, syncer::kInvalidId);
648 void ExpectSyncerNodeUnknown(const BookmarkNode* node) {
649 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
650 EXPECT_EQ(sync_id, syncer::kInvalidId);
653 void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) {
654 const BookmarkNode* bnode =
655 model_associator_->GetChromeNodeFromSyncId(sync_id);
656 ASSERT_TRUE(bnode);
657 EXPECT_EQ(bnode->GetTitle(), base::WideToUTF16Hack(title));
660 void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) {
661 const BookmarkNode* bnode =
662 model_associator_->GetChromeNodeFromSyncId(sync_id);
663 ASSERT_TRUE(bnode);
664 EXPECT_EQ(GURL(url), bnode->url());
667 void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) {
668 const BookmarkNode* node =
669 model_associator_->GetChromeNodeFromSyncId(sync_id);
670 ASSERT_TRUE(node);
671 const BookmarkNode* parent =
672 model_associator_->GetChromeNodeFromSyncId(parent_sync_id);
673 EXPECT_TRUE(parent);
674 EXPECT_EQ(node->parent(), parent);
677 void ExpectModelMatch(syncer::BaseTransaction* trans) {
678 const BookmarkNode* root = model_->root_node();
679 EXPECT_EQ(root->GetIndexOf(model_->bookmark_bar_node()), 0);
680 EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1);
681 EXPECT_EQ(root->GetIndexOf(model_->mobile_node()), 2);
683 std::stack<int64> stack;
684 stack.push(bookmark_bar_id());
685 while (!stack.empty()) {
686 int64 id = stack.top();
687 stack.pop();
688 if (!id) continue;
690 ExpectBrowserNodeMatching(trans, id);
692 syncer::ReadNode gnode(trans);
693 ASSERT_EQ(BaseNode::INIT_OK, gnode.InitByIdLookup(id));
694 stack.push(gnode.GetSuccessorId());
695 if (gnode.GetIsFolder())
696 stack.push(gnode.GetFirstChildId());
700 void ExpectModelMatch() {
701 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
702 ExpectModelMatch(&trans);
705 int64 mobile_bookmarks_id() {
706 return
707 model_associator_->GetSyncIdFromChromeId(model_->mobile_node()->id());
710 int64 other_bookmarks_id() {
711 return
712 model_associator_->GetSyncIdFromChromeId(model_->other_node()->id());
715 int64 bookmark_bar_id() {
716 return model_associator_->GetSyncIdFromChromeId(
717 model_->bookmark_bar_node()->id());
720 protected:
721 BookmarkModel* model_;
722 syncer::TestUserShare test_user_share_;
723 scoped_ptr<BookmarkChangeProcessor> change_processor_;
724 StrictMock<DataTypeErrorHandlerMock> mock_error_handler_;
725 scoped_ptr<BookmarkModelAssociator> model_associator_;
727 private:
728 // Used by both |ui_thread_| and |file_thread_|.
729 base::MessageLoop message_loop_;
730 content::TestBrowserThread ui_thread_;
731 // Needed by |model_|.
732 content::TestBrowserThread file_thread_;
734 syncer::SyncMergeResult local_merge_result_;
735 syncer::SyncMergeResult syncer_merge_result_;
737 TestingProfile profile_;
740 TEST_F(ProfileSyncServiceBookmarkTest, InitialState) {
741 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
742 StartSync();
744 EXPECT_TRUE(other_bookmarks_id());
745 EXPECT_TRUE(bookmark_bar_id());
746 EXPECT_TRUE(mobile_bookmarks_id());
748 ExpectModelMatch();
751 // Populate the sync database then start model association. Sync's bookmarks
752 // should end up being copied into the native model, resulting in a successful
753 // "ExpectModelMatch()".
755 // This code has some use for verifying correctness. It's also a very useful
756 // for profiling bookmark ModelAssociation, an important part of some first-time
757 // sync scenarios. Simply increase the kNumFolders and kNumBookmarksPerFolder
758 // as desired, then run the test under a profiler to find hot spots in the model
759 // association code.
760 TEST_F(ProfileSyncServiceBookmarkTest, InitialModelAssociate) {
761 const int kNumBookmarksPerFolder = 10;
762 const int kNumFolders = 10;
764 CreatePermanentBookmarkNodes();
767 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
768 for (int i = 0; i < kNumFolders; ++i) {
769 int64 folder_id = AddFolderToShare(&trans,
770 base::StringPrintf("folder%05d", i));
771 for (int j = 0; j < kNumBookmarksPerFolder; ++j) {
772 AddBookmarkToShare(&trans,
773 folder_id,
774 base::StringPrintf("bookmark%05d", j));
779 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
780 StartSync();
782 ExpectModelMatch();
786 TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) {
787 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
788 StartSync();
790 // Test addition.
791 const BookmarkNode* folder =
792 model_->AddFolder(model_->other_node(), 0, base::ASCIIToUTF16("foobar"));
793 ExpectSyncerNodeMatching(folder);
794 ExpectModelMatch();
795 const BookmarkNode* folder2 =
796 model_->AddFolder(folder, 0, base::ASCIIToUTF16("nested"));
797 ExpectSyncerNodeMatching(folder2);
798 ExpectModelMatch();
799 const BookmarkNode* url1 = model_->AddURL(
800 folder, 0, base::ASCIIToUTF16("Internets #1 Pies Site"),
801 GURL("http://www.easypie.com/"));
802 ExpectSyncerNodeMatching(url1);
803 ExpectModelMatch();
804 const BookmarkNode* url2 = model_->AddURL(
805 folder, 1, base::ASCIIToUTF16("Airplanes"),
806 GURL("http://www.easyjet.com/"));
807 ExpectSyncerNodeMatching(url2);
808 ExpectModelMatch();
809 // Test addition.
810 const BookmarkNode* mobile_folder =
811 model_->AddFolder(model_->mobile_node(), 0, base::ASCIIToUTF16("pie"));
812 ExpectSyncerNodeMatching(mobile_folder);
813 ExpectModelMatch();
815 // Test modification.
816 model_->SetTitle(url2, base::ASCIIToUTF16("EasyJet"));
817 ExpectModelMatch();
818 model_->Move(url1, folder2, 0);
819 ExpectModelMatch();
820 model_->Move(folder2, model_->bookmark_bar_node(), 0);
821 ExpectModelMatch();
822 model_->SetTitle(folder2, base::ASCIIToUTF16("Not Nested"));
823 ExpectModelMatch();
824 model_->Move(folder, folder2, 0);
825 ExpectModelMatch();
826 model_->SetTitle(folder, base::ASCIIToUTF16("who's nested now?"));
827 ExpectModelMatch();
828 model_->Copy(url2, model_->bookmark_bar_node(), 0);
829 ExpectModelMatch();
830 model_->SetTitle(mobile_folder, base::ASCIIToUTF16("strawberry"));
831 ExpectModelMatch();
833 // Test deletion.
834 // Delete a single item.
835 model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2));
836 ExpectModelMatch();
837 // Delete an item with several children.
838 model_->Remove(folder2->parent(),
839 folder2->parent()->GetIndexOf(folder2));
840 ExpectModelMatch();
841 model_->Remove(model_->mobile_node(), 0);
842 ExpectModelMatch();
845 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) {
846 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
847 StartSync();
849 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
851 FakeServerChange adds(&trans);
852 int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0);
853 int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1);
854 int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/",
855 bookmark_bar_id(), f2);
856 int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0);
857 // u3 is a duplicate URL
858 int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2);
859 // u4 is a duplicate title, different URL.
860 adds.AddURL(L"Some old site", "http://slog.thestranger.com/",
861 bookmark_bar_id(), u1);
862 // u5 tests an empty-string title.
863 std::string javascript_url(
864 "javascript:(function(){var w=window.open(" \
865 "'about:blank','gnotesWin','location=0,menubar=0," \
866 "scrollbars=0,status=0,toolbar=0,width=300," \
867 "height=300,resizable');});");
868 adds.AddURL(std::wstring(), javascript_url, other_bookmarks_id(), 0);
869 int64 u6 = adds.AddURL(
870 L"Sync1", "http://www.syncable.edu/", mobile_bookmarks_id(), 0);
872 syncer::ChangeRecordList::const_iterator it;
873 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
874 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
875 ExpectBrowserNodeUnknown(it->id);
877 adds.ApplyPendingChanges(change_processor_.get());
879 // Make sure the bookmark model received all of the nodes in |adds|.
880 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
881 ExpectBrowserNodeMatching(&trans, it->id);
882 ExpectModelMatch(&trans);
884 // Part two: test modifications.
885 FakeServerChange mods(&trans);
886 // Mess with u2, and move it into empty folder f2
887 // TODO(ncarter): Determine if we allow ModifyURL ops or not.
888 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */
889 std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google");
890 int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0);
892 // Now move f1 after u2.
893 std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C");
894 int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2);
896 // Then add u3 after f1.
897 int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1);
899 std::wstring u6_old_title = mods.ModifyTitle(u6, L"Mobile Folder A");
901 // Test that the property changes have not yet taken effect.
902 ExpectBrowserNodeTitle(u2, u2_old_title);
903 /* ExpectBrowserNodeURL(u2, u2_old_url); */
904 ExpectBrowserNodeParent(u2, u2_old_parent);
906 ExpectBrowserNodeTitle(f1, f1_old_title);
907 ExpectBrowserNodeParent(f1, f1_old_parent);
909 ExpectBrowserNodeParent(u3, u3_old_parent);
911 ExpectBrowserNodeTitle(u6, u6_old_title);
913 // Apply the changes.
914 mods.ApplyPendingChanges(change_processor_.get());
916 // Check for successful application.
917 for (it = mods.changes().begin(); it != mods.changes().end(); ++it)
918 ExpectBrowserNodeMatching(&trans, it->id);
919 ExpectModelMatch(&trans);
921 // Part 3: Test URL deletion.
922 FakeServerChange dels(&trans);
923 dels.Delete(u2);
924 dels.Delete(u3);
925 dels.Delete(u6);
927 ExpectBrowserNodeKnown(u2);
928 ExpectBrowserNodeKnown(u3);
930 dels.ApplyPendingChanges(change_processor_.get());
932 ExpectBrowserNodeUnknown(u2);
933 ExpectBrowserNodeUnknown(u3);
934 ExpectBrowserNodeUnknown(u6);
935 ExpectModelMatch(&trans);
938 // Tests a specific case in ApplyModelChanges where we move the
939 // children out from under a parent, and then delete the parent
940 // in the same changelist. The delete shows up first in the changelist,
941 // requiring the children to be moved to a temporary location.
942 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) {
943 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
944 StartSync();
946 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
948 // Stress the immediate children of other_node because that's where
949 // ApplyModelChanges puts a temporary foster parent node.
950 std::string url("http://dev.chromium.org/");
951 FakeServerChange adds(&trans);
952 int64 f0 = other_bookmarks_id(); // + other_node
953 int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1
954 int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2
955 int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT
956 int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT
957 int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT
958 int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6
959 int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT
961 syncer::ChangeRecordList::const_iterator it;
962 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
963 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
964 ExpectBrowserNodeUnknown(it->id);
966 adds.ApplyPendingChanges(change_processor_.get());
968 // Make sure the bookmark model received all of the nodes in |adds|.
969 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
970 ExpectBrowserNodeMatching(&trans, it->id);
971 ExpectModelMatch(&trans);
973 // We have to do the moves before the deletions, but FakeServerChange will
974 // put the deletion at the front of the changelist.
975 FakeServerChange ops(&trans);
976 ops.ModifyPosition(f6, other_bookmarks_id(), 0);
977 ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here.
978 ops.ModifyPosition(f2, other_bookmarks_id(), u7);
979 ops.ModifyPosition(u7, f2, 0);
980 ops.ModifyPosition(u4, other_bookmarks_id(), f2);
981 ops.ModifyPosition(u5, f6, 0);
982 ops.Delete(f1);
984 ops.ApplyPendingChanges(change_processor_.get());
986 ExpectModelMatch(&trans);
989 // Simulate a server change record containing a valid but non-canonical URL.
990 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) {
991 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
992 StartSync();
995 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
997 FakeServerChange adds(&trans);
998 std::string url("http://dev.chromium.org");
999 EXPECT_NE(GURL(url).spec(), url);
1000 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
1002 adds.ApplyPendingChanges(change_processor_.get());
1004 EXPECT_EQ(1, model_->other_node()->child_count());
1005 ExpectModelMatch(&trans);
1008 // Now reboot the sync service, forcing a merge step.
1009 StopSync();
1010 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
1011 StartSync();
1013 // There should still be just the one bookmark.
1014 EXPECT_EQ(1, model_->other_node()->child_count());
1015 ExpectModelMatch();
1018 // Simulate a server change record containing an invalid URL (per GURL).
1019 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563.
1020 TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) {
1021 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1022 StartSync();
1024 int child_count = 0;
1026 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1028 FakeServerChange adds(&trans);
1029 std::string url("x");
1030 EXPECT_FALSE(GURL(url).is_valid());
1031 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
1033 adds.ApplyPendingChanges(change_processor_.get());
1035 // We're lenient about what should happen -- the model could wind up with
1036 // the node or without it; but things should be consistent, and we
1037 // shouldn't crash.
1038 child_count = model_->other_node()->child_count();
1039 EXPECT_TRUE(child_count == 0 || child_count == 1);
1040 ExpectModelMatch(&trans);
1043 // Now reboot the sync service, forcing a merge step.
1044 StopSync();
1045 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
1046 StartSync();
1048 // Things ought not to have changed.
1049 EXPECT_EQ(model_->other_node()->child_count(), child_count);
1050 ExpectModelMatch();
1054 // Test strings that might pose a problem if the titles ever became used as
1055 // file names in the sync backend.
1056 TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) {
1057 // TODO(ncarter): Bug 1570238 explains the failure of this test.
1058 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1059 StartSync();
1061 const char* names[] = {
1062 // The empty string.
1064 // Illegal Windows filenames.
1065 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4",
1066 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3",
1067 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
1068 // Current/parent directory markers.
1069 ".", "..", "...",
1070 // Files created automatically by the Windows shell.
1071 "Thumbs.db", ".DS_Store",
1072 // Names including Win32-illegal characters, and path separators.
1073 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar",
1074 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar",
1075 "foo[bar",
1077 // Create both folders and bookmarks using each name.
1078 GURL url("http://www.doublemint.com");
1079 for (size_t i = 0; i < arraysize(names); ++i) {
1080 model_->AddFolder(model_->other_node(), 0, base::ASCIIToUTF16(names[i]));
1081 model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16(names[i]), url);
1084 // Verify that the browser model matches the sync model.
1085 EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names));
1086 ExpectModelMatch();
1089 // Stress the internal representation of position by sparse numbers. We want
1090 // to repeatedly bisect the range of available positions, to force the
1091 // syncer code to renumber its ranges. Pick a number big enough so that it
1092 // would exhaust 32bits of room between items a couple of times.
1093 TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) {
1094 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1095 StartSync();
1097 static const int kTimesToInsert = 256;
1099 // Create two book-end nodes to insert between.
1100 model_->AddFolder(model_->other_node(), 0, base::ASCIIToUTF16("Alpha"));
1101 model_->AddFolder(model_->other_node(), 1, base::ASCIIToUTF16("Omega"));
1102 int count = 2;
1104 // Test insertion in first half of range by repeatedly inserting in second
1105 // position.
1106 for (int i = 0; i < kTimesToInsert; ++i) {
1107 base::string16 title =
1108 base::ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i);
1109 model_->AddFolder(model_->other_node(), 1, title);
1110 count++;
1113 // Test insertion in second half of range by repeatedly inserting in
1114 // second-to-last position.
1115 for (int i = 0; i < kTimesToInsert; ++i) {
1116 base::string16 title =
1117 base::ASCIIToUTF16("Post-insertion ") + base::IntToString16(i);
1118 model_->AddFolder(model_->other_node(), count - 1, title);
1119 count++;
1122 // Verify that the browser model matches the sync model.
1123 EXPECT_EQ(model_->other_node()->child_count(), count);
1124 ExpectModelMatch();
1127 // Introduce a consistency violation into the model, and see that it
1128 // puts itself into a lame, error state.
1129 TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) {
1130 EXPECT_CALL(mock_error_handler_,
1131 OnSingleDatatypeUnrecoverableError(_, _));
1133 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1134 StartSync();
1136 // Add a node which will be the target of the consistency violation.
1137 const BookmarkNode* node =
1138 model_->AddFolder(model_->other_node(), 0, base::ASCIIToUTF16("node"));
1139 ExpectSyncerNodeMatching(node);
1141 // Now destroy the syncer node as if we were the ProfileSyncService without
1142 // updating the ProfileSyncService state. This should introduce
1143 // inconsistency between the two models.
1145 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1146 syncer::WriteNode sync_node(&trans);
1147 ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node));
1148 sync_node.Tombstone();
1150 // The models don't match at this point, but the ProfileSyncService
1151 // doesn't know it yet.
1152 ExpectSyncerNodeKnown(node);
1154 // Add a child to the inconsistent node. This should cause detection of the
1155 // problem and the syncer should stop processing changes.
1156 model_->AddFolder(node, 0, base::ASCIIToUTF16("nested"));
1159 // See what happens if we run model association when there are two exact URL
1160 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when
1161 // this happens.
1162 TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) {
1163 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1164 StartSync();
1166 model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("Dup"),
1167 GURL("http://dup.com/"));
1168 model_->AddURL(model_->other_node(), 0, base::ASCIIToUTF16("Dup"),
1169 GURL("http://dup.com/"));
1171 EXPECT_EQ(2, model_->other_node()->child_count());
1173 // Restart the sync service to trigger model association.
1174 StopSync();
1175 StartSync();
1177 EXPECT_EQ(2, model_->other_node()->child_count());
1178 ExpectModelMatch();
1181 TEST_F(ProfileSyncServiceBookmarkTest, ApplySyncDeletesFromJournal) {
1182 // Initialize sync model and bookmark model as:
1183 // URL 0
1184 // Folder 1
1185 // |-- URL 1
1186 // +-- Folder 2
1187 // +-- URL 2
1188 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1189 int64 u0 = 0;
1190 int64 f1 = 0;
1191 int64 u1 = 0;
1192 int64 f2 = 0;
1193 int64 u2 = 0;
1194 StartSync();
1195 int fixed_sync_bk_count = GetSyncBookmarkCount();
1197 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1198 FakeServerChange adds(&trans);
1199 u0 = adds.AddURL(L"URL 0", "http://plus.google.com/", bookmark_bar_id(), 0);
1200 f1 = adds.AddFolder(L"Folder 1", bookmark_bar_id(), u0);
1201 u1 = adds.AddURL(L"URL 1", "http://www.google.com/", f1, 0);
1202 f2 = adds.AddFolder(L"Folder 2", f1, u1);
1203 u2 = adds.AddURL(L"URL 2", "http://mail.google.com/", f2, 0);
1204 adds.ApplyPendingChanges(change_processor_.get());
1206 StopSync();
1208 // Reload bookmark model and disable model saving to make sync changes not
1209 // persisted.
1210 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1211 EXPECT_EQ(6, model_->bookmark_bar_node()->GetTotalNodeCount());
1212 EXPECT_EQ(fixed_sync_bk_count + 5, GetSyncBookmarkCount());
1213 StartSync();
1215 // Remove all folders/bookmarks except u3 added above.
1216 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1217 FakeServerChange dels(&trans);
1218 dels.Delete(u2);
1219 dels.Delete(f2);
1220 dels.Delete(u1);
1221 dels.Delete(f1);
1222 dels.ApplyPendingChanges(change_processor_.get());
1224 StopSync();
1225 // Bookmark bar itself and u0 remain.
1226 EXPECT_EQ(2, model_->bookmark_bar_node()->GetTotalNodeCount());
1228 // Reload bookmarks including ones deleted in sync model from storage.
1229 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1230 EXPECT_EQ(6, model_->bookmark_bar_node()->GetTotalNodeCount());
1231 // Add a bookmark under f1 when sync is off so that f1 will not be
1232 // deleted even when f1 matches delete journal because it's not empty.
1233 model_->AddURL(model_->bookmark_bar_node()->GetChild(1),
1234 0, base::UTF8ToUTF16("local"), GURL("http://www.youtube.com"));
1235 // Sync model has fixed bookmarks nodes and u3.
1236 EXPECT_EQ(fixed_sync_bk_count + 1, GetSyncBookmarkCount());
1237 StartSync();
1238 // Expect 4 bookmarks after model association because u2, f2, u1 are removed
1239 // by delete journal, f1 is not removed by delete journal because it's
1240 // not empty due to www.youtube.com added above.
1241 EXPECT_EQ(4, model_->bookmark_bar_node()->GetTotalNodeCount());
1242 EXPECT_EQ(base::UTF8ToUTF16("URL 0"),
1243 model_->bookmark_bar_node()->GetChild(0)->GetTitle());
1244 EXPECT_EQ(base::UTF8ToUTF16("Folder 1"),
1245 model_->bookmark_bar_node()->GetChild(1)->GetTitle());
1246 EXPECT_EQ(base::UTF8ToUTF16("local"),
1247 model_->bookmark_bar_node()->GetChild(1)->GetChild(0)->GetTitle());
1248 StopSync();
1250 // Verify purging of delete journals.
1251 // Delete journals for u2, f2, u1 remains because they are used in last
1252 // association.
1253 EXPECT_EQ(3u, test_user_share_.GetDeleteJournalSize());
1254 StartSync();
1255 StopSync();
1256 // Reload again and all delete journals should be gone because none is used
1257 // in last association.
1258 ASSERT_TRUE(test_user_share_.Reload());
1259 EXPECT_EQ(0u, test_user_share_.GetDeleteJournalSize());
1262 struct TestData {
1263 const wchar_t* title;
1264 const char* url;
1267 // Map from bookmark node ID to its version.
1268 typedef std::map<int64, int64> BookmarkNodeVersionMap;
1270 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code
1271 // in the bookmark model unittest, to make it simpler to set up test data
1272 // here (and reduce the amount of duplication among tests), and to reduce the
1273 // duplication.
1274 class ProfileSyncServiceBookmarkTestWithData
1275 : public ProfileSyncServiceBookmarkTest {
1276 public:
1277 ProfileSyncServiceBookmarkTestWithData();
1279 protected:
1280 // Populates or compares children of the given bookmark node from/with the
1281 // given test data array with the given size. |running_count| is updated as
1282 // urls are added. It is used to set the creation date (or test the creation
1283 // date for CompareWithTestData()).
1284 void PopulateFromTestData(const BookmarkNode* node,
1285 const TestData* data,
1286 int size,
1287 int* running_count);
1288 void CompareWithTestData(const BookmarkNode* node,
1289 const TestData* data,
1290 int size,
1291 int* running_count);
1293 void ExpectBookmarkModelMatchesTestData();
1294 void WriteTestDataToBookmarkModel();
1296 // Verify transaction versions of bookmark nodes and sync nodes are equal
1297 // recursively. If node is in |version_expected|, versions should match
1298 // there, too.
1299 void ExpectTransactionVersionMatch(
1300 const BookmarkNode* node,
1301 const BookmarkNodeVersionMap& version_expected);
1303 private:
1304 const base::Time start_time_;
1306 DISALLOW_COPY_AND_ASSIGN(ProfileSyncServiceBookmarkTestWithData);
1309 namespace {
1311 // Constants for bookmark model that looks like:
1312 // |-- Bookmark bar
1313 // | |-- u2, http://www.u2.com/
1314 // | |-- f1
1315 // | | |-- f1u4, http://www.f1u4.com/
1316 // | | |-- f1u2, http://www.f1u2.com/
1317 // | | |-- f1u3, http://www.f1u3.com/
1318 // | | +-- f1u1, http://www.f1u1.com/
1319 // | |-- u1, http://www.u1.com/
1320 // | +-- f2
1321 // | |-- f2u2, http://www.f2u2.com/
1322 // | |-- f2u4, http://www.f2u4.com/
1323 // | |-- f2u3, http://www.f2u3.com/
1324 // | +-- f2u1, http://www.f2u1.com/
1325 // +-- Other bookmarks
1326 // | |-- f3
1327 // | | |-- f3u4, http://www.f3u4.com/
1328 // | | |-- f3u2, http://www.f3u2.com/
1329 // | | |-- f3u3, http://www.f3u3.com/
1330 // | | +-- f3u1, http://www.f3u1.com/
1331 // | |-- u4, http://www.u4.com/
1332 // | |-- u3, http://www.u3.com/
1333 // | --- f4
1334 // | | |-- f4u1, http://www.f4u1.com/
1335 // | | |-- f4u2, http://www.f4u2.com/
1336 // | | |-- f4u3, http://www.f4u3.com/
1337 // | | +-- f4u4, http://www.f4u4.com/
1338 // | |-- dup
1339 // | | +-- dupu1, http://www.dupu1.com/
1340 // | +-- dup
1341 // | | +-- dupu2, http://www.dupu1.com/
1342 // | +-- ls , http://www.ls.com/
1343 // |
1344 // +-- Mobile bookmarks
1345 // |-- f5
1346 // | |-- f5u1, http://www.f5u1.com/
1347 // |-- f6
1348 // | |-- f6u1, http://www.f6u1.com/
1349 // | |-- f6u2, http://www.f6u2.com/
1350 // +-- u5, http://www.u5.com/
1352 static TestData kBookmarkBarChildren[] = {
1353 { L"u2", "http://www.u2.com/" },
1354 { L"f1", NULL },
1355 { L"u1", "http://www.u1.com/" },
1356 { L"f2", NULL },
1358 static TestData kF1Children[] = {
1359 { L"f1u4", "http://www.f1u4.com/" },
1360 { L"f1u2", "http://www.f1u2.com/" },
1361 { L"f1u3", "http://www.f1u3.com/" },
1362 { L"f1u1", "http://www.f1u1.com/" },
1364 static TestData kF2Children[] = {
1365 { L"f2u2", "http://www.f2u2.com/" },
1366 { L"f2u4", "http://www.f2u4.com/" },
1367 { L"f2u3", "http://www.f2u3.com/" },
1368 { L"f2u1", "http://www.f2u1.com/" },
1371 static TestData kOtherBookmarkChildren[] = {
1372 { L"f3", NULL },
1373 { L"u4", "http://www.u4.com/" },
1374 { L"u3", "http://www.u3.com/" },
1375 { L"f4", NULL },
1376 { L"dup", NULL },
1377 { L"dup", NULL },
1378 { L" ls ", "http://www.ls.com/" }
1380 static TestData kF3Children[] = {
1381 { L"f3u4", "http://www.f3u4.com/" },
1382 { L"f3u2", "http://www.f3u2.com/" },
1383 { L"f3u3", "http://www.f3u3.com/" },
1384 { L"f3u1", "http://www.f3u1.com/" },
1386 static TestData kF4Children[] = {
1387 { L"f4u1", "http://www.f4u1.com/" },
1388 { L"f4u2", "http://www.f4u2.com/" },
1389 { L"f4u3", "http://www.f4u3.com/" },
1390 { L"f4u4", "http://www.f4u4.com/" },
1392 static TestData kDup1Children[] = {
1393 { L"dupu1", "http://www.dupu1.com/" },
1395 static TestData kDup2Children[] = {
1396 { L"dupu2", "http://www.dupu2.com/" },
1399 static TestData kMobileBookmarkChildren[] = {
1400 { L"f5", NULL },
1401 { L"f6", NULL },
1402 { L"u5", "http://www.u5.com/" },
1404 static TestData kF5Children[] = {
1405 { L"f5u1", "http://www.f5u1.com/" },
1406 { L"f5u2", "http://www.f5u2.com/" },
1408 static TestData kF6Children[] = {
1409 { L"f6u1", "http://www.f6u1.com/" },
1410 { L"f6u2", "http://www.f6u2.com/" },
1413 } // anonymous namespace.
1415 ProfileSyncServiceBookmarkTestWithData::
1416 ProfileSyncServiceBookmarkTestWithData()
1417 : start_time_(base::Time::Now()) {
1420 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData(
1421 const BookmarkNode* node,
1422 const TestData* data,
1423 int size,
1424 int* running_count) {
1425 DCHECK(node);
1426 DCHECK(data);
1427 DCHECK(node->is_folder());
1428 for (int i = 0; i < size; ++i) {
1429 const TestData& item = data[i];
1430 if (item.url) {
1431 const base::Time add_time =
1432 start_time_ + base::TimeDelta::FromMinutes(*running_count);
1433 model_->AddURLWithCreationTime(node, i, base::WideToUTF16Hack(item.title),
1434 GURL(item.url), add_time);
1435 } else {
1436 model_->AddFolder(node, i, base::WideToUTF16Hack(item.title));
1438 (*running_count)++;
1442 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData(
1443 const BookmarkNode* node,
1444 const TestData* data,
1445 int size,
1446 int* running_count) {
1447 DCHECK(node);
1448 DCHECK(data);
1449 DCHECK(node->is_folder());
1450 ASSERT_EQ(size, node->child_count());
1451 for (int i = 0; i < size; ++i) {
1452 const BookmarkNode* child_node = node->GetChild(i);
1453 const TestData& item = data[i];
1454 GURL url = GURL(item.url == NULL ? "" : item.url);
1455 BookmarkNode test_node(url);
1456 test_node.SetTitle(base::WideToUTF16Hack(item.title));
1457 EXPECT_EQ(child_node->GetTitle(), test_node.GetTitle());
1458 if (item.url) {
1459 EXPECT_FALSE(child_node->is_folder());
1460 EXPECT_TRUE(child_node->is_url());
1461 EXPECT_EQ(child_node->url(), test_node.url());
1462 const base::Time expected_time =
1463 start_time_ + base::TimeDelta::FromMinutes(*running_count);
1464 EXPECT_EQ(expected_time.ToInternalValue(),
1465 child_node->date_added().ToInternalValue());
1466 } else {
1467 EXPECT_TRUE(child_node->is_folder());
1468 EXPECT_FALSE(child_node->is_url());
1470 (*running_count)++;
1474 // TODO(munjal): We should implement some way of generating random data and can
1475 // use the same seed to generate the same sequence.
1476 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() {
1477 const BookmarkNode* bookmarks_bar_node = model_->bookmark_bar_node();
1478 int count = 0;
1479 PopulateFromTestData(bookmarks_bar_node,
1480 kBookmarkBarChildren,
1481 arraysize(kBookmarkBarChildren),
1482 &count);
1484 ASSERT_GE(bookmarks_bar_node->child_count(), 4);
1485 const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1);
1486 PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children), &count);
1487 const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3);
1488 PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children), &count);
1490 const BookmarkNode* other_bookmarks_node = model_->other_node();
1491 PopulateFromTestData(other_bookmarks_node,
1492 kOtherBookmarkChildren,
1493 arraysize(kOtherBookmarkChildren),
1494 &count);
1496 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1497 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1498 PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children), &count);
1499 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1500 PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children), &count);
1501 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1502 PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children),
1503 &count);
1504 dup_node = other_bookmarks_node->GetChild(5);
1505 PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children),
1506 &count);
1508 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node();
1509 PopulateFromTestData(mobile_bookmarks_node,
1510 kMobileBookmarkChildren,
1511 arraysize(kMobileBookmarkChildren),
1512 &count);
1514 ASSERT_GE(mobile_bookmarks_node->child_count(), 3);
1515 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0);
1516 PopulateFromTestData(f5_node, kF5Children, arraysize(kF5Children), &count);
1517 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1);
1518 PopulateFromTestData(f6_node, kF6Children, arraysize(kF6Children), &count);
1520 ExpectBookmarkModelMatchesTestData();
1523 void ProfileSyncServiceBookmarkTestWithData::
1524 ExpectBookmarkModelMatchesTestData() {
1525 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1526 int count = 0;
1527 CompareWithTestData(bookmark_bar_node,
1528 kBookmarkBarChildren,
1529 arraysize(kBookmarkBarChildren),
1530 &count);
1532 ASSERT_GE(bookmark_bar_node->child_count(), 4);
1533 const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1);
1534 CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children), &count);
1535 const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3);
1536 CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children), &count);
1538 const BookmarkNode* other_bookmarks_node = model_->other_node();
1539 CompareWithTestData(other_bookmarks_node,
1540 kOtherBookmarkChildren,
1541 arraysize(kOtherBookmarkChildren),
1542 &count);
1544 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1545 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1546 CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children), &count);
1547 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1548 CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children), &count);
1549 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1550 CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children),
1551 &count);
1552 dup_node = other_bookmarks_node->GetChild(5);
1553 CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children),
1554 &count);
1556 const BookmarkNode* mobile_bookmarks_node = model_->mobile_node();
1557 CompareWithTestData(mobile_bookmarks_node,
1558 kMobileBookmarkChildren,
1559 arraysize(kMobileBookmarkChildren),
1560 &count);
1562 ASSERT_GE(mobile_bookmarks_node->child_count(), 3);
1563 const BookmarkNode* f5_node = mobile_bookmarks_node->GetChild(0);
1564 CompareWithTestData(f5_node, kF5Children, arraysize(kF5Children), &count);
1565 const BookmarkNode* f6_node = mobile_bookmarks_node->GetChild(1);
1566 CompareWithTestData(f6_node, kF6Children, arraysize(kF6Children), &count);
1569 // Tests persistence of the profile sync service by unloading the
1570 // database and then reloading it from disk.
1571 TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) {
1572 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1573 StartSync();
1575 WriteTestDataToBookmarkModel();
1577 ExpectModelMatch();
1579 // Force both models to discard their data and reload from disk. This
1580 // simulates what would happen if the browser were to shutdown normally,
1581 // and then relaunch.
1582 StopSync();
1583 UnloadBookmarkModel();
1584 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
1585 StartSync();
1587 ExpectBookmarkModelMatchesTestData();
1589 // With the BookmarkModel contents verified, ExpectModelMatch will
1590 // verify the contents of the sync model.
1591 ExpectModelMatch();
1594 // Tests the merge case when the BookmarkModel is non-empty but the
1595 // sync model is empty. This corresponds to uploading browser
1596 // bookmarks to an initially empty, new account.
1597 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) {
1598 // Don't start the sync service until we've populated the bookmark model.
1599 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1601 WriteTestDataToBookmarkModel();
1603 // Restart sync. This should trigger a merge step during
1604 // initialization -- we expect the browser bookmarks to be written
1605 // to the sync service during this call.
1606 StartSync();
1608 // Verify that the bookmark model hasn't changed, and that the sync model
1609 // matches it exactly.
1610 ExpectBookmarkModelMatchesTestData();
1611 ExpectModelMatch();
1614 // Tests the merge case when the BookmarkModel is empty but the sync model is
1615 // non-empty. This corresponds (somewhat) to a clean install of the browser,
1616 // with no bookmarks, connecting to a sync account that has some bookmarks.
1617 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) {
1618 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1619 StartSync();
1621 WriteTestDataToBookmarkModel();
1623 ExpectModelMatch();
1625 // Force the databse to unload and write itself to disk.
1626 StopSync();
1628 // Blow away the bookmark model -- it should be empty afterwards.
1629 UnloadBookmarkModel();
1630 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1631 EXPECT_EQ(model_->bookmark_bar_node()->child_count(), 0);
1632 EXPECT_EQ(model_->other_node()->child_count(), 0);
1633 EXPECT_EQ(model_->mobile_node()->child_count(), 0);
1635 // Now restart the sync service. Starting it should populate the bookmark
1636 // model -- test for consistency.
1637 StartSync();
1638 ExpectBookmarkModelMatchesTestData();
1639 ExpectModelMatch();
1642 // Tests the merge cases when both the models are expected to be identical
1643 // after the merge.
1644 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) {
1645 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1646 StartSync();
1647 WriteTestDataToBookmarkModel();
1648 ExpectModelMatch();
1649 StopSync();
1650 UnloadBookmarkModel();
1652 // At this point both the bookmark model and the server should have the
1653 // exact same data and it should match the test data.
1654 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1655 StartSync();
1656 ExpectBookmarkModelMatchesTestData();
1657 ExpectModelMatch();
1658 StopSync();
1659 UnloadBookmarkModel();
1661 // Now reorder some bookmarks in the bookmark model and then merge. Make
1662 // sure we get the order of the server after merge.
1663 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1664 ExpectBookmarkModelMatchesTestData();
1665 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node();
1666 ASSERT_TRUE(bookmark_bar);
1667 ASSERT_GT(bookmark_bar->child_count(), 1);
1668 model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1);
1669 StartSync();
1670 ExpectModelMatch();
1671 ExpectBookmarkModelMatchesTestData();
1674 // Tests the merge cases when both the models are expected to be identical
1675 // after the merge.
1676 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) {
1677 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1678 WriteTestDataToBookmarkModel();
1679 ExpectBookmarkModelMatchesTestData();
1681 // Remove some nodes and reorder some nodes.
1682 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1683 int remove_index = 2;
1684 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1685 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index);
1686 ASSERT_TRUE(child_node);
1687 ASSERT_TRUE(child_node->is_url());
1688 model_->Remove(bookmark_bar_node, remove_index);
1689 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1690 child_node = bookmark_bar_node->GetChild(remove_index);
1691 ASSERT_TRUE(child_node);
1692 ASSERT_TRUE(child_node->is_folder());
1693 model_->Remove(bookmark_bar_node, remove_index);
1695 const BookmarkNode* other_node = model_->other_node();
1696 ASSERT_GE(other_node->child_count(), 1);
1697 const BookmarkNode* f3_node = other_node->GetChild(0);
1698 ASSERT_TRUE(f3_node);
1699 ASSERT_TRUE(f3_node->is_folder());
1700 remove_index = 2;
1701 ASSERT_GT(f3_node->child_count(), remove_index);
1702 model_->Remove(f3_node, remove_index);
1703 ASSERT_GT(f3_node->child_count(), remove_index);
1704 model_->Remove(f3_node, remove_index);
1706 StartSync();
1707 ExpectModelMatch();
1708 StopSync();
1710 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1711 WriteTestDataToBookmarkModel();
1712 ExpectBookmarkModelMatchesTestData();
1714 // Remove some nodes and reorder some nodes.
1715 bookmark_bar_node = model_->bookmark_bar_node();
1716 remove_index = 0;
1717 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1718 child_node = bookmark_bar_node->GetChild(remove_index);
1719 ASSERT_TRUE(child_node);
1720 ASSERT_TRUE(child_node->is_url());
1721 model_->Remove(bookmark_bar_node, remove_index);
1722 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1723 child_node = bookmark_bar_node->GetChild(remove_index);
1724 ASSERT_TRUE(child_node);
1725 ASSERT_TRUE(child_node->is_folder());
1726 model_->Remove(bookmark_bar_node, remove_index);
1728 ASSERT_GE(bookmark_bar_node->child_count(), 2);
1729 model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1);
1731 other_node = model_->other_node();
1732 ASSERT_GE(other_node->child_count(), 1);
1733 f3_node = other_node->GetChild(0);
1734 ASSERT_TRUE(f3_node);
1735 ASSERT_TRUE(f3_node->is_folder());
1736 remove_index = 0;
1737 ASSERT_GT(f3_node->child_count(), remove_index);
1738 model_->Remove(f3_node, remove_index);
1739 ASSERT_GT(f3_node->child_count(), remove_index);
1740 model_->Remove(f3_node, remove_index);
1742 ASSERT_GE(other_node->child_count(), 4);
1743 model_->Move(other_node->GetChild(0), other_node, 1);
1744 model_->Move(other_node->GetChild(2), other_node, 3);
1746 StartSync();
1747 ExpectModelMatch();
1749 // After the merge, the model should match the test data.
1750 ExpectBookmarkModelMatchesTestData();
1753 // Tests that when persisted model associations are used, things work fine.
1754 TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) {
1755 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1756 WriteTestDataToBookmarkModel();
1757 StartSync();
1758 ExpectModelMatch();
1759 // Force sync to shut down and write itself to disk.
1760 StopSync();
1761 // Now restart sync. This time it should use the persistent
1762 // associations.
1763 StartSync();
1764 ExpectModelMatch();
1767 // Tests that when persisted model associations are used, things work fine.
1768 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1769 ModelAssociationInvalidPersistence) {
1770 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1771 WriteTestDataToBookmarkModel();
1772 StartSync();
1773 ExpectModelMatch();
1774 // Force sync to shut down and write itself to disk.
1775 StopSync();
1776 // Change the bookmark model before restarting sync service to simulate
1777 // the situation where bookmark model is different from sync model and
1778 // make sure model associator correctly rebuilds associations.
1779 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1780 model_->AddURL(bookmark_bar_node, 0, base::ASCIIToUTF16("xtra"),
1781 GURL("http://www.xtra.com"));
1782 // Now restart sync. This time it will try to use the persistent
1783 // associations and realize that they are invalid and hence will rebuild
1784 // associations.
1785 StartSync();
1786 ExpectModelMatch();
1789 TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) {
1790 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1791 StartSync();
1793 // Write test data to bookmark model and verify that the models match.
1794 WriteTestDataToBookmarkModel();
1795 const BookmarkNode* folder_added = model_->other_node()->GetChild(0);
1796 ASSERT_TRUE(folder_added);
1797 ASSERT_TRUE(folder_added->is_folder());
1799 ExpectModelMatch();
1801 // Sort the other-bookmarks children and expect that the models match.
1802 model_->SortChildren(folder_added);
1803 ExpectModelMatch();
1806 // See what happens if we enable sync but then delete the "Sync Data"
1807 // folder.
1808 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1809 RecoverAfterDeletingSyncDataDirectory) {
1810 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1811 StartSync();
1813 WriteTestDataToBookmarkModel();
1815 StopSync();
1817 // Nuke the sync DB and reload.
1818 TearDown();
1819 SetUp();
1821 // First attempt fails due to a persistence error.
1822 EXPECT_TRUE(CreatePermanentBookmarkNodes());
1823 EXPECT_FALSE(AssociateModels());
1825 // Second attempt succeeds due to the previous error resetting the native
1826 // transaction version.
1827 model_associator_.reset();
1828 EXPECT_TRUE(CreatePermanentBookmarkNodes());
1829 EXPECT_TRUE(AssociateModels());
1831 // Make sure we're back in sync. In real life, the user would need
1832 // to reauthenticate before this happens, but in the test, authentication
1833 // is sidestepped.
1834 ExpectBookmarkModelMatchesTestData();
1835 ExpectModelMatch();
1838 // Verify that the bookmark model is updated about whether the
1839 // associator is currently running.
1840 TEST_F(ProfileSyncServiceBookmarkTest, AssociationState) {
1841 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1843 ExtensiveChangesBookmarkModelObserver observer;
1844 model_->AddObserver(&observer);
1846 StartSync();
1848 EXPECT_EQ(1, observer.get_started());
1849 EXPECT_EQ(0, observer.get_completed_count_at_started());
1850 EXPECT_EQ(1, observer.get_completed());
1852 model_->RemoveObserver(&observer);
1855 // Verify that the creation_time_us changes are applied in the local model at
1856 // association time and update time.
1857 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateDateAdded) {
1858 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1859 WriteTestDataToBookmarkModel();
1861 // Start and stop sync in order to create bookmark nodes in the sync db.
1862 StartSync();
1863 StopSync();
1865 // Modify the date_added field of a bookmark so it doesn't match with
1866 // the sync data.
1867 const BookmarkNode* bookmark_bar_node = model_->bookmark_bar_node();
1868 int remove_index = 2;
1869 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1870 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index);
1871 ASSERT_TRUE(child_node);
1872 EXPECT_TRUE(child_node->is_url());
1873 model_->SetDateAdded(child_node, base::Time::FromInternalValue(10));
1875 StartSync();
1877 // Everything should be back in sync after model association.
1878 ExpectBookmarkModelMatchesTestData();
1879 ExpectModelMatch();
1881 // Now trigger a change while syncing. We add a new bookmark, sync it, then
1882 // updates it's creation time.
1883 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1884 FakeServerChange adds(&trans);
1885 const std::wstring kTitle = L"Some site";
1886 const std::string kUrl = "http://www.whatwhat.yeah/";
1887 const int kCreationTime = 30;
1888 int64 id = adds.AddURL(kTitle, kUrl,
1889 bookmark_bar_id(), 0);
1890 adds.ApplyPendingChanges(change_processor_.get());
1891 FakeServerChange updates(&trans);
1892 updates.ModifyCreationTime(id, kCreationTime);
1893 updates.ApplyPendingChanges(change_processor_.get());
1895 const BookmarkNode* node = model_->bookmark_bar_node()->GetChild(0);
1896 ASSERT_TRUE(node);
1897 EXPECT_TRUE(node->is_url());
1898 EXPECT_EQ(base::WideToUTF16Hack(kTitle), node->GetTitle());
1899 EXPECT_EQ(kUrl, node->url().possibly_invalid_spec());
1900 EXPECT_EQ(node->date_added(), base::Time::FromInternalValue(30));
1903 // Tests that changes to the sync nodes meta info gets reflected in the local
1904 // bookmark model.
1905 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateMetaInfoFromSync) {
1906 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1907 WriteTestDataToBookmarkModel();
1908 StartSync();
1910 // Create bookmark nodes containing meta info.
1911 syncer::WriteTransaction trans(FROM_HERE, test_user_share_.user_share());
1912 FakeServerChange adds(&trans);
1913 BookmarkNode::MetaInfoMap folder_meta_info;
1914 folder_meta_info["folder"] = "foldervalue";
1915 int64 folder_id = adds.AddFolderWithMetaInfo(
1916 L"folder title", &folder_meta_info, bookmark_bar_id(), 0);
1917 BookmarkNode::MetaInfoMap node_meta_info;
1918 node_meta_info["node"] = "nodevalue";
1919 node_meta_info["other"] = "othervalue";
1920 int64 id = adds.AddURLWithMetaInfo(L"node title", "http://www.foo.com",
1921 &node_meta_info, folder_id, 0);
1922 adds.ApplyPendingChanges(change_processor_.get());
1924 // Verify that the nodes are created with the correct meta info.
1925 ASSERT_LT(0, model_->bookmark_bar_node()->child_count());
1926 const BookmarkNode* folder_node = model_->bookmark_bar_node()->GetChild(0);
1927 ASSERT_TRUE(folder_node->GetMetaInfoMap());
1928 EXPECT_EQ(folder_meta_info, *folder_node->GetMetaInfoMap());
1929 ASSERT_LT(0, folder_node->child_count());
1930 const BookmarkNode* node = folder_node->GetChild(0);
1931 ASSERT_TRUE(node->GetMetaInfoMap());
1932 EXPECT_EQ(node_meta_info, *node->GetMetaInfoMap());
1934 // Update meta info on nodes on server
1935 FakeServerChange updates(&trans);
1936 folder_meta_info.erase("folder");
1937 updates.ModifyMetaInfo(folder_id, folder_meta_info);
1938 node_meta_info["node"] = "changednodevalue";
1939 node_meta_info.erase("other");
1940 node_meta_info["newkey"] = "newkeyvalue";
1941 updates.ModifyMetaInfo(id, node_meta_info);
1942 updates.ApplyPendingChanges(change_processor_.get());
1944 // Confirm that the updated values are reflected in the bookmark nodes.
1945 EXPECT_FALSE(folder_node->GetMetaInfoMap());
1946 ASSERT_TRUE(node->GetMetaInfoMap());
1947 EXPECT_EQ(node_meta_info, *node->GetMetaInfoMap());
1950 // Tests that changes to the local bookmark nodes meta info gets reflected in
1951 // the sync nodes.
1952 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateMetaInfoFromModel) {
1953 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1954 WriteTestDataToBookmarkModel();
1955 StartSync();
1956 ExpectBookmarkModelMatchesTestData();
1958 const BookmarkNode* folder_node =
1959 model_->AddFolder(model_->bookmark_bar_node(), 0,
1960 base::ASCIIToUTF16("folder title"));
1961 const BookmarkNode* node = model_->AddURL(folder_node, 0,
1962 base::ASCIIToUTF16("node title"),
1963 GURL("http://www.foo.com"));
1964 ExpectModelMatch();
1966 // Add some meta info and verify sync model matches the changes.
1967 model_->SetNodeMetaInfo(folder_node, "folder", "foldervalue");
1968 model_->SetNodeMetaInfo(node, "node", "nodevalue");
1969 model_->SetNodeMetaInfo(node, "other", "othervalue");
1970 ExpectModelMatch();
1972 // Change/delete existing meta info and verify.
1973 model_->DeleteNodeMetaInfo(folder_node, "folder");
1974 model_->SetNodeMetaInfo(node, "node", "changednodevalue");
1975 model_->DeleteNodeMetaInfo(node, "other");
1976 model_->SetNodeMetaInfo(node, "newkey", "newkeyvalue");
1977 ExpectModelMatch();
1980 // Output transaction versions of |node| and nodes under it to |node_versions|.
1981 void GetTransactionVersions(
1982 const BookmarkNode* root,
1983 BookmarkNodeVersionMap* node_versions) {
1984 node_versions->clear();
1985 std::queue<const BookmarkNode*> nodes;
1986 nodes.push(root);
1987 while (!nodes.empty()) {
1988 const BookmarkNode* n = nodes.front();
1989 nodes.pop();
1991 int64 version = n->sync_transaction_version();
1992 EXPECT_NE(BookmarkNode::kInvalidSyncTransactionVersion, version);
1994 (*node_versions)[n->id()] = version;
1995 for (int i = 0; i < n->child_count(); ++i)
1996 nodes.push(n->GetChild(i));
2000 void ProfileSyncServiceBookmarkTestWithData::ExpectTransactionVersionMatch(
2001 const BookmarkNode* node,
2002 const BookmarkNodeVersionMap& version_expected) {
2003 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
2005 BookmarkNodeVersionMap bnodes_versions;
2006 GetTransactionVersions(node, &bnodes_versions);
2007 for (BookmarkNodeVersionMap::const_iterator it = bnodes_versions.begin();
2008 it != bnodes_versions.end(); ++it) {
2009 syncer::ReadNode sync_node(&trans);
2010 ASSERT_TRUE(model_associator_->InitSyncNodeFromChromeId(it->first,
2011 &sync_node));
2012 EXPECT_EQ(sync_node.GetEntry()->GetTransactionVersion(), it->second);
2013 BookmarkNodeVersionMap::const_iterator expected_ver_it =
2014 version_expected.find(it->first);
2015 if (expected_ver_it != version_expected.end())
2016 EXPECT_EQ(expected_ver_it->second, it->second);
2020 // Test transaction versions of model and nodes are incremented after changes
2021 // are applied.
2022 TEST_F(ProfileSyncServiceBookmarkTestWithData, UpdateTransactionVersion) {
2023 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
2024 StartSync();
2025 WriteTestDataToBookmarkModel();
2026 base::MessageLoop::current()->RunUntilIdle();
2028 BookmarkNodeVersionMap initial_versions;
2030 // Verify transaction versions in sync model and bookmark model (saved as
2031 // transaction version of root node) are equal after
2032 // WriteTestDataToBookmarkModel() created bookmarks.
2034 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
2035 EXPECT_GT(trans.GetModelVersion(syncer::BOOKMARKS), 0);
2036 GetTransactionVersions(model_->root_node(), &initial_versions);
2037 EXPECT_EQ(trans.GetModelVersion(syncer::BOOKMARKS),
2038 initial_versions[model_->root_node()->id()]);
2040 ExpectTransactionVersionMatch(model_->bookmark_bar_node(),
2041 BookmarkNodeVersionMap());
2042 ExpectTransactionVersionMatch(model_->other_node(),
2043 BookmarkNodeVersionMap());
2044 ExpectTransactionVersionMatch(model_->mobile_node(),
2045 BookmarkNodeVersionMap());
2047 // Verify model version is incremented and bookmark node versions remain
2048 // the same.
2049 const BookmarkNode* bookmark_bar = model_->bookmark_bar_node();
2050 model_->Remove(bookmark_bar, 0);
2051 base::MessageLoop::current()->RunUntilIdle();
2052 BookmarkNodeVersionMap new_versions;
2053 GetTransactionVersions(model_->root_node(), &new_versions);
2054 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 1,
2055 new_versions[model_->root_node()->id()]);
2056 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions);
2057 ExpectTransactionVersionMatch(model_->other_node(), initial_versions);
2058 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions);
2060 // Verify model version and version of changed bookmark are incremented and
2061 // versions of others remain same.
2062 const BookmarkNode* changed_bookmark =
2063 model_->bookmark_bar_node()->GetChild(0);
2064 model_->SetTitle(changed_bookmark, base::ASCIIToUTF16("test"));
2065 base::MessageLoop::current()->RunUntilIdle();
2066 GetTransactionVersions(model_->root_node(), &new_versions);
2067 EXPECT_EQ(initial_versions[model_->root_node()->id()] + 2,
2068 new_versions[model_->root_node()->id()]);
2069 EXPECT_LT(initial_versions[changed_bookmark->id()],
2070 new_versions[changed_bookmark->id()]);
2071 initial_versions.erase(changed_bookmark->id());
2072 ExpectTransactionVersionMatch(model_->bookmark_bar_node(), initial_versions);
2073 ExpectTransactionVersionMatch(model_->other_node(), initial_versions);
2074 ExpectTransactionVersionMatch(model_->mobile_node(), initial_versions);
2077 // Test that sync persistence errors are detected and trigger a failed
2078 // association.
2079 TEST_F(ProfileSyncServiceBookmarkTestWithData, PersistenceError) {
2080 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
2081 StartSync();
2082 WriteTestDataToBookmarkModel();
2083 base::MessageLoop::current()->RunUntilIdle();
2085 BookmarkNodeVersionMap initial_versions;
2087 // Verify transaction versions in sync model and bookmark model (saved as
2088 // transaction version of root node) are equal after
2089 // WriteTestDataToBookmarkModel() created bookmarks.
2091 syncer::ReadTransaction trans(FROM_HERE, test_user_share_.user_share());
2092 EXPECT_GT(trans.GetModelVersion(syncer::BOOKMARKS), 0);
2093 GetTransactionVersions(model_->root_node(), &initial_versions);
2094 EXPECT_EQ(trans.GetModelVersion(syncer::BOOKMARKS),
2095 initial_versions[model_->root_node()->id()]);
2097 ExpectTransactionVersionMatch(model_->bookmark_bar_node(),
2098 BookmarkNodeVersionMap());
2099 ExpectTransactionVersionMatch(model_->other_node(),
2100 BookmarkNodeVersionMap());
2101 ExpectTransactionVersionMatch(model_->mobile_node(),
2102 BookmarkNodeVersionMap());
2104 // Now shut down sync and artificially increment the native model's version.
2105 StopSync();
2106 int64 root_version = initial_versions[model_->root_node()->id()];
2107 model_->SetNodeSyncTransactionVersion(model_->root_node(), root_version + 1);
2109 // Upon association, bookmarks should fail to associate.
2110 EXPECT_FALSE(AssociateModels());
2113 } // namespace
2115 } // namespace browser_sync