1 // Copyright 2013 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/dom_distiller/core/dom_distiller_store.h" 6 7 #include "base/bind.h" 8 #include "base/file_util.h" 9 #include "base/files/scoped_temp_dir.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/run_loop.h" 12 #include "base/time/time.h" 13 #include "components/dom_distiller/core/article_entry.h" 14 #include "components/dom_distiller/core/dom_distiller_test_util.h" 15 #include "components/leveldb_proto/testing/fake_db.h" 16 #include "sync/api/attachments/attachment_id.h" 17 #include "sync/api/attachments/attachment_service_proxy_for_test.h" 18 #include "sync/protocol/sync.pb.h" 19 #include "testing/gmock/include/gmock/gmock.h" 20 #include "testing/gtest/include/gtest/gtest.h" 21 22 using base::Time; 23 using leveldb_proto::test::FakeDB; 24 using sync_pb::EntitySpecifics; 25 using syncer::ModelType; 26 using syncer::SyncChange; 27 using syncer::SyncChangeList; 28 using syncer::SyncChangeProcessor; 29 using syncer::SyncData; 30 using syncer::SyncDataList; 31 using syncer::SyncError; 32 using syncer::SyncErrorFactory; 33 using testing::AssertionFailure; 34 using testing::AssertionResult; 35 using testing::AssertionSuccess; 36 37 namespace dom_distiller { 38 39 namespace { 40 41 const ModelType kDomDistillerModelType = syncer::ARTICLES; 42 43 typedef base::hash_map<std::string, ArticleEntry> EntryMap; 44 45 void AddEntry(const ArticleEntry& e, EntryMap* map) { 46 (*map)[e.entry_id()] = e; 47 } 48 49 class FakeSyncErrorFactory : public syncer::SyncErrorFactory { 50 public: 51 virtual syncer::SyncError CreateAndUploadError( 52 const tracked_objects::Location& location, 53 const std::string& message) OVERRIDE { 54 return syncer::SyncError(); 55 } 56 }; 57 58 class FakeSyncChangeProcessor : public syncer::SyncChangeProcessor { 59 public: 60 explicit FakeSyncChangeProcessor(EntryMap* model) : model_(model) {} 61 62 virtual syncer::SyncDataList GetAllSyncData( 63 syncer::ModelType type) const OVERRIDE { 64 ADD_FAILURE() << "FakeSyncChangeProcessor::GetAllSyncData not implemented."; 65 return syncer::SyncDataList(); 66 } 67 68 virtual SyncError ProcessSyncChanges( 69 const tracked_objects::Location&, 70 const syncer::SyncChangeList& changes) OVERRIDE { 71 for (SyncChangeList::const_iterator it = changes.begin(); 72 it != changes.end(); ++it) { 73 AddEntry(GetEntryFromChange(*it), model_); 74 } 75 return SyncError(); 76 } 77 78 private: 79 EntryMap* model_; 80 }; 81 82 ArticleEntry CreateEntry(std::string entry_id, std::string page_url1, 83 std::string page_url2, std::string page_url3) { 84 ArticleEntry entry; 85 entry.set_entry_id(entry_id); 86 if (!page_url1.empty()) { 87 ArticleEntryPage* page = entry.add_pages(); 88 page->set_url(page_url1); 89 } 90 if (!page_url2.empty()) { 91 ArticleEntryPage* page = entry.add_pages(); 92 page->set_url(page_url2); 93 } 94 if (!page_url3.empty()) { 95 ArticleEntryPage* page = entry.add_pages(); 96 page->set_url(page_url3); 97 } 98 return entry; 99 } 100 101 ArticleEntry GetSampleEntry(int id) { 102 static ArticleEntry entries[] = { 103 CreateEntry("entry0", "example.com/1", "example.com/2", "example.com/3"), 104 CreateEntry("entry1", "example.com/1", "", ""), 105 CreateEntry("entry2", "example.com/p1", "example.com/p2", ""), 106 CreateEntry("entry3", "example.com/something/all", "", ""), 107 CreateEntry("entry4", "example.com/somethingelse/1", "", ""), 108 CreateEntry("entry5", "rock.example.com/p1", "rock.example.com/p2", ""), 109 CreateEntry("entry7", "example.com/entry7/1", "example.com/entry7/2", ""), 110 CreateEntry("entry8", "example.com/entry8/1", "", ""), 111 CreateEntry("entry9", "example.com/entry9/all", "", ""), 112 }; 113 EXPECT_LT(id, 9); 114 return entries[id % 9]; 115 } 116 117 class MockDistillerObserver : public DomDistillerObserver { 118 public: 119 MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&)); 120 virtual ~MockDistillerObserver() {} 121 }; 122 123 } // namespace 124 125 class DomDistillerStoreTest : public testing::Test { 126 public: 127 virtual void SetUp() { 128 db_model_.clear(); 129 sync_model_.clear(); 130 store_model_.clear(); 131 next_sync_id_ = 1; 132 } 133 134 virtual void TearDown() { 135 store_.reset(); 136 fake_db_ = NULL; 137 fake_sync_processor_ = NULL; 138 } 139 140 // Creates a simple DomDistillerStore initialized with |store_model_| and 141 // with a FakeDB backed by |db_model_|. 142 void CreateStore() { 143 fake_db_ = new FakeDB<ArticleEntry>(&db_model_); 144 store_.reset(test::util::CreateStoreWithFakeDB(fake_db_, store_model_)); 145 } 146 147 void StartSyncing() { 148 fake_sync_processor_ = new FakeSyncChangeProcessor(&sync_model_); 149 150 store_->MergeDataAndStartSyncing( 151 kDomDistillerModelType, SyncDataFromEntryMap(sync_model_), 152 make_scoped_ptr<SyncChangeProcessor>(fake_sync_processor_), 153 scoped_ptr<SyncErrorFactory>(new FakeSyncErrorFactory())); 154 } 155 156 protected: 157 SyncData CreateSyncData(const ArticleEntry& entry) { 158 EntitySpecifics specifics = SpecificsFromEntry(entry); 159 return SyncData::CreateRemoteData( 160 next_sync_id_++, specifics, Time::UnixEpoch(), 161 syncer::AttachmentIdList(), 162 syncer::AttachmentServiceProxyForTest::Create()); 163 } 164 165 SyncDataList SyncDataFromEntryMap(const EntryMap& model) { 166 SyncDataList data; 167 for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) { 168 data.push_back(CreateSyncData(it->second)); 169 } 170 return data; 171 } 172 173 base::MessageLoop message_loop_; 174 175 EntryMap db_model_; 176 EntryMap sync_model_; 177 FakeDB<ArticleEntry>::EntryMap store_model_; 178 179 scoped_ptr<DomDistillerStore> store_; 180 181 // Both owned by |store_|. 182 FakeDB<ArticleEntry>* fake_db_; 183 FakeSyncChangeProcessor* fake_sync_processor_; 184 185 int64 next_sync_id_; 186 }; 187 188 AssertionResult AreEntriesEqual(const DomDistillerStore::EntryVector& entries, 189 EntryMap expected_entries) { 190 if (entries.size() != expected_entries.size()) 191 return AssertionFailure() << "Expected " << expected_entries.size() 192 << " entries but found " << entries.size(); 193 194 for (DomDistillerStore::EntryVector::const_iterator it = entries.begin(); 195 it != entries.end(); ++it) { 196 EntryMap::iterator expected_it = expected_entries.find(it->entry_id()); 197 if (expected_it == expected_entries.end()) { 198 return AssertionFailure() << "Found unexpected entry with id <" 199 << it->entry_id() << ">"; 200 } 201 if (!AreEntriesEqual(expected_it->second, *it)) { 202 return AssertionFailure() << "Mismatched entry with id <" 203 << it->entry_id() << ">"; 204 } 205 expected_entries.erase(expected_it); 206 } 207 return AssertionSuccess(); 208 } 209 210 AssertionResult AreEntryMapsEqual(const EntryMap& left, const EntryMap& right) { 211 DomDistillerStore::EntryVector entries; 212 for (EntryMap::const_iterator it = left.begin(); it != left.end(); ++it) { 213 entries.push_back(it->second); 214 } 215 return AreEntriesEqual(entries, right); 216 } 217 218 TEST_F(DomDistillerStoreTest, TestDatabaseLoad) { 219 AddEntry(GetSampleEntry(0), &db_model_); 220 AddEntry(GetSampleEntry(1), &db_model_); 221 AddEntry(GetSampleEntry(2), &db_model_); 222 223 CreateStore(); 224 225 fake_db_->InitCallback(true); 226 EXPECT_EQ(fake_db_->GetDirectory(), 227 FakeDB<ArticleEntry>::DirectoryForTestDB()); 228 229 fake_db_->LoadCallback(true); 230 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_)); 231 } 232 233 TEST_F(DomDistillerStoreTest, TestDatabaseLoadMerge) { 234 AddEntry(GetSampleEntry(0), &db_model_); 235 AddEntry(GetSampleEntry(1), &db_model_); 236 AddEntry(GetSampleEntry(2), &db_model_); 237 238 AddEntry(GetSampleEntry(2), &store_model_); 239 AddEntry(GetSampleEntry(3), &store_model_); 240 AddEntry(GetSampleEntry(4), &store_model_); 241 242 EntryMap expected_model(db_model_); 243 AddEntry(GetSampleEntry(3), &expected_model); 244 AddEntry(GetSampleEntry(4), &expected_model); 245 246 CreateStore(); 247 fake_db_->InitCallback(true); 248 fake_db_->LoadCallback(true); 249 250 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 251 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 252 } 253 254 TEST_F(DomDistillerStoreTest, TestAddAndRemoveEntry) { 255 CreateStore(); 256 fake_db_->InitCallback(true); 257 fake_db_->LoadCallback(true); 258 259 EXPECT_TRUE(store_->GetEntries().empty()); 260 EXPECT_TRUE(db_model_.empty()); 261 262 store_->AddEntry(GetSampleEntry(0)); 263 264 EntryMap expected_model; 265 AddEntry(GetSampleEntry(0), &expected_model); 266 267 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 268 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 269 270 store_->RemoveEntry(GetSampleEntry(0)); 271 expected_model.clear(); 272 273 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 274 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 275 } 276 277 TEST_F(DomDistillerStoreTest, TestAddAndUpdateEntry) { 278 CreateStore(); 279 fake_db_->InitCallback(true); 280 fake_db_->LoadCallback(true); 281 282 EXPECT_TRUE(store_->GetEntries().empty()); 283 EXPECT_TRUE(db_model_.empty()); 284 285 store_->AddEntry(GetSampleEntry(0)); 286 287 EntryMap expected_model; 288 AddEntry(GetSampleEntry(0), &expected_model); 289 290 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 291 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 292 293 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0))); 294 295 ArticleEntry updated_entry(GetSampleEntry(0)); 296 updated_entry.set_title("updated title."); 297 EXPECT_TRUE(store_->UpdateEntry(updated_entry)); 298 expected_model.clear(); 299 AddEntry(updated_entry, &expected_model); 300 301 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 302 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 303 304 store_->RemoveEntry(updated_entry); 305 EXPECT_FALSE(store_->UpdateEntry(updated_entry)); 306 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0))); 307 } 308 309 TEST_F(DomDistillerStoreTest, TestSyncMergeWithEmptyDatabase) { 310 AddEntry(GetSampleEntry(0), &sync_model_); 311 AddEntry(GetSampleEntry(1), &sync_model_); 312 AddEntry(GetSampleEntry(2), &sync_model_); 313 314 CreateStore(); 315 fake_db_->InitCallback(true); 316 fake_db_->LoadCallback(true); 317 318 StartSyncing(); 319 320 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), sync_model_)); 321 EXPECT_TRUE(AreEntryMapsEqual(db_model_, sync_model_)); 322 } 323 324 TEST_F(DomDistillerStoreTest, TestSyncMergeAfterDatabaseLoad) { 325 AddEntry(GetSampleEntry(0), &db_model_); 326 AddEntry(GetSampleEntry(1), &db_model_); 327 AddEntry(GetSampleEntry(2), &db_model_); 328 329 AddEntry(GetSampleEntry(2), &sync_model_); 330 AddEntry(GetSampleEntry(3), &sync_model_); 331 AddEntry(GetSampleEntry(4), &sync_model_); 332 333 EntryMap expected_model(db_model_); 334 AddEntry(GetSampleEntry(3), &expected_model); 335 AddEntry(GetSampleEntry(4), &expected_model); 336 337 CreateStore(); 338 fake_db_->InitCallback(true); 339 fake_db_->LoadCallback(true); 340 341 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_)); 342 343 StartSyncing(); 344 345 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 346 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 347 EXPECT_TRUE(AreEntryMapsEqual(sync_model_, expected_model)); 348 } 349 350 TEST_F(DomDistillerStoreTest, TestGetAllSyncData) { 351 AddEntry(GetSampleEntry(0), &db_model_); 352 AddEntry(GetSampleEntry(1), &db_model_); 353 AddEntry(GetSampleEntry(2), &db_model_); 354 355 AddEntry(GetSampleEntry(2), &sync_model_); 356 AddEntry(GetSampleEntry(3), &sync_model_); 357 AddEntry(GetSampleEntry(4), &sync_model_); 358 359 EntryMap expected_model(db_model_); 360 AddEntry(GetSampleEntry(3), &expected_model); 361 AddEntry(GetSampleEntry(4), &expected_model); 362 363 CreateStore(); 364 365 fake_db_->InitCallback(true); 366 fake_db_->LoadCallback(true); 367 368 StartSyncing(); 369 370 SyncDataList data = store_->GetAllSyncData(kDomDistillerModelType); 371 DomDistillerStore::EntryVector entries; 372 for (SyncDataList::iterator it = data.begin(); it != data.end(); ++it) { 373 entries.push_back(EntryFromSpecifics(it->GetSpecifics())); 374 } 375 EXPECT_TRUE(AreEntriesEqual(entries, expected_model)); 376 } 377 378 TEST_F(DomDistillerStoreTest, TestProcessSyncChanges) { 379 AddEntry(GetSampleEntry(0), &db_model_); 380 AddEntry(GetSampleEntry(1), &db_model_); 381 sync_model_ = db_model_; 382 383 EntryMap expected_model(db_model_); 384 AddEntry(GetSampleEntry(2), &expected_model); 385 AddEntry(GetSampleEntry(3), &expected_model); 386 387 CreateStore(); 388 389 fake_db_->InitCallback(true); 390 fake_db_->LoadCallback(true); 391 392 StartSyncing(); 393 394 SyncChangeList changes; 395 changes.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD, 396 CreateSyncData(GetSampleEntry(2)))); 397 changes.push_back(SyncChange(FROM_HERE, SyncChange::ACTION_ADD, 398 CreateSyncData(GetSampleEntry(3)))); 399 400 store_->ProcessSyncChanges(FROM_HERE, changes); 401 402 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 403 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model)); 404 } 405 406 TEST_F(DomDistillerStoreTest, TestSyncMergeWithSecondDomDistillerStore) { 407 AddEntry(GetSampleEntry(0), &db_model_); 408 AddEntry(GetSampleEntry(1), &db_model_); 409 AddEntry(GetSampleEntry(2), &db_model_); 410 411 EntryMap other_db_model; 412 AddEntry(GetSampleEntry(2), &other_db_model); 413 AddEntry(GetSampleEntry(3), &other_db_model); 414 AddEntry(GetSampleEntry(4), &other_db_model); 415 416 EntryMap expected_model(db_model_); 417 AddEntry(GetSampleEntry(3), &expected_model); 418 AddEntry(GetSampleEntry(4), &expected_model); 419 420 CreateStore(); 421 422 fake_db_->InitCallback(true); 423 fake_db_->LoadCallback(true); 424 425 FakeDB<ArticleEntry>* other_fake_db = 426 new FakeDB<ArticleEntry>(&other_db_model); 427 scoped_ptr<DomDistillerStore> owned_other_store(new DomDistillerStore( 428 scoped_ptr<leveldb_proto::ProtoDatabase<ArticleEntry> >(other_fake_db), 429 std::vector<ArticleEntry>(), 430 base::FilePath(FILE_PATH_LITERAL("/fake/other/path")))); 431 DomDistillerStore* other_store = owned_other_store.get(); 432 other_fake_db->InitCallback(true); 433 other_fake_db->LoadCallback(true); 434 435 EXPECT_FALSE(AreEntriesEqual(store_->GetEntries(), expected_model)); 436 EXPECT_FALSE(AreEntriesEqual(other_store->GetEntries(), expected_model)); 437 ASSERT_TRUE(AreEntriesEqual(other_store->GetEntries(), other_db_model)); 438 439 FakeSyncErrorFactory* other_error_factory = new FakeSyncErrorFactory(); 440 store_->MergeDataAndStartSyncing( 441 kDomDistillerModelType, SyncDataFromEntryMap(other_db_model), 442 owned_other_store.PassAs<SyncChangeProcessor>(), 443 make_scoped_ptr<SyncErrorFactory>(other_error_factory)); 444 445 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model)); 446 EXPECT_TRUE(AreEntriesEqual(other_store->GetEntries(), expected_model)); 447 } 448 449 TEST_F(DomDistillerStoreTest, TestObserver) { 450 CreateStore(); 451 MockDistillerObserver observer; 452 store_->AddObserver(&observer); 453 fake_db_->InitCallback(true); 454 fake_db_->LoadCallback(true); 455 std::vector<DomDistillerObserver::ArticleUpdate> expected_updates; 456 DomDistillerObserver::ArticleUpdate update; 457 update.entry_id = GetSampleEntry(0).entry_id(); 458 update.update_type = DomDistillerObserver::ArticleUpdate::ADD; 459 expected_updates.push_back(update); 460 EXPECT_CALL(observer, ArticleEntriesUpdated( 461 test::util::HasExpectedUpdates(expected_updates))); 462 store_->AddEntry(GetSampleEntry(0)); 463 464 expected_updates.clear(); 465 update.entry_id = GetSampleEntry(1).entry_id(); 466 update.update_type = DomDistillerObserver::ArticleUpdate::ADD; 467 expected_updates.push_back(update); 468 EXPECT_CALL(observer, ArticleEntriesUpdated( 469 test::util::HasExpectedUpdates(expected_updates))); 470 store_->AddEntry(GetSampleEntry(1)); 471 472 expected_updates.clear(); 473 update.entry_id = GetSampleEntry(0).entry_id(); 474 update.update_type = DomDistillerObserver::ArticleUpdate::REMOVE; 475 expected_updates.clear(); 476 expected_updates.push_back(update); 477 EXPECT_CALL(observer, ArticleEntriesUpdated( 478 test::util::HasExpectedUpdates(expected_updates))); 479 store_->RemoveEntry(GetSampleEntry(0)); 480 481 // Add entry_id = 3 and update entry_id = 1. 482 expected_updates.clear(); 483 SyncDataList change_data; 484 change_data.push_back(CreateSyncData(GetSampleEntry(3))); 485 ArticleEntry updated_entry(GetSampleEntry(1)); 486 updated_entry.set_title("changed_title"); 487 change_data.push_back(CreateSyncData(updated_entry)); 488 update.entry_id = GetSampleEntry(3).entry_id(); 489 update.update_type = DomDistillerObserver::ArticleUpdate::ADD; 490 expected_updates.push_back(update); 491 update.entry_id = GetSampleEntry(1).entry_id(); 492 update.update_type = DomDistillerObserver::ArticleUpdate::UPDATE; 493 expected_updates.push_back(update); 494 EXPECT_CALL(observer, ArticleEntriesUpdated( 495 test::util::HasExpectedUpdates(expected_updates))); 496 497 FakeSyncErrorFactory* fake_error_factory = new FakeSyncErrorFactory(); 498 EntryMap fake_model; 499 FakeSyncChangeProcessor* fake_sync_change_processor = 500 new FakeSyncChangeProcessor(&fake_model); 501 store_->MergeDataAndStartSyncing( 502 kDomDistillerModelType, change_data, 503 make_scoped_ptr<SyncChangeProcessor>(fake_sync_change_processor), 504 make_scoped_ptr<SyncErrorFactory>(fake_error_factory)); 505 } 506 507 } // namespace dom_distiller 508