text: // 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_availability_store.h"
  
  #include <stdint.h>
  
  #include <map>
  #include <memory>
  #include <string>
  #include <utility>
  #include <vector>
  
  #include "base/bind.h"
  #include "base/callback.h"
  #include "base/macros.h"
  #include "base/test/scoped_feature_list.h"
  #include "components/feature_engagement/internal/proto/availability.pb.h"
  #include "components/feature_engagement/public/feature_list.h"
  #include "components/leveldb_proto/public/proto_database.h"
  #include "components/leveldb_proto/testing/fake_db.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/abseil-cpp/absl/types/optional.h"
  
  namespace feature_engagement {
  
  namespace {
  const base::Feature kPersistentTestFeatureFoo{
      "test_foo", base::FEATURE_DISABLED_BY_DEFAULT};
  const base::Feature kPersistentTestFeatureBar{
      "test_bar", base::FEATURE_DISABLED_BY_DEFAULT};
  const base::Feature kPersistentTestFeatureQux{
      "test_qux", base::FEATURE_DISABLED_BY_DEFAULT};
  const base::Feature kPersistentTestFeatureNop{
      "test_nop", base::FEATURE_DISABLED_BY_DEFAULT};
  
  Availability CreateAvailability(const base::Feature& feature, uint32_t day) {
    Availability availability;
    availability.set_feature_name(feature.name);
    availability.set_day(day);
    return availability;
  }
  
  class PersistentAvailabilityStoreTest : public testing::Test {
   public:
    PersistentAvailabilityStoreTest()
        : db_(nullptr),
          storage_dir_(FILE_PATH_LITERAL("/persistent/store/lalala")) {
      load_callback_ = base::BindOnce(
          &PersistentAvailabilityStoreTest::LoadCallback, base::Unretained(this));
    }
  
    ~PersistentAvailabilityStoreTest() override = default;
  
    // Creates a DB and stores off a pointer to it as a member.
    std::unique_ptr<leveldb_proto::test::FakeDB<Availability>> CreateDB() {
      auto db = std::make_unique<leveldb_proto::test::FakeDB<Availability>>(
          &db_availabilities_);
      db_ = db.get();
      return db;
    }
  
    void LoadCallback(
        bool success,
        std::unique_ptr<std::map<std::string, uint32_t>> availabilities) {
      load_successful_ = success;
      load_results_ = std::move(availabilities);
    }
  
   protected:
    base::test::ScopedFeatureList scoped_feature_list_;
  
    // The end result of the store pipeline.
    PersistentAvailabilityStore::OnLoadedCallback load_callback_;
  
    // Callback results.
    absl::optional<bool> load_successful_;
    std::unique_ptr<std::map<std::string, uint32_t>> load_results_;
  
    // |db_availabilities_| is used during creation of the FakeDB in CreateDB(),
    // to simplify what the DB has stored.
    std::map<std::string, Availability> db_availabilities_;
  
    // The database that is in use.
    leveldb_proto::test::FakeDB<Availability>* db_;
  
    // Constant test data.
    base::FilePath storage_dir_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(PersistentAvailabilityStoreTest);
  };
  
  }  // namespace
  
  TEST_F(PersistentAvailabilityStoreTest, InitFail) {
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
        14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_FALSE(load_successful_.value());
    EXPECT_EQ(0u, load_results_->size());
    EXPECT_EQ(0u, db_availabilities_.size());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, LoadFail) {
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
        14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(false);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_FALSE(load_successful_.value());
    EXPECT_EQ(0u, load_results_->size());
    EXPECT_EQ(0u, db_availabilities_.size());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, EmptyDBEmptyFeatureFilterUpdateFailed) {
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
        14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(true);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->UpdateCallback(false);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_FALSE(load_successful_.value());
    EXPECT_EQ(0u, load_results_->size());
    EXPECT_EQ(0u, db_availabilities_.size());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, EmptyDBEmptyFeatureFilterUpdateOK) {
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
        14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(true);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->UpdateCallback(true);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_TRUE(load_successful_.value());
    EXPECT_EQ(0u, load_results_->size());
    EXPECT_EQ(0u, db_availabilities_.size());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, AllNewFeatures) {
    scoped_feature_list_.InitWithFeatures(
        {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
        {kPersistentTestFeatureQux});
  
    FeatureVector feature_filter;
    feature_filter.push_back(&kPersistentTestFeatureFoo);  // Enabled. Not in DB.
    feature_filter.push_back(&kPersistentTestFeatureBar);  // Enabled. Not in DB.
    feature_filter.push_back(&kPersistentTestFeatureQux);  // Disabled. Not in DB.
  
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), feature_filter, std::move(load_callback_), 14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(true);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->UpdateCallback(true);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_TRUE(load_successful_.value());
    ASSERT_EQ(2u, load_results_->size());
    ASSERT_EQ(2u, db_availabilities_.size());
  
    ASSERT_TRUE(load_results_->find(kPersistentTestFeatureFoo.name) !=
                load_results_->end());
    EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureFoo.name]);
    ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureFoo.name) !=
                db_availabilities_.end());
    EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureFoo.name].day());
  
    ASSERT_TRUE(load_results_->find(kPersistentTestFeatureBar.name) !=
                load_results_->end());
    EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureBar.name]);
    ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureBar.name) !=
                db_availabilities_.end());
    EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureBar.name].day());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, TestAllFilterCombinations) {
    scoped_feature_list_.InitWithFeatures(
        {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
        {kPersistentTestFeatureQux, kPersistentTestFeatureNop});
  
    FeatureVector feature_filter;
    feature_filter.push_back(&kPersistentTestFeatureFoo);  // Enabled. Not in DB.
    feature_filter.push_back(&kPersistentTestFeatureBar);  // Enabled. In DB.
    feature_filter.push_back(&kPersistentTestFeatureQux);  // Disabled. Not in DB.
    feature_filter.push_back(&kPersistentTestFeatureNop);  // Disabled. In DB.
  
    db_availabilities_[kPersistentTestFeatureBar.name] =
        CreateAvailability(kPersistentTestFeatureBar, 10u);
    db_availabilities_[kPersistentTestFeatureNop.name] =
        CreateAvailability(kPersistentTestFeatureNop, 8u);
  
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), feature_filter, std::move(load_callback_), 14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(true);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->UpdateCallback(true);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_TRUE(load_successful_.value());
    ASSERT_EQ(2u, load_results_->size());
    ASSERT_EQ(2u, db_availabilities_.size());
  
    ASSERT_TRUE(load_results_->find(kPersistentTestFeatureFoo.name) !=
                load_results_->end());
    EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureFoo.name]);
    ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureFoo.name) !=
                db_availabilities_.end());
    EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureFoo.name].day());
  
    ASSERT_TRUE(load_results_->find(kPersistentTestFeatureBar.name) !=
                load_results_->end());
    EXPECT_EQ(10u, (*load_results_)[kPersistentTestFeatureBar.name]);
    ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureBar.name) !=
                db_availabilities_.end());
    EXPECT_EQ(10u, db_availabilities_[kPersistentTestFeatureBar.name].day());
  }
  
  TEST_F(PersistentAvailabilityStoreTest, TestAllCombinationsEmptyFilter) {
    scoped_feature_list_.InitWithFeatures(
        {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
        {kPersistentTestFeatureQux, kPersistentTestFeatureNop});
  
    // Empty filter, but the following setup:
    // kPersistentTestFeatureFoo: Enabled. Not in DB.
    // kPersistentTestFeatureBar: Enabled. In DB.
    // kPersistentTestFeatureQux: Disabled. Not in DB.
    // kPersistentTestFeatureNop: Disabled. In DB.
  
    db_availabilities_[kPersistentTestFeatureBar.name] =
        CreateAvailability(kPersistentTestFeatureBar, 10u);
    db_availabilities_[kPersistentTestFeatureNop.name] =
        CreateAvailability(kPersistentTestFeatureNop, 8u);
  
    PersistentAvailabilityStore::LoadAndUpdateStore(
        storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
        14u);
  
    db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->LoadCallback(true);
    EXPECT_FALSE(load_successful_.has_value());
  
    db_->UpdateCallback(true);
  
    EXPECT_TRUE(load_successful_.has_value());
    EXPECT_TRUE(load_successful_.value());
    EXPECT_EQ(0u, load_results_->size());
    EXPECT_EQ(0u, db_availabilities_.size());
  }
  
  }  // namespace feature_engagement
  