1 // Copyright (c) 2011 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 "base/memory/scoped_ptr.h" 6 #include "base/string_util.h" 7 #include "base/utf_string_conversions.h" 8 #include "base/values.h" 9 #include "chrome/browser/bookmarks/bookmark_codec.h" 10 #include "chrome/browser/bookmarks/bookmark_model.h" 11 #include "chrome/browser/bookmarks/bookmark_model_test_utils.h" 12 #include "chrome/browser/bookmarks/bookmark_utils.h" 13 #include "testing/gtest/include/gtest/gtest.h" 14 15 namespace { 16 17 const char kUrl1Title[] = "url1"; 18 const char kUrl1Url[] = "http://www.url1.com"; 19 const char kUrl2Title[] = "url2"; 20 const char kUrl2Url[] = "http://www.url2.com"; 21 const char kUrl3Title[] = "url3"; 22 const char kUrl3Url[] = "http://www.url3.com"; 23 const char kUrl4Title[] = "url4"; 24 const char kUrl4Url[] = "http://www.url4.com"; 25 const char kFolder1Title[] = "folder1"; 26 const char kFolder2Title[] = "folder2"; 27 28 // Helper to get a mutable bookmark node. 29 static BookmarkNode* AsMutable(const BookmarkNode* node) { 30 return const_cast<BookmarkNode*>(node); 31 } 32 33 } // anonymous namespace 34 35 class BookmarkCodecTest : public testing::Test { 36 protected: 37 // Helpers to create bookmark models with different data. 38 BookmarkModel* CreateTestModel1() { 39 scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); 40 const BookmarkNode* bookmark_bar = model->GetBookmarkBarNode(); 41 model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); 42 return model.release(); 43 } 44 BookmarkModel* CreateTestModel2() { 45 scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); 46 const BookmarkNode* bookmark_bar = model->GetBookmarkBarNode(); 47 model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); 48 model->AddURL(bookmark_bar, 1, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url)); 49 return model.release(); 50 } 51 BookmarkModel* CreateTestModel3() { 52 scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); 53 const BookmarkNode* bookmark_bar = model->GetBookmarkBarNode(); 54 model->AddURL(bookmark_bar, 0, ASCIIToUTF16(kUrl1Title), GURL(kUrl1Url)); 55 const BookmarkNode* folder1 = model->AddFolder(bookmark_bar, 1, 56 ASCIIToUTF16(kFolder1Title)); 57 model->AddURL(folder1, 0, ASCIIToUTF16(kUrl2Title), GURL(kUrl2Url)); 58 return model.release(); 59 } 60 61 void GetBookmarksBarChildValue(Value* value, 62 size_t index, 63 DictionaryValue** result_value) { 64 ASSERT_EQ(Value::TYPE_DICTIONARY, value->GetType()); 65 66 DictionaryValue* d_value = static_cast<DictionaryValue*>(value); 67 Value* roots; 68 ASSERT_TRUE(d_value->Get(BookmarkCodec::kRootsKey, &roots)); 69 ASSERT_EQ(Value::TYPE_DICTIONARY, roots->GetType()); 70 71 DictionaryValue* roots_d_value = static_cast<DictionaryValue*>(roots); 72 Value* bb_value; 73 ASSERT_TRUE(roots_d_value->Get(BookmarkCodec::kRootFolderNameKey, 74 &bb_value)); 75 ASSERT_EQ(Value::TYPE_DICTIONARY, bb_value->GetType()); 76 77 DictionaryValue* bb_d_value = static_cast<DictionaryValue*>(bb_value); 78 Value* bb_children_value; 79 ASSERT_TRUE(bb_d_value->Get(BookmarkCodec::kChildrenKey, 80 &bb_children_value)); 81 ASSERT_EQ(Value::TYPE_LIST, bb_children_value->GetType()); 82 83 ListValue* bb_children_l_value = static_cast<ListValue*>(bb_children_value); 84 Value* child_value; 85 ASSERT_TRUE(bb_children_l_value->Get(index, &child_value)); 86 ASSERT_EQ(Value::TYPE_DICTIONARY, child_value->GetType()); 87 88 *result_value = static_cast<DictionaryValue*>(child_value); 89 } 90 91 Value* EncodeHelper(BookmarkModel* model, std::string* checksum) { 92 BookmarkCodec encoder; 93 // Computed and stored checksums should be empty. 94 EXPECT_EQ("", encoder.computed_checksum()); 95 EXPECT_EQ("", encoder.stored_checksum()); 96 97 scoped_ptr<Value> value(encoder.Encode(model)); 98 const std::string& computed_checksum = encoder.computed_checksum(); 99 const std::string& stored_checksum = encoder.stored_checksum(); 100 101 // Computed and stored checksums should not be empty and should be equal. 102 EXPECT_FALSE(computed_checksum.empty()); 103 EXPECT_FALSE(stored_checksum.empty()); 104 EXPECT_EQ(computed_checksum, stored_checksum); 105 106 *checksum = computed_checksum; 107 return value.release(); 108 } 109 110 bool Decode(BookmarkCodec* codec, BookmarkModel* model, const Value& value) { 111 int64 max_id; 112 bool result = codec->Decode(AsMutable(model->GetBookmarkBarNode()), 113 AsMutable(model->other_node()), 114 &max_id, value); 115 model->set_next_node_id(max_id); 116 return result; 117 } 118 119 BookmarkModel* DecodeHelper(const Value& value, 120 const std::string& expected_stored_checksum, 121 std::string* computed_checksum, 122 bool expected_changes) { 123 BookmarkCodec decoder; 124 // Computed and stored checksums should be empty. 125 EXPECT_EQ("", decoder.computed_checksum()); 126 EXPECT_EQ("", decoder.stored_checksum()); 127 128 scoped_ptr<BookmarkModel> model(new BookmarkModel(NULL)); 129 EXPECT_TRUE(Decode(&decoder, model.get(), value)); 130 131 *computed_checksum = decoder.computed_checksum(); 132 const std::string& stored_checksum = decoder.stored_checksum(); 133 134 // Computed and stored checksums should not be empty. 135 EXPECT_FALSE(computed_checksum->empty()); 136 EXPECT_FALSE(stored_checksum.empty()); 137 138 // Stored checksum should be as expected. 139 EXPECT_EQ(expected_stored_checksum, stored_checksum); 140 141 // The two checksums should be equal if expected_changes is true; otherwise 142 // they should be different. 143 if (expected_changes) 144 EXPECT_NE(*computed_checksum, stored_checksum); 145 else 146 EXPECT_EQ(*computed_checksum, stored_checksum); 147 148 return model.release(); 149 } 150 151 void CheckIDs(const BookmarkNode* node, std::set<int64>* assigned_ids) { 152 DCHECK(node); 153 int64 node_id = node->id(); 154 EXPECT_TRUE(assigned_ids->find(node_id) == assigned_ids->end()); 155 assigned_ids->insert(node_id); 156 for (int i = 0; i < node->child_count(); ++i) 157 CheckIDs(node->GetChild(i), assigned_ids); 158 } 159 160 void ExpectIDsUnique(BookmarkModel* model) { 161 std::set<int64> assigned_ids; 162 CheckIDs(model->GetBookmarkBarNode(), &assigned_ids); 163 CheckIDs(model->other_node(), &assigned_ids); 164 } 165 }; 166 167 TEST_F(BookmarkCodecTest, ChecksumEncodeDecodeTest) { 168 scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1()); 169 std::string enc_checksum; 170 scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum)); 171 172 EXPECT_TRUE(value.get() != NULL); 173 174 std::string dec_checksum; 175 scoped_ptr<BookmarkModel> decoded_model(DecodeHelper( 176 *value.get(), enc_checksum, &dec_checksum, false)); 177 } 178 179 TEST_F(BookmarkCodecTest, ChecksumEncodeIdenticalModelsTest) { 180 // Encode two identical models and make sure the check-sums are same as long 181 // as the data is the same. 182 scoped_ptr<BookmarkModel> model1(CreateTestModel1()); 183 std::string enc_checksum1; 184 scoped_ptr<Value> value1(EncodeHelper(model1.get(), &enc_checksum1)); 185 EXPECT_TRUE(value1.get() != NULL); 186 187 scoped_ptr<BookmarkModel> model2(CreateTestModel1()); 188 std::string enc_checksum2; 189 scoped_ptr<Value> value2(EncodeHelper(model2.get(), &enc_checksum2)); 190 EXPECT_TRUE(value2.get() != NULL); 191 192 ASSERT_EQ(enc_checksum1, enc_checksum2); 193 } 194 195 TEST_F(BookmarkCodecTest, ChecksumManualEditTest) { 196 scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel1()); 197 std::string enc_checksum; 198 scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum)); 199 200 EXPECT_TRUE(value.get() != NULL); 201 202 // Change something in the encoded value before decoding it. 203 DictionaryValue* child1_value; 204 GetBookmarksBarChildValue(value.get(), 0, &child1_value); 205 std::string title; 206 ASSERT_TRUE(child1_value->GetString(BookmarkCodec::kNameKey, &title)); 207 child1_value->SetString(BookmarkCodec::kNameKey, title + "1"); 208 209 std::string dec_checksum; 210 scoped_ptr<BookmarkModel> decoded_model1(DecodeHelper( 211 *value.get(), enc_checksum, &dec_checksum, true)); 212 213 // Undo the change and make sure the checksum is same as original. 214 child1_value->SetString(BookmarkCodec::kNameKey, title); 215 scoped_ptr<BookmarkModel> decoded_model2(DecodeHelper( 216 *value.get(), enc_checksum, &dec_checksum, false)); 217 } 218 219 TEST_F(BookmarkCodecTest, ChecksumManualEditIDsTest) { 220 scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3()); 221 222 // The test depends on existence of multiple children under bookmark bar, so 223 // make sure that's the case. 224 int bb_child_count = model_to_encode->GetBookmarkBarNode()->child_count(); 225 ASSERT_GT(bb_child_count, 1); 226 227 std::string enc_checksum; 228 scoped_ptr<Value> value(EncodeHelper(model_to_encode.get(), &enc_checksum)); 229 230 EXPECT_TRUE(value.get() != NULL); 231 232 // Change IDs for all children of bookmark bar to be 1. 233 DictionaryValue* child_value; 234 for (int i = 0; i < bb_child_count; ++i) { 235 GetBookmarksBarChildValue(value.get(), i, &child_value); 236 std::string id; 237 ASSERT_TRUE(child_value->GetString(BookmarkCodec::kIdKey, &id)); 238 child_value->SetString(BookmarkCodec::kIdKey, "1"); 239 } 240 241 std::string dec_checksum; 242 scoped_ptr<BookmarkModel> decoded_model(DecodeHelper( 243 *value.get(), enc_checksum, &dec_checksum, true)); 244 245 ExpectIDsUnique(decoded_model.get()); 246 247 // add a few extra nodes to bookmark model and make sure IDs are still uniuqe. 248 const BookmarkNode* bb_node = decoded_model->GetBookmarkBarNode(); 249 decoded_model->AddURL(bb_node, 0, ASCIIToUTF16("new url1"), 250 GURL("http://newurl1.com")); 251 decoded_model->AddURL(bb_node, 0, ASCIIToUTF16("new url2"), 252 GURL("http://newurl2.com")); 253 254 ExpectIDsUnique(decoded_model.get()); 255 } 256 257 TEST_F(BookmarkCodecTest, PersistIDsTest) { 258 scoped_ptr<BookmarkModel> model_to_encode(CreateTestModel3()); 259 BookmarkCodec encoder; 260 scoped_ptr<Value> model_value(encoder.Encode(model_to_encode.get())); 261 262 BookmarkModel decoded_model(NULL); 263 BookmarkCodec decoder; 264 ASSERT_TRUE(Decode(&decoder, &decoded_model, *model_value.get())); 265 BookmarkModelTestUtils::AssertModelsEqual(model_to_encode.get(), 266 &decoded_model, 267 true); 268 269 // Add a couple of more items to the decoded bookmark model and make sure 270 // ID persistence is working properly. 271 const BookmarkNode* bookmark_bar = decoded_model.GetBookmarkBarNode(); 272 decoded_model.AddURL( 273 bookmark_bar, bookmark_bar->child_count(), ASCIIToUTF16(kUrl3Title), 274 GURL(kUrl3Url)); 275 const BookmarkNode* folder2_node = decoded_model.AddFolder( 276 bookmark_bar, bookmark_bar->child_count(), ASCIIToUTF16(kFolder2Title)); 277 decoded_model.AddURL(folder2_node, 0, ASCIIToUTF16(kUrl4Title), 278 GURL(kUrl4Url)); 279 280 BookmarkCodec encoder2; 281 scoped_ptr<Value> model_value2(encoder2.Encode(&decoded_model)); 282 283 BookmarkModel decoded_model2(NULL); 284 BookmarkCodec decoder2; 285 ASSERT_TRUE(Decode(&decoder2, &decoded_model2, *model_value2.get())); 286 BookmarkModelTestUtils::AssertModelsEqual(&decoded_model, 287 &decoded_model2, 288 true); 289 } 290