• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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