blob: 0c64aa3c0efe1806ebe93825a54a6300ae0b580b [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 "chrome/browser/ui/app_list/app_list_syncable_service.h"
#include "base/files/scoped_temp_dir.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/app_list/app_list_test_util.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/crx_file/id_util.h"
#include "components/sync/model/attachments/attachment_service_proxy_for_test.h"
#include "components/sync/model/fake_sync_change_processor.h"
#include "components/sync/model/sync_error_factory.h"
#include "components/sync/model/sync_error_factory_mock.h"
#include "components/sync/protocol/sync.pb.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/constants.h"
#include "ui/app_list/app_list_item.h"
#include "ui/app_list/app_list_model.h"
using namespace crx_file::id_util;
namespace {
scoped_refptr<extensions::Extension> MakeApp(
const std::string& name,
const std::string& id,
extensions::Extension::InitFromValueFlags flags) {
std::string err;
base::DictionaryValue value;
value.SetString("name", name);
value.SetString("version", "0.0");
value.SetString("app.launch.web_url", "http://google.com");
scoped_refptr<extensions::Extension> app = extensions::Extension::Create(
base::FilePath(), extensions::Manifest::INTERNAL, value, flags, id, &err);
EXPECT_EQ(err, "");
return app;
}
// Creates next by natural sort ordering application id. Application id has to
// have 32 chars each in range 'a' to 'p' inclusively.
std::string CreateNextAppId(const std::string& app_id) {
DCHECK(crx_file::id_util::IdIsValid(app_id));
std::string next_app_id = app_id;
size_t index = next_app_id.length() - 1;
while (index > 0 && next_app_id[index] == 'p')
next_app_id[index--] = 'a';
DCHECK(next_app_id[index] != 'p');
next_app_id[index]++;
DCHECK(crx_file::id_util::IdIsValid(next_app_id));
return next_app_id;
}
constexpr char kUnset[] = "__unset__";
constexpr char kDefault[] = "__default__";
// These constants are defined as functions so their values can be derived via
// function calls. The constant naming scheme is kept to maintain readability.
const std::string kInvalidOrdinalsId() {
return GenerateId("invalid_ordinals");
}
const std::string kEmptyItemNameId() {
return GenerateId("empty_item_name");
}
const std::string kEmptyItemNameUnsetId() {
return GenerateId("empty_item_name_unset");
}
const std::string kEmptyParentId() {
return GenerateId("empty_parent_id");
}
const std::string kEmptyParentUnsetId() {
return GenerateId("empty_parent_id_unset");
}
const std::string kEmptyOrdinalsId() {
return GenerateId("empty_ordinals");
}
const std::string kEmptyOrdinalsUnsetId() {
return GenerateId("empty_ordinals_unset");
}
const std::string kDupeItemId() {
return GenerateId("dupe_item_id");
}
const std::string kParentId() {
return GenerateId("parent_id");
}
syncer::SyncData CreateAppRemoteData(const std::string& id,
const std::string& name,
const std::string& parent_id,
const std::string& item_ordinal,
const std::string& item_pin_ordinal) {
sync_pb::EntitySpecifics specifics;
sync_pb::AppListSpecifics* app_list = specifics.mutable_app_list();
if (id != kUnset)
app_list->set_item_id(id);
app_list->set_item_type(sync_pb::AppListSpecifics_AppListItemType_TYPE_APP);
if (name != kUnset)
app_list->set_item_name(name);
if (parent_id != kUnset)
app_list->set_parent_id(parent_id);
if (item_ordinal != kUnset)
app_list->set_item_ordinal(item_ordinal);
if (item_pin_ordinal != kUnset)
app_list->set_item_pin_ordinal(item_pin_ordinal);
return syncer::SyncData::CreateRemoteData(
std::hash<std::string>{}(id), specifics, base::Time(),
syncer::AttachmentIdList(),
syncer::AttachmentServiceProxyForTest::Create());
}
syncer::SyncDataList CreateBadAppRemoteData(const std::string& id) {
syncer::SyncDataList sync_list;
// Invalid item_ordinal and item_pin_ordinal.
sync_list.push_back(CreateAppRemoteData(
id == kDefault ? kInvalidOrdinalsId() : id, "item_name", kParentId(),
"$$invalid_ordinal$$", "$$invalid_ordinal$$"));
// Empty item name.
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyItemNameId() : id, "",
kParentId(), "ordinal", "pinordinal"));
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyItemNameUnsetId() : id, kUnset,
kParentId(), "ordinal", "pinordinal"));
// Empty parent ID.
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyParentId() : id, "item_name",
"", "ordinal", "pinordinal"));
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyParentUnsetId() : id,
"item_name", kUnset, "ordinal", "pinordinal"));
// Empty item_ordinal and item_pin_ordinal.
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyOrdinalsId() : id, "item_name",
kParentId(), "", ""));
sync_list.push_back(
CreateAppRemoteData(id == kDefault ? kEmptyOrdinalsUnsetId() : id,
"item_name", kParentId(), kUnset, kUnset));
// Duplicate item_id.
sync_list.push_back(CreateAppRemoteData(id == kDefault ? kDupeItemId() : id,
"item_name", kParentId(), "ordinal",
"pinordinal"));
sync_list.push_back(CreateAppRemoteData(id == kDefault ? kDupeItemId() : id,
"item_name_dupe", kParentId(),
"ordinal", "pinordinal"));
// Empty item_id.
sync_list.push_back(CreateAppRemoteData("", "item_name", kParentId(),
"ordinal", "pinordinal"));
sync_list.push_back(CreateAppRemoteData(kUnset, "item_name", kParentId(),
"ordinal", "pinordinal"));
// All fields empty.
sync_list.push_back(CreateAppRemoteData("", "", "", "", ""));
sync_list.push_back(
CreateAppRemoteData(kUnset, kUnset, kUnset, kUnset, kUnset));
return sync_list;
}
} // namespace
class AppListSyncableServiceTest : public AppListTestBase {
public:
AppListSyncableServiceTest() = default;
~AppListSyncableServiceTest() override = default;
void SetUp() override {
AppListTestBase::SetUp();
// Make sure we have a Profile Manager.
DCHECK(temp_dir_.CreateUniqueTempDir());
TestingBrowserProcess::GetGlobal()->SetProfileManager(
new ProfileManagerWithoutInit(temp_dir_.GetPath()));
extensions::ExtensionSystem* extension_system =
extensions::ExtensionSystem::Get(profile_.get());
DCHECK(extension_system);
app_list_syncable_service_.reset(
new app_list::AppListSyncableService(profile_.get(), extension_system));
}
void TearDown() override { app_list_syncable_service_.reset(); }
app_list::AppListModel* model() {
return app_list_syncable_service_->GetModel();
}
const app_list::AppListSyncableService::SyncItem* GetSyncItem(
const std::string& id) const {
return app_list_syncable_service_->GetSyncItem(id);
}
protected:
app_list::AppListSyncableService* app_list_syncable_service() {
return app_list_syncable_service_.get();
}
private:
base::ScopedTempDir temp_dir_;
std::unique_ptr<app_list::AppListSyncableService> app_list_syncable_service_;
DISALLOW_COPY_AND_ASSIGN(AppListSyncableServiceTest);
};
TEST_F(AppListSyncableServiceTest, OEMFolderForConflictingPos) {
// Create a "web store" app.
const std::string web_store_app_id(extensions::kWebStoreAppId);
scoped_refptr<extensions::Extension> store =
MakeApp("webstore", web_store_app_id,
extensions::Extension::WAS_INSTALLED_BY_DEFAULT);
service_->AddExtension(store.get());
// Create some app. Note its id should be greater than web store app id in
// order to move app in case of conflicting pos after web store app.
const std::string some_app_id = CreateNextAppId(extensions::kWebStoreAppId);
scoped_refptr<extensions::Extension> some_app =
MakeApp("some_app", some_app_id,
extensions ::Extension::WAS_INSTALLED_BY_DEFAULT);
service_->AddExtension(some_app.get());
app_list::AppListItem* web_store_item = model()->FindItem(web_store_app_id);
ASSERT_TRUE(web_store_item);
app_list::AppListItem* some_app_item = model()->FindItem(some_app_id);
ASSERT_TRUE(some_app_item);
// Simulate position conflict.
model()->SetItemPosition(web_store_item, some_app_item->position());
// Install an OEM app. It must be placed by default after web store app but in
// case of app of the same position should be shifted next.
const std::string oem_app_id = CreateNextAppId(some_app_id);
scoped_refptr<extensions::Extension> oem_app = MakeApp(
"oem_app", oem_app_id, extensions::Extension::WAS_INSTALLED_BY_OEM);
service_->AddExtension(oem_app.get());
size_t web_store_app_index;
size_t some_app_index;
size_t oem_app_index;
size_t oem_folder_index;
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(
web_store_app_id, &web_store_app_index));
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(some_app_id,
&some_app_index));
// OEM item is not top level element.
EXPECT_FALSE(model()->top_level_item_list()->FindItemIndex(oem_app_id,
&oem_app_index));
// But OEM folder is.
EXPECT_TRUE(model()->top_level_item_list()->FindItemIndex(
app_list::AppListSyncableService::kOemFolderId, &oem_folder_index));
// Ensure right item sequence.
EXPECT_EQ(some_app_index, web_store_app_index + 1);
EXPECT_EQ(oem_folder_index, web_store_app_index + 2);
}
TEST_F(AppListSyncableServiceTest, InitialMerge) {
const std::string kItemId1 = GenerateId("item_id1");
const std::string kItemId2 = GenerateId("item_id2");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(kItemId1, "item_name1",
GenerateId("parent_id1"), "ordinal",
"pinordinal"));
sync_list.push_back(CreateAppRemoteData(kItemId2, "item_name2",
GenerateId("parent_id2"), "ordinal",
"pinordinal"));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
content::RunAllTasksUntilIdle();
ASSERT_TRUE(GetSyncItem(kItemId1));
EXPECT_EQ("item_name1", GetSyncItem(kItemId1)->item_name);
EXPECT_EQ(GenerateId("parent_id1"), GetSyncItem(kItemId1)->parent_id);
EXPECT_EQ("ordinal", GetSyncItem(kItemId1)->item_ordinal.ToDebugString());
EXPECT_EQ("pinordinal",
GetSyncItem(kItemId1)->item_pin_ordinal.ToDebugString());
ASSERT_TRUE(GetSyncItem(kItemId2));
EXPECT_EQ("item_name2", GetSyncItem(kItemId2)->item_name);
EXPECT_EQ(GenerateId("parent_id2"), GetSyncItem(kItemId2)->parent_id);
EXPECT_EQ("ordinal", GetSyncItem(kItemId2)->item_ordinal.ToDebugString());
EXPECT_EQ("pinordinal",
GetSyncItem(kItemId2)->item_pin_ordinal.ToDebugString());
}
TEST_F(AppListSyncableServiceTest, InitialMerge_BadData) {
const syncer::SyncDataList sync_list = CreateBadAppRemoteData(kDefault);
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
content::RunAllTasksUntilIdle();
// Invalid item_ordinal and item_pin_ordinal.
// Invalid item_ordinal is fixed up.
ASSERT_TRUE(GetSyncItem(kInvalidOrdinalsId()));
EXPECT_EQ("n",
GetSyncItem(kInvalidOrdinalsId())->item_ordinal.ToDebugString());
EXPECT_EQ(
"INVALID[$$invalid_ordinal$$]",
GetSyncItem(kInvalidOrdinalsId())->item_pin_ordinal.ToDebugString());
// Empty item name.
ASSERT_TRUE(GetSyncItem(kEmptyItemNameId()));
EXPECT_EQ("", GetSyncItem(kEmptyItemNameId())->item_name);
EXPECT_TRUE(GetSyncItem(kEmptyItemNameUnsetId()));
EXPECT_EQ("", GetSyncItem(kEmptyItemNameUnsetId())->item_name);
// Empty parent ID.
ASSERT_TRUE(GetSyncItem(kEmptyParentId()));
EXPECT_EQ("", GetSyncItem(kEmptyParentId())->parent_id);
EXPECT_TRUE(GetSyncItem(kEmptyParentUnsetId()));
EXPECT_EQ("", GetSyncItem(kEmptyParentUnsetId())->parent_id);
// Empty item_ordinal and item_pin_ordinal.
// Empty item_ordinal is fixed up.
ASSERT_TRUE(GetSyncItem(kEmptyOrdinalsId()));
EXPECT_EQ("n", GetSyncItem(kEmptyOrdinalsId())->item_ordinal.ToDebugString());
EXPECT_EQ("INVALID[]",
GetSyncItem(kEmptyOrdinalsId())->item_pin_ordinal.ToDebugString());
ASSERT_TRUE(GetSyncItem(kEmptyOrdinalsUnsetId()));
EXPECT_EQ("n",
GetSyncItem(kEmptyOrdinalsUnsetId())->item_ordinal.ToDebugString());
EXPECT_EQ(
"INVALID[]",
GetSyncItem(kEmptyOrdinalsUnsetId())->item_pin_ordinal.ToDebugString());
// Duplicate item_id overrides previous.
ASSERT_TRUE(GetSyncItem(kDupeItemId()));
EXPECT_EQ("item_name_dupe", GetSyncItem(kDupeItemId())->item_name);
}
TEST_F(AppListSyncableServiceTest, InitialMergeAndUpdate) {
const std::string kItemId1 = GenerateId("item_id1");
const std::string kItemId2 = GenerateId("item_id2");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(kItemId1, "item_name1", kParentId(),
"ordinal", "pinordinal"));
sync_list.push_back(CreateAppRemoteData(kItemId2, "item_name2", kParentId(),
"ordinal", "pinordinal"));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
content::RunAllTasksUntilIdle();
ASSERT_TRUE(GetSyncItem(kItemId1));
ASSERT_TRUE(GetSyncItem(kItemId2));
syncer::SyncChangeList change_list;
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateAppRemoteData(kItemId1, "item_name1x", GenerateId("parent_id1x"),
"ordinalx", "pinordinalx")));
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE,
CreateAppRemoteData(kItemId2, "item_name2x", GenerateId("parent_id2x"),
"ordinalx", "pinordinalx")));
app_list_syncable_service()->ProcessSyncChanges(base::Location(),
change_list);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(GetSyncItem(kItemId1));
EXPECT_EQ("item_name1x", GetSyncItem(kItemId1)->item_name);
EXPECT_EQ(GenerateId("parent_id1x"), GetSyncItem(kItemId1)->parent_id);
EXPECT_EQ("ordinalx", GetSyncItem(kItemId1)->item_ordinal.ToDebugString());
EXPECT_EQ("pinordinalx",
GetSyncItem(kItemId1)->item_pin_ordinal.ToDebugString());
ASSERT_TRUE(GetSyncItem(kItemId2));
EXPECT_EQ("item_name2x", GetSyncItem(kItemId2)->item_name);
EXPECT_EQ(GenerateId("parent_id2x"), GetSyncItem(kItemId2)->parent_id);
EXPECT_EQ("ordinalx", GetSyncItem(kItemId2)->item_ordinal.ToDebugString());
EXPECT_EQ("pinordinalx",
GetSyncItem(kItemId2)->item_pin_ordinal.ToDebugString());
}
TEST_F(AppListSyncableServiceTest, InitialMergeAndUpdate_BadData) {
const std::string kItemId = GenerateId("item_id");
syncer::SyncDataList sync_list;
sync_list.push_back(CreateAppRemoteData(kItemId, "item_name", kParentId(),
"ordinal", "pinordinal"));
app_list_syncable_service()->MergeDataAndStartSyncing(
syncer::APP_LIST, sync_list,
base::MakeUnique<syncer::FakeSyncChangeProcessor>(),
base::MakeUnique<syncer::SyncErrorFactoryMock>());
content::RunAllTasksUntilIdle();
ASSERT_TRUE(GetSyncItem(kItemId));
syncer::SyncChangeList change_list;
const syncer::SyncDataList update_list = CreateBadAppRemoteData(kItemId);
for (syncer::SyncDataList::const_iterator iter = update_list.begin();
iter != update_list.end(); ++iter) {
change_list.push_back(syncer::SyncChange(
FROM_HERE, syncer::SyncChange::ACTION_UPDATE, *iter));
}
// Validate items with bad data are processed without crashing.
app_list_syncable_service()->ProcessSyncChanges(base::Location(),
change_list);
content::RunAllTasksUntilIdle();
ASSERT_TRUE(GetSyncItem(kItemId));
}