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/run_loop.h"
11 #include "base/time/time.h"
12 #include "components/dom_distiller/core/article_entry.h"
13 #include "sync/protocol/sync.pb.h"
14 #include "testing/gmock/include/gmock/gmock.h"
15 #include "testing/gtest/include/gtest/gtest.h"
18 using sync_pb::EntitySpecifics;
19 using syncer::ModelType;
20 using syncer::SyncChange;
21 using syncer::SyncChangeList;
22 using syncer::SyncChangeProcessor;
23 using syncer::SyncData;
24 using syncer::SyncDataList;
25 using syncer::SyncError;
26 using syncer::SyncErrorFactory;
27 using testing::AssertionFailure;
28 using testing::AssertionResult;
29 using testing::AssertionSuccess;
31 namespace dom_distiller {
35 const ModelType kDomDistillerModelType = syncer::ARTICLES;
37 typedef base::hash_map<std::string, ArticleEntry> EntryMap;
39 void AddEntry(const ArticleEntry& e, EntryMap* map) {
40 (*map)[e.entry_id()] = e;
43 std::vector<ArticleEntry> EntryMapToList(const EntryMap& entries) {
44 std::vector<ArticleEntry> entry_list;
45 for (EntryMap::const_iterator it = entries.begin(); it != entries.end();
47 entry_list.push_back(it->second);
52 class FakeDB : public DomDistillerDatabaseInterface {
53 typedef base::Callback<void(bool)> Callback;
56 explicit FakeDB(EntryMap* db) : db_(db) {}
58 virtual void Destroy() OVERRIDE {}
61 const base::FilePath& database_dir,
62 DomDistillerDatabaseInterface::InitCallback callback) OVERRIDE {
64 init_callback_ = callback;
67 virtual void SaveEntries(
68 scoped_ptr<EntryVector> entries_to_save,
69 DomDistillerDatabaseInterface::SaveCallback callback) OVERRIDE {
70 for (EntryVector::iterator it = entries_to_save->begin();
71 it != entries_to_save->end();
73 (*db_)[it->entry_id()] = *it;
75 save_callback_ = callback;
78 virtual void LoadEntries(
79 DomDistillerDatabaseInterface::LoadCallback callback) OVERRIDE {
80 scoped_ptr<EntryVector> entries(new EntryVector());
81 for (EntryMap::iterator it = db_->begin(); it != db_->end(); ++it) {
82 entries->push_back(it->second);
85 base::Bind(RunLoadCallback, callback, base::Passed(&entries));
88 base::FilePath& GetDirectory() { return dir_; }
90 void InitCallback(bool success) {
91 init_callback_.Run(success);
92 init_callback_.Reset();
95 void LoadCallback(bool success) {
96 load_callback_.Run(success);
97 load_callback_.Reset();
100 void SaveCallback(bool success) {
101 save_callback_.Run(success);
102 save_callback_.Reset();
106 static void RunLoadCallback(
107 DomDistillerDatabaseInterface::LoadCallback callback,
108 scoped_ptr<EntryVector> entries,
110 callback.Run(success, entries.Pass());
116 Callback init_callback_;
117 Callback load_callback_;
118 Callback save_callback_;
121 class FakeSyncErrorFactory : public syncer::SyncErrorFactory {
123 virtual syncer::SyncError CreateAndUploadError(
124 const tracked_objects::Location& location,
125 const std::string& message) OVERRIDE {
126 return syncer::SyncError();
130 class FakeSyncChangeProcessor : public syncer::SyncChangeProcessor {
132 FakeSyncChangeProcessor(EntryMap* model) : model_(model) {}
134 virtual syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const
136 ADD_FAILURE() << "FakeSyncChangeProcessor::GetAllSyncData not implemented.";
137 return syncer::SyncDataList();
140 virtual SyncError ProcessSyncChanges(
141 const tracked_objects::Location&,
142 const syncer::SyncChangeList& changes) OVERRIDE {
143 for (SyncChangeList::const_iterator it = changes.begin();
146 AddEntry(GetEntryFromChange(*it), model_);
155 ArticleEntry CreateEntry(std::string entry_id,
156 std::string page_url1,
157 std::string page_url2,
158 std::string page_url3) {
160 entry.set_entry_id(entry_id);
161 if (!page_url1.empty()) {
162 ArticleEntryPage* page = entry.add_pages();
163 page->set_url(page_url1);
165 if (!page_url2.empty()) {
166 ArticleEntryPage* page = entry.add_pages();
167 page->set_url(page_url2);
169 if (!page_url3.empty()) {
170 ArticleEntryPage* page = entry.add_pages();
171 page->set_url(page_url3);
176 ArticleEntry GetSampleEntry(int id) {
177 static ArticleEntry entries[] = {
178 CreateEntry("entry0", "example.com/1", "example.com/2", "example.com/3"),
179 CreateEntry("entry1", "example.com/1", "", ""),
180 CreateEntry("entry2", "example.com/p1", "example.com/p2", ""),
181 CreateEntry("entry3", "example.com/something/all", "", ""),
182 CreateEntry("entry4", "example.com/somethingelse/1", "", ""),
183 CreateEntry("entry5", "rock.example.com/p1", "rock.example.com/p2", ""),
184 CreateEntry("entry7", "example.com/entry7/1", "example.com/entry7/2", ""),
185 CreateEntry("entry8", "example.com/entry8/1", "", ""),
186 CreateEntry("entry9", "example.com/entry9/all", "", ""), };
188 return entries[id % 9];
193 class DomDistillerStoreTest : public testing::Test {
195 virtual void SetUp() {
196 base::FilePath db_dir_ = base::FilePath(FILE_PATH_LITERAL("/fake/path"));
199 store_model_.clear();
203 virtual void TearDown() {
206 fake_sync_processor_ = NULL;
209 // Creates a simple DomDistillerStore initialized with |store_model_| and
210 // with a FakeDB backed by |db_model_|.
212 fake_db_ = new FakeDB(&db_model_);
213 store_.reset(new DomDistillerStore(
214 scoped_ptr<DomDistillerDatabaseInterface>(fake_db_),
215 EntryMapToList(store_model_),
219 void StartSyncing() {
220 fake_sync_processor_ = new FakeSyncChangeProcessor(&sync_model_);
222 store_->MergeDataAndStartSyncing(
223 kDomDistillerModelType,
224 SyncDataFromEntryMap(sync_model_),
225 make_scoped_ptr<SyncChangeProcessor>(fake_sync_processor_),
226 scoped_ptr<SyncErrorFactory>(new FakeSyncErrorFactory()));
230 SyncData CreateSyncData(const ArticleEntry& entry) {
231 EntitySpecifics specifics = SpecificsFromEntry(entry);
232 return SyncData::CreateRemoteData(
233 next_sync_id_++, specifics, Time::UnixEpoch());
236 SyncDataList SyncDataFromEntryMap(const EntryMap& model) {
238 for (EntryMap::const_iterator it = model.begin(); it != model.end(); ++it) {
239 data.push_back(CreateSyncData(it->second));
245 EntryMap sync_model_;
246 EntryMap store_model_;
248 scoped_ptr<DomDistillerStore> store_;
250 // Both owned by |store_|.
252 FakeSyncChangeProcessor* fake_sync_processor_;
256 base::FilePath db_dir_;
259 AssertionResult AreEntriesEqual(const EntryVector& entries,
260 EntryMap expected_entries) {
261 if (entries.size() != expected_entries.size())
262 return AssertionFailure() << "Expected " << expected_entries.size()
263 << " entries but found " << entries.size();
265 for (EntryVector::const_iterator it = entries.begin(); it != entries.end();
267 EntryMap::iterator expected_it = expected_entries.find(it->entry_id());
268 if (expected_it == expected_entries.end()) {
269 return AssertionFailure() << "Found unexpected entry with id <"
270 << it->entry_id() << ">";
272 if (!AreEntriesEqual(expected_it->second, *it)) {
273 return AssertionFailure() << "Mismatched entry with id <"
274 << it->entry_id() << ">";
276 expected_entries.erase(expected_it);
278 return AssertionSuccess();
281 AssertionResult AreEntryMapsEqual(const EntryMap& left, const EntryMap& right) {
283 for (EntryMap::const_iterator it = left.begin(); it != left.end(); ++it) {
284 entries.push_back(it->second);
286 return AreEntriesEqual(entries, right);
289 TEST_F(DomDistillerStoreTest, TestDatabaseLoad) {
290 AddEntry(GetSampleEntry(0), &db_model_);
291 AddEntry(GetSampleEntry(1), &db_model_);
292 AddEntry(GetSampleEntry(2), &db_model_);
296 fake_db_->InitCallback(true);
297 EXPECT_EQ(fake_db_->GetDirectory(), db_dir_);
299 fake_db_->LoadCallback(true);
300 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
303 TEST_F(DomDistillerStoreTest, TestDatabaseLoadMerge) {
304 AddEntry(GetSampleEntry(0), &db_model_);
305 AddEntry(GetSampleEntry(1), &db_model_);
306 AddEntry(GetSampleEntry(2), &db_model_);
308 AddEntry(GetSampleEntry(2), &store_model_);
309 AddEntry(GetSampleEntry(3), &store_model_);
310 AddEntry(GetSampleEntry(4), &store_model_);
312 EntryMap expected_model(db_model_);
313 AddEntry(GetSampleEntry(3), &expected_model);
314 AddEntry(GetSampleEntry(4), &expected_model);
317 fake_db_->InitCallback(true);
318 fake_db_->LoadCallback(true);
320 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
321 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
324 TEST_F(DomDistillerStoreTest, TestAddEntry) {
326 fake_db_->InitCallback(true);
327 fake_db_->LoadCallback(true);
329 EXPECT_TRUE(store_->GetEntries().empty());
330 EXPECT_TRUE(db_model_.empty());
332 store_->AddEntry(GetSampleEntry(0));
334 EntryMap expected_model;
335 AddEntry(GetSampleEntry(0), &expected_model);
337 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
338 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
341 TEST_F(DomDistillerStoreTest, TestSyncMergeWithEmptyDatabase) {
342 AddEntry(GetSampleEntry(0), &sync_model_);
343 AddEntry(GetSampleEntry(1), &sync_model_);
344 AddEntry(GetSampleEntry(2), &sync_model_);
347 fake_db_->InitCallback(true);
348 fake_db_->LoadCallback(true);
352 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), sync_model_));
353 EXPECT_TRUE(AreEntryMapsEqual(db_model_, sync_model_));
356 TEST_F(DomDistillerStoreTest, TestSyncMergeAfterDatabaseLoad) {
357 AddEntry(GetSampleEntry(0), &db_model_);
358 AddEntry(GetSampleEntry(1), &db_model_);
359 AddEntry(GetSampleEntry(2), &db_model_);
361 AddEntry(GetSampleEntry(2), &sync_model_);
362 AddEntry(GetSampleEntry(3), &sync_model_);
363 AddEntry(GetSampleEntry(4), &sync_model_);
365 EntryMap expected_model(db_model_);
366 AddEntry(GetSampleEntry(3), &expected_model);
367 AddEntry(GetSampleEntry(4), &expected_model);
370 fake_db_->InitCallback(true);
371 fake_db_->LoadCallback(true);
373 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), db_model_));
377 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
378 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
379 EXPECT_TRUE(AreEntryMapsEqual(sync_model_, expected_model));
382 TEST_F(DomDistillerStoreTest, TestGetAllSyncData) {
383 AddEntry(GetSampleEntry(0), &db_model_);
384 AddEntry(GetSampleEntry(1), &db_model_);
385 AddEntry(GetSampleEntry(2), &db_model_);
387 AddEntry(GetSampleEntry(2), &sync_model_);
388 AddEntry(GetSampleEntry(3), &sync_model_);
389 AddEntry(GetSampleEntry(4), &sync_model_);
391 EntryMap expected_model(db_model_);
392 AddEntry(GetSampleEntry(3), &expected_model);
393 AddEntry(GetSampleEntry(4), &expected_model);
397 fake_db_->InitCallback(true);
398 fake_db_->LoadCallback(true);
402 SyncDataList data = store_->GetAllSyncData(kDomDistillerModelType);
404 for (SyncDataList::iterator it = data.begin(); it != data.end(); ++it) {
405 entries.push_back(EntryFromSpecifics(it->GetSpecifics()));
407 EXPECT_TRUE(AreEntriesEqual(entries, expected_model));
410 TEST_F(DomDistillerStoreTest, TestProcessSyncChanges) {
411 AddEntry(GetSampleEntry(0), &db_model_);
412 AddEntry(GetSampleEntry(1), &db_model_);
413 sync_model_ = db_model_;
415 EntryMap expected_model(db_model_);
416 AddEntry(GetSampleEntry(2), &expected_model);
417 AddEntry(GetSampleEntry(3), &expected_model);
421 fake_db_->InitCallback(true);
422 fake_db_->LoadCallback(true);
426 SyncChangeList changes;
427 changes.push_back(SyncChange(
428 FROM_HERE, SyncChange::ACTION_ADD, CreateSyncData(GetSampleEntry(2))));
429 changes.push_back(SyncChange(
430 FROM_HERE, SyncChange::ACTION_ADD, CreateSyncData(GetSampleEntry(3))));
432 store_->ProcessSyncChanges(FROM_HERE, changes);
434 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
435 EXPECT_TRUE(AreEntryMapsEqual(db_model_, expected_model));
438 TEST_F(DomDistillerStoreTest, TestSyncMergeWithSecondDomDistillerStore) {
439 AddEntry(GetSampleEntry(0), &db_model_);
440 AddEntry(GetSampleEntry(1), &db_model_);
441 AddEntry(GetSampleEntry(2), &db_model_);
443 EntryMap other_db_model;
444 AddEntry(GetSampleEntry(2), &other_db_model);
445 AddEntry(GetSampleEntry(3), &other_db_model);
446 AddEntry(GetSampleEntry(4), &other_db_model);
448 EntryMap expected_model(db_model_);
449 AddEntry(GetSampleEntry(3), &expected_model);
450 AddEntry(GetSampleEntry(4), &expected_model);
454 fake_db_->InitCallback(true);
455 fake_db_->LoadCallback(true);
457 FakeDB* other_fake_db = new FakeDB(&other_db_model);
458 scoped_ptr<DomDistillerStore> owned_other_store(new DomDistillerStore(
459 scoped_ptr<DomDistillerDatabaseInterface>(other_fake_db),
460 std::vector<ArticleEntry>(),
461 base::FilePath(FILE_PATH_LITERAL("/fake/other/path"))));
462 DomDistillerStore* other_store = owned_other_store.get();
463 other_fake_db->InitCallback(true);
464 other_fake_db->LoadCallback(true);
466 EXPECT_FALSE(AreEntriesEqual(store_->GetEntries(), expected_model));
467 EXPECT_FALSE(AreEntriesEqual(other_store->GetEntries(), expected_model));
468 ASSERT_TRUE(AreEntriesEqual(other_store->GetEntries(), other_db_model));
470 FakeSyncErrorFactory* other_error_factory = new FakeSyncErrorFactory();
471 store_->MergeDataAndStartSyncing(
472 kDomDistillerModelType,
473 SyncDataFromEntryMap(other_db_model),
474 owned_other_store.PassAs<SyncChangeProcessor>(),
475 make_scoped_ptr<SyncErrorFactory>(other_error_factory));
477 EXPECT_TRUE(AreEntriesEqual(store_->GetEntries(), expected_model));
478 EXPECT_TRUE(AreEntriesEqual(other_store->GetEntries(), expected_model));
481 } // namespace dom_distiller