1// Copyright 2014 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#include "components/enhanced_bookmarks/enhanced_bookmark_model.h"
6
7#include "base/base64.h"
8#include "base/macros.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/message_loop/message_loop.h"
11#include "base/run_loop.h"
12#include "base/strings/string_util.h"
13#include "base/strings/utf_string_conversions.h"
14#include "components/bookmarks/browser/bookmark_model.h"
15#include "components/bookmarks/browser/bookmark_node.h"
16#include "components/bookmarks/test/test_bookmark_client.h"
17#include "components/enhanced_bookmarks/enhanced_bookmark_model_observer.h"
18#include "components/enhanced_bookmarks/proto/metadata.pb.h"
19#include "testing/gtest/include/gtest/gtest.h"
20#include "url/gurl.h"
21
22using enhanced_bookmarks::EnhancedBookmarkModel;
23
24namespace {
25const std::string BOOKMARK_URL("http://example.com/index.html");
26}  // namespace
27
28class EnhancedBookmarkModelTest
29    : public testing::Test,
30      public enhanced_bookmarks::EnhancedBookmarkModelObserver {
31 public:
32  EnhancedBookmarkModelTest()
33      : loaded_calls_(0),
34        shutting_down_calls_(0),
35        added_calls_(0),
36        removed_calls_(0),
37        all_user_nodes_removed_calls_(0),
38        remote_id_changed_calls_(0),
39        last_added_(NULL),
40        last_removed_(NULL),
41        last_remote_id_node_(NULL) {}
42  virtual ~EnhancedBookmarkModelTest() {}
43
44  virtual void SetUp() OVERRIDE {
45    message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_DEFAULT));
46    bookmark_client_.reset(new bookmarks::TestBookmarkClient());
47    bookmark_model_.reset(bookmark_client_->CreateModel().release());
48    model_.reset(new EnhancedBookmarkModel(bookmark_model_.get(), "v1.0"));
49    model_->AddObserver(this);
50  }
51
52  virtual void TearDown() OVERRIDE {
53    if (model_)
54      model_->Shutdown();
55    model_.reset();
56    bookmark_model_.reset();
57    bookmark_client_.reset();
58    message_loop_.reset();
59  }
60
61 protected:
62  const BookmarkNode* AddBookmark() {
63    return AddBookmark("Some title", bookmark_model_->other_node());
64  }
65
66  const BookmarkNode* AddFolder() {
67    return AddFolder("Some title", bookmark_model_->other_node());
68  }
69
70  const BookmarkNode* AddBookmark(const std::string& name,
71                                  const BookmarkNode* parent) {
72    return model_->AddURL(parent,
73                          0,  // index.
74                          base::ASCIIToUTF16(name),
75                          GURL(BOOKMARK_URL),
76                          base::Time::Now());
77  }
78
79  const BookmarkNode* AddFolder(const std::string& name,
80                                const BookmarkNode* parent) {
81    return model_->AddFolder(parent, 0, base::ASCIIToUTF16(name));
82  }
83
84  std::string GetVersion(const BookmarkNode* node) {
85    return GetMetaInfoField(node, "stars.version");
86  }
87
88  std::string GetId(const BookmarkNode* node) {
89    return GetMetaInfoField(node, "stars.id");
90  }
91
92  std::string GetOldId(const BookmarkNode* node) {
93    return GetMetaInfoField(node, "stars.oldId");
94  }
95
96  std::string GetMetaInfoField(const BookmarkNode* node,
97                               const std::string& name) {
98    std::string value;
99    if (!node->GetMetaInfo(name, &value))
100      return std::string();
101    return value;
102  }
103
104  scoped_ptr<base::MessageLoop> message_loop_;
105  scoped_ptr<bookmarks::TestBookmarkClient> bookmark_client_;
106  scoped_ptr<BookmarkModel> bookmark_model_;
107  scoped_ptr<EnhancedBookmarkModel> model_;
108
109  // EnhancedBookmarkModelObserver implementation:
110  virtual void EnhancedBookmarkModelLoaded() OVERRIDE { loaded_calls_++; }
111  virtual void EnhancedBookmarkModelShuttingDown() OVERRIDE {
112    shutting_down_calls_++;
113  }
114  virtual void EnhancedBookmarkAdded(const BookmarkNode* node) OVERRIDE {
115    added_calls_++;
116    last_added_ = node;
117  }
118  virtual void EnhancedBookmarkRemoved(const BookmarkNode* node) OVERRIDE {
119    removed_calls_++;
120    last_removed_ = node;
121  }
122  virtual void EnhancedBookmarkAllUserNodesRemoved() OVERRIDE {
123    all_user_nodes_removed_calls_++;
124  }
125  virtual void EnhancedBookmarkRemoteIdChanged(
126      const BookmarkNode* node,
127      const std::string& old_remote_id,
128      const std::string& remote_id) OVERRIDE {
129    remote_id_changed_calls_++;
130    last_remote_id_node_ = node;
131    last_old_remote_id_ = old_remote_id;
132    last_remote_id_ = remote_id;
133  }
134
135  // Observer call counters:
136  int loaded_calls_;
137  int shutting_down_calls_;
138  int added_calls_;
139  int removed_calls_;
140  int all_user_nodes_removed_calls_;
141  int remote_id_changed_calls_;
142
143  // Observer parameter cache:
144  const BookmarkNode* last_added_;
145  const BookmarkNode* last_removed_;
146  const BookmarkNode* last_remote_id_node_;
147  std::string last_old_remote_id_;
148  std::string last_remote_id_;
149
150 private:
151  DISALLOW_COPY_AND_ASSIGN(EnhancedBookmarkModelTest);
152};
153
154TEST_F(EnhancedBookmarkModelTest, TestEmptySnippet) {
155  const BookmarkNode* node = AddBookmark();
156
157  std::string snippet(model_->GetSnippet(node));
158  EXPECT_EQ(snippet, "");
159};
160
161TEST_F(EnhancedBookmarkModelTest, TestSnippet) {
162  const BookmarkNode* node = AddBookmark();
163
164  // Binary serialize the protobuf.
165  image::collections::PageData data;
166  data.set_snippet("I'm happy!");
167  ASSERT_TRUE(data.IsInitialized());
168  std::string output;
169  bool result = data.SerializeToString(&output);
170  ASSERT_TRUE(result);
171
172  // base64 encode the output.
173  std::string encoded;
174  base::Base64Encode(output, &encoded);
175  bookmark_model_->SetNodeMetaInfo(node, "stars.pageData", encoded);
176
177  std::string snippet(model_->GetSnippet(node));
178  EXPECT_EQ(snippet, "I'm happy!");
179}
180
181TEST_F(EnhancedBookmarkModelTest, TestBadEncodingSnippet) {
182  const BookmarkNode* node = AddBookmark();
183
184  // Binary serialize the protobuf.
185  image::collections::PageData data;
186  data.set_snippet("You are happy!");
187  ASSERT_TRUE(data.IsInitialized());
188  std::string output;
189  bool result = data.SerializeToString(&output);
190  ASSERT_TRUE(result);
191
192  // don't base 64 encode the output.
193  bookmark_model_->SetNodeMetaInfo(node, "stars.pageData", output);
194
195  std::string snippet(model_->GetSnippet(node));
196  EXPECT_EQ(snippet, "");
197}
198
199TEST_F(EnhancedBookmarkModelTest, TestOriginalImage) {
200  const BookmarkNode* node = AddBookmark();
201
202  image::collections::ImageData data;
203  // Intentionally make raw pointer.
204  image::collections::ImageData_ImageInfo* info =
205      new image::collections::ImageData_ImageInfo;
206  info->set_url("http://example.com/foobar");
207  info->set_width(15);
208  info->set_height(55);
209  // This method consumes the pointer.
210  data.set_allocated_original_info(info);
211
212  std::string output;
213  bool result = data.SerializePartialToString(&output);
214  ASSERT_TRUE(result);
215
216  // base64 encode the output.
217  std::string encoded;
218  base::Base64Encode(output, &encoded);
219  bookmark_model_->SetNodeMetaInfo(node, "stars.imageData", encoded);
220
221  GURL url;
222  int width;
223  int height;
224  result = model_->GetOriginalImage(node, &url, &width, &height);
225  ASSERT_TRUE(result);
226  EXPECT_EQ(url, GURL("http://example.com/foobar"));
227  EXPECT_EQ(width, 15);
228  EXPECT_EQ(height, 55);
229}
230
231TEST_F(EnhancedBookmarkModelTest, TestThumbnailImage) {
232  const BookmarkNode* node = AddBookmark();
233
234  image::collections::ImageData data;
235  // Intentionally make raw pointer.
236  image::collections::ImageData_ImageInfo* info =
237      new image::collections::ImageData_ImageInfo;
238  info->set_url("http://example.com/foobar");
239  info->set_width(15);
240  info->set_height(55);
241  // This method consumes the pointer.
242  data.set_allocated_thumbnail_info(info);
243
244  std::string output;
245  bool result = data.SerializePartialToString(&output);
246  ASSERT_TRUE(result);
247
248  // base64 encode the output.
249  std::string encoded;
250  base::Base64Encode(output, &encoded);
251  bookmark_model_->SetNodeMetaInfo(node, "stars.imageData", encoded);
252
253  GURL url;
254  int width;
255  int height;
256  result = model_->GetThumbnailImage(node, &url, &width, &height);
257  ASSERT_TRUE(result);
258  EXPECT_EQ(url, GURL("http://example.com/foobar"));
259  EXPECT_EQ(width, 15);
260  EXPECT_EQ(height, 55);
261}
262
263TEST_F(EnhancedBookmarkModelTest, TestOriginalImageMissingDimensions) {
264  const BookmarkNode* node = AddBookmark();
265
266  image::collections::ImageData data;
267  // Intentionally make raw pointer.
268  image::collections::ImageData_ImageInfo* info =
269      new image::collections::ImageData_ImageInfo;
270  info->set_url("http://example.com/foobar");
271  // This method consumes the pointer.
272  data.set_allocated_original_info(info);
273
274  std::string output;
275  bool result = data.SerializePartialToString(&output);
276  ASSERT_TRUE(result);
277
278  // base64 encode the output.
279  std::string encoded;
280  base::Base64Encode(output, &encoded);
281  bookmark_model_->SetNodeMetaInfo(node, "stars.imageData", encoded);
282
283  GURL url;
284  int width;
285  int height;
286  result = model_->GetOriginalImage(node, &url, &width, &height);
287  ASSERT_FALSE(result);
288}
289
290TEST_F(EnhancedBookmarkModelTest, TestOriginalImageBadUrl) {
291  const BookmarkNode* node = AddBookmark();
292
293  image::collections::ImageData data;
294  // Intentionally make raw pointer.
295  image::collections::ImageData_ImageInfo* info =
296      new image::collections::ImageData_ImageInfo;
297  info->set_url("asdf. 13r");
298  info->set_width(15);
299  info->set_height(55);
300  // This method consumes the pointer.
301  data.set_allocated_original_info(info);
302
303  std::string output;
304  bool result = data.SerializePartialToString(&output);
305  ASSERT_TRUE(result);
306
307  // base64 encode the output.
308  std::string encoded;
309  base::Base64Encode(output, &encoded);
310  bookmark_model_->SetNodeMetaInfo(node, "stars.imageData", encoded);
311
312  GURL url;
313  int width;
314  int height;
315  result = model_->GetOriginalImage(node, &url, &width, &height);
316  ASSERT_FALSE(result);
317}
318
319TEST_F(EnhancedBookmarkModelTest, TestEncodeDecode) {
320  const BookmarkNode* node = AddBookmark();
321
322  bool result =
323      model_->SetOriginalImage(node, GURL("http://example.com/i.jpg"), 22, 33);
324  ASSERT_TRUE(result);
325
326  GURL url;
327  int width;
328  int height;
329  result = model_->GetOriginalImage(node, &url, &width, &height);
330  ASSERT_TRUE(result);
331  EXPECT_EQ(url, GURL("http://example.com/i.jpg"));
332  EXPECT_EQ(width, 22);
333  EXPECT_EQ(height, 33);
334  EXPECT_EQ("v1.0", GetVersion(node));
335}
336
337TEST_F(EnhancedBookmarkModelTest, TestDoubleEncodeDecode) {
338  const BookmarkNode* node = AddBookmark();
339
340  // Encode some information.
341  bool result =
342      model_->SetOriginalImage(node, GURL("http://example.com/i.jpg"), 22, 33);
343  ASSERT_TRUE(result);
344  // Encode some different information.
345  result =
346      model_->SetOriginalImage(node, GURL("http://example.com/i.jpg"), 33, 44);
347  ASSERT_TRUE(result);
348
349  GURL url;
350  int width;
351  int height;
352  result = model_->GetOriginalImage(node, &url, &width, &height);
353  ASSERT_TRUE(result);
354  EXPECT_EQ(url, GURL("http://example.com/i.jpg"));
355  EXPECT_EQ(width, 33);
356  EXPECT_EQ(height, 44);
357  EXPECT_EQ("v1.0", GetVersion(node));
358}
359
360TEST_F(EnhancedBookmarkModelTest, TestRemoteId) {
361  const BookmarkNode* node = AddBookmark();
362  // Verify that the remote id starts with the correct prefix.
363  EXPECT_TRUE(StartsWithASCII(model_->GetRemoteId(node), "ebc_", true));
364
365  // Getting the remote id for nodes that don't have them should return the
366  // empty string.
367  const BookmarkNode* existing_node =
368      bookmark_model_->AddURL(bookmark_model_->other_node(),
369                              0,
370                              base::ASCIIToUTF16("Title"),
371                              GURL(GURL(BOOKMARK_URL)));
372  EXPECT_TRUE(model_->GetRemoteId(existing_node).empty());
373
374  // Folder nodes should not have a remote id set on creation.
375  const BookmarkNode* folder_node = AddFolder();
376  EXPECT_TRUE(model_->GetRemoteId(folder_node).empty());
377}
378
379TEST_F(EnhancedBookmarkModelTest, TestEmptyDescription) {
380  const BookmarkNode* node = AddBookmark();
381
382  std::string description(model_->GetDescription(node));
383  EXPECT_EQ(description, "");
384}
385
386TEST_F(EnhancedBookmarkModelTest, TestDescription) {
387  const BookmarkNode* node = AddBookmark();
388  const std::string description("This is the most useful description of all.");
389
390  // Set the description.
391  model_->SetDescription(node, description);
392
393  // Check the description is the one that was set.
394  EXPECT_EQ(model_->GetDescription(node), description);
395  EXPECT_EQ("v1.0", GetVersion(node));
396}
397
398// If there is no notes field, the description should fall back on the snippet.
399TEST_F(EnhancedBookmarkModelTest, TestDescriptionFallback) {
400  const BookmarkNode* node = AddBookmark();
401
402  // Binary serialize the protobuf.
403  image::collections::PageData data;
404  data.set_snippet("Joe Bar Team");
405  ASSERT_TRUE(data.IsInitialized());
406  std::string output;
407  bool result = data.SerializeToString(&output);
408  ASSERT_TRUE(result);
409
410  // base64 encode the output.
411  std::string encoded;
412  base::Base64Encode(output, &encoded);
413  bookmark_model_->SetNodeMetaInfo(node, "stars.pageData", encoded);
414
415  // The snippet is used as the description.
416  std::string snippet(model_->GetSnippet(node));
417  EXPECT_EQ("Joe Bar Team", model_->GetDescription(node));
418
419  // Set the description.
420  const std::string description("This is the most useful description of all.");
421  model_->SetDescription(node, description);
422
423  // Check the description is the one that was set.
424  EXPECT_EQ(model_->GetDescription(node), description);
425}
426
427// Makes sure that the stars.version field is set every time
428// EnhancedBookmarkModel makes a change to a node.
429TEST_F(EnhancedBookmarkModelTest, TestVersionField) {
430  const BookmarkNode* node = AddBookmark();
431  EXPECT_EQ("", GetVersion(node));
432
433  model_->SetDescription(node, "foo");
434  EXPECT_EQ("v1.0", GetVersion(node));
435
436  // Add a suffix to the version to set.
437  model_->SetVersionSuffix("alpha");
438
439  model_->SetDescription(node, "foo");
440  // Since the description didn't actually change, the version field should
441  // not either.
442  EXPECT_EQ("v1.0", GetVersion(node));
443
444  model_->SetDescription(node, "bar");
445  EXPECT_EQ("v1.0/alpha", GetVersion(node));
446}
447
448// Verifies that duplicate nodes are reset when the model is created.
449TEST_F(EnhancedBookmarkModelTest, ResetDuplicateNodesOnInitialization) {
450  model_->Shutdown();
451
452  const BookmarkNode* parent = bookmark_model_->other_node();
453  const BookmarkNode* node1 = bookmark_model_->AddURL(
454      parent, 0, base::ASCIIToUTF16("Some title"), GURL(BOOKMARK_URL));
455  const BookmarkNode* node2 = bookmark_model_->AddURL(
456      parent, 0, base::ASCIIToUTF16("Some title"), GURL(BOOKMARK_URL));
457  const BookmarkNode* node3 = bookmark_model_->AddURL(
458      parent, 0, base::ASCIIToUTF16("Some title"), GURL(BOOKMARK_URL));
459  const BookmarkNode* node4 = bookmark_model_->AddURL(
460      parent, 0, base::ASCIIToUTF16("Some title"), GURL(BOOKMARK_URL));
461
462  bookmark_model_->SetNodeMetaInfo(node1, "stars.id", "c_1");
463  bookmark_model_->SetNodeMetaInfo(node2, "stars.id", "c_2");
464  bookmark_model_->SetNodeMetaInfo(node3, "stars.id", "c_1");
465  bookmark_model_->SetNodeMetaInfo(node4, "stars.id", "c_1");
466  EXPECT_EQ("c_1", GetId(node1));
467  EXPECT_EQ("c_2", GetId(node2));
468  EXPECT_EQ("c_1", GetId(node3));
469  EXPECT_EQ("c_1", GetId(node4));
470
471  model_.reset(new EnhancedBookmarkModel(bookmark_model_.get(), "v2.0"));
472  base::RunLoop().RunUntilIdle();
473  EXPECT_EQ("c_2", GetId(node2));
474  EXPECT_EQ("", GetId(node1));
475  EXPECT_EQ("", GetId(node3));
476  EXPECT_EQ("", GetId(node4));
477  EXPECT_EQ("c_1", GetOldId(node1));
478  EXPECT_EQ("c_1", GetOldId(node3));
479  EXPECT_EQ("c_1", GetOldId(node4));
480  EXPECT_EQ("v2.0", GetVersion(node1));
481  EXPECT_EQ("v2.0", GetVersion(node3));
482  EXPECT_EQ("v2.0", GetVersion(node4));
483}
484
485// Verifies that duplicate nodes are reset if one is created.
486TEST_F(EnhancedBookmarkModelTest, ResetDuplicateAddedNodes) {
487  BookmarkNode::MetaInfoMap meta_info;
488  meta_info["stars.id"] = "c_1";
489  const BookmarkNode* parent = bookmark_model_->other_node();
490
491  const BookmarkNode* node1 =
492      bookmark_model_->AddURLWithCreationTimeAndMetaInfo(
493          parent,
494          0,
495          base::ASCIIToUTF16("Some title"),
496          GURL(BOOKMARK_URL),
497          base::Time::Now(),
498          &meta_info);
499  EXPECT_EQ("c_1", GetId(node1));
500
501  const BookmarkNode* node2 =
502      bookmark_model_->AddURLWithCreationTimeAndMetaInfo(
503          parent,
504          0,
505          base::ASCIIToUTF16("Some title"),
506          GURL(BOOKMARK_URL),
507          base::Time::Now(),
508          &meta_info);
509
510  base::RunLoop().RunUntilIdle();
511  EXPECT_EQ("", GetId(node1));
512  EXPECT_EQ("", GetId(node2));
513  EXPECT_EQ("c_1", GetOldId(node1));
514  EXPECT_EQ("c_1", GetOldId(node2));
515  EXPECT_EQ("v1.0", GetVersion(node1));
516  EXPECT_EQ("v1.0", GetVersion(node2));
517}
518
519// Verifies that duplicate nodes are reset if an id is changed to a duplicate
520// value.
521TEST_F(EnhancedBookmarkModelTest, ResetDuplicateChangedNodes) {
522  const BookmarkNode* node1 = AddBookmark();
523  const BookmarkNode* node2 = AddBookmark();
524
525  bookmark_model_->SetNodeMetaInfo(node1, "stars.id", "c_1");
526  EXPECT_EQ("c_1", GetId(node1));
527
528  bookmark_model_->SetNodeMetaInfo(node2, "stars.id", "c_1");
529  base::RunLoop().RunUntilIdle();
530  EXPECT_EQ("", GetId(node1));
531  EXPECT_EQ("", GetId(node2));
532  EXPECT_EQ("c_1", GetOldId(node1));
533  EXPECT_EQ("c_1", GetOldId(node2));
534  EXPECT_EQ("v1.0", GetVersion(node1));
535  EXPECT_EQ("v1.0", GetVersion(node2));
536}
537
538TEST_F(EnhancedBookmarkModelTest, SetMultipleMetaInfo) {
539  const BookmarkNode* node = AddBookmark();
540  BookmarkNode::MetaInfoMap meta_info;
541  meta_info["a"] = "aa";
542  meta_info["b"] = "bb";
543
544  model_->SetVersionSuffix("1");
545  model_->SetMultipleMetaInfo(node, meta_info);
546  EXPECT_EQ("aa", GetMetaInfoField(node, "a"));
547  EXPECT_EQ("bb", GetMetaInfoField(node, "b"));
548  EXPECT_EQ("v1.0/1", GetVersion(node));
549
550  // Not present fields does not erase the fields already set on the node.
551  meta_info["a"] = "aaa";
552  model_->SetVersionSuffix("2");
553  model_->SetMultipleMetaInfo(node, meta_info);
554  EXPECT_EQ("aaa", GetMetaInfoField(node, "a"));
555  EXPECT_EQ("bb", GetMetaInfoField(node, "b"));
556  EXPECT_EQ("v1.0/2", GetVersion(node));
557
558  // Not actually changing any values should not set the version field.
559  model_->SetVersionSuffix("3");
560  model_->SetMultipleMetaInfo(node, meta_info);
561  EXPECT_EQ("v1.0/2", GetVersion(node));
562}
563
564TEST_F(EnhancedBookmarkModelTest, ObserverShuttingDownEvent) {
565  EXPECT_EQ(0, shutting_down_calls_);
566  model_->Shutdown();
567  EXPECT_EQ(1, shutting_down_calls_);
568  model_.reset();
569}
570
571TEST_F(EnhancedBookmarkModelTest, ObserverNodeAddedEvent) {
572  EXPECT_EQ(0, added_calls_);
573  const BookmarkNode* node = AddBookmark();
574  EXPECT_EQ(1, added_calls_);
575  EXPECT_EQ(node, last_added_);
576
577  const BookmarkNode* folder = AddFolder();
578  EXPECT_EQ(2, added_calls_);
579  EXPECT_EQ(folder, last_added_);
580}
581
582TEST_F(EnhancedBookmarkModelTest, ObserverNodeRemovedEvent) {
583  const BookmarkNode* node = AddBookmark();
584  const BookmarkNode* folder = AddFolder();
585
586  EXPECT_EQ(0, removed_calls_);
587  bookmark_model_->Remove(node->parent(), node->parent()->GetIndexOf(node));
588  EXPECT_EQ(1, removed_calls_);
589  EXPECT_EQ(node, last_removed_);
590
591  bookmark_model_->Remove(folder->parent(),
592                          folder->parent()->GetIndexOf(folder));
593  EXPECT_EQ(2, removed_calls_);
594  EXPECT_EQ(folder, last_removed_);
595}
596
597TEST_F(EnhancedBookmarkModelTest, ObserverAllUserNodesRemovedEvent) {
598  AddBookmark();
599  AddFolder();
600  EXPECT_EQ(0, all_user_nodes_removed_calls_);
601  bookmark_model_->RemoveAllUserBookmarks();
602  EXPECT_EQ(0, removed_calls_);
603  EXPECT_EQ(1, all_user_nodes_removed_calls_);
604}
605
606TEST_F(EnhancedBookmarkModelTest, ObserverRemoteIdChangedEvent) {
607  const BookmarkNode* node1 = AddFolder();
608  const BookmarkNode* node2 = AddFolder();
609
610  EXPECT_EQ(0, remote_id_changed_calls_);
611  bookmark_model_->SetNodeMetaInfo(node1, "stars.id", "c_1");
612  base::RunLoop().RunUntilIdle();
613  EXPECT_EQ(1, remote_id_changed_calls_);
614  EXPECT_EQ(node1, last_remote_id_node_);
615  EXPECT_EQ("", last_old_remote_id_);
616  EXPECT_EQ("c_1", last_remote_id_);
617
618  bookmark_model_->SetNodeMetaInfo(node2, "stars.id", "c_2");
619  base::RunLoop().RunUntilIdle();
620  EXPECT_EQ(2, remote_id_changed_calls_);
621  EXPECT_EQ(node2, last_remote_id_node_);
622  EXPECT_EQ("", last_old_remote_id_);
623  EXPECT_EQ("c_2", last_remote_id_);
624
625  bookmark_model_->SetNodeMetaInfo(node1, "stars.id", "c_3");
626  base::RunLoop().RunUntilIdle();
627  EXPECT_EQ(3, remote_id_changed_calls_);
628  EXPECT_EQ(node1, last_remote_id_node_);
629  EXPECT_EQ("c_1", last_old_remote_id_);
630  EXPECT_EQ("c_3", last_remote_id_);
631
632  // Set to duplicate ids.
633  bookmark_model_->SetNodeMetaInfo(node2, "stars.id", "c_3");
634  EXPECT_EQ(4, remote_id_changed_calls_);
635  EXPECT_EQ(node2, last_remote_id_node_);
636  EXPECT_EQ("c_2", last_old_remote_id_);
637  EXPECT_EQ("c_3", last_remote_id_);
638  base::RunLoop().RunUntilIdle();
639  EXPECT_EQ(6, remote_id_changed_calls_);
640  EXPECT_EQ("", last_remote_id_);
641}
642
643TEST_F(EnhancedBookmarkModelTest, ShutDownWhileResetDuplicationScheduled) {
644  const BookmarkNode* node1 = AddBookmark();
645  const BookmarkNode* node2 = AddBookmark();
646  bookmark_model_->SetNodeMetaInfo(node1, "stars.id", "c_1");
647  bookmark_model_->SetNodeMetaInfo(node2, "stars.id", "c_1");
648  model_->Shutdown();
649  model_.reset();
650  base::RunLoop().RunUntilIdle();
651}
652