1 // Copyright 2017 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.
4 
5 #include "components/feature_engagement/internal/persistent_availability_store.h"
6 
7 #include <stdint.h>
8 
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14 
15 #include "base/bind.h"
16 #include "base/callback.h"
17 #include "base/macros.h"
18 #include "base/optional.h"
19 #include "base/test/scoped_feature_list.h"
20 #include "components/feature_engagement/internal/proto/availability.pb.h"
21 #include "components/feature_engagement/public/feature_list.h"
22 #include "components/leveldb_proto/public/proto_database.h"
23 #include "components/leveldb_proto/testing/fake_db.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 
26 namespace feature_engagement {
27 
28 namespace {
29 const base::Feature kPersistentTestFeatureFoo{
30     "test_foo", base::FEATURE_DISABLED_BY_DEFAULT};
31 const base::Feature kPersistentTestFeatureBar{
32     "test_bar", base::FEATURE_DISABLED_BY_DEFAULT};
33 const base::Feature kPersistentTestFeatureQux{
34     "test_qux", base::FEATURE_DISABLED_BY_DEFAULT};
35 const base::Feature kPersistentTestFeatureNop{
36     "test_nop", base::FEATURE_DISABLED_BY_DEFAULT};
37 
CreateAvailability(const base::Feature & feature,uint32_t day)38 Availability CreateAvailability(const base::Feature& feature, uint32_t day) {
39   Availability availability;
40   availability.set_feature_name(feature.name);
41   availability.set_day(day);
42   return availability;
43 }
44 
45 class PersistentAvailabilityStoreTest : public testing::Test {
46  public:
PersistentAvailabilityStoreTest()47   PersistentAvailabilityStoreTest()
48       : db_(nullptr),
49         storage_dir_(FILE_PATH_LITERAL("/persistent/store/lalala")) {
50     load_callback_ = base::Bind(&PersistentAvailabilityStoreTest::LoadCallback,
51                                 base::Unretained(this));
52   }
53 
54   ~PersistentAvailabilityStoreTest() override = default;
55 
56   // Creates a DB and stores off a pointer to it as a member.
CreateDB()57   std::unique_ptr<leveldb_proto::test::FakeDB<Availability>> CreateDB() {
58     auto db = std::make_unique<leveldb_proto::test::FakeDB<Availability>>(
59         &db_availabilities_);
60     db_ = db.get();
61     return db;
62   }
63 
LoadCallback(bool success,std::unique_ptr<std::map<std::string,uint32_t>> availabilities)64   void LoadCallback(
65       bool success,
66       std::unique_ptr<std::map<std::string, uint32_t>> availabilities) {
67     load_successful_ = success;
68     load_results_ = std::move(availabilities);
69   }
70 
71  protected:
72   base::test::ScopedFeatureList scoped_feature_list_;
73 
74   // The end result of the store pipeline.
75   PersistentAvailabilityStore::OnLoadedCallback load_callback_;
76 
77   // Callback results.
78   base::Optional<bool> load_successful_;
79   std::unique_ptr<std::map<std::string, uint32_t>> load_results_;
80 
81   // |db_availabilities_| is used during creation of the FakeDB in CreateDB(),
82   // to simplify what the DB has stored.
83   std::map<std::string, Availability> db_availabilities_;
84 
85   // The database that is in use.
86   leveldb_proto::test::FakeDB<Availability>* db_;
87 
88   // Constant test data.
89   base::FilePath storage_dir_;
90 
91  private:
92   DISALLOW_COPY_AND_ASSIGN(PersistentAvailabilityStoreTest);
93 };
94 
95 }  // namespace
96 
TEST_F(PersistentAvailabilityStoreTest,InitFail)97 TEST_F(PersistentAvailabilityStoreTest, InitFail) {
98   PersistentAvailabilityStore::LoadAndUpdateStore(
99       storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
100       14u);
101 
102   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
103 
104   EXPECT_TRUE(load_successful_.has_value());
105   EXPECT_FALSE(load_successful_.value());
106   EXPECT_EQ(0u, load_results_->size());
107   EXPECT_EQ(0u, db_availabilities_.size());
108 }
109 
TEST_F(PersistentAvailabilityStoreTest,LoadFail)110 TEST_F(PersistentAvailabilityStoreTest, LoadFail) {
111   PersistentAvailabilityStore::LoadAndUpdateStore(
112       storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
113       14u);
114 
115   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
116   EXPECT_FALSE(load_successful_.has_value());
117 
118   db_->LoadCallback(false);
119 
120   EXPECT_TRUE(load_successful_.has_value());
121   EXPECT_FALSE(load_successful_.value());
122   EXPECT_EQ(0u, load_results_->size());
123   EXPECT_EQ(0u, db_availabilities_.size());
124 }
125 
TEST_F(PersistentAvailabilityStoreTest,EmptyDBEmptyFeatureFilterUpdateFailed)126 TEST_F(PersistentAvailabilityStoreTest, EmptyDBEmptyFeatureFilterUpdateFailed) {
127   PersistentAvailabilityStore::LoadAndUpdateStore(
128       storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
129       14u);
130 
131   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
132   EXPECT_FALSE(load_successful_.has_value());
133 
134   db_->LoadCallback(true);
135   EXPECT_FALSE(load_successful_.has_value());
136 
137   db_->UpdateCallback(false);
138 
139   EXPECT_TRUE(load_successful_.has_value());
140   EXPECT_FALSE(load_successful_.value());
141   EXPECT_EQ(0u, load_results_->size());
142   EXPECT_EQ(0u, db_availabilities_.size());
143 }
144 
TEST_F(PersistentAvailabilityStoreTest,EmptyDBEmptyFeatureFilterUpdateOK)145 TEST_F(PersistentAvailabilityStoreTest, EmptyDBEmptyFeatureFilterUpdateOK) {
146   PersistentAvailabilityStore::LoadAndUpdateStore(
147       storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
148       14u);
149 
150   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
151   EXPECT_FALSE(load_successful_.has_value());
152 
153   db_->LoadCallback(true);
154   EXPECT_FALSE(load_successful_.has_value());
155 
156   db_->UpdateCallback(true);
157 
158   EXPECT_TRUE(load_successful_.has_value());
159   EXPECT_TRUE(load_successful_.value());
160   EXPECT_EQ(0u, load_results_->size());
161   EXPECT_EQ(0u, db_availabilities_.size());
162 }
163 
TEST_F(PersistentAvailabilityStoreTest,AllNewFeatures)164 TEST_F(PersistentAvailabilityStoreTest, AllNewFeatures) {
165   scoped_feature_list_.InitWithFeatures(
166       {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
167       {kPersistentTestFeatureQux});
168 
169   FeatureVector feature_filter;
170   feature_filter.push_back(&kPersistentTestFeatureFoo);  // Enabled. Not in DB.
171   feature_filter.push_back(&kPersistentTestFeatureBar);  // Enabled. Not in DB.
172   feature_filter.push_back(&kPersistentTestFeatureQux);  // Disabled. Not in DB.
173 
174   PersistentAvailabilityStore::LoadAndUpdateStore(
175       storage_dir_, CreateDB(), feature_filter, std::move(load_callback_), 14u);
176 
177   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
178   EXPECT_FALSE(load_successful_.has_value());
179 
180   db_->LoadCallback(true);
181   EXPECT_FALSE(load_successful_.has_value());
182 
183   db_->UpdateCallback(true);
184 
185   EXPECT_TRUE(load_successful_.has_value());
186   EXPECT_TRUE(load_successful_.value());
187   ASSERT_EQ(2u, load_results_->size());
188   ASSERT_EQ(2u, db_availabilities_.size());
189 
190   ASSERT_TRUE(load_results_->find(kPersistentTestFeatureFoo.name) !=
191               load_results_->end());
192   EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureFoo.name]);
193   ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureFoo.name) !=
194               db_availabilities_.end());
195   EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureFoo.name].day());
196 
197   ASSERT_TRUE(load_results_->find(kPersistentTestFeatureBar.name) !=
198               load_results_->end());
199   EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureBar.name]);
200   ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureBar.name) !=
201               db_availabilities_.end());
202   EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureBar.name].day());
203 }
204 
TEST_F(PersistentAvailabilityStoreTest,TestAllFilterCombinations)205 TEST_F(PersistentAvailabilityStoreTest, TestAllFilterCombinations) {
206   scoped_feature_list_.InitWithFeatures(
207       {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
208       {kPersistentTestFeatureQux, kPersistentTestFeatureNop});
209 
210   FeatureVector feature_filter;
211   feature_filter.push_back(&kPersistentTestFeatureFoo);  // Enabled. Not in DB.
212   feature_filter.push_back(&kPersistentTestFeatureBar);  // Enabled. In DB.
213   feature_filter.push_back(&kPersistentTestFeatureQux);  // Disabled. Not in DB.
214   feature_filter.push_back(&kPersistentTestFeatureNop);  // Disabled. In DB.
215 
216   db_availabilities_[kPersistentTestFeatureBar.name] =
217       CreateAvailability(kPersistentTestFeatureBar, 10u);
218   db_availabilities_[kPersistentTestFeatureNop.name] =
219       CreateAvailability(kPersistentTestFeatureNop, 8u);
220 
221   PersistentAvailabilityStore::LoadAndUpdateStore(
222       storage_dir_, CreateDB(), feature_filter, std::move(load_callback_), 14u);
223 
224   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
225   EXPECT_FALSE(load_successful_.has_value());
226 
227   db_->LoadCallback(true);
228   EXPECT_FALSE(load_successful_.has_value());
229 
230   db_->UpdateCallback(true);
231 
232   EXPECT_TRUE(load_successful_.has_value());
233   EXPECT_TRUE(load_successful_.value());
234   ASSERT_EQ(2u, load_results_->size());
235   ASSERT_EQ(2u, db_availabilities_.size());
236 
237   ASSERT_TRUE(load_results_->find(kPersistentTestFeatureFoo.name) !=
238               load_results_->end());
239   EXPECT_EQ(14u, (*load_results_)[kPersistentTestFeatureFoo.name]);
240   ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureFoo.name) !=
241               db_availabilities_.end());
242   EXPECT_EQ(14u, db_availabilities_[kPersistentTestFeatureFoo.name].day());
243 
244   ASSERT_TRUE(load_results_->find(kPersistentTestFeatureBar.name) !=
245               load_results_->end());
246   EXPECT_EQ(10u, (*load_results_)[kPersistentTestFeatureBar.name]);
247   ASSERT_TRUE(db_availabilities_.find(kPersistentTestFeatureBar.name) !=
248               db_availabilities_.end());
249   EXPECT_EQ(10u, db_availabilities_[kPersistentTestFeatureBar.name].day());
250 }
251 
TEST_F(PersistentAvailabilityStoreTest,TestAllCombinationsEmptyFilter)252 TEST_F(PersistentAvailabilityStoreTest, TestAllCombinationsEmptyFilter) {
253   scoped_feature_list_.InitWithFeatures(
254       {kPersistentTestFeatureFoo, kPersistentTestFeatureBar},
255       {kPersistentTestFeatureQux, kPersistentTestFeatureNop});
256 
257   // Empty filter, but the following setup:
258   // kPersistentTestFeatureFoo: Enabled. Not in DB.
259   // kPersistentTestFeatureBar: Enabled. In DB.
260   // kPersistentTestFeatureQux: Disabled. Not in DB.
261   // kPersistentTestFeatureNop: Disabled. In DB.
262 
263   db_availabilities_[kPersistentTestFeatureBar.name] =
264       CreateAvailability(kPersistentTestFeatureBar, 10u);
265   db_availabilities_[kPersistentTestFeatureNop.name] =
266       CreateAvailability(kPersistentTestFeatureNop, 8u);
267 
268   PersistentAvailabilityStore::LoadAndUpdateStore(
269       storage_dir_, CreateDB(), FeatureVector(), std::move(load_callback_),
270       14u);
271 
272   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
273   EXPECT_FALSE(load_successful_.has_value());
274 
275   db_->LoadCallback(true);
276   EXPECT_FALSE(load_successful_.has_value());
277 
278   db_->UpdateCallback(true);
279 
280   EXPECT_TRUE(load_successful_.has_value());
281   EXPECT_TRUE(load_successful_.value());
282   EXPECT_EQ(0u, load_results_->size());
283   EXPECT_EQ(0u, db_availabilities_.size());
284 }
285 
286 }  // namespace feature_engagement
287