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/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/dom_distiller/core/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"
23 using dom_distiller::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(syncer::ModelType type) const
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();
74 AddEntry(GetEntryFromChange(*it), model_);
83 ArticleEntry CreateEntry(std::string entry_id,
84 std::string page_url1,
85 std::string page_url2,
86 std::string page_url3) {
88 entry.set_entry_id(entry_id);
89 if (!page_url1.empty()) {
90 ArticleEntryPage* page = entry.add_pages();
91 page->set_url(page_url1);
93 if (!page_url2.empty()) {
94 ArticleEntryPage* page = entry.add_pages();
95 page->set_url(page_url2);
97 if (!page_url3.empty()) {
98 ArticleEntryPage* page = entry.add_pages();
99 page->set_url(page_url3);
104 ArticleEntry GetSampleEntry(int id) {
105 static ArticleEntry entries[] = {
106 CreateEntry("entry0", "example.com/1", "example.com/2", "example.com/3"),
107 CreateEntry("entry1", "example.com/1", "", ""),
108 CreateEntry("entry2", "example.com/p1", "example.com/p2", ""),
109 CreateEntry("entry3", "example.com/something/all", "", ""),
110 CreateEntry("entry4", "example.com/somethingelse/1", "", ""),
111 CreateEntry("entry5", "rock.example.com/p1", "rock.example.com/p2", ""),
112 CreateEntry("entry7", "example.com/entry7/1", "example.com/entry7/2", ""),
113 CreateEntry("entry8", "example.com/entry8/1", "", ""),
114 CreateEntry("entry9", "example.com/entry9/all", "", ""), };
116 return entries[id % 9];
119 class MockDistillerObserver : public DomDistillerObserver {
121 MOCK_METHOD1(ArticleEntriesUpdated, void(const std::vector<ArticleUpdate>&));
122 virtual ~MockDistillerObserver() {}
127 class DomDistillerStoreTest : public testing::Test {
129 virtual void SetUp() {
132 store_model_.clear();
136 virtual void TearDown() {
139 fake_sync_processor_ = NULL;
142 // Creates a simple DomDistillerStore initialized with |store_model_| and
143 // with a FakeDB backed by |db_model_|.
145 fake_db_ = new FakeDB(&db_model_);
146 store_.reset(test::util::CreateStoreWithFakeDB(fake_db_, store_model_));
149 void StartSyncing() {
150 fake_sync_processor_ = new FakeSyncChangeProcessor(&sync_model_);
152 store_->MergeDataAndStartSyncing(
153 kDomDistillerModelType,
154 SyncDataFromEntryMap(sync_model_),
155 make_scoped_ptr<SyncChangeProcessor>(fake_sync_processor_),
156 scoped_ptr<SyncErrorFactory>(new FakeSyncErrorFactory()));
160 SyncData CreateSyncData(const ArticleEntry& entry) {
161 EntitySpecifics specifics = SpecificsFromEntry(entry);
162 return SyncData::CreateRemoteData(
166 syncer::AttachmentIdList(),
167 syncer::AttachmentServiceProxyForTest::Create());
170 SyncDataList SyncDataFromEntryMap(const EntryMap& model) {
172 for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) {
173 data.push_back(CreateSyncData(it->second));
178 base::MessageLoop message_loop_;
181 EntryMap sync_model_;
182 FakeDB::EntryMap store_model_;
184 scoped_ptr<DomDistillerStore> store_;
186 // Both owned by |store_|.
188 FakeSyncChangeProcessor* fake_sync_processor_;
193 AssertionResult AreEntriesEqual(const EntryVector& entries,
194 EntryMap expected_entries) {
195 if (entries.size() != expected_entries.size())
196 return AssertionFailure() << "Expected " << expected_entries.size()
197 << " entries but found " << entries.size();
199 for (EntryVector::const_iterator it = entries.begin(); it != entries.end();
201 EntryMap::iterator expected_it = expected_entries.find(it->entry_id());
202 if (expected_it == expected_entries.end()) {
203 return AssertionFailure() << "Found unexpected entry with id <"
204 << it->entry_id() << ">";
206 if (!AreEntriesEqual(expected_it->second, *it)) {
207 return AssertionFailure() << "Mismatched entry with id <"
208 << it->entry_id() << ">";
210 expected_entries.erase(expected_it);
212 return AssertionSuccess();
215 AssertionResult AreEntryMapsEqual(const EntryMap& left, const EntryMap& right) {
217 for (EntryMap::const_iterator it = left.begin(); it != left.end(); ++it) {
218 entries.push_back(it->second);
220 return AreEntriesEqual(entries, right);
223 TEST_F(DomDistillerStoreTest, TestDatabaseLoad) {
224 AddEntry(GetSampleEntry(0), &db_model_);
225 AddEntry(GetSampleEntry(1), &db_model_);
226 AddEntry(GetSampleEntry(2), &db_model_);
230 fake_db_->InitCallback(true);
231 EXPECT_EQ(fake_db_->GetDirectory(), FakeDB::DirectoryForTestDB());
233 fake_db_->LoadCallback(true);
234 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
237 TEST_F(DomDistillerStoreTest, TestDatabaseLoadMerge) {
238 AddEntry(GetSampleEntry(0), &db_model_);
239 AddEntry(GetSampleEntry(1), &db_model_);
240 AddEntry(GetSampleEntry(2), &db_model_);
242 AddEntry(GetSampleEntry(2), &store_model_);
243 AddEntry(GetSampleEntry(3), &store_model_);
244 AddEntry(GetSampleEntry(4), &store_model_);
246 EntryMap expected_model(db_model_);
247 AddEntry(GetSampleEntry(3), &expected_model);
248 AddEntry(GetSampleEntry(4), &expected_model);
251 fake_db_->InitCallback(true);
252 fake_db_->LoadCallback(true);
254 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
255 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
258 TEST_F(DomDistillerStoreTest, TestAddAndRemoveEntry) {
260 fake_db_->InitCallback(true);
261 fake_db_->LoadCallback(true);
263 EXPECT_TRUE(store_->GetEntries().empty());
264 EXPECT_TRUE(db_model_.empty());
266 store_->AddEntry(GetSampleEntry(0));
268 EntryMap expected_model;
269 AddEntry(GetSampleEntry(0), &expected_model);
271 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
272 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
274 store_->RemoveEntry(GetSampleEntry(0));
275 expected_model.clear();
277 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
278 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
281 TEST_F(DomDistillerStoreTest, TestAddAndUpdateEntry) {
283 fake_db_->InitCallback(true);
284 fake_db_->LoadCallback(true);
286 EXPECT_TRUE(store_->GetEntries().empty());
287 EXPECT_TRUE(db_model_.empty());
289 store_->AddEntry(GetSampleEntry(0));
291 EntryMap expected_model;
292 AddEntry(GetSampleEntry(0), &expected_model);
294 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
295 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
297 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0)));
299 ArticleEntry updated_entry(GetSampleEntry(0));
300 updated_entry.set_title("updated title.");
301 EXPECT_TRUE(store_->UpdateEntry(updated_entry));
302 expected_model.clear();
303 AddEntry(updated_entry, &expected_model);
305 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
306 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
308 store_->RemoveEntry(updated_entry);
309 EXPECT_FALSE(store_->UpdateEntry(updated_entry));
310 EXPECT_FALSE(store_->UpdateEntry(GetSampleEntry(0)));
313 TEST_F(DomDistillerStoreTest, TestSyncMergeWithEmptyDatabase) {
314 AddEntry(GetSampleEntry(0), &sync_model_);
315 AddEntry(GetSampleEntry(1), &sync_model_);
316 AddEntry(GetSampleEntry(2), &sync_model_);
319 fake_db_->InitCallback(true);
320 fake_db_->LoadCallback(true);
324 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), sync_model_));
325 EXPECT_TRUE(AreEntryMapsEqual(db_model_, sync_model_));
328 TEST_F(DomDistillerStoreTest, TestSyncMergeAfterDatabaseLoad) {
329 AddEntry(GetSampleEntry(0), &db_model_);
330 AddEntry(GetSampleEntry(1), &db_model_);
331 AddEntry(GetSampleEntry(2), &db_model_);
333 AddEntry(GetSampleEntry(2), &sync_model_);
334 AddEntry(GetSampleEntry(3), &sync_model_);
335 AddEntry(GetSampleEntry(4), &sync_model_);
337 EntryMap expected_model(db_model_);
338 AddEntry(GetSampleEntry(3), &expected_model);
339 AddEntry(GetSampleEntry(4), &expected_model);
342 fake_db_->InitCallback(true);
343 fake_db_->LoadCallback(true);
345 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
349 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
350 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
351 EXPECT_TRUE(AreEntryMapsEqual(sync_model_, expected_model));
354 TEST_F(DomDistillerStoreTest, TestGetAllSyncData) {
355 AddEntry(GetSampleEntry(0), &db_model_);
356 AddEntry(GetSampleEntry(1), &db_model_);
357 AddEntry(GetSampleEntry(2), &db_model_);
359 AddEntry(GetSampleEntry(2), &sync_model_);
360 AddEntry(GetSampleEntry(3), &sync_model_);
361 AddEntry(GetSampleEntry(4), &sync_model_);
363 EntryMap expected_model(db_model_);
364 AddEntry(GetSampleEntry(3), &expected_model);
365 AddEntry(GetSampleEntry(4), &expected_model);
369 fake_db_->InitCallback(true);
370 fake_db_->LoadCallback(true);
374 SyncDataList data = store_->GetAllSyncData(kDomDistillerModelType);
376 for (SyncDataList::iterator it = data.begin(); it != data.end(); ++it) {
377 entries.push_back(EntryFromSpecifics(it->GetSpecifics()));
379 EXPECT_TRUE(AreEntriesEqual(entries, expected_model));
382 TEST_F(DomDistillerStoreTest, TestProcessSyncChanges) {
383 AddEntry(GetSampleEntry(0), &db_model_);
384 AddEntry(GetSampleEntry(1), &db_model_);
385 sync_model_ = db_model_;
387 EntryMap expected_model(db_model_);
388 AddEntry(GetSampleEntry(2), &expected_model);
389 AddEntry(GetSampleEntry(3), &expected_model);
393 fake_db_->InitCallback(true);
394 fake_db_->LoadCallback(true);
398 SyncChangeList changes;
399 changes.push_back(SyncChange(
400 FROM_HERE, SyncChange::ACTION_ADD, CreateSyncData(GetSampleEntry(2))));
401 changes.push_back(SyncChange(
402 FROM_HERE, SyncChange::ACTION_ADD, CreateSyncData(GetSampleEntry(3))));
404 store_->ProcessSyncChanges(FROM_HERE, changes);
406 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
407 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
410 TEST_F(DomDistillerStoreTest, TestSyncMergeWithSecondDomDistillerStore) {
411 AddEntry(GetSampleEntry(0), &db_model_);
412 AddEntry(GetSampleEntry(1), &db_model_);
413 AddEntry(GetSampleEntry(2), &db_model_);
415 EntryMap other_db_model;
416 AddEntry(GetSampleEntry(2), &other_db_model);
417 AddEntry(GetSampleEntry(3), &other_db_model);
418 AddEntry(GetSampleEntry(4), &other_db_model);
420 EntryMap expected_model(db_model_);
421 AddEntry(GetSampleEntry(3), &expected_model);
422 AddEntry(GetSampleEntry(4), &expected_model);
426 fake_db_->InitCallback(true);
427 fake_db_->LoadCallback(true);
429 FakeDB* other_fake_db = new FakeDB(&other_db_model);
430 scoped_ptr<DomDistillerStore> owned_other_store(new DomDistillerStore(
431 scoped_ptr<DomDistillerDatabaseInterface>(other_fake_db),
432 std::vector<ArticleEntry>(),
433 base::FilePath(FILE_PATH_LITERAL("/fake/other/path"))));
434 DomDistillerStore* other_store = owned_other_store.get();
435 other_fake_db->InitCallback(true);
436 other_fake_db->LoadCallback(true);
438 EXPECT_FALSE(AreEntriesEqual(store_->GetEntries(), expected_model));
439 EXPECT_FALSE(AreEntriesEqual(other_store->GetEntries(), expected_model));
440 ASSERT_TRUE(AreEntriesEqual(other_store->GetEntries(), other_db_model));
442 FakeSyncErrorFactory* other_error_factory = new FakeSyncErrorFactory();
443 store_->MergeDataAndStartSyncing(
444 kDomDistillerModelType,
445 SyncDataFromEntryMap(other_db_model),
446 owned_other_store.PassAs<SyncChangeProcessor>(),
447 make_scoped_ptr<SyncErrorFactory>(other_error_factory));
449 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
450 EXPECT_TRUE(AreEntriesEqual(other_store->GetEntries(), expected_model));
453 TEST_F(DomDistillerStoreTest, TestObserver) {
455 MockDistillerObserver observer;
456 store_->AddObserver(&observer);
457 fake_db_->InitCallback(true);
458 fake_db_->LoadCallback(true);
459 std::vector<DomDistillerObserver::ArticleUpdate> expected_updates;
460 DomDistillerObserver::ArticleUpdate update;
461 update.entry_id = GetSampleEntry(0).entry_id();
462 update.update_type = DomDistillerObserver::ArticleUpdate::ADD;
463 expected_updates.push_back(update);
466 ArticleEntriesUpdated(test::util::HasExpectedUpdates(expected_updates)));
467 store_->AddEntry(GetSampleEntry(0));
469 expected_updates.clear();
470 update.entry_id = GetSampleEntry(1).entry_id();
471 update.update_type = DomDistillerObserver::ArticleUpdate::ADD;
472 expected_updates.push_back(update);
475 ArticleEntriesUpdated(test::util::HasExpectedUpdates(expected_updates)));
476 store_->AddEntry(GetSampleEntry(1));
478 expected_updates.clear();
479 update.entry_id = GetSampleEntry(0).entry_id();
480 update.update_type = DomDistillerObserver::ArticleUpdate::REMOVE;
481 expected_updates.clear();
482 expected_updates.push_back(update);
485 ArticleEntriesUpdated(test::util::HasExpectedUpdates(expected_updates)));
486 store_->RemoveEntry(GetSampleEntry(0));
488 // Add entry_id = 3 and update entry_id = 1.
489 expected_updates.clear();
490 SyncDataList change_data;
491 change_data.push_back(CreateSyncData(GetSampleEntry(3)));
492 ArticleEntry updated_entry(GetSampleEntry(1));
493 updated_entry.set_title("changed_title");
494 change_data.push_back(CreateSyncData(updated_entry));
495 update.entry_id = GetSampleEntry(3).entry_id();
496 update.update_type = DomDistillerObserver::ArticleUpdate::ADD;
497 expected_updates.push_back(update);
498 update.entry_id = GetSampleEntry(1).entry_id();
499 update.update_type = DomDistillerObserver::ArticleUpdate::UPDATE;
500 expected_updates.push_back(update);
503 ArticleEntriesUpdated(test::util::HasExpectedUpdates(expected_updates)));
505 FakeSyncErrorFactory* fake_error_factory = new FakeSyncErrorFactory();
507 FakeSyncChangeProcessor* fake_sync_change_processor =
508 new FakeSyncChangeProcessor(&fake_model);
509 store_->MergeDataAndStartSyncing(
510 kDomDistillerModelType,
512 make_scoped_ptr<SyncChangeProcessor>(fake_sync_change_processor),
513 make_scoped_ptr<SyncErrorFactory>(fake_error_factory));
516 } // namespace dom_distiller