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/download/internal/background_service/download_store.h"
6 
7 #include <algorithm>
8 #include <memory>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/guid.h"
13 #include "base/optional.h"
14 #include "components/download/internal/background_service/entry.h"
15 #include "components/download/internal/background_service/proto/entry.pb.h"
16 #include "components/download/internal/background_service/proto_conversions.h"
17 #include "components/download/internal/background_service/test/entry_utils.h"
18 #include "components/leveldb_proto/testing/fake_db.h"
19 #include "testing/gmock/include/gmock/gmock.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 
22 using testing::_;
23 
24 namespace download {
25 
26 class DownloadStoreTest : public testing::Test {
27  public:
DownloadStoreTest()28   DownloadStoreTest() : db_(nullptr) {}
29 
30   ~DownloadStoreTest() override = default;
31 
CreateDatabase()32   void CreateDatabase() {
33     auto db = std::make_unique<leveldb_proto::test::FakeDB<protodb::Entry>>(
34         &db_entries_);
35     db_ = db.get();
36     store_.reset(new DownloadStore(std::move(db)));
37   }
38 
InitCallback(std::vector<Entry> * loaded_entries,bool success,std::unique_ptr<std::vector<Entry>> entries)39   void InitCallback(std::vector<Entry>* loaded_entries,
40                     bool success,
41                     std::unique_ptr<std::vector<Entry>> entries) {
42     loaded_entries->swap(*entries);
43   }
44 
LoadCallback(std::vector<protodb::Entry> * loaded_entries,bool success,std::unique_ptr<std::vector<protodb::Entry>> entries)45   void LoadCallback(std::vector<protodb::Entry>* loaded_entries,
46                     bool success,
47                     std::unique_ptr<std::vector<protodb::Entry>> entries) {
48     loaded_entries->swap(*entries);
49   }
50 
RecoverCallback(bool success)51   void RecoverCallback(bool success) { hard_recover_result_ = success; }
52 
53   MOCK_METHOD1(StoreCallback, void(bool));
54 
PrepopulateSampleEntries()55   void PrepopulateSampleEntries() {
56     Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
57     Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
58     db_entries_.insert(
59         std::make_pair(item1.guid, ProtoConversions::EntryToProto(item1)));
60     db_entries_.insert(
61         std::make_pair(item2.guid, ProtoConversions::EntryToProto(item2)));
62   }
63 
64  protected:
65   std::map<std::string, protodb::Entry> db_entries_;
66   leveldb_proto::test::FakeDB<protodb::Entry>* db_;
67   std::unique_ptr<DownloadStore> store_;
68   base::Optional<bool> hard_recover_result_;
69 
70   DISALLOW_COPY_AND_ASSIGN(DownloadStoreTest);
71 };
72 
TEST_F(DownloadStoreTest,Initialize)73 TEST_F(DownloadStoreTest, Initialize) {
74   PrepopulateSampleEntries();
75   CreateDatabase();
76   ASSERT_FALSE(store_->IsInitialized());
77 
78   std::vector<Entry> preloaded_entries;
79   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
80                                     base::Unretained(this),
81                                     &preloaded_entries));
82   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
83   db_->LoadCallback(true);
84 
85   ASSERT_TRUE(store_->IsInitialized());
86   ASSERT_EQ(2u, preloaded_entries.size());
87 }
88 
TEST_F(DownloadStoreTest,HardRecover)89 TEST_F(DownloadStoreTest, HardRecover) {
90   PrepopulateSampleEntries();
91   CreateDatabase();
92   ASSERT_FALSE(store_->IsInitialized());
93 
94   std::vector<Entry> preloaded_entries;
95   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
96                                     base::Unretained(this),
97                                     &preloaded_entries));
98   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
99   db_->LoadCallback(true);
100 
101   ASSERT_TRUE(store_->IsInitialized());
102   ASSERT_EQ(2u, preloaded_entries.size());
103 
104   store_->HardRecover(base::BindOnce(&DownloadStoreTest::RecoverCallback,
105                                      base::Unretained(this)));
106 
107   ASSERT_FALSE(store_->IsInitialized());
108 
109   db_->DestroyCallback(true);
110   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
111 
112   ASSERT_TRUE(store_->IsInitialized());
113   ASSERT_TRUE(hard_recover_result_.has_value());
114   ASSERT_TRUE(hard_recover_result_.value());
115 }
116 
TEST_F(DownloadStoreTest,HardRecoverDestroyFails)117 TEST_F(DownloadStoreTest, HardRecoverDestroyFails) {
118   PrepopulateSampleEntries();
119   CreateDatabase();
120   ASSERT_FALSE(store_->IsInitialized());
121 
122   std::vector<Entry> preloaded_entries;
123   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
124                                     base::Unretained(this),
125                                     &preloaded_entries));
126   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
127   db_->LoadCallback(true);
128 
129   ASSERT_TRUE(store_->IsInitialized());
130   ASSERT_EQ(2u, preloaded_entries.size());
131 
132   store_->HardRecover(base::BindOnce(&DownloadStoreTest::RecoverCallback,
133                                      base::Unretained(this)));
134 
135   ASSERT_FALSE(store_->IsInitialized());
136 
137   db_->DestroyCallback(false);
138 
139   ASSERT_FALSE(store_->IsInitialized());
140   ASSERT_TRUE(hard_recover_result_.has_value());
141   ASSERT_FALSE(hard_recover_result_.value());
142 }
143 
TEST_F(DownloadStoreTest,HardRecoverInitFails)144 TEST_F(DownloadStoreTest, HardRecoverInitFails) {
145   PrepopulateSampleEntries();
146   CreateDatabase();
147   ASSERT_FALSE(store_->IsInitialized());
148 
149   std::vector<Entry> preloaded_entries;
150   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
151                                     base::Unretained(this),
152                                     &preloaded_entries));
153   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
154   db_->LoadCallback(true);
155 
156   ASSERT_TRUE(store_->IsInitialized());
157   ASSERT_EQ(2u, preloaded_entries.size());
158 
159   store_->HardRecover(base::BindOnce(&DownloadStoreTest::RecoverCallback,
160                                      base::Unretained(this)));
161 
162   ASSERT_FALSE(store_->IsInitialized());
163 
164   db_->DestroyCallback(true);
165   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
166 
167   ASSERT_FALSE(store_->IsInitialized());
168   ASSERT_TRUE(hard_recover_result_.has_value());
169   ASSERT_FALSE(hard_recover_result_.value());
170 }
171 
TEST_F(DownloadStoreTest,Update)172 TEST_F(DownloadStoreTest, Update) {
173   PrepopulateSampleEntries();
174   CreateDatabase();
175 
176   std::vector<Entry> preloaded_entries;
177   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
178                                     base::Unretained(this),
179                                     &preloaded_entries));
180   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
181   db_->LoadCallback(true);
182   ASSERT_TRUE(store_->IsInitialized());
183   ASSERT_EQ(2u, preloaded_entries.size());
184 
185   Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
186   Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
187   EXPECT_CALL(*this, StoreCallback(true)).Times(2);
188   store_->Update(item1, base::BindOnce(&DownloadStoreTest::StoreCallback,
189                                        base::Unretained(this)));
190   db_->UpdateCallback(true);
191   store_->Update(item2, base::BindOnce(&DownloadStoreTest::StoreCallback,
192                                        base::Unretained(this)));
193   db_->UpdateCallback(true);
194 
195   // Query the database directly and check for the entry.
196   auto protos = std::make_unique<std::vector<protodb::Entry>>();
197   db_->LoadEntries(base::BindOnce(&DownloadStoreTest::LoadCallback,
198                                   base::Unretained(this), protos.get()));
199   db_->LoadCallback(true);
200   ASSERT_EQ(4u, protos->size());
201   ASSERT_TRUE(test::CompareEntryList(
202       {preloaded_entries[0], preloaded_entries[1], item1, item2},
203       *ProtoConversions::EntryVectorFromProto(std::move(protos))));
204 }
205 
TEST_F(DownloadStoreTest,Remove)206 TEST_F(DownloadStoreTest, Remove) {
207   PrepopulateSampleEntries();
208   CreateDatabase();
209 
210   std::vector<Entry> preloaded_entries;
211   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
212                                     base::Unretained(this),
213                                     &preloaded_entries));
214   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
215   db_->LoadCallback(true);
216   ASSERT_EQ(2u, preloaded_entries.size());
217 
218   // Remove the entry.
219   EXPECT_CALL(*this, StoreCallback(true)).Times(1);
220   store_->Remove(preloaded_entries[0].guid,
221                  base::BindOnce(&DownloadStoreTest::StoreCallback,
222                                 base::Unretained(this)));
223   db_->UpdateCallback(true);
224 
225   // Query the database directly and check for the entry removed.
226   auto protos = std::make_unique<std::vector<protodb::Entry>>();
227   db_->LoadEntries(base::BindOnce(&DownloadStoreTest::LoadCallback,
228                                   base::Unretained(this), protos.get()));
229   db_->LoadCallback(true);
230   ASSERT_EQ(1u, protos->size());
231   ASSERT_TRUE(test::CompareEntryList(
232       {preloaded_entries[1]},
233       *ProtoConversions::EntryVectorFromProto(std::move(protos))));
234 }
235 
TEST_F(DownloadStoreTest,InitializeFailed)236 TEST_F(DownloadStoreTest, InitializeFailed) {
237   PrepopulateSampleEntries();
238   CreateDatabase();
239 
240   std::vector<Entry> preloaded_entries;
241   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
242                                     base::Unretained(this),
243                                     &preloaded_entries));
244   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kError);
245   ASSERT_FALSE(store_->IsInitialized());
246   ASSERT_TRUE(preloaded_entries.empty());
247 }
248 
TEST_F(DownloadStoreTest,InitialLoadFailed)249 TEST_F(DownloadStoreTest, InitialLoadFailed) {
250   PrepopulateSampleEntries();
251   CreateDatabase();
252 
253   std::vector<Entry> preloaded_entries;
254   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
255                                     base::Unretained(this),
256                                     &preloaded_entries));
257   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
258   db_->LoadCallback(false);
259   ASSERT_FALSE(store_->IsInitialized());
260   ASSERT_TRUE(preloaded_entries.empty());
261 }
262 
TEST_F(DownloadStoreTest,UnsuccessfulUpdateOrRemove)263 TEST_F(DownloadStoreTest, UnsuccessfulUpdateOrRemove) {
264   Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
265   CreateDatabase();
266 
267   std::vector<Entry> entries;
268   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
269                                     base::Unretained(this), &entries));
270   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
271   db_->LoadCallback(true);
272   ASSERT_TRUE(store_->IsInitialized());
273   ASSERT_TRUE(entries.empty());
274 
275   // Update failed.
276   EXPECT_CALL(*this, StoreCallback(false)).Times(1);
277   store_->Update(item1, base::BindOnce(&DownloadStoreTest::StoreCallback,
278                                        base::Unretained(this)));
279   db_->UpdateCallback(false);
280 
281   // Remove failed.
282   EXPECT_CALL(*this, StoreCallback(false)).Times(1);
283   store_->Remove(item1.guid, base::BindOnce(&DownloadStoreTest::StoreCallback,
284                                             base::Unretained(this)));
285   db_->UpdateCallback(false);
286 }
287 
TEST_F(DownloadStoreTest,AddThenRemove)288 TEST_F(DownloadStoreTest, AddThenRemove) {
289   CreateDatabase();
290 
291   std::vector<Entry> entries;
292   store_->Initialize(base::BindOnce(&DownloadStoreTest::InitCallback,
293                                     base::Unretained(this), &entries));
294   db_->InitStatusCallback(leveldb_proto::Enums::InitStatus::kOK);
295   db_->LoadCallback(true);
296   ASSERT_TRUE(entries.empty());
297 
298   Entry item1 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
299   Entry item2 = test::BuildEntry(DownloadClient::TEST, base::GenerateGUID());
300   EXPECT_CALL(*this, StoreCallback(true)).Times(2);
301   store_->Update(item1, base::BindOnce(&DownloadStoreTest::StoreCallback,
302                                        base::Unretained(this)));
303   db_->UpdateCallback(true);
304   store_->Update(item2, base::BindOnce(&DownloadStoreTest::StoreCallback,
305                                        base::Unretained(this)));
306   db_->UpdateCallback(true);
307 
308   // Query the database directly and check for the entry.
309   auto protos = std::make_unique<std::vector<protodb::Entry>>();
310   db_->LoadEntries(base::BindOnce(&DownloadStoreTest::LoadCallback,
311                                   base::Unretained(this), protos.get()));
312   db_->LoadCallback(true);
313   ASSERT_EQ(2u, protos->size());
314 
315   // Remove the entry.
316   EXPECT_CALL(*this, StoreCallback(true)).Times(1);
317   store_->Remove(item1.guid, base::BindOnce(&DownloadStoreTest::StoreCallback,
318                                             base::Unretained(this)));
319   db_->UpdateCallback(true);
320 
321   // Query the database directly and check for the entry removed.
322   protos->clear();
323   db_->LoadEntries(base::BindOnce(&DownloadStoreTest::LoadCallback,
324                                   base::Unretained(this), protos.get()));
325   db_->LoadCallback(true);
326   ASSERT_EQ(1u, protos->size());
327   ASSERT_TRUE(test::CompareEntryList(
328       {item2}, *ProtoConversions::EntryVectorFromProto(std::move(protos))));
329 }
330 
331 }  // namespace download
332