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