| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/feature_engagement/internal/persistent_event_store.h" |
| |
| #include <map> |
| |
| #include "base/files/file_path.h" |
| #include "base/optional.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "components/feature_engagement/internal/proto/feature_event.pb.h" |
| #include "components/feature_engagement/internal/stats.h" |
| #include "components/feature_engagement/internal/test/event_util.h" |
| #include "components/leveldb_proto/proto_database.h" |
| #include "components/leveldb_proto/testing/fake_db.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace feature_engagement { |
| |
| namespace { |
| |
| void VerifyEventsInListAndMap(const std::map<std::string, Event>& map, |
| const std::vector<Event>& list) { |
| ASSERT_EQ(map.size(), list.size()); |
| |
| for (const auto& event : list) { |
| const auto& it = map.find(event.name()); |
| ASSERT_NE(map.end(), it); |
| test::VerifyEventsEqual(&event, &it->second); |
| } |
| } |
| |
| class PersistentEventStoreTest : public ::testing::Test { |
| public: |
| PersistentEventStoreTest() : db_(nullptr) { |
| load_callback_ = base::Bind(&PersistentEventStoreTest::LoadCallback, |
| base::Unretained(this)); |
| } |
| |
| void TearDown() override { |
| db_events_.clear(); |
| db_ = nullptr; |
| store_.reset(); |
| } |
| |
| protected: |
| void SetUpDB() { |
| DCHECK(!db_); |
| DCHECK(!store_); |
| |
| auto db = std::make_unique<leveldb_proto::test::FakeDB<Event>>(&db_events_); |
| db_ = db.get(); |
| store_.reset(new PersistentEventStore(std::move(db))); |
| } |
| |
| void LoadCallback(bool success, std::unique_ptr<std::vector<Event>> events) { |
| load_successful_ = success; |
| load_results_ = std::move(events); |
| } |
| |
| // Callback results. |
| base::Optional<bool> load_successful_; |
| std::unique_ptr<std::vector<Event>> load_results_; |
| |
| EventStore::OnLoadedCallback load_callback_; |
| std::map<std::string, Event> db_events_; |
| leveldb_proto::test::FakeDB<Event>* db_; |
| std::unique_ptr<EventStore> store_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(PersistentEventStoreTest, SuccessfulInitAndLoadEmptyStore) { |
| SetUpDB(); |
| |
| base::HistogramTester histogram_tester; |
| |
| store_->Load(load_callback_); |
| // The initialize should not trigger a response to the callback. |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| EXPECT_FALSE(load_successful_.has_value()); |
| |
| // The load should trigger a response to the callback. |
| db_->LoadCallback(true); |
| EXPECT_TRUE(load_successful_.value()); |
| |
| // Validate that we have no entries. |
| EXPECT_NE(nullptr, load_results_); |
| EXPECT_TRUE(load_results_->empty()); |
| |
| // Verify histograms. |
| std::string suffix = |
| stats::ToDbHistogramSuffix(stats::StoreType::EVENTS_STORE); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Init." + suffix, 1, 1); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Load." + suffix, 1, 1); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.TotalEvents", 0, 1); |
| } |
| |
| TEST_F(PersistentEventStoreTest, SuccessfulInitAndLoadWithEvents) { |
| // Populate fake Event entries. |
| Event event1; |
| event1.set_name("event1"); |
| test::SetEventCountForDay(&event1, 1, 1); |
| |
| Event event2; |
| event2.set_name("event2"); |
| test::SetEventCountForDay(&event2, 1, 3); |
| test::SetEventCountForDay(&event2, 2, 5); |
| |
| db_events_.insert(std::pair<std::string, Event>(event1.name(), event1)); |
| db_events_.insert(std::pair<std::string, Event>(event2.name(), event2)); |
| |
| SetUpDB(); |
| |
| base::HistogramTester histogram_tester; |
| |
| // The initialize should not trigger a response to the callback. |
| store_->Load(load_callback_); |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| EXPECT_FALSE(load_successful_.has_value()); |
| |
| // The load should trigger a response to the callback. |
| db_->LoadCallback(true); |
| EXPECT_TRUE(load_successful_.value()); |
| EXPECT_NE(nullptr, load_results_); |
| |
| // Validate that we have the two events that we expect. |
| VerifyEventsInListAndMap(db_events_, *load_results_); |
| |
| // Verify histograms. |
| std::string suffix = |
| stats::ToDbHistogramSuffix(stats::StoreType::EVENTS_STORE); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Init." + suffix, 1, 1); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Load." + suffix, 1, 1); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.TotalEvents", 3, 1); |
| } |
| |
| TEST_F(PersistentEventStoreTest, SuccessfulInitBadLoad) { |
| base::HistogramTester histogram_tester; |
| SetUpDB(); |
| |
| store_->Load(load_callback_); |
| |
| // The initialize should not trigger a response to the callback. |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| EXPECT_FALSE(load_successful_.has_value()); |
| |
| // The load will fail and should trigger the callback. |
| db_->LoadCallback(false); |
| EXPECT_FALSE(load_successful_.value()); |
| EXPECT_FALSE(store_->IsReady()); |
| |
| // Histograms. |
| std::string suffix = |
| stats::ToDbHistogramSuffix(stats::StoreType::EVENTS_STORE); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Init." + suffix, 1, 1); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Load." + suffix, 0, 1); |
| histogram_tester.ExpectTotalCount("InProductHelp.Db.TotalEvents", 0); |
| } |
| |
| TEST_F(PersistentEventStoreTest, BadInit) { |
| base::HistogramTester histogram_tester; |
| SetUpDB(); |
| |
| store_->Load(load_callback_); |
| |
| // The initialize will fail and should trigger the callback. |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError); |
| EXPECT_FALSE(load_successful_.value()); |
| EXPECT_FALSE(store_->IsReady()); |
| |
| // Histograms. |
| std::string suffix = |
| stats::ToDbHistogramSuffix(stats::StoreType::EVENTS_STORE); |
| histogram_tester.ExpectBucketCount("InProductHelp.Db.Init." + suffix, 0, 1); |
| histogram_tester.ExpectTotalCount("InProductHelp.Db.Load." + suffix, 0); |
| histogram_tester.ExpectTotalCount("InProductHelp.Db.TotalEvents", 0); |
| } |
| |
| TEST_F(PersistentEventStoreTest, IsReady) { |
| SetUpDB(); |
| EXPECT_FALSE(store_->IsReady()); |
| |
| store_->Load(load_callback_); |
| EXPECT_FALSE(store_->IsReady()); |
| |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| EXPECT_FALSE(store_->IsReady()); |
| |
| db_->LoadCallback(true); |
| EXPECT_TRUE(store_->IsReady()); |
| } |
| |
| TEST_F(PersistentEventStoreTest, WriteEvent) { |
| SetUpDB(); |
| |
| store_->Load(load_callback_); |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| db_->LoadCallback(true); |
| |
| Event event; |
| event.set_name("event"); |
| test::SetEventCountForDay(&event, 1, 2); |
| |
| store_->WriteEvent(event); |
| db_->UpdateCallback(true); |
| |
| EXPECT_EQ(1U, db_events_.size()); |
| |
| const auto& it = db_events_.find("event"); |
| EXPECT_NE(db_events_.end(), it); |
| test::VerifyEventsEqual(&event, &it->second); |
| } |
| |
| TEST_F(PersistentEventStoreTest, WriteAndDeleteEvent) { |
| SetUpDB(); |
| |
| store_->Load(load_callback_); |
| db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK); |
| db_->LoadCallback(true); |
| |
| Event event; |
| event.set_name("event"); |
| test::SetEventCountForDay(&event, 1, 2); |
| |
| store_->WriteEvent(event); |
| db_->UpdateCallback(true); |
| |
| EXPECT_EQ(1U, db_events_.size()); |
| |
| store_->DeleteEvent("event"); |
| db_->UpdateCallback(true); |
| |
| const auto& it = db_events_.find("event"); |
| EXPECT_EQ(db_events_.end(), it); |
| } |
| |
| } // namespace feature_engagement |