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.
5 #include "components/dom_distiller/core/dom_distiller_store.h"
8 #include "base/files/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/internal_api/public/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"
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;
37 namespace dom_distiller {
41 const ModelType kDomDistillerModelType = syncer::ARTICLES;
43 typedef base::hash_map<std::string, ArticleEntry> EntryMap;
45 void AddEntry(const ArticleEntry& e, EntryMap* map) {
46 (*map)[e.entry_id()] = e;
49 class FakeSyncErrorFactory : public syncer::SyncErrorFactory {
51 virtual syncer::SyncError CreateAndUploadError(
52 const tracked_objects::Location& location,
53 const std::string& message) OVERRIDE {
54 return syncer::SyncError();
58 class FakeSyncChangeProcessor : public syncer::SyncChangeProcessor {
60 explicit FakeSyncChangeProcessor(EntryMap* model) : model_(model) {}
62 virtual syncer::SyncDataList GetAllSyncData(
63 syncer::ModelType type) const OVERRIDE {
64 ADD_FAILURE() << "FakeSyncChangeProcessor::GetAllSyncData not implemented.";
65 return syncer::SyncDataList();
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_);
82 ArticleEntry CreateEntry(std::string entry_id, std::string page_url1,
83 std::string page_url2, std::string page_url3) {
85 entry.set_entry_id(entry_id);
86 if (!page_url1.empty()) {
87 ArticleEntryPage* page = entry.add_pages();
88 page->set_url(page_url1);
90 if (!page_url2.empty()) {
91 ArticleEntryPage* page = entry.add_pages();
92 page->set_url(page_url2);
94 if (!page_url3.empty()) {
95 ArticleEntryPage* page = entry.add_pages();
96 page->set_url(page_url3);
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", "", ""),
114 return entries[id % 9];
117 class MockDistillerObserver : public DomDistillerObserver {
119 MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&));
120 virtual ~MockDistillerObserver() {}
125 class DomDistillerStoreTest : public testing::Test {
127 virtual void SetUp() {
130 store_model_.clear();
134 virtual void TearDown() {
137 fake_sync_processor_ = NULL;
140 // Creates a simple DomDistillerStore initialized with |store_model_| and
141 // with a FakeDB backed by |db_model_|.
143 fake_db_ = new FakeDB<ArticleEntry>(&db_model_);
144 store_.reset(test::util::CreateStoreWithFakeDB(fake_db_, store_model_));
147 void StartSyncing() {
148 fake_sync_processor_ = new FakeSyncChangeProcessor(&sync_model_);
150 store_->MergeDataAndStartSyncing(
151 kDomDistillerModelType, SyncDataFromEntryMap(sync_model_),
152 make_scoped_ptr<SyncChangeProcessor>(fake_sync_processor_),
153 scoped_ptr<SyncErrorFactory>(new FakeSyncErrorFactory()));
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());
165 SyncDataList SyncDataFromEntryMap(const EntryMap& model) {
167 for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) {
168 data.push_back(CreateSyncData(it->second));
173 base::MessageLoop message_loop_;
176 EntryMap sync_model_;
177 FakeDB<ArticleEntry>::EntryMap store_model_;
179 scoped_ptr<DomDistillerStore> store_;
181 // Both owned by |store_|.
182 FakeDB<ArticleEntry>* fake_db_;
183 FakeSyncChangeProcessor* fake_sync_processor_;
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();
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() << ">";
201 if (!AreEntriesEqual(expected_it->second, *it)) {
202 return AssertionFailure() << "Mismatched entry with id <"
203 << it->entry_id() << ">";
205 expected_entries.erase(expected_it);
207 return AssertionSuccess();
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);
215 return AreEntriesEqual(entries, right);
218 TEST_F(DomDistillerStoreTest, TestDatabaseLoad) {
219 AddEntry(GetSampleEntry(0), &db_model_);
220 AddEntry(GetSampleEntry(1), &db_model_);
221 AddEntry(GetSampleEntry(2), &db_model_);
225 fake_db_->InitCallback(true);
226 EXPECT_EQ(fake_db_->GetDirectory(),
227 FakeDB<ArticleEntry>::DirectoryForTestDB());
229 fake_db_->LoadCallback(true);
230 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
233 TEST_F(DomDistillerStoreTest, TestDatabaseLoadMerge) {
234 AddEntry(GetSampleEntry(0), &db_model_);
235 AddEntry(GetSampleEntry(1), &db_model_);
236 AddEntry(GetSampleEntry(2), &db_model_);
238 AddEntry(GetSampleEntry(2), &store_model_);
239 AddEntry(GetSampleEntry(3), &store_model_);
240 AddEntry(GetSampleEntry(4), &store_model_);
242 EntryMap expected_model(db_model_);
243 AddEntry(GetSampleEntry(3), &expected_model);
244 AddEntry(GetSampleEntry(4), &expected_model);
247 fake_db_->InitCallback(true);
248 fake_db_->LoadCallback(true);
250 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
251 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
254 TEST_F(DomDistillerStoreTest, TestAddAndRemoveEntry) {
256 fake_db_->InitCallback(true);
257 fake_db_->LoadCallback(true);
259 EXPECT_TRUE(store_->GetEntries().empty());
260 EXPECT_TRUE(db_model_.empty());
262 store_->AddEntry(GetSampleEntry(0));
264 EntryMap expected_model;
265 AddEntry(GetSampleEntry(0), &expected_model);
267 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
268 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
270 store_->RemoveEntry(GetSampleEntry(0));
271 expected_model.clear();
273 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
274 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
277 TEST_F(DomDistillerStoreTest, TestAddAndUpdateEntry) {
279 fake_db_->InitCallback(true);
280 fake_db_->LoadCallback(true);
282 EXPECT_TRUE(store_->GetEntries().empty());
283 EXPECT_TRUE(db_model_.empty());
285 store_->AddEntry(GetSampleEntry(0));
287 EntryMap expected_model;
288 AddEntry(GetSampleEntry(0), &expected_model);
290 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
291 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
293 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0)));
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);
301 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
302 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
304 store_->RemoveEntry(updated_entry);
305 EXPECT_FALSE(store_->UpdateEntry(updated_entry));
306 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0)));
309 TEST_F(DomDistillerStoreTest, TestSyncMergeWithEmptyDatabase) {
310 AddEntry(GetSampleEntry(0), &sync_model_);
311 AddEntry(GetSampleEntry(1), &sync_model_);
312 AddEntry(GetSampleEntry(2), &sync_model_);
315 fake_db_->InitCallback(true);
316 fake_db_->LoadCallback(true);
320 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), sync_model_));
321 EXPECT_TRUE(AreEntryMapsEqual(db_model_, sync_model_));
324 TEST_F(DomDistillerStoreTest, TestSyncMergeAfterDatabaseLoad) {
325 AddEntry(GetSampleEntry(0), &db_model_);
326 AddEntry(GetSampleEntry(1), &db_model_);
327 AddEntry(GetSampleEntry(2), &db_model_);
329 AddEntry(GetSampleEntry(2), &sync_model_);
330 AddEntry(GetSampleEntry(3), &sync_model_);
331 AddEntry(GetSampleEntry(4), &sync_model_);
333 EntryMap expected_model(db_model_);
334 AddEntry(GetSampleEntry(3), &expected_model);
335 AddEntry(GetSampleEntry(4), &expected_model);
338 fake_db_->InitCallback(true);
339 fake_db_->LoadCallback(true);
341 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
345 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
346 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
347 EXPECT_TRUE(AreEntryMapsEqual(sync_model_, expected_model));
350 TEST_F(DomDistillerStoreTest, TestGetAllSyncData) {
351 AddEntry(GetSampleEntry(0), &db_model_);
352 AddEntry(GetSampleEntry(1), &db_model_);
353 AddEntry(GetSampleEntry(2), &db_model_);
355 AddEntry(GetSampleEntry(2), &sync_model_);
356 AddEntry(GetSampleEntry(3), &sync_model_);
357 AddEntry(GetSampleEntry(4), &sync_model_);
359 EntryMap expected_model(db_model_);
360 AddEntry(GetSampleEntry(3), &expected_model);
361 AddEntry(GetSampleEntry(4), &expected_model);
365 fake_db_->InitCallback(true);
366 fake_db_->LoadCallback(true);
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()));
375 EXPECT_TRUE(AreEntriesEqual(entries, expected_model));
378 TEST_F(DomDistillerStoreTest, TestProcessSyncChanges) {
379 AddEntry(GetSampleEntry(0), &db_model_);
380 AddEntry(GetSampleEntry(1), &db_model_);
381 sync_model_ = db_model_;
383 EntryMap expected_model(db_model_);
384 AddEntry(GetSampleEntry(2), &expected_model);
385 AddEntry(GetSampleEntry(3), &expected_model);
389 fake_db_->InitCallback(true);
390 fake_db_->LoadCallback(true);
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))));
400 store_->ProcessSyncChanges(FROM_HERE, changes);
402 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
403 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
406 TEST_F(DomDistillerStoreTest, TestSyncMergeWithSecondDomDistillerStore) {
407 AddEntry(GetSampleEntry(0), &db_model_);
408 AddEntry(GetSampleEntry(1), &db_model_);
409 AddEntry(GetSampleEntry(2), &db_model_);
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);
416 EntryMap expected_model(db_model_);
417 AddEntry(GetSampleEntry(3), &expected_model);
418 AddEntry(GetSampleEntry(4), &expected_model);
422 fake_db_->InitCallback(true);
423 fake_db_->LoadCallback(true);
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);
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));
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));
445 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
446 EXPECT_TRUE(AreEntriesEqual(other_store->GetEntries(), expected_model));
449 TEST_F(DomDistillerStoreTest, TestObserver) {
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));
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));
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));
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)));
497 FakeSyncErrorFactory* fake_error_factory = new FakeSyncErrorFactory();
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));
507 } // namespace dom_distiller