Home | History | Annotate | Download | only in core
      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