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