• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #include "chrome/browser/bookmarks/bookmark_codec.h"
6 
7 #include "base/file_util.h"
8 #include "base/files/file_path.h"
9 #include "base/json/json_file_value_serializer.h"
10 #include "base/json/json_string_value_serializer.h"
11 #include "base/memory/scoped_ptr.h"
12 #include "base/path_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/values.h"
16 #include "chrome/browser/bookmarks/bookmark_model.h"
17 #include "chrome/common/chrome_paths.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 
20 namespace {
21 
22 const char kUrl1Title[] = "url1";
23 const char kUrl1Url[] = "http://www.url1.com";
24 const char kUrl2Title[] = "url2";
25 const char kUrl2Url[] = "http://www.url2.com";
26 const char kUrl3Title[] = "url3";
27 const char kUrl3Url[] = "http://www.url3.com";
28 const char kUrl4Title[] = "url4";
29 const char kUrl4Url[] = "http://www.url4.com";
30 const char kFolder1Title[] = "folder1";
31 const char kFolder2Title[] = "folder2";
32 
33 // Helper to get a mutable bookmark node.
AsMutable(const BookmarkNode * node)34 BookmarkNode* AsMutable(const BookmarkNode* node) {
35   return const_cast<BookmarkNode*>(node);
36 }
37 
38 // Helper to verify the two given bookmark nodes.
AssertNodesEqual(const BookmarkNode * expected,const BookmarkNode * actual)39 void AssertNodesEqual(const BookmarkNode* expected,
40                       const BookmarkNode* actual) {
41   ASSERT_TRUE(expected);
42   ASSERT_TRUE(actual);
43   EXPECT_EQ(expected->id(), actual->id());
44   EXPECT_EQ(expected->GetTitle(), actual->GetTitle());
45   EXPECT_EQ(expected->type(), actual->type());
46   EXPECT_TRUE(expected->date_added() == actual->date_added());
47   if (expected->is_url()) {
48     EXPECT_EQ(expected->url(), actual->url());
49   } else {
50     EXPECT_TRUE(expected->date_folder_modified() ==
51                 actual->date_folder_modified());
52     ASSERT_EQ(expected->child_count(), actual->child_count());
53     for (int i = 0; i < expected->child_count(); ++i)
54       AssertNodesEqual(expected->GetChild(i), actual->GetChild(i));
55   }
56 }
57 
58 // Verifies that the two given bookmark models are the same.
AssertModelsEqual(BookmarkModel * expected,BookmarkModel * actual)59 void AssertModelsEqual(BookmarkModel* expected, BookmarkModel* actual) {
60   ASSERT_NO_FATAL_FAILURE(AssertNodesEqual(expected->bookmark_bar_node(),
61                                            actual->bookmark_bar_node()));
62   ASSERT_NO_FATAL_FAILURE(
63       AssertNodesEqual(expected->other_node(), actual->other_node()));
64   ASSERT_NO_FATAL_FAILURE(
65       AssertNodesEqual(expected->mobile_node(), actual->mobile_node()));
66 }
67 
68 }  // namespace
69 
70 class BookmarkCodecTest : public testing::Test {
71  protected:
72   // Helpers to create bookmark models with different data.
CreateTestModel1()73   BookmarkModel* CreateTestModel1() {
74     scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL));
75     const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
76     model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
77     return model.release();
78   }
CreateTestModel2()79   BookmarkModel* CreateTestModel2() {
80     scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL));
81     const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
82     model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
83     model->AddURL(bookmark_bar, 1, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url));
84     return model.release();
85   }
CreateTestModel3()86   BookmarkModel* CreateTestModel3() {
87     scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL));
88     const BookmarkNode* bookmark_bar = model->bookmark_bar_node();
89     model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url));
90     const BookmarkNode* folder1 = model->AddFolder(bookmark_bar, 1,
91                                                    ASCIIToUTF16(kFolder1Title));
92     model->AddURL(folder1, 0, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url));
93     return model.release();
94   }
95 
GetBookmarksBarChildValue(Value * value,size_t index,DictionaryValue ** result_value)96   void GetBookmarksBarChildValue(Value* value,
97                                  size_t index,
98                                  DictionaryValue** result_value) {
99     ASSERT_EQ(Value::TYPE_DICTIONARY, value->GetType());
100 
101     DictionaryValue* d_value = static_cast<DictionaryValue*>(value);
102     Value* roots;
103     ASSERT_TRUE(d_value->Get(BookmarkCodec::kRootsKey, &roots));
104     ASSERT_EQ(Value::TYPE_DICTIONARY, roots->GetType());
105 
106     DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots);
107     Value* bb_value;
108     ASSERT_TRUE(roots_d_value->Get(BookmarkCodec::kRootFolderNameKey,
109                                    &bb_value));
110     ASSERT_EQ(Value::TYPE_DICTIONARY, bb_value->GetType());
111 
112     DictionaryValue* bb_d_value = static_cast<DictionaryValue*>(bb_value);
113     Value* bb_children_value;
114     ASSERT_TRUE(bb_d_value->Get(BookmarkCodec::kChildrenKey,
115                                 &bb_children_value));
116     ASSERT_EQ(Value::TYPE_LIST, bb_children_value->GetType());
117 
118     ListValue* bb_children_l_value = static_cast<ListValue*>(bb_children_value);
119     Value* child_value;
120     ASSERT_TRUE(bb_children_l_value->Get(index, &child_value));
121     ASSERT_EQ(Value::TYPE_DICTIONARY, child_value->GetType());
122 
123     *result_value = static_cast<DictionaryValue*>(child_value);
124   }
125 
EncodeHelper(BookmarkModel * model,std::string * checksum)126   Value* EncodeHelper(BookmarkModel* model, std::string* checksum) {
127     BookmarkCodec encoder;
128     // Computed and stored checksums should be empty.
129     EXPECT_EQ("", encoder.computed_checksum());
130     EXPECT_EQ("", encoder.stored_checksum());
131 
132     scoped_ptr<Value> value(encoder.Encode(model));
133     const std::string& computed_checksum = encoder.computed_checksum();
134     const std::string& stored_checksum = encoder.stored_checksum();
135 
136     // Computed and stored checksums should not be empty and should be equal.
137     EXPECT_FALSE(computed_checksum.empty());
138     EXPECT_FALSE(stored_checksum.empty());
139     EXPECT_EQ(computed_checksum, stored_checksum);
140 
141     *checksum = computed_checksum;
142     return value.release();
143   }
144 
Decode(BookmarkCodec * codec,BookmarkModel * model,const Value & value)145   bool Decode(BookmarkCodec* codec, BookmarkModel* model, const Value& value) {
146     int64 max_id;
147     bool result = codec->Decode(AsMutable(model->bookmark_bar_node()),
148                                 AsMutable(model->other_node()),
149                                 AsMutable(model->mobile_node()),
150                                 &max_id, value);
151     model->set_next_node_id(max_id);
152     AsMutable(model->root_node())->
153         SetMetaInfoMap(codec->model_meta_info_map());
154     AsMutable(model->root_node())->
155         set_sync_transaction_version(codec->model_sync_transaction_version());
156 
157     return result;
158   }
159 
DecodeHelper(const Value & value,const std::string & expected_stored_checksum,std::string * computed_checksum,bool expected_changes)160   BookmarkModel* DecodeHelper(const Value& value,
161                               const std::string& expected_stored_checksum,
162                               std::string* computed_checksum,
163                               bool expected_changes) {
164     BookmarkCodec decoder;
165     // Computed and stored checksums should be empty.
166     EXPECT_EQ("", decoder.computed_checksum());
167     EXPECT_EQ("", decoder.stored_checksum());
168 
169     scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL));
170     EXPECT_TRUE(Decode(&decoder, model.get(), value));
171 
172     *computed_checksum = decoder.computed_checksum();
173     const std::string& stored_checksum = decoder.stored_checksum();
174 
175     // Computed and stored checksums should not be empty.
176     EXPECT_FALSE(computed_checksum->empty());
177     EXPECT_FALSE(stored_checksum.empty());
178 
179     // Stored checksum should be as expected.
180     EXPECT_EQ(expected_stored_checksum, stored_checksum);
181 
182     // The two checksums should be equal if expected_changes is true; otherwise
183     // they should be different.
184     if (expected_changes)
185       EXPECT_NE(*computed_checksum, stored_checksum);
186     else
187       EXPECT_EQ(*computed_checksum, stored_checksum);
188 
189     return model.release();
190   }
191 
CheckIDs(const BookmarkNode * node,std::set<int64> * assigned_ids)192   void CheckIDs(const BookmarkNode* node, std::set<int64>* assigned_ids) {
193     DCHECK(node);
194     int64 node_id = node->id();
195     EXPECT_TRUE(assigned_ids->find(node_id) == assigned_ids->end());
196     assigned_ids->insert(node_id);
197     for (int i = 0; i < node->child_count(); ++i)
198       CheckIDs(node->GetChild(i), assigned_ids);
199   }
200 
ExpectIDsUnique(BookmarkModel * model)201   void ExpectIDsUnique(BookmarkModel* model) {
202     std::set<int64> assigned_ids;
203     CheckIDs(model->bookmark_bar_node(), &assigned_ids);
204     CheckIDs(model->other_node(), &assigned_ids);
205     CheckIDs(model->mobile_node(), &assigned_ids);
206   }
207 };
208 
TEST_F(BookmarkCodecTest,ChecksumEncodeDecodeTest)209 TEST_F(BookmarkCodecTest, ChecksumEncodeDecodeTest) {
210   scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1());
211   std::string enc_checksum;
212   scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum));
213 
214   EXPECT_TRUE(value.get() != NULL);
215 
216   std::string dec_checksum;
217   scoped_ptr<BookmarkModel> decoded_model(DecodeHelper(
218       *value.get(), enc_checksum, &dec_checksum, false));
219 }
220 
TEST_F(BookmarkCodecTest,ChecksumEncodeIdenticalModelsTest)221 TEST_F(BookmarkCodecTest, ChecksumEncodeIdenticalModelsTest) {
222   // Encode two identical models and make sure the check-sums are same as long
223   // as the data is the same.
224   scoped_ptr<BookmarkModel> model1(CreateTestModel1());
225   std::string enc_checksum1;
226   scoped_ptr<Value> value1(EncodeHelper(model1.get(), &enc_checksum1));
227   EXPECT_TRUE(value1.get() != NULL);
228 
229   scoped_ptr<BookmarkModel> model2(CreateTestModel1());
230   std::string enc_checksum2;
231   scoped_ptr<Value> value2(EncodeHelper(model2.get(), &enc_checksum2));
232   EXPECT_TRUE(value2.get() != NULL);
233 
234   ASSERT_EQ(enc_checksum1, enc_checksum2);
235 }
236 
TEST_F(BookmarkCodecTest,ChecksumManualEditTest)237 TEST_F(BookmarkCodecTest, ChecksumManualEditTest) {
238   scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1());
239   std::string enc_checksum;
240   scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum));
241 
242   EXPECT_TRUE(value.get() != NULL);
243 
244   // Change something in the encoded value before decoding it.
245   DictionaryValue* child1_value;
246   GetBookmarksBarChildValue(value.get(), 0, &child1_value);
247   std::string title;
248   ASSERT_TRUE(child1_value->GetString(BookmarkCodec::kNameKey, &title));
249   child1_value->SetString(BookmarkCodec::kNameKey, title + "1");
250 
251   std::string dec_checksum;
252   scoped_ptr<BookmarkModel> decoded_model1(DecodeHelper(
253       *value.get(), enc_checksum, &dec_checksum, true));
254 
255   // Undo the change and make sure the checksum is same as original.
256   child1_value->SetString(BookmarkCodec::kNameKey, title);
257   scoped_ptr<BookmarkModel> decoded_model2(DecodeHelper(
258       *value.get(), enc_checksum, &dec_checksum, false));
259 }
260 
TEST_F(BookmarkCodecTest,ChecksumManualEditIDsTest)261 TEST_F(BookmarkCodecTest, ChecksumManualEditIDsTest) {
262   scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3());
263 
264   // The test depends on existence of multiple children under bookmark bar, so
265   // make sure that's the case.
266   int bb_child_count = model_to_encode->bookmark_bar_node()->child_count();
267   ASSERT_GT(bb_child_count, 1);
268 
269   std::string enc_checksum;
270   scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum));
271 
272   EXPECT_TRUE(value.get() != NULL);
273 
274   // Change IDs for all children of bookmark bar to be 1.
275   DictionaryValue* child_value;
276   for (int i = 0; i < bb_child_count; ++i) {
277     GetBookmarksBarChildValue(value.get(), i, &child_value);
278     std::string id;
279     ASSERT_TRUE(child_value->GetString(BookmarkCodec::kIdKey, &id));
280     child_value->SetString(BookmarkCodec::kIdKey, "1");
281   }
282 
283   std::string dec_checksum;
284   scoped_ptr<BookmarkModel> decoded_model(DecodeHelper(
285       *value.get(), enc_checksum, &dec_checksum, true));
286 
287   ExpectIDsUnique(decoded_model.get());
288 
289   // add a few extra nodes to bookmark model and make sure IDs are still uniuqe.
290   const BookmarkNode* bb_node = decoded_model->bookmark_bar_node();
291   decoded_model->AddURL(bb_node, 0, ASCIIToUTF16("new url1"),
292                         GURL("http://newurl1.com"));
293   decoded_model->AddURL(bb_node, 0, ASCIIToUTF16("new url2"),
294                         GURL("http://newurl2.com"));
295 
296   ExpectIDsUnique(decoded_model.get());
297 }
298 
TEST_F(BookmarkCodecTest,PersistIDsTest)299 TEST_F(BookmarkCodecTest, PersistIDsTest) {
300   scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3());
301   BookmarkCodec encoder;
302   scoped_ptr<Value> model_value(encoder.Encode(model_to_encode.get()));
303 
304   BookmarkModel decoded_model(NULL);
305   BookmarkCodec decoder;
306   ASSERT_TRUE(Decode(&decoder, &decoded_model, *model_value.get()));
307   ASSERT_NO_FATAL_FAILURE(
308       AssertModelsEqual(model_to_encode.get(), &decoded_model));
309 
310   // Add a couple of more items to the decoded bookmark model and make sure
311   // ID persistence is working properly.
312   const BookmarkNode* bookmark_bar = decoded_model.bookmark_bar_node();
313   decoded_model.AddURL(
314       bookmark_bar, bookmark_bar->child_count(), ASCIIToUTF16(kUrl3Title),
315       GURL(kUrl3Url));
316   const BookmarkNode* folder2_node = decoded_model.AddFolder(
317       bookmark_bar, bookmark_bar->child_count(), ASCIIToUTF16(kFolder2Title));
318   decoded_model.AddURL(folder2_node, 0, ASCIIToUTF16(kUrl4Title),
319                        GURL(kUrl4Url));
320 
321   BookmarkCodec encoder2;
322   scoped_ptr<Value> model_value2(encoder2.Encode(&decoded_model));
323 
324   BookmarkModel decoded_model2(NULL);
325   BookmarkCodec decoder2;
326   ASSERT_TRUE(Decode(&decoder2, &decoded_model2, *model_value2.get()));
327   ASSERT_NO_FATAL_FAILURE(AssertModelsEqual(&decoded_model, &decoded_model2));
328 }
329 
TEST_F(BookmarkCodecTest,CanDecodeModelWithoutMobileBookmarks)330 TEST_F(BookmarkCodecTest, CanDecodeModelWithoutMobileBookmarks) {
331   base::FilePath test_data_directory;
332   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory));
333   base::FilePath test_file = test_data_directory.AppendASCII(
334       "bookmarks/model_without_sync.json");
335   ASSERT_TRUE(base::PathExists(test_file));
336 
337   JSONFileValueSerializer serializer(test_file);
338   scoped_ptr<Value> root(serializer.Deserialize(NULL, NULL));
339 
340   BookmarkModel decoded_model(NULL);
341   BookmarkCodec decoder;
342   ASSERT_TRUE(Decode(&decoder, &decoded_model, *root.get()));
343   ExpectIDsUnique(&decoded_model);
344 
345   const BookmarkNode* bbn = decoded_model.bookmark_bar_node();
346   ASSERT_EQ(1, bbn->child_count());
347 
348   const BookmarkNode* child = bbn->GetChild(0);
349   EXPECT_EQ(BookmarkNode::FOLDER, child->type());
350   EXPECT_EQ(ASCIIToUTF16("Folder A"), child->GetTitle());
351   ASSERT_EQ(1, child->child_count());
352 
353   child = child->GetChild(0);
354   EXPECT_EQ(BookmarkNode::URL, child->type());
355   EXPECT_EQ(ASCIIToUTF16("Bookmark Manager"), child->GetTitle());
356 
357   const BookmarkNode* other = decoded_model.other_node();
358   ASSERT_EQ(1, other->child_count());
359 
360   child = other->GetChild(0);
361   EXPECT_EQ(BookmarkNode::FOLDER, child->type());
362   EXPECT_EQ(ASCIIToUTF16("Folder B"), child->GetTitle());
363   ASSERT_EQ(1, child->child_count());
364 
365   child = child->GetChild(0);
366   EXPECT_EQ(BookmarkNode::URL, child->type());
367   EXPECT_EQ(ASCIIToUTF16("Get started with Google Chrome"), child->GetTitle());
368 
369   ASSERT_TRUE(decoded_model.mobile_node() != NULL);
370 }
371 
TEST_F(BookmarkCodecTest,EncodeAndDecodeMetaInfo)372 TEST_F(BookmarkCodecTest, EncodeAndDecodeMetaInfo) {
373   // Add meta info and encode.
374   scoped_ptr<BookmarkModel> model(CreateTestModel1());
375   model->SetNodeMetaInfo(model->root_node(), "model_info", "value1");
376   model->SetNodeMetaInfo(model->bookmark_bar_node()->GetChild(0),
377                          "node_info", "value2");
378   std::string checksum;
379   scoped_ptr<Value> value(EncodeHelper(model.get(), &checksum));
380   ASSERT_TRUE(value.get() != NULL);
381 
382   // Decode and check for meta info.
383   model.reset(DecodeHelper(*value, checksum, &checksum, false));
384   std::string meta_value;
385   EXPECT_TRUE(model->root_node()->GetMetaInfo("model_info", &meta_value));
386   EXPECT_EQ("value1", meta_value);
387   EXPECT_FALSE(model->root_node()->GetMetaInfo("other_key", &meta_value));
388   const BookmarkNode* bbn = model->bookmark_bar_node();
389   ASSERT_EQ(1, bbn->child_count());
390   const BookmarkNode* child = bbn->GetChild(0);
391   EXPECT_TRUE(child->GetMetaInfo("node_info", &meta_value));
392   EXPECT_EQ("value2", meta_value);
393   EXPECT_FALSE(child->GetMetaInfo("other_key", &meta_value));
394 }
395 
TEST_F(BookmarkCodecTest,EncodeAndDecodeSyncTransactionVersion)396 TEST_F(BookmarkCodecTest, EncodeAndDecodeSyncTransactionVersion) {
397   // Add sync transaction version and encode.
398   scoped_ptr<BookmarkModel> model(CreateTestModel2());
399   model->SetNodeSyncTransactionVersion(model->root_node(), 1);
400   const BookmarkNode* bbn = model->bookmark_bar_node();
401   model->SetNodeSyncTransactionVersion(bbn->GetChild(1), 42);
402 
403   std::string checksum;
404   scoped_ptr<Value> value(EncodeHelper(model.get(), &checksum));
405   ASSERT_TRUE(value.get() != NULL);
406 
407   // Decode and verify.
408   model.reset(DecodeHelper(*value, checksum, &checksum, false));
409   EXPECT_EQ(1, model->root_node()->sync_transaction_version());
410   bbn = model->bookmark_bar_node();
411   EXPECT_EQ(42, bbn->GetChild(1)->sync_transaction_version());
412   EXPECT_EQ(BookmarkNode::kInvalidSyncTransactionVersion,
413             bbn->GetChild(0)->sync_transaction_version());
414 }
415 
416 // Verifies that we can still decode the old codec format after changing the
417 // way meta info is stored.
TEST_F(BookmarkCodecTest,CanDecodeMetaInfoAsString)418 TEST_F(BookmarkCodecTest, CanDecodeMetaInfoAsString) {
419   base::FilePath test_data_directory;
420   ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_directory));
421   base::FilePath test_file = test_data_directory.AppendASCII(
422       "bookmarks/meta_info_as_string.json");
423   ASSERT_TRUE(base::PathExists(test_file));
424 
425   JSONFileValueSerializer serializer(test_file);
426   scoped_ptr<Value> root(serializer.Deserialize(NULL, NULL));
427 
428   BookmarkModel model(NULL);
429   BookmarkCodec decoder;
430   ASSERT_TRUE(Decode(&decoder, &model, *root.get()));
431 
432   EXPECT_EQ(1, model.root_node()->sync_transaction_version());
433   const BookmarkNode* bbn = model.bookmark_bar_node();
434   EXPECT_EQ(BookmarkNode::kInvalidSyncTransactionVersion,
435             bbn->GetChild(0)->sync_transaction_version());
436   EXPECT_EQ(42, bbn->GetChild(1)->sync_transaction_version());
437 
438   const char kSyncTransactionVersionKey[] = "sync.transaction_version";
439   const char kNormalKey[] = "key";
440   const char kNestedKey[] = "nested.key";
441   std::string meta_value;
442   EXPECT_FALSE(model.root_node()->GetMetaInfo(kSyncTransactionVersionKey,
443                                                &meta_value));
444   EXPECT_FALSE(bbn->GetChild(1)->GetMetaInfo(kSyncTransactionVersionKey,
445                                              &meta_value));
446   EXPECT_TRUE(bbn->GetChild(0)->GetMetaInfo(kNormalKey, &meta_value));
447   EXPECT_EQ("value", meta_value);
448   EXPECT_TRUE(bbn->GetChild(1)->GetMetaInfo(kNormalKey, &meta_value));
449   EXPECT_EQ("value2", meta_value);
450   EXPECT_TRUE(bbn->GetChild(0)->GetMetaInfo(kNestedKey, &meta_value));
451   EXPECT_EQ("value3", meta_value);
452 }
453