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