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