Commit e0be430f authored by Hesen Zhang's avatar Hesen Zhang Committed by Commit Bot

[Video Tutorials] Setup TutorialStore.

- This CL implements persistence layer of video tutorial metadatas.

Bug: 1115755
Change-Id: Ic86d088cc0ad2934d172abc50e35f134e612f8f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2391932
Commit-Queue: Hesen Zhang <hesen@chromium.org>
Reviewed-by: default avatarShakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#805081}
parent 7f67680c
......@@ -11,10 +11,13 @@ source_set("internal") {
sources = [
"proto_conversions.cc",
"proto_conversions.h",
"store.h",
"tutorial_group.cc",
"tutorial_group.h",
"tutorial_service_impl.cc",
"tutorial_service_impl.h",
"tutorial_store.cc",
"tutorial_store.h",
]
public_deps = [ "//components/image_fetcher/core" ]
......@@ -79,12 +82,17 @@ source_set("unit_tests") {
sources = [
"proto_conversions_unittest.cc",
"tutorial_group_unittest.cc",
"tutorial_store_unittest.cc",
]
deps = [
":internal",
"//base",
"//base/test:test_support",
"//chrome/browser/video_tutorials/proto",
"//chrome/browser/video_tutorials/test:test_lib",
"//components/leveldb_proto",
"//components/leveldb_proto:test_support",
"//testing/gmock",
"//testing/gtest",
]
......
......@@ -3,33 +3,16 @@
// found in the LICENSE file.
#include "chrome/browser/video_tutorials/internal/proto_conversions.h"
#include "chrome/browser/video_tutorials/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace video_tutorials {
namespace {
const char kTestTitle[] = "Test Title";
const char kTestURL[] = "https://www.example.com";
void ResetTutorialEntry(Tutorial* entry) {
*entry = Tutorial(FeatureType::kTest, kTestTitle, kTestURL, kTestURL,
kTestURL, kTestURL, 60);
}
void ResetTutorialGroup(TutorialGroup* group) {
*group = TutorialGroup("cn");
group->tutorials.clear();
Tutorial entry1;
ResetTutorialEntry(&entry1);
group->tutorials.emplace_back(entry1);
group->tutorials.emplace_back(entry1);
}
// Verify round-way conversion of Tutorial struct.
TEST(VideoTutorialsProtoConversionsTest, TutorialConversion) {
Tutorial expected, actual;
ResetTutorialEntry(&expected);
test::BuildTestEntry(&expected);
TutorialProto intermediate;
TutorialToProto(&expected, &intermediate);
TutorialFromProto(&intermediate, &actual);
......@@ -39,7 +22,7 @@ TEST(VideoTutorialsProtoConversionsTest, TutorialConversion) {
// Verify round-way conversion of TutorialGroup struct.
TEST(VideoTutorialsProtoConversionsTest, TutorialGroupConversion) {
TutorialGroup expected, actual;
ResetTutorialGroup(&expected);
test::BuildTestGroup(&expected);
TutorialGroupProto intermediate;
TutorialGroupToProto(&expected, &intermediate);
TutorialGroupFromProto(&intermediate, &actual);
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_STORE_H_
#define CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_STORE_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/macros.h"
#include "base/optional.h"
namespace video_tutorials {
// Interface of video tutorials collection store.
template <typename T>
class Store {
public:
using Keys = std::unique_ptr<std::vector<std::string>>;
using Entries = std::vector<std::unique_ptr<T>>;
using LoadKeysCallback = base::OnceCallback<void(bool, Keys)>;
using LoadEntriesCallback = base::OnceCallback<void(bool, Entries)>;
using UpdateCallback = base::OnceCallback<void(bool)>;
using DeleteCallback = base::OnceCallback<void(bool)>;
// Initialize the db and load keys into memory.
virtual void InitAndLoadKeys(LoadKeysCallback callback) = 0;
// Load entries with the given keys into memory.
virtual void LoadEntries(const std::vector<std::string>& keys,
LoadEntriesCallback callback) = 0;
// Add a new entry or update an existing entry.
virtual void Update(const std::string& key,
const T& entry,
UpdateCallback callback) = 0;
// Delete entries from database.
virtual void Delete(const std::vector<std::string>& keys,
DeleteCallback callback) = 0;
Store() = default;
virtual ~Store() = default;
Store(const Store& other) = delete;
Store& operator=(const Store& other) = delete;
};
} // namespace video_tutorials
#endif // CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_STORE_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/video_tutorials/internal/tutorial_store.h"
#include <utility>
#include "base/stl_util.h"
namespace leveldb_proto {
void DataToProto(video_tutorials::TutorialGroup* data,
video_tutorials::proto::VideoTutorialGroup* proto) {
TutorialGroupToProto(data, proto);
}
void ProtoToData(video_tutorials::proto::VideoTutorialGroup* proto,
video_tutorials::TutorialGroup* data) {
TutorialGroupFromProto(proto, data);
}
} // namespace leveldb_proto
namespace video_tutorials {
TutorialStore::TutorialStore(TutorialProtoDb db) : db_(std::move(db)) {}
TutorialStore::~TutorialStore() = default;
void TutorialStore::InitAndLoadKeys(LoadKeysCallback callback) {
db_->Init(base::BindOnce(&TutorialStore::OnDbInitialized,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void TutorialStore::LoadEntries(const std::vector<std::string>& keys,
LoadEntriesCallback callback) {
db_->LoadEntriesWithFilter(
base::BindRepeating(
[](const std::vector<std::string>& key_dict, const std::string& key) {
return base::Contains(key_dict, key);
},
keys),
base::BindOnce(&TutorialStore::OnEntriesLoaded,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void TutorialStore::Update(const std::string& key,
const TutorialGroup& group,
UpdateCallback callback) {
auto entries_to_save = std::make_unique<KeyEntryVector>();
auto entry_to_save = group;
entries_to_save->emplace_back(key, std::move(entry_to_save));
db_->UpdateEntries(std::move(entries_to_save),
std::make_unique<KeyVector>() /*keys_to_remove*/,
std::move(callback));
}
void TutorialStore::Delete(const std::vector<std::string>& keys,
DeleteCallback callback) {
auto keys_to_delete = std::make_unique<KeyVector>(keys);
db_->UpdateEntries(std::make_unique<KeyEntryVector>() /*entries_to_save*/,
std::move(keys_to_delete), std::move(callback));
}
void TutorialStore::OnDbInitialized(LoadKeysCallback callback,
leveldb_proto::Enums::InitStatus status) {
if (status != leveldb_proto::Enums::InitStatus::kOK) {
std::move(callback).Run(false, Keys());
return;
}
db_->LoadKeys(base::BindOnce(&TutorialStore::OnKeysLoaded,
weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void TutorialStore::OnKeysLoaded(LoadKeysCallback callback,
bool success,
std::unique_ptr<KeyVector> keys) {
std::move(callback).Run(success, success ? std::move(keys) : Keys());
}
void TutorialStore::OnEntriesLoaded(
LoadEntriesCallback callback,
bool success,
std::unique_ptr<std::vector<TutorialGroup>> loaded_entries) {
if (!success || !loaded_entries) {
std::move(callback).Run(success, Entries());
return;
}
Entries entries;
for (auto& loaded_entry : *loaded_entries)
entries.emplace_back(std::make_unique<TutorialGroup>(loaded_entry));
std::move(callback).Run(true, std::move(entries));
}
} // namespace video_tutorials
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_TUTORIAL_STORE_H_
#define CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_TUTORIAL_STORE_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/memory/weak_ptr.h"
#include "chrome/browser/video_tutorials/internal/proto_conversions.h"
#include "chrome/browser/video_tutorials/internal/store.h"
#include "chrome/browser/video_tutorials/internal/tutorial_group.h"
#include "components/leveldb_proto/public/proto_database.h"
namespace leveldb_proto {
void DataToProto(video_tutorials::TutorialGroup* data,
video_tutorials::proto::VideoTutorialGroup* proto);
void ProtoToData(video_tutorials::proto::VideoTutorialGroup* proto,
video_tutorials::TutorialGroup* data);
} // namespace leveldb_proto
namespace video_tutorials {
// Persist layer of video tutorial groups.
class TutorialStore : public Store<TutorialGroup> {
public:
using TutorialProtoDb = std::unique_ptr<
leveldb_proto::ProtoDatabase<video_tutorials::proto::VideoTutorialGroup,
TutorialGroup>>;
explicit TutorialStore(TutorialProtoDb db);
~TutorialStore() override;
TutorialStore(const TutorialStore& other) = delete;
TutorialStore& operator=(const TutorialStore& other) = delete;
private:
using KeyEntryVector = std::vector<std::pair<std::string, TutorialGroup>>;
using KeyVector = std::vector<std::string>;
using EntryVector = std::vector<TutorialGroup>;
// Store<TutorialGroup> implementation.
void InitAndLoadKeys(LoadKeysCallback callback) override;
void LoadEntries(const std::vector<std::string>& keys,
LoadEntriesCallback callback) override;
void Update(const std::string& key,
const TutorialGroup& group,
UpdateCallback callback) override;
void Delete(const std::vector<std::string>& keys,
DeleteCallback callback) override;
// Called when db is initialized.
void OnDbInitialized(LoadKeysCallback callback,
leveldb_proto::Enums::InitStatus status);
// Called when keys are loaded after initialization.
void OnKeysLoaded(LoadKeysCallback callback,
bool success,
std::unique_ptr<std::vector<std::string>> loaded_keys);
// Called when entries are loaded from db.
void OnEntriesLoaded(
LoadEntriesCallback callback,
bool success,
std::unique_ptr<std::vector<TutorialGroup>> loaded_entries);
TutorialProtoDb db_;
base::WeakPtrFactory<TutorialStore> weak_ptr_factory_{this};
};
} // namespace video_tutorials
#endif // CHROME_BROWSER_VIDEO_TUTORIALS_INTERNAL_TUTORIAL_STORE_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/video_tutorials/internal/tutorial_store.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/test/task_environment.h"
#include "chrome/browser/video_tutorials/test/test_utils.h"
#include "components/leveldb_proto/public/proto_database.h"
#include "components/leveldb_proto/testing/fake_db.h"
#include "testing/gtest/include/gtest/gtest.h"
using leveldb_proto::test::FakeDB;
using InitStatus = leveldb_proto::Enums::InitStatus;
namespace video_tutorials {
class TutorialStoreTest : public testing::Test {
public:
using TutorialGroupProto = video_tutorials::proto::VideoTutorialGroup;
using EntriesMap = std::map<std::string, std::unique_ptr<TutorialGroup>>;
using ProtoMap = std::map<std::string, TutorialGroupProto>;
using KeysAndEntries = std::map<std::string, TutorialGroup>;
using TestEntries = std::vector<TutorialGroup>;
using TestKeys = std::vector<std::string>;
TutorialStoreTest() : load_result_(false), db_(nullptr) {}
~TutorialStoreTest() override = default;
TutorialStoreTest(const TutorialStoreTest& other) = delete;
TutorialStoreTest& operator=(const TutorialStoreTest& other) = delete;
protected:
void Init(TestEntries input, InitStatus status) {
CreateTestDbEntries(std::move(input));
auto db = std::make_unique<FakeDB<TutorialGroupProto, TutorialGroup>>(
&db_entries_);
db_ = db.get();
store_ = std::make_unique<TutorialStore>(std::move(db));
store_->InitAndLoadKeys(base::BindOnce(&TutorialStoreTest::OnKeysLoaded,
base::Unretained(this)));
db_->InitStatusCallback(status);
}
void OnKeysLoaded(bool success,
std::unique_ptr<std::vector<std::string>> loaded_keys) {
load_result_ = success;
loaded_keys_.clear();
if (success && loaded_keys)
loaded_keys_ = *loaded_keys;
}
void CreateTestDbEntries(TestEntries input) {
for (auto& entry : input) {
TutorialGroupProto proto;
TutorialGroupToProto(&entry, &proto);
db_entries_.emplace(entry.locale, proto);
}
}
void VerifyLoadEntries(const TestKeys& keys,
bool expected_success,
TestEntries expected_entries) {
store_->LoadEntries(keys,
base::BindOnce(&TutorialStoreTest::OnEntriesLoaded,
base::Unretained(this), expected_success,
expected_entries));
db_->LoadCallback(true);
}
void OnEntriesLoaded(
bool expected_success,
TestEntries expected_entries,
bool success,
std::vector<std::unique_ptr<TutorialGroup>> loaded_entries) {
EXPECT_EQ(expected_success, success);
EXPECT_EQ(loaded_entries.size(), expected_entries.size());
TestEntries actual_loaded_entries;
for (auto& loaded_entry : loaded_entries) {
actual_loaded_entries.emplace_back(*loaded_entry.get());
}
EXPECT_EQ(expected_entries, actual_loaded_entries);
}
// Verifies the entries in the db is |expected|.
void VerifyDataInDb(std::unique_ptr<KeysAndEntries> expected) {
db_->LoadKeysAndEntries(base::BindOnce(&TutorialStoreTest::OnVerifyDataInDb,
base::Unretained(this),
std::move(expected)));
db_->LoadCallback(true);
}
void OnVerifyDataInDb(std::unique_ptr<KeysAndEntries> expected,
bool success,
std::unique_ptr<KeysAndEntries> loaded_entries) {
EXPECT_TRUE(success);
DCHECK(expected);
DCHECK(loaded_entries);
for (auto it = loaded_entries->begin(); it != loaded_entries->end(); it++) {
EXPECT_NE(expected->count(it->first), 0u);
auto& actual_loaded_group = it->second;
auto& expected_group = expected->at(it->first);
EXPECT_EQ(actual_loaded_group, expected_group);
}
}
bool load_result() const { return load_result_; }
const EntriesMap& loaded_keys_and_entries() const {
return loaded_keys_and_entries_;
}
const std::vector<std::string>& loaded_keys() const { return loaded_keys_; }
FakeDB<TutorialGroupProto, TutorialGroup>* db() { return db_; }
Store<TutorialGroup>* store() { return store_.get(); }
private:
base::test::TaskEnvironment task_environment_;
bool load_result_{false};
TestKeys loaded_keys_;
EntriesMap loaded_keys_and_entries_;
ProtoMap db_entries_;
FakeDB<TutorialGroupProto, TutorialGroup>* db_{nullptr};
std::unique_ptr<Store<TutorialGroup>> store_;
};
// Test loading keys from a non-empty database in initialization successfully.
TEST_F(TutorialStoreTest, LoadedKeysSuccess) {
auto test_data = TestEntries();
TutorialGroup test_group;
test::BuildTestGroup(&test_group);
std::string locale = test_group.locale;
test_data.emplace_back(std::move(test_group));
Init(std::move(test_data), InitStatus::kOK);
db()->LoadKeysCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_EQ(loaded_keys().size(), 1u);
EXPECT_EQ(loaded_keys().front(), locale);
}
// Test loading keys from a non-empty database failed.
TEST_F(TutorialStoreTest, LoadKeysFailed) {
auto test_data = TestEntries();
TutorialGroup test_group;
test::BuildTestGroup(&test_group);
std::string locale = test_group.locale;
test_data.emplace_back(std::move(test_group));
Init(std::move(test_data), InitStatus::kOK);
db()->LoadKeysCallback(false);
EXPECT_EQ(load_result(), false);
EXPECT_TRUE(loaded_keys().empty());
}
// Test loading entries with loaded keys successfully.
TEST_F(TutorialStoreTest, LoadedEntriesSuccess) {
auto test_data = TestEntries();
TutorialGroup test_group;
test::BuildTestGroup(&test_group);
std::string locale = test_group.locale;
test_data.emplace_back(std::move(test_group));
Init(test_data, InitStatus::kOK);
db()->LoadKeysCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_EQ(loaded_keys().size(), 1u);
EXPECT_EQ(loaded_keys().front(), locale);
VerifyLoadEntries(loaded_keys() /*keys*/, true /*expected_success*/,
test_data /*expected_loaded_entries*/);
}
// Test adding and updating data successfully.
TEST_F(TutorialStoreTest, AddAndUpdateDataSuccess) {
auto test_data = TestEntries();
Init(std::move(test_data), InitStatus::kOK);
db()->LoadKeysCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_TRUE(loaded_keys().empty());
// Add a group successfully.
TutorialGroup test_group;
test::BuildTestGroup(&test_group);
store()->Update(test_group.locale, test_group,
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->UpdateCallback(true);
auto expected = std::make_unique<KeysAndEntries>();
expected->emplace(test_group.locale, std::move(test_group));
VerifyDataInDb(std::move(expected));
}
// Test deleting entries with keys .
TEST_F(TutorialStoreTest, Delete) {
auto test_data = TestEntries();
TutorialGroup test_group;
test::BuildTestGroup(&test_group);
std::string locale = test_group.locale;
test_data.emplace_back(std::move(test_group));
Init(test_data, InitStatus::kOK);
db()->LoadKeysCallback(true);
EXPECT_EQ(load_result(), true);
EXPECT_EQ(loaded_keys().size(), 1u);
EXPECT_EQ(loaded_keys().front(), locale);
std::vector<std::string> keys{locale};
store()->Delete(std::move(keys),
base::BindOnce([](bool success) { EXPECT_TRUE(success); }));
db()->UpdateCallback(true);
// No entry is expected in db.
auto expected = std::make_unique<KeysAndEntries>();
VerifyDataInDb(std::move(expected));
}
} // namespace video_tutorials
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
source_set("test_lib") {
testonly = true
visibility = [
"//chrome/browser/video_tutorials/internal:unit_tests",
"//chrome/browser/video_tutorials:video_tutorials_unit_tests",
]
sources = [
"test_utils.cc",
"test_utils.h",
]
deps = [
"//chrome/browser/video_tutorials/internal",
"//testing/gmock",
"//testing/gtest",
]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/video_tutorials/test/test_utils.h"
namespace video_tutorials {
namespace test {
const char kTestTitle[] = "Test Title";
void BuildTestEntry(Tutorial* entry) {
*entry = Tutorial(
FeatureType::kTest, kTestTitle, "https://www.example.com/video_url",
"https://www.example.com/share_url", "https://www.example.com/poster_url",
"https://www.example.com/caption_url", 60);
}
void BuildTestGroup(TutorialGroup* group) {
*group = TutorialGroup("en");
group->tutorials.clear();
Tutorial entry1;
BuildTestEntry(&entry1);
group->tutorials.emplace_back(entry1);
group->tutorials.emplace_back(entry1);
}
} // namespace test
} // namespace video_tutorials
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_VIDEO_TUTORIALS_TEST_TEST_UTILS_H_
#define CHROME_BROWSER_VIDEO_TUTORIALS_TEST_TEST_UTILS_H_
#include <string>
#include <vector>
#include "chrome/browser/video_tutorials/internal/tutorial_group.h"
namespace video_tutorials {
namespace test {
// Build a TutorialGroup filled with fake data for test purpose.
void BuildTestGroup(TutorialGroup* group);
// Build a Tutorial entry filled with fake data for test purpose.
void BuildTestEntry(Tutorial* entry);
} // namespace test
} // namespace video_tutorials
#endif // CHROME_BROWSER_VIDEO_TUTORIALS_TEST_TEST_UTILS_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment