1 // Copyright 2018 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/feed/core/feed_content_database.h"
6
7 #include <map>
8
9 #include "base/bind.h"
10 #include "base/task/thread_pool.h"
11 #include "base/test/metrics/histogram_tester.h"
12 #include "base/test/task_environment.h"
13 #include "components/feed/core/feed_content_mutation.h"
14 #include "components/feed/core/proto/content_storage.pb.h"
15 #include "components/leveldb_proto/testing/fake_db.h"
16 #include "testing/gmock/include/gmock/gmock.h"
17 #include "testing/gtest/include/gtest/gtest.h"
18
19 using base::HistogramTester;
20 using leveldb_proto::test::FakeDB;
21 using testing::_;
22
23 namespace feed {
24
25 namespace {
26
27 const char kContentKeyPrefix[] = "ContentKey";
28 const char kContentKey1[] = "ContentKey1";
29 const char kContentKey2[] = "ContentKey2";
30 const char kContentKey3[] = "ContentKey3";
31 const char kContentData1[] = "Content Data1";
32 const char kContentData2[] = "Content Data2";
33
34 const char kUmaCommitMutationSizeHistogramName[] =
35 "ContentSuggestions.Feed.ContentStorage.CommitMutationCount";
36 const char kUmaLoadKeysTimeHistogramName[] =
37 "ContentSuggestions.Feed.ContentStorage.LoadKeysTime";
38 const char kUmaLoadTimeHistogramName[] =
39 "ContentSuggestions.Feed.ContentStorage.LoadTime";
40 const char kUmaOperationCommitTimeHistogramName[] =
41 "ContentSuggestions.Feed.ContentStorage.OperationCommitTime";
42 const char kUmaSizeHistogramName[] =
43 "ContentSuggestions.Feed.ContentStorage.Count";
44
45 } // namespace
46
47 class FeedContentDatabaseTest : public testing::Test {
48 public:
FeedContentDatabaseTest()49 FeedContentDatabaseTest() : content_db_(nullptr) {}
50
CreateDatabase(bool init_database)51 void CreateDatabase(bool init_database) {
52 // The FakeDBs are owned by |feed_db_|, so clear our pointers before
53 // resetting |feed_db_| itself.
54 content_db_ = nullptr;
55 // Explicitly destroy any existing database before creating a new one.
56 feed_db_.reset();
57
58 auto storage_db =
59 std::make_unique<FakeDB<ContentStorageProto>>(&content_db_storage_);
60
61 task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
62 {base::MayBlock(), base::TaskPriority::USER_VISIBLE});
63
64 content_db_ = storage_db.get();
65 feed_db_ = std::make_unique<FeedContentDatabase>(std::move(storage_db),
66 task_runner_);
67 if (init_database) {
68 InitStatusCallback(content_db_, leveldb_proto::Enums::InitStatus::kOK);
69 ASSERT_TRUE(db()->IsInitialized());
70 }
71 }
72
InjectContentStorageProto(const std::string & key,const std::string & data)73 void InjectContentStorageProto(const std::string& key,
74 const std::string& data) {
75 ContentStorageProto storage_proto;
76 storage_proto.set_key(key);
77 storage_proto.set_content_data(data);
78 content_db_storage_[key] = storage_proto;
79 }
80
81 // Since the FakeDB implementation doesn't run callbacks on the same task
82 // runner as the original request was made (like the real ProtoDatabase impl
83 // does), we explicitly post all callbacks onto the DB task runner here.
InitStatusCallback(FakeDB<ContentStorageProto> * storage_db,leveldb_proto::Enums::InitStatus status)84 void InitStatusCallback(FakeDB<ContentStorageProto>* storage_db,
85 leveldb_proto::Enums::InitStatus status) {
86 task_runner()->PostTask(FROM_HERE,
87 base::BindOnce(
88 [](FakeDB<ContentStorageProto>* storage_db,
89 leveldb_proto::Enums::InitStatus status) {
90 storage_db->InitStatusCallback(status);
91 },
92 storage_db, status));
93 RunUntilIdle();
94 }
LoadCallback(FakeDB<ContentStorageProto> * storage_db,bool success)95 void LoadCallback(FakeDB<ContentStorageProto>* storage_db, bool success) {
96 task_runner()->PostTask(
97 FROM_HERE,
98 base::BindOnce([](FakeDB<ContentStorageProto>* storage_db,
99 bool success) { storage_db->LoadCallback(success); },
100 storage_db, success));
101 RunUntilIdle();
102 }
LoadKeysCallback(FakeDB<ContentStorageProto> * storage_db,bool success)103 void LoadKeysCallback(FakeDB<ContentStorageProto>* storage_db, bool success) {
104 task_runner()->PostTask(
105 FROM_HERE,
106 base::BindOnce(
107 [](FakeDB<ContentStorageProto>* storage_db, bool success) {
108 storage_db->LoadKeysCallback(success);
109 },
110 storage_db, success));
111 RunUntilIdle();
112 }
UpdateCallback(FakeDB<ContentStorageProto> * storage_db,bool success)113 void UpdateCallback(FakeDB<ContentStorageProto>* storage_db, bool success) {
114 task_runner()->PostTask(
115 FROM_HERE,
116 base::BindOnce(
117 [](FakeDB<ContentStorageProto>* storage_db, bool success) {
118 storage_db->UpdateCallback(success);
119 },
120 storage_db, success));
121 RunUntilIdle();
122 }
123
RunUntilIdle()124 void RunUntilIdle() { task_environment_.RunUntilIdle(); }
125
task_runner()126 scoped_refptr<base::SequencedTaskRunner> task_runner() {
127 return task_runner_;
128 }
129
storage_db()130 FakeDB<ContentStorageProto>* storage_db() { return content_db_; }
131
db()132 FeedContentDatabase* db() { return feed_db_.get(); }
133
histogram()134 HistogramTester& histogram() { return histogram_; }
135
136 MOCK_METHOD2(OnContentEntriesReceived,
137 void(bool, std::vector<std::pair<std::string, std::string>>));
138 MOCK_METHOD2(OnContentKeyReceived, void(bool, std::vector<std::string>));
139 MOCK_METHOD1(OnStorageCommitted, void(bool));
140
141 private:
142 base::test::TaskEnvironment task_environment_;
143
144 std::map<std::string, ContentStorageProto> content_db_storage_;
145
146 scoped_refptr<base::SequencedTaskRunner> task_runner_;
147
148 // Owned by |feed_db_|.
149 FakeDB<ContentStorageProto>* content_db_;
150
151 std::unique_ptr<FeedContentDatabase> feed_db_;
152
153 HistogramTester histogram_;
154
155 DISALLOW_COPY_AND_ASSIGN(FeedContentDatabaseTest);
156 };
157
TEST_F(FeedContentDatabaseTest,Init)158 TEST_F(FeedContentDatabaseTest, Init) {
159 ASSERT_FALSE(db());
160
161 CreateDatabase(/*init_database=*/false);
162
163 InitStatusCallback(storage_db(), leveldb_proto::Enums::InitStatus::kOK);
164
165 EXPECT_TRUE(db()->IsInitialized());
166 }
167
TEST_F(FeedContentDatabaseTest,LoadContentAfterInitSuccess)168 TEST_F(FeedContentDatabaseTest, LoadContentAfterInitSuccess) {
169 CreateDatabase(/*init_database=*/true);
170
171 EXPECT_CALL(*this, OnContentEntriesReceived(_, _));
172 db()->LoadContent(
173 {kContentKey1},
174 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
175 base::Unretained(this)));
176 LoadCallback(storage_db(), true);
177
178 histogram().ExpectTotalCount(kUmaLoadTimeHistogramName, 1);
179 }
180
TEST_F(FeedContentDatabaseTest,LoadContentsEntries)181 TEST_F(FeedContentDatabaseTest, LoadContentsEntries) {
182 CreateDatabase(/*init_database=*/true);
183
184 // Store |kContentKey1| and |kContentKey2|.
185 InjectContentStorageProto(kContentKey1, kContentData1);
186 InjectContentStorageProto(kContentKey2, kContentData2);
187
188 // Try to Load |kContentKey2| and |kContentKey3|, only |kContentKey2| should
189 // return.
190 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
191 .WillOnce([](bool success,
192 std::vector<std::pair<std::string, std::string>> results) {
193 EXPECT_TRUE(success);
194 ASSERT_EQ(results.size(), 1U);
195 EXPECT_EQ(results[0].first, kContentKey2);
196 EXPECT_EQ(results[0].second, kContentData2);
197 });
198 db()->LoadContent(
199 {kContentKey2, kContentKey3},
200 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
201 base::Unretained(this)));
202 LoadCallback(storage_db(), true);
203
204 histogram().ExpectTotalCount(kUmaLoadTimeHistogramName, 1);
205 }
206
TEST_F(FeedContentDatabaseTest,LoadContentsEntriesByPrefix)207 TEST_F(FeedContentDatabaseTest, LoadContentsEntriesByPrefix) {
208 CreateDatabase(/*init_database=*/true);
209
210 // Store |kContentKey1|, |kContentKey2|.
211 InjectContentStorageProto(kContentKey1, kContentData1);
212 InjectContentStorageProto(kContentKey2, kContentData2);
213
214 // Try to Load "ContentKey", both |kContentKey1| and |kContentKey2| should
215 // return.
216 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
217 .WillOnce([](bool success,
218 std::vector<std::pair<std::string, std::string>> results) {
219 EXPECT_TRUE(success);
220 ASSERT_EQ(results.size(), 2U);
221 EXPECT_EQ(results[0].first, kContentKey1);
222 EXPECT_EQ(results[0].second, kContentData1);
223 EXPECT_EQ(results[1].first, kContentKey2);
224 EXPECT_EQ(results[1].second, kContentData2);
225 });
226 db()->LoadContentByPrefix(
227 kContentKeyPrefix,
228 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
229 base::Unretained(this)));
230 LoadCallback(storage_db(), true);
231
232 histogram().ExpectTotalCount(kUmaLoadTimeHistogramName, 1);
233 }
234
TEST_F(FeedContentDatabaseTest,LoadAllContentKeys)235 TEST_F(FeedContentDatabaseTest, LoadAllContentKeys) {
236 CreateDatabase(/*init_database=*/true);
237
238 // Store |kContentKey1|, |kContentKey2|.
239 InjectContentStorageProto(kContentKey1, kContentData1);
240 InjectContentStorageProto(kContentKey2, kContentData2);
241
242 EXPECT_CALL(*this, OnContentKeyReceived(_, _))
243 .WillOnce([](bool success, std::vector<std::string> results) {
244 EXPECT_TRUE(success);
245 ASSERT_EQ(results.size(), 2U);
246 EXPECT_EQ(results[0], kContentKey1);
247 EXPECT_EQ(results[1], kContentKey2);
248 });
249 db()->LoadAllContentKeys(base::BindOnce(
250 &FeedContentDatabaseTest::OnContentKeyReceived, base::Unretained(this)));
251 LoadKeysCallback(storage_db(), true);
252
253 histogram().ExpectBucketCount(kUmaSizeHistogramName,
254 /*size=*/2, 1);
255 histogram().ExpectTotalCount(kUmaLoadKeysTimeHistogramName, 1);
256 }
257
TEST_F(FeedContentDatabaseTest,SaveContent)258 TEST_F(FeedContentDatabaseTest, SaveContent) {
259 CreateDatabase(/*init_database=*/true);
260
261 // Store |kContentKey1|, |kContentKey2|.
262 std::unique_ptr<ContentMutation> content_mutation =
263 std::make_unique<ContentMutation>();
264 content_mutation->AppendUpsertOperation(kContentKey1, kContentData1);
265 content_mutation->AppendUpsertOperation(kContentKey2, kContentData2);
266 EXPECT_CALL(*this, OnStorageCommitted(true));
267 db()->CommitContentMutation(
268 std::move(content_mutation),
269 base::BindOnce(&FeedContentDatabaseTest::OnStorageCommitted,
270 base::Unretained(this)));
271 UpdateCallback(storage_db(), true);
272 UpdateCallback(storage_db(), true);
273
274 // Make sure they're there.
275 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
276 .WillOnce([](bool success,
277 std::vector<std::pair<std::string, std::string>> results) {
278 EXPECT_TRUE(success);
279 ASSERT_EQ(results.size(), 2U);
280 EXPECT_EQ(results[0].first, kContentKey1);
281 EXPECT_EQ(results[0].second, kContentData1);
282 EXPECT_EQ(results[1].first, kContentKey2);
283 EXPECT_EQ(results[1].second, kContentData2);
284 });
285 db()->LoadContent(
286 {kContentKey1, kContentKey2},
287 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
288 base::Unretained(this)));
289 LoadCallback(storage_db(), true);
290
291 histogram().ExpectBucketCount(kUmaCommitMutationSizeHistogramName,
292 /*operations=*/2, 1);
293 histogram().ExpectTotalCount(kUmaOperationCommitTimeHistogramName, 1);
294 }
295
TEST_F(FeedContentDatabaseTest,DeleteContent)296 TEST_F(FeedContentDatabaseTest, DeleteContent) {
297 CreateDatabase(/*init_database=*/true);
298
299 // Store |kContentKey1| and |kContentKey2|.
300 InjectContentStorageProto(kContentKey1, kContentData1);
301 InjectContentStorageProto(kContentKey2, kContentData2);
302
303 // Delete |kContentKey2| and |kContentKey3|
304 std::unique_ptr<ContentMutation> content_mutation =
305 std::make_unique<ContentMutation>();
306 content_mutation->AppendDeleteOperation(kContentKey2);
307 content_mutation->AppendDeleteOperation(kContentKey3);
308 EXPECT_CALL(*this, OnStorageCommitted(true));
309 db()->CommitContentMutation(
310 std::move(content_mutation),
311 base::BindOnce(&FeedContentDatabaseTest::OnStorageCommitted,
312 base::Unretained(this)));
313 UpdateCallback(storage_db(), true);
314 UpdateCallback(storage_db(), true);
315
316 // Make sure only |kContentKey2| got deleted.
317 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
318 .WillOnce([](bool success,
319 std::vector<std::pair<std::string, std::string>> results) {
320 EXPECT_TRUE(success);
321 EXPECT_EQ(results.size(), 1U);
322 EXPECT_EQ(results[0].first, kContentKey1);
323 EXPECT_EQ(results[0].second, kContentData1);
324 });
325 db()->LoadContent(
326 {kContentKey1, kContentKey2},
327 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
328 base::Unretained(this)));
329 LoadCallback(storage_db(), true);
330
331 histogram().ExpectBucketCount(kUmaCommitMutationSizeHistogramName,
332 /*operations=*/2, 1);
333 histogram().ExpectTotalCount(kUmaOperationCommitTimeHistogramName, 1);
334 }
335
TEST_F(FeedContentDatabaseTest,DeleteContentByPrefix)336 TEST_F(FeedContentDatabaseTest, DeleteContentByPrefix) {
337 CreateDatabase(/*init_database=*/true);
338
339 // Store |kContentKey1| and |kContentKey2|.
340 InjectContentStorageProto(kContentKey1, kContentData1);
341 InjectContentStorageProto(kContentKey2, kContentData2);
342
343 // Delete |kContentKey1| and |kContentKey2|
344 std::unique_ptr<ContentMutation> content_mutation =
345 std::make_unique<ContentMutation>();
346 content_mutation->AppendDeleteByPrefixOperation(kContentKeyPrefix);
347 EXPECT_CALL(*this, OnStorageCommitted(true));
348 db()->CommitContentMutation(
349 std::move(content_mutation),
350 base::BindOnce(&FeedContentDatabaseTest::OnStorageCommitted,
351 base::Unretained(this)));
352 UpdateCallback(storage_db(), true);
353
354 // Make sure |kContentKey1| and |kContentKey2| got deleted.
355 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
356 .WillOnce([](bool success,
357 std::vector<std::pair<std::string, std::string>> results) {
358 EXPECT_TRUE(success);
359 EXPECT_EQ(results.size(), 0U);
360 });
361 db()->LoadContent(
362 {kContentKey1, kContentKey2},
363 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
364 base::Unretained(this)));
365 LoadCallback(storage_db(), true);
366
367 histogram().ExpectBucketCount(kUmaCommitMutationSizeHistogramName,
368 /*operations=*/1, 1);
369 histogram().ExpectTotalCount(kUmaOperationCommitTimeHistogramName, 1);
370 }
371
TEST_F(FeedContentDatabaseTest,DeleteAllContent)372 TEST_F(FeedContentDatabaseTest, DeleteAllContent) {
373 CreateDatabase(/*init_database=*/true);
374
375 // Store |kContentKey1| and |kContentKey2|.
376 InjectContentStorageProto(kContentKey1, kContentData1);
377 InjectContentStorageProto(kContentKey2, kContentData2);
378
379 // Delete all content, meaning |kContentKey1| and |kContentKey2| are expected
380 // to be deleted.
381 std::unique_ptr<ContentMutation> content_mutation =
382 std::make_unique<ContentMutation>();
383 content_mutation->AppendDeleteAllOperation();
384 EXPECT_CALL(*this, OnStorageCommitted(true));
385 db()->CommitContentMutation(
386 std::move(content_mutation),
387 base::BindOnce(&FeedContentDatabaseTest::OnStorageCommitted,
388 base::Unretained(this)));
389 UpdateCallback(storage_db(), true);
390
391 // Make sure |kContentKey1| and |kContentKey2| got deleted.
392 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
393 .WillOnce([](bool success,
394 std::vector<std::pair<std::string, std::string>> results) {
395 EXPECT_TRUE(success);
396 EXPECT_EQ(results.size(), 0U);
397 });
398 db()->LoadContent(
399 {kContentKey1, kContentKey2},
400 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
401 base::Unretained(this)));
402 LoadCallback(storage_db(), true);
403
404 histogram().ExpectBucketCount(kUmaCommitMutationSizeHistogramName,
405 /*operations=*/1, 1);
406 histogram().ExpectTotalCount(kUmaOperationCommitTimeHistogramName, 1);
407 }
408
TEST_F(FeedContentDatabaseTest,SaveAndDeleteContent)409 TEST_F(FeedContentDatabaseTest, SaveAndDeleteContent) {
410 CreateDatabase(/*init_database=*/true);
411
412 // Store |kContentKey1|, |kContentKey2|.
413 std::unique_ptr<ContentMutation> content_mutation =
414 std::make_unique<ContentMutation>();
415 content_mutation->AppendUpsertOperation(kContentKey1, kContentData1);
416 content_mutation->AppendUpsertOperation(kContentKey2, kContentData2);
417 content_mutation->AppendDeleteOperation(kContentKey2);
418 content_mutation->AppendDeleteOperation(kContentKey3);
419 EXPECT_CALL(*this, OnStorageCommitted(true));
420 db()->CommitContentMutation(
421 std::move(content_mutation),
422 base::BindOnce(&FeedContentDatabaseTest::OnStorageCommitted,
423 base::Unretained(this)));
424 UpdateCallback(storage_db(), true);
425 UpdateCallback(storage_db(), true);
426 UpdateCallback(storage_db(), true);
427 UpdateCallback(storage_db(), true);
428
429 // Make sure only |kContentKey2| got deleted.
430 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
431 .WillOnce([](bool success,
432 std::vector<std::pair<std::string, std::string>> results) {
433 EXPECT_TRUE(success);
434 EXPECT_EQ(results.size(), 1U);
435 EXPECT_EQ(results[0].first, kContentKey1);
436 EXPECT_EQ(results[0].second, kContentData1);
437 });
438 db()->LoadContent(
439 {kContentKey1, kContentKey2},
440 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
441 base::Unretained(this)));
442 LoadCallback(storage_db(), true);
443
444 histogram().ExpectBucketCount(kUmaCommitMutationSizeHistogramName,
445 /*operations=*/4, 1);
446 histogram().ExpectTotalCount(kUmaOperationCommitTimeHistogramName, 1);
447 }
448
TEST_F(FeedContentDatabaseTest,LoadContentsFail)449 TEST_F(FeedContentDatabaseTest, LoadContentsFail) {
450 CreateDatabase(/*init_database=*/true);
451
452 // Store |kContentKey1| and |kContentKey2|.
453 InjectContentStorageProto(kContentKey1, kContentData1);
454 InjectContentStorageProto(kContentKey2, kContentData2);
455
456 // Try to Load |kContentKey2| and |kContentKey3|,.
457 EXPECT_CALL(*this, OnContentEntriesReceived(_, _))
458 .WillOnce([](bool success,
459 std::vector<std::pair<std::string, std::string>> results) {
460 EXPECT_FALSE(success);
461 });
462 db()->LoadContent(
463 {kContentKey2, kContentKey3},
464 base::BindOnce(&FeedContentDatabaseTest::OnContentEntriesReceived,
465 base::Unretained(this)));
466 LoadCallback(storage_db(), false);
467
468 histogram().ExpectTotalCount(kUmaLoadTimeHistogramName, 1);
469 }
470
TEST_F(FeedContentDatabaseTest,LoadAllContentKeysFail)471 TEST_F(FeedContentDatabaseTest, LoadAllContentKeysFail) {
472 CreateDatabase(/*init_database=*/true);
473
474 // Store |kContentKey1|, |kContentKey2|.
475 InjectContentStorageProto(kContentKey1, kContentData1);
476 InjectContentStorageProto(kContentKey2, kContentData2);
477
478 EXPECT_CALL(*this, OnContentKeyReceived(_, _))
479 .WillOnce([](bool success, std::vector<std::string> results) {
480 EXPECT_FALSE(success);
481 });
482 db()->LoadAllContentKeys(base::BindOnce(
483 &FeedContentDatabaseTest::OnContentKeyReceived, base::Unretained(this)));
484 LoadKeysCallback(storage_db(), false);
485
486 histogram().ExpectTotalCount(kUmaSizeHistogramName, 0);
487 histogram().ExpectTotalCount(kUmaLoadKeysTimeHistogramName, 1);
488 }
489
490 } // namespace feed
491