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