blob: 25b20d66a5bc9164bfc831d067d4fbc64c154c0e [file] [log] [blame]
// Copyright 2017 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 "extensions/browser/api/lock_screen_data/lock_screen_item_storage.h"
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/containers/contains.h"
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/login/login_state/login_state.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/user_prefs/user_prefs.h"
#include "components/value_store/test_value_store_factory.h"
#include "content/public/test/test_browser_context.h"
#include "extensions/browser/api/lock_screen_data/data_item.h"
#include "extensions/browser/api/lock_screen_data/lock_screen_value_store_migrator.h"
#include "extensions/browser/api/lock_screen_data/operation_result.h"
#include "extensions/browser/api/storage/local_value_store_cache.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_test.h"
#include "extensions/browser/test_extensions_browser_client.h"
#include "extensions/common/api/lock_screen_data.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace extensions {
namespace lock_screen_data {
namespace {
constexpr char kTestUserIdHash[] = "user_id_hash";
constexpr char kTestSymmetricKey[] = "fake_symmetric_key";
constexpr char kTestExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
constexpr char kSecondTestExtensionId[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
void RecordCreateResult(OperationResult* result_out,
const DataItem** item_out,
OperationResult result,
const DataItem* item) {
*result_out = result;
*item_out = item;
}
void RecordGetAllItemsResult(std::vector<std::string>* items_out,
const std::vector<const DataItem*>& items) {
items_out->clear();
for (const DataItem* item : items)
items_out->push_back(item->id());
}
void RecordWriteResult(OperationResult* result_out, OperationResult result) {
*result_out = result;
}
void RecordReadResult(OperationResult* result_out,
std::unique_ptr<std::vector<char>>* content_out,
OperationResult result,
std::unique_ptr<std::vector<char>> content) {
*result_out = result;
*content_out = std::move(content);
}
class TestEventRouter : public extensions::EventRouter {
public:
explicit TestEventRouter(content::BrowserContext* context)
: extensions::EventRouter(context, nullptr) {}
TestEventRouter(const TestEventRouter&) = delete;
TestEventRouter& operator=(const TestEventRouter&) = delete;
~TestEventRouter() override = default;
bool ExtensionHasEventListener(const std::string& extension_id,
const std::string& event_name) const override {
return event_name ==
extensions::api::lock_screen_data::OnDataItemsAvailable::kEventName;
}
void BroadcastEvent(std::unique_ptr<extensions::Event> event) override {}
void DispatchEventToExtension(
const std::string& extension_id,
std::unique_ptr<extensions::Event> event) override {
if (event->event_name !=
extensions::api::lock_screen_data::OnDataItemsAvailable::kEventName) {
return;
}
ASSERT_TRUE(event->event_args);
ASSERT_TRUE(!event->event_args->GetList().empty());
const base::Value& arg_value = event->event_args->GetList()[0];
std::unique_ptr<extensions::api::lock_screen_data::DataItemsAvailableEvent>
event_args = extensions::api::lock_screen_data::
DataItemsAvailableEvent::FromValue(arg_value);
ASSERT_TRUE(event_args);
was_locked_values_.push_back(event_args->was_locked);
}
const std::vector<bool>& was_locked_values() const {
return was_locked_values_;
}
void ClearWasLockedValues() { was_locked_values_.clear(); }
private:
std::vector<bool> was_locked_values_;
};
std::unique_ptr<KeyedService> TestEventRouterFactoryFunction(
content::BrowserContext* context) {
return std::make_unique<TestEventRouter>(context);
}
// Keeps track of all fake data items registered during a test.
class ItemRegistry {
public:
explicit ItemRegistry(const std::string& extension_id)
: extension_id_(extension_id) {}
ItemRegistry(const ItemRegistry&) = delete;
ItemRegistry& operator=(const ItemRegistry&) = delete;
~ItemRegistry() = default;
// Adds a new item to set of registered items.
bool Add(const std::string& item_id) {
EXPECT_FALSE(items_.count(item_id));
if (!allow_new_)
return false;
items_.insert(item_id);
return true;
}
// Removes an item from the set of registered items.
void Remove(const std::string& item_id) {
ASSERT_TRUE(items_.count(item_id));
items_.erase(item_id);
}
void RemoveAll() { items_.clear(); }
// Gets the set of registered data items.
void HandleGetRequest(DataItem::RegisteredValuesCallback callback) {
if (!throttle_get_) {
RunCallback(std::move(callback));
return;
}
ASSERT_TRUE(pending_callback_.is_null());
pending_callback_ = std::move(callback);
}
// Completes a pending |HandleGetRequest| request.
void RunPendingCallback() {
ASSERT_FALSE(pending_callback_.is_null());
RunCallback(std::move(pending_callback_));
}
bool HasPendingCallback() const { return !pending_callback_.is_null(); }
void set_allow_new(bool allow_new) { allow_new_ = allow_new; }
void set_fail(bool fail) { fail_ = fail; }
void set_throttle_get(bool throttle_get) { throttle_get_ = throttle_get; }
private:
void RunCallback(DataItem::RegisteredValuesCallback callback) {
std::move(callback).Run(
fail_ ? OperationResult::kFailed : OperationResult::kSuccess,
ItemsToValue());
}
std::unique_ptr<base::DictionaryValue> ItemsToValue() {
if (fail_)
return nullptr;
std::unique_ptr<base::DictionaryValue> result =
std::make_unique<base::DictionaryValue>();
for (const std::string& item_id : items_)
result->SetKey(item_id, base::Value(base::Value::Type::DICTIONARY));
return result;
}
const std::string extension_id_;
// Whether data item registration should succeed.
bool allow_new_ = true;
// Whether data item retrievals should fail.
bool fail_ = false;
// Whether the data item retrivals should be throttled. If set,
// |HandleGetRequest| callback will be saved to |pending_callback_| without
// returning. Test will have to invoke |RunPendingCallback| in order to
// complete the request.
bool throttle_get_ = false;
DataItem::RegisteredValuesCallback pending_callback_;
// Set of registered item ids.
std::set<std::string> items_;
};
// Keeps track of all operations requested from the test data item.
// The operations will remain in pending state until completed by calling
// CompleteNextOperation.
// This is owned by the test class, but data items created during the test have
// a reference to the object. More than one data item can have a reference to
// this - data items with the same ID will get the same operation queue.
class OperationQueue {
public:
enum class OperationType { kWrite, kRead, kDelete };
struct PendingOperation {
explicit PendingOperation(OperationType type) : type(type) {}
OperationType type;
// Set only for write - data to be written.
std::vector<char> data;
// Callback for write operation.
DataItem::WriteCallback write_callback;
// Callback for read operation.
DataItem::ReadCallback read_callback;
// Callback for delete operation.
DataItem::WriteCallback delete_callback;
};
OperationQueue(const std::string& id, ItemRegistry* item_registry)
: id_(id), item_registry_(item_registry) {}
OperationQueue(const OperationQueue&) = delete;
OperationQueue& operator=(const OperationQueue&) = delete;
~OperationQueue() = default;
void Register(DataItem::WriteCallback callback) {
bool registered = item_registry_->Add(id_);
std::move(callback).Run(registered ? OperationResult::kSuccess
: OperationResult::kFailed);
}
void AddWrite(const std::vector<char>& data,
DataItem::WriteCallback callback) {
PendingOperation operation(OperationType::kWrite);
operation.data = data;
operation.write_callback = std::move(callback);
pending_operations_.emplace(std::move(operation));
}
void AddRead(DataItem::ReadCallback callback) {
PendingOperation operation(OperationType::kRead);
operation.read_callback = std::move(callback);
pending_operations_.emplace(std::move(operation));
}
void AddDelete(DataItem::WriteCallback callback) {
PendingOperation operation(OperationType::kDelete);
operation.delete_callback = std::move(callback);
pending_operations_.emplace(std::move(operation));
}
// Completes the next pendig operation.
// |expected_type| - the expected type of the next operation - this will fail
// if the operation does not match.
// |result| - the intended operation result.
void CompleteNextOperation(OperationType expected_type,
OperationResult result) {
ASSERT_FALSE(pending_operations_.empty());
ASSERT_FALSE(deleted_);
PendingOperation& operation = pending_operations_.front();
ASSERT_EQ(expected_type, operation.type);
switch (expected_type) {
case OperationType::kWrite: {
if (result == OperationResult::kSuccess)
content_ = operation.data;
DataItem::WriteCallback callback = std::move(operation.write_callback);
pending_operations_.pop();
std::move(callback).Run(result);
break;
}
case OperationType::kDelete: {
if (result == OperationResult::kSuccess) {
deleted_ = true;
item_registry_->Remove(id_);
content_ = std::vector<char>();
}
DataItem::WriteCallback callback = std::move(operation.delete_callback);
pending_operations_.pop();
std::move(callback).Run(result);
break;
}
case OperationType::kRead: {
std::unique_ptr<std::vector<char>> result_data;
if (result == OperationResult::kSuccess) {
result_data = std::make_unique<std::vector<char>>(content_.begin(),
content_.end());
}
DataItem::ReadCallback callback = std::move(operation.read_callback);
pending_operations_.pop();
std::move(callback).Run(result, std::move(result_data));
break;
}
default:
ADD_FAILURE() << "Unexpected operation";
return;
}
}
bool HasPendingOperations() const { return !pending_operations_.empty(); }
bool deleted() const { return deleted_; }
const std::vector<char>& content() const { return content_; }
void set_content(const std::vector<char>& content) { content_ = content; }
private:
std::string id_;
ItemRegistry* item_registry_;
base::queue<PendingOperation> pending_operations_;
std::vector<char> content_;
bool deleted_ = false;
};
// Test data item - routes all requests to the OperationQueue provided through
// the ctor - the owning test is responsible for completing the started
// operations.
class TestDataItem : public DataItem {
public:
// |operations| - Operation queue used by this data item - not owned by this,
// and expected to outlive this object.
TestDataItem(const std::string& id,
const std::string& extension_id,
const std::string& crypto_key,
OperationQueue* operations)
: DataItem(id, extension_id, nullptr, nullptr, nullptr, crypto_key),
operations_(operations) {}
TestDataItem(const TestDataItem&) = delete;
TestDataItem& operator=(const TestDataItem&) = delete;
~TestDataItem() override = default;
void Register(WriteCallback callback) override {
operations_->Register(std::move(callback));
}
void Write(const std::vector<char>& data, WriteCallback callback) override {
operations_->AddWrite(data, std::move(callback));
}
void Read(ReadCallback callback) override {
operations_->AddRead(std::move(callback));
}
void Delete(WriteCallback callback) override {
operations_->AddDelete(std::move(callback));
}
private:
OperationQueue* operations_;
};
class TestLockScreenValueStoreMigrator : public LockScreenValueStoreMigrator {
public:
TestLockScreenValueStoreMigrator() = default;
TestLockScreenValueStoreMigrator(const TestLockScreenValueStoreMigrator&) =
delete;
TestLockScreenValueStoreMigrator& operator=(
const TestLockScreenValueStoreMigrator&) = delete;
~TestLockScreenValueStoreMigrator() override = default;
void Run(const std::set<ExtensionId>& extensions_to_migrate,
ExtensionMigratedCallback callback) override {
ASSERT_TRUE(migration_callback_.is_null());
ASSERT_TRUE(extensions_to_migrate_.empty());
migration_callback_ = std::move(callback);
extensions_to_migrate_ = extensions_to_migrate;
}
bool IsMigratingExtensionData(
const ExtensionId& extension_id) const override {
return extensions_to_migrate_.count(extension_id) > 0;
}
void ClearDataForExtension(const ExtensionId& extension_id,
base::OnceClosure callback) override {
EXPECT_EQ(clear_data_callbacks_.count(extension_id), 0u);
EXPECT_GT(extensions_to_migrate_.count(extension_id), 0u);
extensions_to_migrate_.erase(extension_id);
clear_data_callbacks_[extension_id] = std::move(callback);
}
bool ClearingDataForExtension(const ExtensionId& extension_id) const {
return clear_data_callbacks_.count(extension_id) > 0;
}
bool FinishMigration(const ExtensionId& extension_id) {
if (!IsMigratingExtensionData(extension_id))
return false;
migration_callback_.Run(extension_id);
return true;
}
bool FinishClearData(const ExtensionId& extension_id) {
if (clear_data_callbacks_.count(extension_id) == 0)
return false;
base::OnceClosure callback = std::move(clear_data_callbacks_[extension_id]);
clear_data_callbacks_.erase(extension_id);
std::move(callback).Run();
return true;
}
const std::set<ExtensionId>& extensions_to_migrate() const {
return extensions_to_migrate_;
}
private:
ExtensionMigratedCallback migration_callback_;
std::set<ExtensionId> extensions_to_migrate_;
std::map<ExtensionId, base::OnceClosure> clear_data_callbacks_;
};
class LockScreenItemStorageTest : public ExtensionsTest {
public:
LockScreenItemStorageTest() = default;
LockScreenItemStorageTest(const LockScreenItemStorageTest&) = delete;
LockScreenItemStorageTest& operator=(const LockScreenItemStorageTest&) =
delete;
~LockScreenItemStorageTest() override = default;
void SetUp() override {
ExtensionsTest::SetUp();
ASSERT_TRUE(test_dir_.CreateUniqueTempDir());
LockScreenItemStorage::RegisterLocalState(local_state_.registry());
user_prefs::UserPrefs::Set(browser_context(), &testing_pref_service_);
extensions_browser_client()->set_lock_screen_context(&lock_screen_context_);
chromeos::LoginState::Initialize();
chromeos::LoginState::Get()->SetLoggedInStateAndPrimaryUser(
chromeos::LoginState::LOGGED_IN_ACTIVE,
chromeos::LoginState::LOGGED_IN_USER_REGULAR, kTestUserIdHash);
extension_ = CreateTestExtension(kTestExtensionId);
item_registry_ = std::make_unique<ItemRegistry>(extension()->id());
// Inject custom data item factory to be used with LockScreenItemStorage
// instances.
value_store_cache_factory_ =
base::BindRepeating(&LockScreenItemStorageTest::CreateValueStoreCache,
base::Unretained(this));
LockScreenItemStorage::SetValueStoreCacheFactoryForTesting(
&value_store_cache_factory_);
migrator_factory_ = base::BindRepeating(
&LockScreenItemStorageTest::CreateMigrator, base::Unretained(this));
LockScreenItemStorage::SetValueStoreMigratorFactoryForTesting(
&migrator_factory_);
item_factory_ = base::BindRepeating(&LockScreenItemStorageTest::CreateItem,
base::Unretained(this));
registered_items_getter_ = base::BindRepeating(
&LockScreenItemStorageTest::GetRegisteredItems, base::Unretained(this));
item_store_deleter_ = base::BindRepeating(
&LockScreenItemStorageTest::RemoveAllItems, base::Unretained(this));
LockScreenItemStorage::SetItemProvidersForTesting(
&registered_items_getter_, &item_factory_, &item_store_deleter_);
ResetLockScreenItemStorage();
}
void TearDown() override {
lock_screen_item_storage_.reset();
operations_.clear();
item_registry_.reset();
LockScreenItemStorage::SetItemProvidersForTesting(nullptr, nullptr,
nullptr);
chromeos::LoginState::Shutdown();
ExtensionsTest::TearDown();
}
OperationQueue* GetOperations(const std::string& id) {
return operations_[id].get();
}
void UnsetLockScreenItemStorage() { lock_screen_item_storage_.reset(); }
void ResetLockScreenItemStorage() {
lock_screen_item_storage_.reset();
value_store_migrator_ = nullptr;
lock_screen_item_storage_ = std::make_unique<LockScreenItemStorage>(
browser_context(), &local_state_, kTestSymmetricKey,
test_dir_.GetPath().AppendASCII("deprecated_value_store"),
test_dir_.GetPath().AppendASCII("value_store"));
}
// Utility method for setting test item content.
bool SetItemContent(const std::string& id, const std::vector<char>& content) {
OperationQueue* item_operations = GetOperations(id);
if (!item_operations) {
ADD_FAILURE() << "No item operations";
return false;
}
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), id, content,
base::BindOnce(&RecordWriteResult, &write_result));
if (!item_operations->HasPendingOperations()) {
ADD_FAILURE() << "Write not registered";
return false;
}
item_operations->CompleteNextOperation(
OperationQueue::OperationType::kWrite, OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, write_result);
return write_result == OperationResult::kSuccess;
}
const DataItem* CreateNewItem() {
OperationResult create_result = OperationResult::kFailed;
const DataItem* item = nullptr;
lock_screen_item_storage()->CreateItem(
extension()->id(),
base::BindOnce(&RecordCreateResult, &create_result, &item));
EXPECT_EQ(OperationResult::kSuccess, create_result);
return item;
}
// Utility method for creating a new testing data item, and setting its
// content.
const DataItem* CreateItemWithContent(const std::vector<char>& content) {
const DataItem* item = CreateNewItem();
if (!item) {
ADD_FAILURE() << "Item creation failed";
return nullptr;
}
if (!SetItemContent(item->id(), content))
return nullptr;
return item;
}
void GetAllItems(std::vector<std::string>* all_items) {
lock_screen_item_storage()->GetAllForExtension(
extension()->id(), base::BindOnce(&RecordGetAllItemsResult, all_items));
}
// Finds an item with the ID |id| in list of items |items|.
const DataItem* FindItem(const std::string& id,
const std::vector<const DataItem*> items) {
for (const auto* item : items) {
if (item && item->id() == id)
return item;
}
return nullptr;
}
struct ExtensionPersistedState {
ExtensionId extension_id;
int storage_version;
int item_count;
};
void InitExtensionLocalState(
const std::vector<ExtensionPersistedState>& states) {
for (const auto& state : states) {
ASSERT_TRUE(state.storage_version == 1 || state.storage_version == 2)
<< "Failed to init local state " << state.extension_id;
DictionaryPrefUpdateDeprecated update(&local_state_,
"lockScreenDataItems");
if (state.storage_version == 1) {
update->SetPath({kTestUserIdHash, state.extension_id},
base::Value(state.item_count));
} else {
base::Value info(base::Value::Type::DICTIONARY);
info.SetKey("item_count", base::Value(state.item_count));
info.SetKey("storage_version", base::Value(2));
update->SetPath({kTestUserIdHash, state.extension_id}, std::move(info));
}
}
}
struct MigratedItem {
std::string id;
std::vector<char> content;
};
bool FinishMigration(const ExtensionId& extension_id,
const std::vector<MigratedItem>& items) {
if (extension_id != extension_->id())
return false;
if (!value_store_migrator())
return false;
for (const auto& item : items) {
item_registry_->Add(item.id);
GetOrCreateOperations(item.id)->set_content(item.content);
}
return value_store_migrator()->FinishMigration(extension_id);
}
LockScreenItemStorage* lock_screen_item_storage() {
return lock_screen_item_storage_.get();
}
content::BrowserContext* lock_screen_context() {
return &lock_screen_context_;
}
const Extension* extension() const { return extension_.get(); }
const base::FilePath& test_dir() const { return test_dir_.GetPath(); }
PrefService* local_state() { return &local_state_; }
ItemRegistry* item_registry() { return item_registry_.get(); }
TestLockScreenValueStoreMigrator* value_store_migrator() {
return value_store_migrator_;
}
scoped_refptr<const Extension> CreateTestExtension(
const ExtensionId& extension_id) {
DictionaryBuilder app_builder;
app_builder.Set("background",
DictionaryBuilder()
.Set("scripts", ListBuilder().Append("script").Build())
.Build());
ListBuilder app_handlers_builder;
app_handlers_builder.Append(DictionaryBuilder()
.Set("action", "new_note")
.Set("enabled_on_lock_screen", true)
.Build());
scoped_refptr<const Extension> extension =
ExtensionBuilder()
.SetID(extension_id)
.SetManifest(
DictionaryBuilder()
.Set("name", "Test app")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("app", app_builder.Build())
.Set("action_handlers", app_handlers_builder.Build())
.Set("permissions",
ListBuilder().Append("lockScreen").Build())
.Build())
.Build();
ExtensionRegistry::Get(browser_context())->AddEnabled(extension);
return extension;
}
void set_can_create_deprecated_value_store(bool value) {
can_create_deprecated_value_store_ = value;
}
private:
OperationQueue* GetOrCreateOperations(const std::string& id) {
OperationQueue* operation_queue = GetOperations(id);
if (!operation_queue) {
operations_[id] =
std::make_unique<OperationQueue>(id, item_registry_.get());
operation_queue = operations_[id].get();
}
return operation_queue;
}
// Callback for creating value store cache - this is the callback passed to
// LockScreenItemStorage via SetValueStoreCacheFactoryForTesting.
std::unique_ptr<LocalValueStoreCache> CreateValueStoreCache(
const base::FilePath& root) {
std::vector<base::FilePath> allowed_paths = {
test_dir_.GetPath()
.AppendASCII("value_store")
.AppendASCII(kTestUserIdHash)};
if (can_create_deprecated_value_store_) {
allowed_paths.push_back(
test_dir_.GetPath().AppendASCII("deprecated_value_store"));
}
EXPECT_TRUE(base::Contains(allowed_paths, root))
<< "Unexpected value store path " << root.value();
return std::make_unique<LocalValueStoreCache>(
base::MakeRefCounted<value_store::TestValueStoreFactory>());
}
// Callback for creating value store migrator - this is the callback passed to
// LockScreenItemStorage via SetValueStoreMigratorFactoryForTesting.
std::unique_ptr<LockScreenValueStoreMigrator> CreateMigrator() {
auto result = std::make_unique<TestLockScreenValueStoreMigrator>();
value_store_migrator_ = result.get();
return result;
}
// Callback for creating test data items - this is the callback passed to
// LockScreenItemStorage via SetItemFactoryForTesting.
std::unique_ptr<DataItem> CreateItem(const std::string& id,
const std::string& extension_id,
const std::string& crypto_key) {
EXPECT_EQ(extension()->id(), extension_id);
EXPECT_EQ(kTestSymmetricKey, crypto_key);
OperationQueue* operation_queue = GetOrCreateOperations(id);
// If there is an operation queue for the item id, reuse it in order to
// retain state on LockScreenItemStorage restart.
return std::make_unique<TestDataItem>(id, extension_id, crypto_key,
operation_queue);
}
void GetRegisteredItems(const std::string& extension_id,
DataItem::RegisteredValuesCallback callback) {
if (extension()->id() != extension_id) {
std::move(callback).Run(OperationResult::kUnknownExtension, nullptr);
return;
}
item_registry_->HandleGetRequest(std::move(callback));
}
void RemoveAllItems(const std::string& extension_id,
base::OnceClosure callback) {
ASSERT_EQ(extension()->id(), extension_id);
item_registry_->RemoveAll();
std::move(callback).Run();
}
std::unique_ptr<LockScreenItemStorage> lock_screen_item_storage_;
content::TestBrowserContext lock_screen_context_;
TestingPrefServiceSimple local_state_;
base::ScopedTempDir test_dir_;
sync_preferences::TestingPrefServiceSyncable testing_pref_service_;
LockScreenItemStorage::ValueStoreCacheFactoryCallback
value_store_cache_factory_;
LockScreenItemStorage::ValueStoreMigratorFactoryCallback migrator_factory_;
LockScreenItemStorage::ItemFactoryCallback item_factory_;
LockScreenItemStorage::RegisteredItemsGetter registered_items_getter_;
LockScreenItemStorage::ItemStoreDeleter item_store_deleter_;
scoped_refptr<const Extension> extension_;
std::unique_ptr<ItemRegistry> item_registry_;
std::map<std::string, std::unique_ptr<OperationQueue>> operations_;
// Whether the test is expected to create deprecated value store version.
bool can_create_deprecated_value_store_ = false;
TestLockScreenValueStoreMigrator* value_store_migrator_ = nullptr;
};
} // namespace
TEST_F(LockScreenItemStorageTest, GetDependingOnSessionState) {
// Session state not initialized.
EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(browser_context()));
EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(lock_screen_context()));
// Locked session.
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(browser_context()));
EXPECT_EQ(lock_screen_item_storage(),
LockScreenItemStorage::GetIfAllowed(lock_screen_context()));
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(lock_screen_item_storage(),
LockScreenItemStorage::GetIfAllowed(browser_context()));
EXPECT_FALSE(LockScreenItemStorage::GetIfAllowed(lock_screen_context()));
}
TEST_F(LockScreenItemStorageTest, SetAndGetContent) {
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_FALSE(value_store_migrator());
const DataItem* item = CreateNewItem();
ASSERT_TRUE(item);
std::vector<std::string> all_items;
GetAllItems(&all_items);
ASSERT_EQ(1u, all_items.size());
EXPECT_EQ(item->id(), all_items[0]);
OperationQueue* item_operations = GetOperations(item->id());
ASSERT_TRUE(item_operations);
EXPECT_FALSE(item_operations->HasPendingOperations());
std::vector<char> content = {'f', 'i', 'l', 'e'};
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), item->id(), content,
base::BindOnce(&RecordWriteResult, &write_result));
item_operations->CompleteNextOperation(OperationQueue::OperationType::kWrite,
OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, write_result);
EXPECT_EQ(content, item_operations->content());
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), item->id(),
base::BindOnce(&RecordReadResult, &read_result, &read_content));
item_operations->CompleteNextOperation(OperationQueue::OperationType::kRead,
OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, read_result);
EXPECT_EQ(content, *read_content);
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), item->id(),
base::BindOnce(&RecordWriteResult, &delete_result));
item_operations->CompleteNextOperation(OperationQueue::OperationType::kDelete,
OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, delete_result);
EXPECT_TRUE(item_operations->deleted());
}
TEST_F(LockScreenItemStorageTest, FailToInitializeData) {
lock_screen_item_storage()->SetSessionLocked(true);
const DataItem* item = CreateNewItem();
ASSERT_TRUE(item);
const std::string item_id = item->id();
ResetLockScreenItemStorage();
item_registry()->set_fail(true);
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), item_id, {'x'},
base::BindOnce(&RecordWriteResult, &write_result));
EXPECT_EQ(OperationResult::kNotFound, write_result);
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), item_id,
base::BindOnce(&RecordReadResult, &read_result, &read_content));
EXPECT_EQ(OperationResult::kNotFound, read_result);
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), "non_existen",
base::BindOnce(&RecordWriteResult, &delete_result));
EXPECT_EQ(OperationResult::kNotFound, delete_result);
OperationQueue* operations = GetOperations(item_id);
ASSERT_TRUE(operations);
EXPECT_FALSE(operations->HasPendingOperations());
item_registry()->set_fail(false);
const DataItem* new_item = CreateNewItem();
ASSERT_TRUE(new_item);
write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), new_item->id(), {'y'},
base::BindOnce(&RecordWriteResult, &write_result));
OperationQueue* new_item_operations = GetOperations(new_item->id());
ASSERT_TRUE(new_item_operations);
new_item_operations->CompleteNextOperation(
OperationQueue::OperationType::kWrite, OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, write_result);
std::vector<std::string> items;
GetAllItems(&items);
ASSERT_EQ(1u, items.size());
EXPECT_EQ(new_item->id(), items[0]);
}
TEST_F(LockScreenItemStorageTest, RequestsDuringInitialLoad) {
lock_screen_item_storage()->SetSessionLocked(true);
const DataItem* item = CreateNewItem();
ASSERT_TRUE(item);
const std::string item_id = item->id();
item_registry()->set_throttle_get(true);
ResetLockScreenItemStorage();
EXPECT_FALSE(item_registry()->HasPendingCallback());
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), item_id, {'x'},
base::BindOnce(&RecordWriteResult, &write_result));
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), item_id,
base::BindOnce(&RecordReadResult, &read_result, &read_content));
std::vector<std::string> items;
lock_screen_item_storage()->GetAllForExtension(
extension()->id(), base::BindOnce(&RecordGetAllItemsResult, &items));
EXPECT_TRUE(items.empty());
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), item_id,
base::BindOnce(&RecordWriteResult, &delete_result));
OperationQueue* operations = GetOperations(item_id);
ASSERT_TRUE(operations);
EXPECT_FALSE(operations->HasPendingOperations());
OperationResult create_result = OperationResult::kFailed;
const DataItem* new_item = nullptr;
lock_screen_item_storage()->CreateItem(
extension()->id(),
base::BindOnce(&RecordCreateResult, &create_result, &new_item));
EXPECT_FALSE(new_item);
EXPECT_TRUE(item_registry()->HasPendingCallback());
item_registry()->RunPendingCallback();
EXPECT_TRUE(operations->HasPendingOperations());
operations->CompleteNextOperation(OperationQueue::OperationType::kWrite,
OperationResult::kSuccess);
operations->CompleteNextOperation(OperationQueue::OperationType::kRead,
OperationResult::kSuccess);
operations->CompleteNextOperation(OperationQueue::OperationType::kDelete,
OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, write_result);
EXPECT_EQ(OperationResult::kSuccess, read_result);
ASSERT_TRUE(read_content);
EXPECT_EQ(std::vector<char>({'x'}), *read_content);
EXPECT_EQ(OperationResult::kSuccess, delete_result);
EXPECT_EQ(OperationResult::kSuccess, create_result);
EXPECT_TRUE(new_item);
ASSERT_EQ(1u, items.size());
EXPECT_EQ(item_id, items[0]);
GetAllItems(&items);
ASSERT_EQ(1u, items.size());
EXPECT_EQ(new_item->id(), items[0]);
}
TEST_F(LockScreenItemStorageTest, HandleNonExistent) {
lock_screen_item_storage()->SetSessionLocked(true);
const DataItem* item = CreateNewItem();
ASSERT_TRUE(item);
std::vector<char> content = {'x'};
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), "non_existent", content,
base::BindOnce(&RecordWriteResult, &write_result));
EXPECT_EQ(OperationResult::kNotFound, write_result);
write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
"non_existent", item->id(), content,
base::BindOnce(&RecordWriteResult, &write_result));
EXPECT_EQ(OperationResult::kNotFound, write_result);
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), "non_existent",
base::BindOnce(&RecordReadResult, &read_result, &read_content));
EXPECT_EQ(OperationResult::kNotFound, read_result);
read_result = OperationResult::kFailed;
lock_screen_item_storage()->GetItemContent(
"non_existent", item->id(),
base::BindOnce(&RecordReadResult, &read_result, &read_content));
EXPECT_EQ(OperationResult::kNotFound, read_result);
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), "non_existen",
base::BindOnce(&RecordWriteResult, &delete_result));
EXPECT_EQ(OperationResult::kNotFound, delete_result);
delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
"non_existent", item->id(),
base::BindOnce(&RecordWriteResult, &delete_result));
EXPECT_EQ(OperationResult::kNotFound, delete_result);
}
TEST_F(LockScreenItemStorageTest, HandleFailure) {
lock_screen_item_storage()->SetSessionLocked(true);
const DataItem* item = CreateItemWithContent({'x'});
ASSERT_TRUE(item);
OperationQueue* operations = GetOperations(item->id());
ASSERT_TRUE(operations);
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), item->id(), {'x'},
base::BindOnce(&RecordWriteResult, &write_result));
operations->CompleteNextOperation(OperationQueue::OperationType::kWrite,
OperationResult::kInvalidKey);
EXPECT_EQ(OperationResult::kInvalidKey, write_result);
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), item->id(),
base::BindOnce(&RecordReadResult, &read_result, &read_content));
operations->CompleteNextOperation(OperationQueue::OperationType::kRead,
OperationResult::kWrongKey);
EXPECT_EQ(OperationResult::kWrongKey, read_result);
EXPECT_FALSE(read_content);
EXPECT_FALSE(operations->HasPendingOperations());
}
TEST_F(LockScreenItemStorageTest, DataItemsAvailableEventOnUnlock) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
EXPECT_TRUE(event_router->was_locked_values().empty());
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_TRUE(event_router->was_locked_values().empty());
// No event since no data items associated with the app exist.
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_TRUE(event_router->was_locked_values().empty());
lock_screen_item_storage()->SetSessionLocked(true);
const DataItem* item = CreateItemWithContent({'f', 'i', 'l', 'e', '1'});
const std::string item_id = item->id();
EXPECT_TRUE(event_router->was_locked_values().empty());
// There's an available data item, so unlock should trigger the event.
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({true}), event_router->was_locked_values());
event_router->ClearWasLockedValues();
// Update the item content while the session is unlocked.
EXPECT_TRUE(SetItemContent(item_id, {'f', 'i', 'l', 'e', '2'}));
lock_screen_item_storage()->SetSessionLocked(true);
// Data item is still around - notify the app it's available.
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({true}), event_router->was_locked_values());
event_router->ClearWasLockedValues();
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_TRUE(SetItemContent(item_id, {'f', 'i', 'l', 'e', '3'}));
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({true}), event_router->was_locked_values());
event_router->ClearWasLockedValues();
// When the item is deleted, the data item avilable event should stop firing.
OperationResult delete_result;
lock_screen_item_storage()->DeleteItem(
extension()->id(), item_id,
base::BindOnce(&RecordWriteResult, &delete_result));
OperationQueue* operations = GetOperations(item_id);
ASSERT_TRUE(operations);
operations->CompleteNextOperation(OperationQueue::OperationType::kDelete,
OperationResult::kSuccess);
lock_screen_item_storage()->SetSessionLocked(false);
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_TRUE(event_router->was_locked_values().empty());
}
TEST_F(LockScreenItemStorageTest,
NoDataItemsAvailableEventAfterFailedCreation) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
lock_screen_item_storage()->SetSessionLocked(true);
item_registry()->set_allow_new(false);
OperationResult create_result = OperationResult::kFailed;
const DataItem* item = nullptr;
lock_screen_item_storage()->CreateItem(
extension()->id(),
base::BindOnce(&RecordCreateResult, &create_result, &item));
EXPECT_EQ(OperationResult::kFailed, create_result);
lock_screen_item_storage()->SetSessionLocked(false);
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_TRUE(event_router->was_locked_values().empty());
}
TEST_F(LockScreenItemStorageTest, DataItemsAvailableEventOnRestart) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
EXPECT_TRUE(event_router->was_locked_values().empty());
lock_screen_item_storage()->SetSessionLocked(true);
EXPECT_TRUE(event_router->was_locked_values().empty());
const DataItem* item = CreateItemWithContent({'f', 'i', 'l', 'e', '1'});
EXPECT_TRUE(event_router->was_locked_values().empty());
const std::string item_id = item->id();
ResetLockScreenItemStorage();
EXPECT_TRUE(event_router->was_locked_values().empty());
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({false}), event_router->was_locked_values());
event_router->ClearWasLockedValues();
// The event should be dispatched on next unlock event, as long as a valid
// item exists.
ResetLockScreenItemStorage();
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({false}), event_router->was_locked_values());
event_router->ClearWasLockedValues();
ResetLockScreenItemStorage();
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), item_id,
base::BindOnce(&RecordWriteResult, &delete_result));
OperationQueue* operations = GetOperations(item_id);
ASSERT_TRUE(operations);
operations->CompleteNextOperation(OperationQueue::OperationType::kDelete,
OperationResult::kSuccess);
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_TRUE(event_router->was_locked_values().empty());
}
TEST_F(LockScreenItemStorageTest, ClearDataOnUninstall) {
const DataItem* item = CreateItemWithContent({'x'});
ASSERT_TRUE(item);
ExtensionRegistry::Get(browser_context())->RemoveEnabled(extension()->id());
ExtensionRegistry::Get(browser_context())
->TriggerOnUninstalled(extension(), UNINSTALL_REASON_FOR_TESTING);
ExtensionRegistry::Get(browser_context())->AddEnabled(extension());
std::vector<std::string> items;
GetAllItems(&items);
EXPECT_TRUE(items.empty());
}
TEST_F(LockScreenItemStorageTest,
ClearOnUninstallWhileLockScreenItemStorageNotSet) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
const DataItem* item = CreateItemWithContent({'x'});
ASSERT_TRUE(item);
UnsetLockScreenItemStorage();
ExtensionRegistry::Get(browser_context())->RemoveEnabled(extension()->id());
ExtensionRegistry::Get(browser_context())
->TriggerOnUninstalled(extension(), UNINSTALL_REASON_FOR_TESTING);
ResetLockScreenItemStorage();
ExtensionRegistry::Get(browser_context())->AddEnabled(extension());
lock_screen_item_storage()->SetSessionLocked(false);
std::vector<std::string> items;
GetAllItems(&items);
EXPECT_TRUE(items.empty());
EXPECT_TRUE(event_router->was_locked_values().empty());
}
TEST_F(LockScreenItemStorageTest, OperationsBlockedOnMigration) {
EXPECT_FALSE(value_store_migrator());
lock_screen_item_storage()->SetSessionLocked(true);
// Create an item in the extension's value store.
const DataItem* initial_item = CreateNewItem();
ASSERT_TRUE(initial_item);
const std::string initial_item_id = initial_item->id();
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
scoped_refptr<const Extension> second_extension =
CreateTestExtension(kSecondTestExtensionId);
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 2 /*item_count*/},
{second_extension->id(), 1 /*storage_version*/, 2 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
// This time, the lock screen item storage should have created the store
// migrator.
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({extension()->id(), second_extension->id()}),
value_store_migrator()->extensions_to_migrate());
const std::string migrated_item_id = "migrated";
const std::vector<char> migrated_item_content = {'a', 'b', 'c'};
// Verify that requests for the migrating extension are throttled while
// migration is in progress.
OperationResult write_result = OperationResult::kFailed;
lock_screen_item_storage()->SetItemContent(
extension()->id(), initial_item_id, {'x'},
base::BindOnce(&RecordWriteResult, &write_result));
OperationResult read_result = OperationResult::kFailed;
std::unique_ptr<std::vector<char>> read_content;
lock_screen_item_storage()->GetItemContent(
extension()->id(), migrated_item_id,
base::BindOnce(&RecordReadResult, &read_result, &read_content));
std::vector<std::string> items;
lock_screen_item_storage()->GetAllForExtension(
extension()->id(), base::BindOnce(&RecordGetAllItemsResult, &items));
EXPECT_TRUE(items.empty());
OperationResult delete_result = OperationResult::kFailed;
lock_screen_item_storage()->DeleteItem(
extension()->id(), initial_item_id,
base::BindOnce(&RecordWriteResult, &delete_result));
OperationQueue* initial_item_operations = GetOperations(initial_item_id);
ASSERT_TRUE(initial_item_operations);
EXPECT_FALSE(initial_item_operations->HasPendingOperations());
OperationQueue* migrated_item_operations = GetOperations(migrated_item_id);
EXPECT_FALSE(migrated_item_operations &&
migrated_item_operations->HasPendingOperations());
// Extension's data operations should not be unblocked by another extension
// migration finishing.
FinishMigration(second_extension->id(), std::vector<MigratedItem>());
EXPECT_FALSE(initial_item_operations->HasPendingOperations());
EXPECT_FALSE(migrated_item_operations &&
migrated_item_operations->HasPendingOperations());
OperationResult create_result = OperationResult::kFailed;
const DataItem* new_item = nullptr;
lock_screen_item_storage()->CreateItem(
extension()->id(),
base::BindOnce(&RecordCreateResult, &create_result, &new_item));
EXPECT_FALSE(new_item);
// Finish item migration - all queued operations should be now run.
ASSERT_TRUE(FinishMigration(extension()->id(),
{{migrated_item_id, migrated_item_content}}));
migrated_item_operations = GetOperations(migrated_item_id);
ASSERT_TRUE(migrated_item_operations);
EXPECT_TRUE(migrated_item_operations->HasPendingOperations());
EXPECT_TRUE(initial_item_operations->HasPendingOperations());
initial_item_operations->CompleteNextOperation(
OperationQueue::OperationType::kWrite, OperationResult::kSuccess);
migrated_item_operations->CompleteNextOperation(
OperationQueue::OperationType::kRead, OperationResult::kSuccess);
initial_item_operations->CompleteNextOperation(
OperationQueue::OperationType::kDelete, OperationResult::kSuccess);
EXPECT_EQ(OperationResult::kSuccess, write_result);
EXPECT_EQ(OperationResult::kSuccess, read_result);
ASSERT_TRUE(read_content);
EXPECT_EQ(migrated_item_content, *read_content);
EXPECT_EQ(OperationResult::kSuccess, delete_result);
EXPECT_EQ(OperationResult::kSuccess, create_result);
EXPECT_TRUE(new_item);
EXPECT_EQ(2u, items.size());
EXPECT_TRUE(base::Contains(items, migrated_item_id));
EXPECT_TRUE(base::Contains(items, initial_item_id));
GetAllItems(&items);
ASSERT_EQ(2u, items.size());
EXPECT_TRUE(base::Contains(items, migrated_item_id));
EXPECT_TRUE(base::Contains(items, new_item->id()));
}
TEST_F(LockScreenItemStorageTest,
OperationsBlockedOnAnotherExtensionMigration) {
EXPECT_FALSE(value_store_migrator());
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
scoped_refptr<const Extension> second_extension =
CreateTestExtension(kSecondTestExtensionId);
InitExtensionLocalState(
{{extension()->id(), 2 /*storage_version*/, 1 /*item_count*/},
{second_extension->id(), 1 /*storage_version*/, 1 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
// The lock screen item storage should have created the store migrator for
// |second_extension| only.
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({second_extension->id()}),
value_store_migrator()->extensions_to_migrate());
// Operations on the |extension()| data items should proceed normally, given
// that its data is not being migrated.
const DataItem* item = CreateItemWithContent({'f', 'i', 'l', 'e', '1'});
EXPECT_TRUE(item);
std::vector<std::string> items;
GetAllItems(&items);
EXPECT_EQ(std::vector<std::string>{item->id()}, items);
}
TEST_F(LockScreenItemStorageTest, MigrationNotReAttemptedAfterSuccess) {
EXPECT_FALSE(value_store_migrator());
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 1 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({extension()->id()}),
value_store_migrator()->extensions_to_migrate());
const std::string migrated_item_id = "migrated";
const std::vector<char> migrated_item_content = {'a', 'b', 'c'};
ASSERT_TRUE(FinishMigration(extension()->id(),
{{migrated_item_id, migrated_item_content}}));
// Reset the storage, and verify that storage migrator is not recreated, as
// the data migration for the extension has been completed.
set_can_create_deprecated_value_store(false);
ResetLockScreenItemStorage();
EXPECT_FALSE(value_store_migrator());
std::vector<std::string> items;
GetAllItems(&items);
EXPECT_EQ(std::vector<std::string>{"migrated"}, items);
}
TEST_F(LockScreenItemStorageTest,
MigrationNotReAttemptedAfterSuccess_NoItemsMigrated) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
EXPECT_FALSE(value_store_migrator());
// Create an item in the extension's value store. The test will verify it's
// counted in the post-migration local state even if no additional items are
// migrated.
const DataItem* initial_item = CreateNewItem();
ASSERT_TRUE(initial_item);
const std::string initial_item_id = initial_item->id();
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 1 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
lock_screen_item_storage()->SetSessionLocked(true);
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({extension()->id()}),
value_store_migrator()->extensions_to_migrate());
ASSERT_TRUE(FinishMigration(extension()->id(), std::vector<MigratedItem>()));
// Make sure that extension is notified about available items after
// migration if no items got migrated, but an item existed in the target
// storage before migration started - e.g. if it's a left-over from previous
// migration attempt.
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_EQ(std::vector<bool>({true}), event_router->was_locked_values());
// Reset the storage, and verify that storage migrator is not recreated.
set_can_create_deprecated_value_store(false);
ResetLockScreenItemStorage();
EXPECT_FALSE(value_store_migrator());
std::vector<std::string> items;
GetAllItems(&items);
EXPECT_EQ(std::vector<std::string>{initial_item_id}, items);
}
TEST_F(LockScreenItemStorageTest,
ReAttemptMigrationIfStorageIsResetBeforeRegisteredItemsAreReFetched) {
EXPECT_FALSE(value_store_migrator());
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 1 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({extension()->id()}),
value_store_migrator()->extensions_to_migrate());
// Throttle registered item fetch, to delay update to the local state prefs
// after data migration.
item_registry()->set_throttle_get(true);
const std::string migrated_item_id = "migrated";
const std::vector<char> migrated_item_content = {'a', 'b', 'c'};
ASSERT_TRUE(FinishMigration(extension()->id(),
{{migrated_item_id, migrated_item_content}}));
// Reset the storage, and verify that storage migrator is created again, as
// item storage got reset before it refetched info about registered items.
ResetLockScreenItemStorage();
EXPECT_TRUE(value_store_migrator());
}
TEST_F(LockScreenItemStorageTest,
ItemAvailableEventNotSentIfItemsLostDuringMigration) {
TestEventRouter* event_router = static_cast<TestEventRouter*>(
extensions::EventRouterFactory::GetInstance()->SetTestingFactoryAndUse(
browser_context(),
base::BindRepeating(&TestEventRouterFactoryFunction)));
ASSERT_TRUE(event_router);
EXPECT_FALSE(value_store_migrator());
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 1 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
EXPECT_EQ(std::set<ExtensionId>({extension()->id()}),
value_store_migrator()->extensions_to_migrate());
lock_screen_item_storage()->SetSessionLocked(true);
ASSERT_TRUE(FinishMigration(extension()->id(), std::vector<MigratedItem>()));
// Make sure that extension is notified about available items after
// migration if no items got migrated, but an item exists in the storage.
lock_screen_item_storage()->SetSessionLocked(false);
EXPECT_TRUE(event_router->was_locked_values().empty());
// Reset the storage, and verify that storage migrator is not recreated.
set_can_create_deprecated_value_store(false);
ResetLockScreenItemStorage();
EXPECT_FALSE(value_store_migrator());
}
TEST_F(LockScreenItemStorageTest, AttemptMigrationEvenWhenNoDataRecorded) {
EXPECT_FALSE(value_store_migrator());
// Update the local state so it seems that lock screen notes have previously
// been used by the extension, but currently, no items are recorded to exist
// for the extension.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 0 /*item_count*/}});
// Verify that the migrator gets created regardles of the persisted item
// count - to handle an edge case where item storage is shut down after an
// item is created but before the item creation was recorded in the local
// state.
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
EXPECT_TRUE(value_store_migrator());
}
TEST_F(LockScreenItemStorageTest, ExtensionUninstalledDuringMigration) {
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 2 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
ExtensionRegistry::Get(browser_context())->RemoveEnabled(extension()->id());
ExtensionRegistry::Get(browser_context())
->TriggerOnUninstalled(extension(), UNINSTALL_REASON_FOR_TESTING);
ASSERT_TRUE(value_store_migrator()->FinishClearData(extension()->id()));
// There should be no migrator, as the extension data should have been
// cleared.
set_can_create_deprecated_value_store(false);
ResetLockScreenItemStorage();
EXPECT_FALSE(value_store_migrator());
}
TEST_F(LockScreenItemStorageTest,
ExtensionUninstalledDuringMigration_StorageResetBeforeDataCleared) {
// Update the local state so it seems that there are extensions that have
// entries in the deprecated value store, and recreate the item storage to
// pick up the local state changes.
InitExtensionLocalState(
{{extension()->id(), 1 /*storage_version*/, 2 /*item_count*/}});
set_can_create_deprecated_value_store(true);
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
ExtensionRegistry::Get(browser_context())->RemoveEnabled(extension()->id());
ExtensionRegistry::Get(browser_context())
->TriggerOnUninstalled(extension(), UNINSTALL_REASON_FOR_TESTING);
EXPECT_TRUE(
value_store_migrator()->ClearingDataForExtension(extension()->id()));
// Reset lock screen storage before data cleanup completes.
// When the storage is recreated it should create migrator and start data
// cleanup right away.
ResetLockScreenItemStorage();
ASSERT_TRUE(value_store_migrator());
EXPECT_TRUE(
value_store_migrator()->ClearingDataForExtension(extension()->id()));
ASSERT_TRUE(value_store_migrator()->FinishClearData(extension()->id()));
// There should be no migrator, as the extension data should have been
// cleared.
set_can_create_deprecated_value_store(false);
ResetLockScreenItemStorage();
EXPECT_FALSE(value_store_migrator());
}
} // namespace lock_screen_data
} // namespace extensions