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