1 // Copyright (c) 2011 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.
4
5 // TODO(akalin): This file is basically just a unit test for
6 // BookmarkChangeProcessor. Write unit tests for
7 // BookmarkModelAssociator separately.
8
9 #include <stack>
10 #include <vector>
11
12 #include "base/file_path.h"
13 #include "base/file_util.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/message_loop.h"
16 #include "base/string16.h"
17 #include "base/string_number_conversions.h"
18 #include "base/string_util.h"
19 #include "base/utf_string_conversions.h"
20 #include "chrome/browser/bookmarks/bookmark_model.h"
21 #include "chrome/browser/sync/abstract_profile_sync_service_test.h"
22 #include "chrome/browser/sync/engine/syncapi.h"
23 #include "chrome/browser/sync/glue/bookmark_change_processor.h"
24 #include "chrome/browser/sync/glue/bookmark_model_associator.h"
25 #include "chrome/browser/sync/syncable/directory_manager.h"
26 #include "chrome/test/sync/engine/test_id_factory.h"
27 #include "chrome/test/sync/engine/test_user_share.h"
28 #include "chrome/test/testing_profile.h"
29 #include "content/browser/browser_thread.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32
33 namespace browser_sync {
34
35 namespace {
36
37 using testing::_;
38 using testing::InvokeWithoutArgs;
39 using testing::Mock;
40 using testing::StrictMock;
41
42 class TestBookmarkModelAssociator : public BookmarkModelAssociator {
43 public:
TestBookmarkModelAssociator(BookmarkModel * bookmark_model,sync_api::UserShare * user_share,UnrecoverableErrorHandler * unrecoverable_error_handler)44 TestBookmarkModelAssociator(
45 BookmarkModel* bookmark_model,
46 sync_api::UserShare* user_share,
47 UnrecoverableErrorHandler* unrecoverable_error_handler)
48 : BookmarkModelAssociator(bookmark_model, user_share,
49 unrecoverable_error_handler),
50 user_share_(user_share) {}
51
52 // TODO(akalin): This logic lazily creates any tagged node that is
53 // requested. A better way would be to have utility functions to
54 // create sync nodes from some bookmark structure and to use that.
GetSyncIdForTaggedNode(const std::string & tag,int64 * sync_id)55 virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) {
56 std::wstring tag_wide;
57 if (!UTF8ToWide(tag.c_str(), tag.length(), &tag_wide)) {
58 NOTREACHED() << "Unable to convert UTF8 to wide for string: " << tag;
59 return false;
60 }
61
62 bool root_exists = false;
63 syncable::ModelType type = model_type();
64 {
65 sync_api::WriteTransaction trans(user_share_);
66 sync_api::ReadNode uber_root(&trans);
67 uber_root.InitByRootLookup();
68
69 sync_api::ReadNode root(&trans);
70 root_exists = root.InitByTagLookup(
71 ProfileSyncServiceTestHelper::GetTagForType(type));
72 }
73
74 if (!root_exists) {
75 bool created = ProfileSyncServiceTestHelper::CreateRoot(
76 type,
77 user_share_,
78 &id_factory_);
79 if (!created)
80 return false;
81 }
82
83 sync_api::WriteTransaction trans(user_share_);
84 sync_api::ReadNode root(&trans);
85 EXPECT_TRUE(root.InitByTagLookup(
86 ProfileSyncServiceTestHelper::GetTagForType(type)));
87
88 // First, try to find a node with the title among the root's children.
89 // This will be the case if we are testing model persistence, and
90 // are reloading a sync repository created earlier in the test.
91 int64 last_child_id = sync_api::kInvalidId;
92 for (int64 id = root.GetFirstChildId(); id != sync_api::kInvalidId; /***/) {
93 sync_api::ReadNode child(&trans);
94 child.InitByIdLookup(id);
95 last_child_id = id;
96 if (tag_wide == child.GetTitle()) {
97 *sync_id = id;
98 return true;
99 }
100 id = child.GetSuccessorId();
101 }
102
103 sync_api::ReadNode predecessor_node(&trans);
104 sync_api::ReadNode* predecessor = NULL;
105 if (last_child_id != sync_api::kInvalidId) {
106 predecessor_node.InitByIdLookup(last_child_id);
107 predecessor = &predecessor_node;
108 }
109 sync_api::WriteNode node(&trans);
110 // Create new fake tagged nodes at the end of the ordering.
111 node.InitByCreation(type, root, predecessor);
112 node.SetIsFolder(true);
113 node.SetTitle(tag_wide);
114 node.SetExternalId(0);
115 *sync_id = node.GetId();
116 return true;
117 }
118
119 private:
120 sync_api::UserShare* user_share_;
121 browser_sync::TestIdFactory id_factory_;
122 };
123
124 // FakeServerChange constructs a list of sync_api::ChangeRecords while modifying
125 // the sync model, and can pass the ChangeRecord list to a
126 // sync_api::SyncObserver (i.e., the ProfileSyncService) to test the client
127 // change-application behavior.
128 // Tests using FakeServerChange should be careful to avoid back-references,
129 // since FakeServerChange will send the edits in the order specified.
130 class FakeServerChange {
131 public:
FakeServerChange(sync_api::WriteTransaction * trans)132 explicit FakeServerChange(sync_api::WriteTransaction* trans) : trans_(trans) {
133 }
134
135 // Pretend that the server told the syncer to add a bookmark object.
Add(const std::wstring & title,const std::string & url,bool is_folder,int64 parent_id,int64 predecessor_id)136 int64 Add(const std::wstring& title,
137 const std::string& url,
138 bool is_folder,
139 int64 parent_id,
140 int64 predecessor_id) {
141 sync_api::ReadNode parent(trans_);
142 EXPECT_TRUE(parent.InitByIdLookup(parent_id));
143 sync_api::WriteNode node(trans_);
144 if (predecessor_id == 0) {
145 EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, NULL));
146 } else {
147 sync_api::ReadNode predecessor(trans_);
148 EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id));
149 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
150 EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent,
151 &predecessor));
152 }
153 EXPECT_EQ(node.GetPredecessorId(), predecessor_id);
154 EXPECT_EQ(node.GetParentId(), parent_id);
155 node.SetIsFolder(is_folder);
156 node.SetTitle(title);
157 if (!is_folder)
158 node.SetURL(GURL(url));
159 sync_api::SyncManager::ChangeRecord record;
160 record.action = sync_api::SyncManager::ChangeRecord::ACTION_ADD;
161 record.id = node.GetId();
162 changes_.push_back(record);
163 return node.GetId();
164 }
165
166 // Add a bookmark folder.
AddFolder(const std::wstring & title,int64 parent_id,int64 predecessor_id)167 int64 AddFolder(const std::wstring& title,
168 int64 parent_id,
169 int64 predecessor_id) {
170 return Add(title, std::string(), true, parent_id, predecessor_id);
171 }
172
173 // Add a bookmark.
AddURL(const std::wstring & title,const std::string & url,int64 parent_id,int64 predecessor_id)174 int64 AddURL(const std::wstring& title,
175 const std::string& url,
176 int64 parent_id,
177 int64 predecessor_id) {
178 return Add(title, url, false, parent_id, predecessor_id);
179 }
180
181 // Pretend that the server told the syncer to delete an object.
Delete(int64 id)182 void Delete(int64 id) {
183 {
184 // Delete the sync node.
185 sync_api::WriteNode node(trans_);
186 EXPECT_TRUE(node.InitByIdLookup(id));
187 EXPECT_FALSE(node.GetFirstChildId());
188 node.Remove();
189 }
190 {
191 // Verify the deletion.
192 sync_api::ReadNode node(trans_);
193 EXPECT_FALSE(node.InitByIdLookup(id));
194 }
195
196 sync_api::SyncManager::ChangeRecord record;
197 record.action = sync_api::SyncManager::ChangeRecord::ACTION_DELETE;
198 record.id = id;
199 // Deletions are always first in the changelist, but we can't actually do
200 // WriteNode::Remove() on the node until its children are moved. So, as
201 // a practical matter, users of FakeServerChange must move or delete
202 // children before parents. Thus, we must insert the deletion record
203 // at the front of the vector.
204 changes_.insert(changes_.begin(), record);
205 }
206
207 // Set a new title value, and return the old value.
ModifyTitle(int64 id,const std::wstring & new_title)208 std::wstring ModifyTitle(int64 id, const std::wstring& new_title) {
209 sync_api::WriteNode node(trans_);
210 EXPECT_TRUE(node.InitByIdLookup(id));
211 std::wstring old_title = node.GetTitle();
212 node.SetTitle(new_title);
213 SetModified(id);
214 return old_title;
215 }
216
217 // Set a new parent and predecessor value. Return the old parent id.
218 // We could return the old predecessor id, but it turns out not to be
219 // very useful for assertions.
ModifyPosition(int64 id,int64 parent_id,int64 predecessor_id)220 int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) {
221 sync_api::ReadNode parent(trans_);
222 EXPECT_TRUE(parent.InitByIdLookup(parent_id));
223 sync_api::WriteNode node(trans_);
224 EXPECT_TRUE(node.InitByIdLookup(id));
225 int64 old_parent_id = node.GetParentId();
226 if (predecessor_id == 0) {
227 EXPECT_TRUE(node.SetPosition(parent, NULL));
228 } else {
229 sync_api::ReadNode predecessor(trans_);
230 EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id));
231 EXPECT_EQ(predecessor.GetParentId(), parent.GetId());
232 EXPECT_TRUE(node.SetPosition(parent, &predecessor));
233 }
234 SetModified(id);
235 return old_parent_id;
236 }
237
238 // Pass the fake change list to |service|.
ApplyPendingChanges(ChangeProcessor * processor)239 void ApplyPendingChanges(ChangeProcessor* processor) {
240 processor->ApplyChangesFromSyncModel(trans_,
241 changes_.size() ? &changes_[0] : NULL, changes_.size());
242 }
243
changes()244 const std::vector<sync_api::SyncManager::ChangeRecord>& changes() {
245 return changes_;
246 }
247
248 private:
249 // Helper function to push an ACTION_UPDATE record onto the back
250 // of the changelist.
SetModified(int64 id)251 void SetModified(int64 id) {
252 // Coalesce multi-property edits.
253 if (!changes_.empty() && changes_.back().id == id &&
254 changes_.back().action ==
255 sync_api::SyncManager::ChangeRecord::ACTION_UPDATE)
256 return;
257 sync_api::SyncManager::ChangeRecord record;
258 record.action = sync_api::SyncManager::ChangeRecord::ACTION_UPDATE;
259 record.id = id;
260 changes_.push_back(record);
261 }
262
263 // The transaction on which everything happens.
264 sync_api::WriteTransaction *trans_;
265
266 // The change list we construct.
267 std::vector<sync_api::SyncManager::ChangeRecord> changes_;
268 };
269
270 class MockUnrecoverableErrorHandler : public UnrecoverableErrorHandler {
271 public:
272 MOCK_METHOD2(OnUnrecoverableError,
273 void(const tracked_objects::Location&, const std::string&));
274 };
275
276 class ProfileSyncServiceBookmarkTest : public testing::Test {
277 protected:
278 enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE };
279 enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE };
280
ProfileSyncServiceBookmarkTest()281 ProfileSyncServiceBookmarkTest()
282 : ui_thread_(BrowserThread::UI, &message_loop_),
283 file_thread_(BrowserThread::FILE, &message_loop_),
284 model_(NULL) {
285 }
286
~ProfileSyncServiceBookmarkTest()287 virtual ~ProfileSyncServiceBookmarkTest() {
288 StopSync();
289 UnloadBookmarkModel();
290 }
291
SetUp()292 virtual void SetUp() {
293 test_user_share_.SetUp();
294 }
295
TearDown()296 virtual void TearDown() {
297 test_user_share_.TearDown();
298 }
299
300 // Load (or re-load) the bookmark model. |load| controls use of the
301 // bookmarks file on disk. |save| controls whether the newly loaded
302 // bookmark model will write out a bookmark file as it goes.
LoadBookmarkModel(LoadOption load,SaveOption save)303 void LoadBookmarkModel(LoadOption load, SaveOption save) {
304 bool delete_bookmarks = load == DELETE_EXISTING_STORAGE;
305 profile_.CreateBookmarkModel(delete_bookmarks);
306 model_ = profile_.GetBookmarkModel();
307 // Wait for the bookmarks model to load.
308 profile_.BlockUntilBookmarkModelLoaded();
309 // This noticeably speeds up the unit tests that request it.
310 if (save == DONT_SAVE_TO_STORAGE)
311 model_->ClearStore();
312 message_loop_.RunAllPending();
313 }
314
StartSync()315 void StartSync() {
316 // Set up model associator.
317 model_associator_.reset(new TestBookmarkModelAssociator(
318 profile_.GetBookmarkModel(),
319 test_user_share_.user_share(),
320 &mock_unrecoverable_error_handler_));
321 EXPECT_TRUE(model_associator_->AssociateModels());
322 MessageLoop::current()->RunAllPending();
323
324 // Set up change processor.
325 change_processor_.reset(
326 new BookmarkChangeProcessor(model_associator_.get(),
327 &mock_unrecoverable_error_handler_));
328 change_processor_->Start(&profile_, test_user_share_.user_share());
329 }
330
StopSync()331 void StopSync() {
332 change_processor_->Stop();
333 change_processor_.reset();
334
335 EXPECT_TRUE(model_associator_->DisassociateModels());
336 model_associator_.reset();
337
338 message_loop_.RunAllPending();
339
340 // TODO(akalin): Actually close the database and flush it to disk
341 // (and make StartSync reload from disk). This would require
342 // refactoring TestUserShare.
343 }
344
UnloadBookmarkModel()345 void UnloadBookmarkModel() {
346 profile_.CreateBookmarkModel(false /* delete_bookmarks */);
347 model_ = NULL;
348 message_loop_.RunAllPending();
349 }
350
InitSyncNodeFromChromeNode(const BookmarkNode * bnode,sync_api::BaseNode * sync_node)351 bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode,
352 sync_api::BaseNode* sync_node) {
353 return model_associator_->InitSyncNodeFromChromeId(bnode->id(),
354 sync_node);
355 }
356
ExpectSyncerNodeMatching(sync_api::BaseTransaction * trans,const BookmarkNode * bnode)357 void ExpectSyncerNodeMatching(sync_api::BaseTransaction* trans,
358 const BookmarkNode* bnode) {
359 sync_api::ReadNode gnode(trans);
360 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode));
361 // Non-root node titles and parents must match.
362 if (bnode != model_->GetBookmarkBarNode() &&
363 bnode != model_->other_node()) {
364 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(gnode.GetTitle()));
365 EXPECT_EQ(
366 model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()),
367 bnode->parent());
368 }
369 EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder());
370 if (bnode->is_url())
371 EXPECT_EQ(bnode->GetURL(), gnode.GetURL());
372
373 // Check for position matches.
374 int browser_index = bnode->parent()->GetIndexOf(bnode);
375 if (browser_index == 0) {
376 EXPECT_EQ(gnode.GetPredecessorId(), 0);
377 } else {
378 const BookmarkNode* bprev =
379 bnode->parent()->GetChild(browser_index - 1);
380 sync_api::ReadNode gprev(trans);
381 ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev));
382 EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId());
383 EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId());
384 }
385 if (browser_index == bnode->parent()->child_count() - 1) {
386 EXPECT_EQ(gnode.GetSuccessorId(), 0);
387 } else {
388 const BookmarkNode* bnext =
389 bnode->parent()->GetChild(browser_index + 1);
390 sync_api::ReadNode gnext(trans);
391 ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext));
392 EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId());
393 EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId());
394 }
395 if (bnode->child_count()) {
396 EXPECT_TRUE(gnode.GetFirstChildId());
397 }
398 }
399
ExpectSyncerNodeMatching(const BookmarkNode * bnode)400 void ExpectSyncerNodeMatching(const BookmarkNode* bnode) {
401 sync_api::ReadTransaction trans(test_user_share_.user_share());
402 ExpectSyncerNodeMatching(&trans, bnode);
403 }
404
ExpectBrowserNodeMatching(sync_api::BaseTransaction * trans,int64 sync_id)405 void ExpectBrowserNodeMatching(sync_api::BaseTransaction* trans,
406 int64 sync_id) {
407 EXPECT_TRUE(sync_id);
408 const BookmarkNode* bnode =
409 model_associator_->GetChromeNodeFromSyncId(sync_id);
410 ASSERT_TRUE(bnode);
411 int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id());
412 EXPECT_EQ(id, sync_id);
413 ExpectSyncerNodeMatching(trans, bnode);
414 }
415
ExpectBrowserNodeUnknown(int64 sync_id)416 void ExpectBrowserNodeUnknown(int64 sync_id) {
417 EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id));
418 }
419
ExpectBrowserNodeKnown(int64 sync_id)420 void ExpectBrowserNodeKnown(int64 sync_id) {
421 EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id));
422 }
423
ExpectSyncerNodeKnown(const BookmarkNode * node)424 void ExpectSyncerNodeKnown(const BookmarkNode* node) {
425 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
426 EXPECT_NE(sync_id, sync_api::kInvalidId);
427 }
428
ExpectSyncerNodeUnknown(const BookmarkNode * node)429 void ExpectSyncerNodeUnknown(const BookmarkNode* node) {
430 int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id());
431 EXPECT_EQ(sync_id, sync_api::kInvalidId);
432 }
433
ExpectBrowserNodeTitle(int64 sync_id,const std::wstring & title)434 void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) {
435 const BookmarkNode* bnode =
436 model_associator_->GetChromeNodeFromSyncId(sync_id);
437 ASSERT_TRUE(bnode);
438 EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title));
439 }
440
ExpectBrowserNodeURL(int64 sync_id,const std::string & url)441 void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) {
442 const BookmarkNode* bnode =
443 model_associator_->GetChromeNodeFromSyncId(sync_id);
444 ASSERT_TRUE(bnode);
445 EXPECT_EQ(GURL(url), bnode->GetURL());
446 }
447
ExpectBrowserNodeParent(int64 sync_id,int64 parent_sync_id)448 void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) {
449 const BookmarkNode* node =
450 model_associator_->GetChromeNodeFromSyncId(sync_id);
451 ASSERT_TRUE(node);
452 const BookmarkNode* parent =
453 model_associator_->GetChromeNodeFromSyncId(parent_sync_id);
454 EXPECT_TRUE(parent);
455 EXPECT_EQ(node->parent(), parent);
456 }
457
ExpectModelMatch(sync_api::BaseTransaction * trans)458 void ExpectModelMatch(sync_api::BaseTransaction* trans) {
459 const BookmarkNode* root = model_->root_node();
460 EXPECT_EQ(root->GetIndexOf(model_->GetBookmarkBarNode()), 0);
461 EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1);
462
463 std::stack<int64> stack;
464 stack.push(bookmark_bar_id());
465 while (!stack.empty()) {
466 int64 id = stack.top();
467 stack.pop();
468 if (!id) continue;
469
470 ExpectBrowserNodeMatching(trans, id);
471
472 sync_api::ReadNode gnode(trans);
473 ASSERT_TRUE(gnode.InitByIdLookup(id));
474 stack.push(gnode.GetFirstChildId());
475 stack.push(gnode.GetSuccessorId());
476 }
477 }
478
ExpectModelMatch()479 void ExpectModelMatch() {
480 sync_api::ReadTransaction trans(test_user_share_.user_share());
481 ExpectModelMatch(&trans);
482 }
483
other_bookmarks_id()484 int64 other_bookmarks_id() {
485 return
486 model_associator_->GetSyncIdFromChromeId(model_->other_node()->id());
487 }
488
bookmark_bar_id()489 int64 bookmark_bar_id() {
490 return model_associator_->GetSyncIdFromChromeId(
491 model_->GetBookmarkBarNode()->id());
492 }
493
494 private:
495 // Used by both |ui_thread_| and |file_thread_|.
496 MessageLoop message_loop_;
497 BrowserThread ui_thread_;
498 // Needed by |model_|.
499 BrowserThread file_thread_;
500
501 TestingProfile profile_;
502 scoped_ptr<TestBookmarkModelAssociator> model_associator_;
503
504 protected:
505 BookmarkModel* model_;
506 TestUserShare test_user_share_;
507 scoped_ptr<BookmarkChangeProcessor> change_processor_;
508 StrictMock<MockUnrecoverableErrorHandler> mock_unrecoverable_error_handler_;
509 };
510
TEST_F(ProfileSyncServiceBookmarkTest,InitialState)511 TEST_F(ProfileSyncServiceBookmarkTest, InitialState) {
512 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
513 StartSync();
514
515 EXPECT_TRUE(other_bookmarks_id());
516 EXPECT_TRUE(bookmark_bar_id());
517
518 ExpectModelMatch();
519 }
520
TEST_F(ProfileSyncServiceBookmarkTest,BookmarkModelOperations)521 TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) {
522 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
523 StartSync();
524
525 // Test addition.
526 const BookmarkNode* folder =
527 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foobar"));
528 ExpectSyncerNodeMatching(folder);
529 ExpectModelMatch();
530 const BookmarkNode* folder2 =
531 model_->AddFolder(folder, 0, ASCIIToUTF16("nested"));
532 ExpectSyncerNodeMatching(folder2);
533 ExpectModelMatch();
534 const BookmarkNode* url1 = model_->AddURL(
535 folder, 0, ASCIIToUTF16("Internets #1 Pies Site"),
536 GURL("http://www.easypie.com/"));
537 ExpectSyncerNodeMatching(url1);
538 ExpectModelMatch();
539 const BookmarkNode* url2 = model_->AddURL(
540 folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/"));
541 ExpectSyncerNodeMatching(url2);
542 ExpectModelMatch();
543
544 // Test modification.
545 model_->SetTitle(url2, ASCIIToUTF16("EasyJet"));
546 ExpectModelMatch();
547 model_->Move(url1, folder2, 0);
548 ExpectModelMatch();
549 model_->Move(folder2, model_->GetBookmarkBarNode(), 0);
550 ExpectModelMatch();
551 model_->SetTitle(folder2, ASCIIToUTF16("Not Nested"));
552 ExpectModelMatch();
553 model_->Move(folder, folder2, 0);
554 ExpectModelMatch();
555 model_->SetTitle(folder, ASCIIToUTF16("who's nested now?"));
556 ExpectModelMatch();
557 model_->Copy(url2, model_->GetBookmarkBarNode(), 0);
558 ExpectModelMatch();
559
560 // Test deletion.
561 // Delete a single item.
562 model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2));
563 ExpectModelMatch();
564 // Delete an item with several children.
565 model_->Remove(folder2->parent(),
566 folder2->parent()->GetIndexOf(folder2));
567 ExpectModelMatch();
568 }
569
TEST_F(ProfileSyncServiceBookmarkTest,ServerChangeProcessing)570 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) {
571 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
572 StartSync();
573
574 sync_api::WriteTransaction trans(test_user_share_.user_share());
575
576 FakeServerChange adds(&trans);
577 int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0);
578 int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1);
579 int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/",
580 bookmark_bar_id(), f2);
581 int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0);
582 // u3 is a duplicate URL
583 int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2);
584 // u4 is a duplicate title, different URL.
585 adds.AddURL(L"Some old site", "http://slog.thestranger.com/",
586 bookmark_bar_id(), u1);
587 // u5 tests an empty-string title.
588 std::string javascript_url(
589 "javascript:(function(){var w=window.open(" \
590 "'about:blank','gnotesWin','location=0,menubar=0," \
591 "scrollbars=0,status=0,toolbar=0,width=300," \
592 "height=300,resizable');});");
593 adds.AddURL(L"", javascript_url, other_bookmarks_id(), 0);
594
595 std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it;
596 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
597 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
598 ExpectBrowserNodeUnknown(it->id);
599
600 adds.ApplyPendingChanges(change_processor_.get());
601
602 // Make sure the bookmark model received all of the nodes in |adds|.
603 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
604 ExpectBrowserNodeMatching(&trans, it->id);
605 ExpectModelMatch(&trans);
606
607 // Part two: test modifications.
608 FakeServerChange mods(&trans);
609 // Mess with u2, and move it into empty folder f2
610 // TODO(ncarter): Determine if we allow ModifyURL ops or not.
611 /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */
612 std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google");
613 int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0);
614
615 // Now move f1 after u2.
616 std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C");
617 int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2);
618
619 // Then add u3 after f1.
620 int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1);
621
622 // Test that the property changes have not yet taken effect.
623 ExpectBrowserNodeTitle(u2, u2_old_title);
624 /* ExpectBrowserNodeURL(u2, u2_old_url); */
625 ExpectBrowserNodeParent(u2, u2_old_parent);
626
627 ExpectBrowserNodeTitle(f1, f1_old_title);
628 ExpectBrowserNodeParent(f1, f1_old_parent);
629
630 ExpectBrowserNodeParent(u3, u3_old_parent);
631
632 // Apply the changes.
633 mods.ApplyPendingChanges(change_processor_.get());
634
635 // Check for successful application.
636 for (it = mods.changes().begin(); it != mods.changes().end(); ++it)
637 ExpectBrowserNodeMatching(&trans, it->id);
638 ExpectModelMatch(&trans);
639
640 // Part 3: Test URL deletion.
641 FakeServerChange dels(&trans);
642 dels.Delete(u2);
643 dels.Delete(u3);
644
645 ExpectBrowserNodeKnown(u2);
646 ExpectBrowserNodeKnown(u3);
647
648 dels.ApplyPendingChanges(change_processor_.get());
649
650 ExpectBrowserNodeUnknown(u2);
651 ExpectBrowserNodeUnknown(u3);
652 ExpectModelMatch(&trans);
653 }
654
655 // Tests a specific case in ApplyModelChanges where we move the
656 // children out from under a parent, and then delete the parent
657 // in the same changelist. The delete shows up first in the changelist,
658 // requiring the children to be moved to a temporary location.
TEST_F(ProfileSyncServiceBookmarkTest,ServerChangeRequiringFosterParent)659 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) {
660 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
661 StartSync();
662
663 sync_api::WriteTransaction trans(test_user_share_.user_share());
664
665 // Stress the immediate children of other_node because that's where
666 // ApplyModelChanges puts a temporary foster parent node.
667 std::string url("http://dev.chromium.org/");
668 FakeServerChange adds(&trans);
669 int64 f0 = other_bookmarks_id(); // + other_node
670 int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1
671 int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2
672 int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT
673 int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT
674 int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT
675 int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6
676 int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT
677
678 std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it;
679 // The bookmark model shouldn't yet have seen any of the nodes of |adds|.
680 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
681 ExpectBrowserNodeUnknown(it->id);
682
683 adds.ApplyPendingChanges(change_processor_.get());
684
685 // Make sure the bookmark model received all of the nodes in |adds|.
686 for (it = adds.changes().begin(); it != adds.changes().end(); ++it)
687 ExpectBrowserNodeMatching(&trans, it->id);
688 ExpectModelMatch(&trans);
689
690 // We have to do the moves before the deletions, but FakeServerChange will
691 // put the deletion at the front of the changelist.
692 FakeServerChange ops(&trans);
693 ops.ModifyPosition(f6, other_bookmarks_id(), 0);
694 ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here.
695 ops.ModifyPosition(f2, other_bookmarks_id(), u7);
696 ops.ModifyPosition(u7, f2, 0);
697 ops.ModifyPosition(u4, other_bookmarks_id(), f2);
698 ops.ModifyPosition(u5, f6, 0);
699 ops.Delete(f1);
700
701 ops.ApplyPendingChanges(change_processor_.get());
702
703 ExpectModelMatch(&trans);
704 }
705
706 // Simulate a server change record containing a valid but non-canonical URL.
TEST_F(ProfileSyncServiceBookmarkTest,ServerChangeWithNonCanonicalURL)707 TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) {
708 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
709 StartSync();
710
711 {
712 sync_api::WriteTransaction trans(test_user_share_.user_share());
713
714 FakeServerChange adds(&trans);
715 std::string url("http://dev.chromium.org");
716 EXPECT_NE(GURL(url).spec(), url);
717 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
718
719 adds.ApplyPendingChanges(change_processor_.get());
720
721 EXPECT_TRUE(model_->other_node()->child_count() == 1);
722 ExpectModelMatch(&trans);
723 }
724
725 // Now reboot the sync service, forcing a merge step.
726 StopSync();
727 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
728 StartSync();
729
730 // There should still be just the one bookmark.
731 EXPECT_TRUE(model_->other_node()->child_count() == 1);
732 ExpectModelMatch();
733 }
734
735 // Simulate a server change record containing an invalid URL (per GURL).
736 // TODO(ncarter): Disabled due to crashes. Fix bug 1677563.
TEST_F(ProfileSyncServiceBookmarkTest,DISABLED_ServerChangeWithInvalidURL)737 TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) {
738 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
739 StartSync();
740
741 int child_count = 0;
742 {
743 sync_api::WriteTransaction trans(test_user_share_.user_share());
744
745 FakeServerChange adds(&trans);
746 std::string url("x");
747 EXPECT_FALSE(GURL(url).is_valid());
748 adds.AddURL(L"u1", url, other_bookmarks_id(), 0);
749
750 adds.ApplyPendingChanges(change_processor_.get());
751
752 // We're lenient about what should happen -- the model could wind up with
753 // the node or without it; but things should be consistent, and we
754 // shouldn't crash.
755 child_count = model_->other_node()->child_count();
756 EXPECT_TRUE(child_count == 0 || child_count == 1);
757 ExpectModelMatch(&trans);
758 }
759
760 // Now reboot the sync service, forcing a merge step.
761 StopSync();
762 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
763 StartSync();
764
765 // Things ought not to have changed.
766 EXPECT_EQ(model_->other_node()->child_count(), child_count);
767 ExpectModelMatch();
768 }
769
770
771 // Test strings that might pose a problem if the titles ever became used as
772 // file names in the sync backend.
TEST_F(ProfileSyncServiceBookmarkTest,CornerCaseNames)773 TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) {
774 // TODO(ncarter): Bug 1570238 explains the failure of this test.
775 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
776 StartSync();
777
778 const char* names[] = {
779 // The empty string.
780 "",
781 // Illegal Windows filenames.
782 "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4",
783 "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3",
784 "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
785 // Current/parent directory markers.
786 ".", "..", "...",
787 // Files created automatically by the Windows shell.
788 "Thumbs.db", ".DS_Store",
789 // Names including Win32-illegal characters, and path separators.
790 "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar",
791 "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar",
792 "foo[bar",
793 };
794 // Create both folders and bookmarks using each name.
795 GURL url("http://www.doublemint.com");
796 for (size_t i = 0; i < arraysize(names); ++i) {
797 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16(names[i]));
798 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url);
799 }
800
801 // Verify that the browser model matches the sync model.
802 EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names));
803 ExpectModelMatch();
804 }
805
806 // Stress the internal representation of position by sparse numbers. We want
807 // to repeatedly bisect the range of available positions, to force the
808 // syncer code to renumber its ranges. Pick a number big enough so that it
809 // would exhaust 32bits of room between items a couple of times.
TEST_F(ProfileSyncServiceBookmarkTest,RepeatedMiddleInsertion)810 TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) {
811 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
812 StartSync();
813
814 static const int kTimesToInsert = 256;
815
816 // Create two book-end nodes to insert between.
817 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("Alpha"));
818 model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("Omega"));
819 int count = 2;
820
821 // Test insertion in first half of range by repeatedly inserting in second
822 // position.
823 for (int i = 0; i < kTimesToInsert; ++i) {
824 string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i);
825 model_->AddFolder(model_->other_node(), 1, title);
826 count++;
827 }
828
829 // Test insertion in second half of range by repeatedly inserting in
830 // second-to-last position.
831 for (int i = 0; i < kTimesToInsert; ++i) {
832 string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i);
833 model_->AddFolder(model_->other_node(), count - 1, title);
834 count++;
835 }
836
837 // Verify that the browser model matches the sync model.
838 EXPECT_EQ(model_->other_node()->child_count(), count);
839 ExpectModelMatch();
840 }
841
842 // Introduce a consistency violation into the model, and see that it
843 // puts itself into a lame, error state.
TEST_F(ProfileSyncServiceBookmarkTest,UnrecoverableErrorSuspendsService)844 TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) {
845 EXPECT_CALL(mock_unrecoverable_error_handler_,
846 OnUnrecoverableError(_, _));
847
848 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
849 StartSync();
850
851 // Add a node which will be the target of the consistency violation.
852 const BookmarkNode* node =
853 model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("node"));
854 ExpectSyncerNodeMatching(node);
855
856 // Now destroy the syncer node as if we were the ProfileSyncService without
857 // updating the ProfileSyncService state. This should introduce
858 // inconsistency between the two models.
859 {
860 sync_api::WriteTransaction trans(test_user_share_.user_share());
861 sync_api::WriteNode sync_node(&trans);
862 ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node));
863 sync_node.Remove();
864 }
865 // The models don't match at this point, but the ProfileSyncService
866 // doesn't know it yet.
867 ExpectSyncerNodeKnown(node);
868
869 // Add a child to the inconsistent node. This should cause detection of the
870 // problem and the syncer should stop processing changes.
871 model_->AddFolder(node, 0, ASCIIToUTF16("nested"));
872 }
873
874 // See what happens if we run model association when there are two exact URL
875 // duplicate bookmarks. The BookmarkModelAssociator should not fall over when
876 // this happens.
TEST_F(ProfileSyncServiceBookmarkTest,MergeDuplicates)877 TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) {
878 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
879 StartSync();
880
881 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"),
882 GURL("http://dup.com/"));
883 model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"),
884 GURL("http://dup.com/"));
885
886 EXPECT_EQ(2, model_->other_node()->child_count());
887
888 // Restart the sync service to trigger model association.
889 StopSync();
890 StartSync();
891
892 EXPECT_EQ(2, model_->other_node()->child_count());
893 ExpectModelMatch();
894 }
895
896 struct TestData {
897 const wchar_t* title;
898 const char* url;
899 };
900
901 // TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code
902 // in the bookmark model unittest, to make it simpler to set up test data
903 // here (and reduce the amount of duplication among tests), and to reduce the
904 // duplication.
905 class ProfileSyncServiceBookmarkTestWithData
906 : public ProfileSyncServiceBookmarkTest {
907 protected:
908 // Populates or compares children of the given bookmark node from/with the
909 // given test data array with the given size.
910 void PopulateFromTestData(const BookmarkNode* node,
911 const TestData* data,
912 int size);
913 void CompareWithTestData(const BookmarkNode* node,
914 const TestData* data,
915 int size);
916
917 void ExpectBookmarkModelMatchesTestData();
918 void WriteTestDataToBookmarkModel();
919 };
920
921 namespace {
922
923 // Constants for bookmark model that looks like:
924 // |-- Bookmark bar
925 // | |-- u2, http://www.u2.com/
926 // | |-- f1
927 // | | |-- f1u4, http://www.f1u4.com/
928 // | | |-- f1u2, http://www.f1u2.com/
929 // | | |-- f1u3, http://www.f1u3.com/
930 // | | +-- f1u1, http://www.f1u1.com/
931 // | |-- u1, http://www.u1.com/
932 // | +-- f2
933 // | |-- f2u2, http://www.f2u2.com/
934 // | |-- f2u4, http://www.f2u4.com/
935 // | |-- f2u3, http://www.f2u3.com/
936 // | +-- f2u1, http://www.f2u1.com/
937 // +-- Other bookmarks
938 // |-- f3
939 // | |-- f3u4, http://www.f3u4.com/
940 // | |-- f3u2, http://www.f3u2.com/
941 // | |-- f3u3, http://www.f3u3.com/
942 // | +-- f3u1, http://www.f3u1.com/
943 // |-- u4, http://www.u4.com/
944 // |-- u3, http://www.u3.com/
945 // --- f4
946 // | |-- f4u1, http://www.f4u1.com/
947 // | |-- f4u2, http://www.f4u2.com/
948 // | |-- f4u3, http://www.f4u3.com/
949 // | +-- f4u4, http://www.f4u4.com/
950 // |-- dup
951 // | +-- dupu1, http://www.dupu1.com/
952 // +-- dup
953 // +-- dupu2, http://www.dupu1.com/
954 //
955 static TestData kBookmarkBarChildren[] = {
956 { L"u2", "http://www.u2.com/" },
957 { L"f1", NULL },
958 { L"u1", "http://www.u1.com/" },
959 { L"f2", NULL },
960 };
961 static TestData kF1Children[] = {
962 { L"f1u4", "http://www.f1u4.com/" },
963 { L"f1u2", "http://www.f1u2.com/" },
964 { L"f1u3", "http://www.f1u3.com/" },
965 { L"f1u1", "http://www.f1u1.com/" },
966 };
967 static TestData kF2Children[] = {
968 { L"f2u2", "http://www.f2u2.com/" },
969 { L"f2u4", "http://www.f2u4.com/" },
970 { L"f2u3", "http://www.f2u3.com/" },
971 { L"f2u1", "http://www.f2u1.com/" },
972 };
973
974 static TestData kOtherBookmarkChildren[] = {
975 { L"f3", NULL },
976 { L"u4", "http://www.u4.com/" },
977 { L"u3", "http://www.u3.com/" },
978 { L"f4", NULL },
979 { L"dup", NULL },
980 { L"dup", NULL },
981 };
982 static TestData kF3Children[] = {
983 { L"f3u4", "http://www.f3u4.com/" },
984 { L"f3u2", "http://www.f3u2.com/" },
985 { L"f3u3", "http://www.f3u3.com/" },
986 { L"f3u1", "http://www.f3u1.com/" },
987 };
988 static TestData kF4Children[] = {
989 { L"f4u1", "http://www.f4u1.com/" },
990 { L"f4u2", "http://www.f4u2.com/" },
991 { L"f4u3", "http://www.f4u3.com/" },
992 { L"f4u4", "http://www.f4u4.com/" },
993 };
994 static TestData kDup1Children[] = {
995 { L"dupu1", "http://www.dupu1.com/" },
996 };
997 static TestData kDup2Children[] = {
998 { L"dupu2", "http://www.dupu2.com/" },
999 };
1000
1001 } // anonymous namespace.
1002
PopulateFromTestData(const BookmarkNode * node,const TestData * data,int size)1003 void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData(
1004 const BookmarkNode* node, const TestData* data, int size) {
1005 DCHECK(node);
1006 DCHECK(data);
1007 DCHECK(node->is_folder());
1008 for (int i = 0; i < size; ++i) {
1009 const TestData& item = data[i];
1010 if (item.url) {
1011 model_->AddURL(node, i, WideToUTF16Hack(item.title), GURL(item.url));
1012 } else {
1013 model_->AddFolder(node, i, WideToUTF16Hack(item.title));
1014 }
1015 }
1016 }
1017
CompareWithTestData(const BookmarkNode * node,const TestData * data,int size)1018 void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData(
1019 const BookmarkNode* node, const TestData* data, int size) {
1020 DCHECK(node);
1021 DCHECK(data);
1022 DCHECK(node->is_folder());
1023 ASSERT_EQ(size, node->child_count());
1024 for (int i = 0; i < size; ++i) {
1025 const BookmarkNode* child_node = node->GetChild(i);
1026 const TestData& item = data[i];
1027 EXPECT_EQ(child_node->GetTitle(), WideToUTF16Hack(item.title));
1028 if (item.url) {
1029 EXPECT_FALSE(child_node->is_folder());
1030 EXPECT_TRUE(child_node->is_url());
1031 EXPECT_EQ(child_node->GetURL(), GURL(item.url));
1032 } else {
1033 EXPECT_TRUE(child_node->is_folder());
1034 EXPECT_FALSE(child_node->is_url());
1035 }
1036 }
1037 }
1038
1039 // TODO(munjal): We should implement some way of generating random data and can
1040 // use the same seed to generate the same sequence.
WriteTestDataToBookmarkModel()1041 void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() {
1042 const BookmarkNode* bookmarks_bar_node = model_->GetBookmarkBarNode();
1043 PopulateFromTestData(bookmarks_bar_node,
1044 kBookmarkBarChildren,
1045 arraysize(kBookmarkBarChildren));
1046
1047 ASSERT_GE(bookmarks_bar_node->child_count(), 4);
1048 const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1);
1049 PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children));
1050 const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3);
1051 PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children));
1052
1053 const BookmarkNode* other_bookmarks_node = model_->other_node();
1054 PopulateFromTestData(other_bookmarks_node,
1055 kOtherBookmarkChildren,
1056 arraysize(kOtherBookmarkChildren));
1057
1058 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1059 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1060 PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children));
1061 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1062 PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children));
1063 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1064 PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children));
1065 dup_node = other_bookmarks_node->GetChild(5);
1066 PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children));
1067
1068 ExpectBookmarkModelMatchesTestData();
1069 }
1070
1071 void ProfileSyncServiceBookmarkTestWithData::
ExpectBookmarkModelMatchesTestData()1072 ExpectBookmarkModelMatchesTestData() {
1073 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode();
1074 CompareWithTestData(bookmark_bar_node,
1075 kBookmarkBarChildren,
1076 arraysize(kBookmarkBarChildren));
1077
1078 ASSERT_GE(bookmark_bar_node->child_count(), 4);
1079 const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1);
1080 CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children));
1081 const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3);
1082 CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children));
1083
1084 const BookmarkNode* other_bookmarks_node = model_->other_node();
1085 CompareWithTestData(other_bookmarks_node,
1086 kOtherBookmarkChildren,
1087 arraysize(kOtherBookmarkChildren));
1088
1089 ASSERT_GE(other_bookmarks_node->child_count(), 6);
1090 const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0);
1091 CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children));
1092 const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3);
1093 CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children));
1094 const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4);
1095 CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children));
1096 dup_node = other_bookmarks_node->GetChild(5);
1097 CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children));
1098 }
1099
1100 // Tests persistence of the profile sync service by unloading the
1101 // database and then reloading it from disk.
TEST_F(ProfileSyncServiceBookmarkTestWithData,Persistence)1102 TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) {
1103 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1104 StartSync();
1105
1106 WriteTestDataToBookmarkModel();
1107
1108 ExpectModelMatch();
1109
1110 // Force both models to discard their data and reload from disk. This
1111 // simulates what would happen if the browser were to shutdown normally,
1112 // and then relaunch.
1113 StopSync();
1114 UnloadBookmarkModel();
1115 LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE);
1116 StartSync();
1117
1118 ExpectBookmarkModelMatchesTestData();
1119
1120 // With the BookmarkModel contents verified, ExpectModelMatch will
1121 // verify the contents of the sync model.
1122 ExpectModelMatch();
1123 }
1124
1125 // Tests the merge case when the BookmarkModel is non-empty but the
1126 // sync model is empty. This corresponds to uploading browser
1127 // bookmarks to an initially empty, new account.
TEST_F(ProfileSyncServiceBookmarkTestWithData,MergeWithEmptySyncModel)1128 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) {
1129 // Don't start the sync service until we've populated the bookmark model.
1130 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1131
1132 WriteTestDataToBookmarkModel();
1133
1134 // Restart sync. This should trigger a merge step during
1135 // initialization -- we expect the browser bookmarks to be written
1136 // to the sync service during this call.
1137 StartSync();
1138
1139 // Verify that the bookmark model hasn't changed, and that the sync model
1140 // matches it exactly.
1141 ExpectBookmarkModelMatchesTestData();
1142 ExpectModelMatch();
1143 }
1144
1145 // Tests the merge case when the BookmarkModel is empty but the sync model is
1146 // non-empty. This corresponds (somewhat) to a clean install of the browser,
1147 // with no bookmarks, connecting to a sync account that has some bookmarks.
TEST_F(ProfileSyncServiceBookmarkTestWithData,MergeWithEmptyBookmarkModel)1148 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) {
1149 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1150 StartSync();
1151
1152 WriteTestDataToBookmarkModel();
1153
1154 ExpectModelMatch();
1155
1156 // Force the databse to unload and write itself to disk.
1157 StopSync();
1158
1159 // Blow away the bookmark model -- it should be empty afterwards.
1160 UnloadBookmarkModel();
1161 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1162 EXPECT_EQ(model_->GetBookmarkBarNode()->child_count(), 0);
1163 EXPECT_EQ(model_->other_node()->child_count(), 0);
1164
1165 // Now restart the sync service. Starting it should populate the bookmark
1166 // model -- test for consistency.
1167 StartSync();
1168 ExpectBookmarkModelMatchesTestData();
1169 ExpectModelMatch();
1170 }
1171
1172 // Tests the merge cases when both the models are expected to be identical
1173 // after the merge.
TEST_F(ProfileSyncServiceBookmarkTestWithData,MergeExpectedIdenticalModels)1174 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) {
1175 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1176 StartSync();
1177 WriteTestDataToBookmarkModel();
1178 ExpectModelMatch();
1179 StopSync();
1180 UnloadBookmarkModel();
1181
1182 // At this point both the bookmark model and the server should have the
1183 // exact same data and it should match the test data.
1184 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1185 StartSync();
1186 ExpectBookmarkModelMatchesTestData();
1187 ExpectModelMatch();
1188 StopSync();
1189 UnloadBookmarkModel();
1190
1191 // Now reorder some bookmarks in the bookmark model and then merge. Make
1192 // sure we get the order of the server after merge.
1193 LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE);
1194 ExpectBookmarkModelMatchesTestData();
1195 const BookmarkNode* bookmark_bar = model_->GetBookmarkBarNode();
1196 ASSERT_TRUE(bookmark_bar);
1197 ASSERT_GT(bookmark_bar->child_count(), 1);
1198 model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1);
1199 StartSync();
1200 ExpectModelMatch();
1201 ExpectBookmarkModelMatchesTestData();
1202 }
1203
1204 // Tests the merge cases when both the models are expected to be identical
1205 // after the merge.
TEST_F(ProfileSyncServiceBookmarkTestWithData,MergeModelsWithSomeExtras)1206 TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) {
1207 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1208 WriteTestDataToBookmarkModel();
1209 ExpectBookmarkModelMatchesTestData();
1210
1211 // Remove some nodes and reorder some nodes.
1212 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode();
1213 int remove_index = 2;
1214 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1215 const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index);
1216 ASSERT_TRUE(child_node);
1217 ASSERT_TRUE(child_node->is_url());
1218 model_->Remove(bookmark_bar_node, remove_index);
1219 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1220 child_node = bookmark_bar_node->GetChild(remove_index);
1221 ASSERT_TRUE(child_node);
1222 ASSERT_TRUE(child_node->is_folder());
1223 model_->Remove(bookmark_bar_node, remove_index);
1224
1225 const BookmarkNode* other_node = model_->other_node();
1226 ASSERT_GE(other_node->child_count(), 1);
1227 const BookmarkNode* f3_node = other_node->GetChild(0);
1228 ASSERT_TRUE(f3_node);
1229 ASSERT_TRUE(f3_node->is_folder());
1230 remove_index = 2;
1231 ASSERT_GT(f3_node->child_count(), remove_index);
1232 model_->Remove(f3_node, remove_index);
1233 ASSERT_GT(f3_node->child_count(), remove_index);
1234 model_->Remove(f3_node, remove_index);
1235
1236 StartSync();
1237 ExpectModelMatch();
1238 StopSync();
1239
1240 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1241 WriteTestDataToBookmarkModel();
1242 ExpectBookmarkModelMatchesTestData();
1243
1244 // Remove some nodes and reorder some nodes.
1245 bookmark_bar_node = model_->GetBookmarkBarNode();
1246 remove_index = 0;
1247 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1248 child_node = bookmark_bar_node->GetChild(remove_index);
1249 ASSERT_TRUE(child_node);
1250 ASSERT_TRUE(child_node->is_url());
1251 model_->Remove(bookmark_bar_node, remove_index);
1252 ASSERT_GT(bookmark_bar_node->child_count(), remove_index);
1253 child_node = bookmark_bar_node->GetChild(remove_index);
1254 ASSERT_TRUE(child_node);
1255 ASSERT_TRUE(child_node->is_folder());
1256 model_->Remove(bookmark_bar_node, remove_index);
1257
1258 ASSERT_GE(bookmark_bar_node->child_count(), 2);
1259 model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1);
1260
1261 other_node = model_->other_node();
1262 ASSERT_GE(other_node->child_count(), 1);
1263 f3_node = other_node->GetChild(0);
1264 ASSERT_TRUE(f3_node);
1265 ASSERT_TRUE(f3_node->is_folder());
1266 remove_index = 0;
1267 ASSERT_GT(f3_node->child_count(), remove_index);
1268 model_->Remove(f3_node, remove_index);
1269 ASSERT_GT(f3_node->child_count(), remove_index);
1270 model_->Remove(f3_node, remove_index);
1271
1272 ASSERT_GE(other_node->child_count(), 4);
1273 model_->Move(other_node->GetChild(0), other_node, 1);
1274 model_->Move(other_node->GetChild(2), other_node, 3);
1275
1276 StartSync();
1277 ExpectModelMatch();
1278
1279 // After the merge, the model should match the test data.
1280 ExpectBookmarkModelMatchesTestData();
1281 }
1282
1283 // Tests that when persisted model associations are used, things work fine.
TEST_F(ProfileSyncServiceBookmarkTestWithData,ModelAssociationPersistence)1284 TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) {
1285 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1286 WriteTestDataToBookmarkModel();
1287 StartSync();
1288 ExpectModelMatch();
1289 // Force sync to shut down and write itself to disk.
1290 StopSync();
1291 // Now restart sync. This time it should use the persistent
1292 // associations.
1293 StartSync();
1294 ExpectModelMatch();
1295 }
1296
1297 // Tests that when persisted model associations are used, things work fine.
TEST_F(ProfileSyncServiceBookmarkTestWithData,ModelAssociationInvalidPersistence)1298 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1299 ModelAssociationInvalidPersistence) {
1300 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1301 WriteTestDataToBookmarkModel();
1302 StartSync();
1303 ExpectModelMatch();
1304 // Force sync to shut down and write itself to disk.
1305 StopSync();
1306 // Change the bookmark model before restarting sync service to simulate
1307 // the situation where bookmark model is different from sync model and
1308 // make sure model associator correctly rebuilds associations.
1309 const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode();
1310 model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"),
1311 GURL("http://www.xtra.com"));
1312 // Now restart sync. This time it will try to use the persistent
1313 // associations and realize that they are invalid and hence will rebuild
1314 // associations.
1315 StartSync();
1316 ExpectModelMatch();
1317 }
1318
TEST_F(ProfileSyncServiceBookmarkTestWithData,SortChildren)1319 TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) {
1320 LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE);
1321 StartSync();
1322
1323 // Write test data to bookmark model and verify that the models match.
1324 WriteTestDataToBookmarkModel();
1325 const BookmarkNode* folder_added = model_->other_node()->GetChild(0);
1326 ASSERT_TRUE(folder_added);
1327 ASSERT_TRUE(folder_added->is_folder());
1328
1329 ExpectModelMatch();
1330
1331 // Sort the other-bookmarks children and expect that hte models match.
1332 model_->SortChildren(folder_added);
1333 ExpectModelMatch();
1334 }
1335
1336 // See what happens if we enable sync but then delete the "Sync Data"
1337 // folder.
TEST_F(ProfileSyncServiceBookmarkTestWithData,RecoverAfterDeletingSyncDataDirectory)1338 TEST_F(ProfileSyncServiceBookmarkTestWithData,
1339 RecoverAfterDeletingSyncDataDirectory) {
1340 LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE);
1341 StartSync();
1342
1343 WriteTestDataToBookmarkModel();
1344
1345 StopSync();
1346
1347 // Nuke the sync DB and reload.
1348 test_user_share_.TearDown();
1349 test_user_share_.SetUp();
1350
1351 StartSync();
1352
1353 // Make sure we're back in sync. In real life, the user would need
1354 // to reauthenticate before this happens, but in the test, authentication
1355 // is sidestepped.
1356 ExpectBookmarkModelMatchesTestData();
1357 ExpectModelMatch();
1358 }
1359
1360 } // namespace
1361
1362 } // namespace browser_sync
1363