blob: d816ab388ca51a1380fa17e4b2504647aa747671 [file] [log] [blame]
// Copyright 2018 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 "components/sync_bookmarks/bookmark_specifics_conversions.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/guid.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/test/test_bookmark_client.h"
#include "components/favicon/core/test/mock_favicon_service.h"
#include "components/sync/base/client_tag_hash.h"
#include "components/sync/base/unique_position.h"
#include "components/sync/protocol/bookmark_specifics.pb.h"
#include "components/sync/protocol/entity_data.h"
#include "components/sync/protocol/entity_specifics.pb.h"
#include "components/sync/protocol/model_type_state.pb.h"
#include "components/sync_bookmarks/synced_bookmark_tracker.h"
#include "components/sync_bookmarks/synced_bookmark_tracker_entity.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image.h"
#include "url/gurl.h"
namespace sync_bookmarks {
namespace {
using bookmarks::TestBookmarkClient;
using testing::_;
using testing::Eq;
using testing::Ge;
using testing::IsEmpty;
using testing::Not;
using testing::NotNull;
// Fork of enum InvalidBookmarkSpecificsError.
enum class InvalidBookmarkSpecificsError {
kEmptySpecifics = 0,
kInvalidURL = 1,
kIconURLWithoutFavicon = 2,
kInvalidIconURL = 3,
kNonUniqueMetaInfoKeys = 4,
kInvalidGUID = 5,
kInvalidParentGUID = 6,
kInvalidUniquePosition = 7,
kBannedGUID = 8,
kMaxValue = kBannedGUID,
};
sync_pb::UniquePosition RandomUniquePosition() {
return syncer::UniquePosition::InitialPosition(
syncer::UniquePosition::RandomSuffix())
.ToProto();
}
// Returns a single-color 16x16 image using |color|.
gfx::Image CreateTestImage(SkColor color) {
SkBitmap bitmap;
bitmap.allocN32Pixels(16, 16);
bitmap.eraseColor(color);
return gfx::Image::CreateFrom1xBitmap(bitmap);
}
TEST(BookmarkSpecificsConversionsTest, ShouldCreateSpecificsFromBookmarkNode) {
const GURL kUrl("http://www.url.com");
const std::string kTitle = "Title";
const base::Time kTime = base::Time::Now();
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
const syncer::UniquePosition kUniquePosition =
syncer::UniquePosition::InitialPosition(
syncer::UniquePosition::RandomSuffix());
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
kUrl);
ASSERT_THAT(node, NotNull());
model->SetDateAdded(node, kTime);
model->SetNodeMetaInfo(node, kKey1, kValue1);
model->SetNodeMetaInfo(node, kKey2, kValue2);
sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
node, model.get(), kUniquePosition.ToProto(),
/*force_favicon_load=*/false);
const sync_pb::BookmarkSpecifics& bm_specifics = specifics.bookmark();
EXPECT_THAT(bm_specifics.guid(), Eq(node->guid().AsLowercaseString()));
EXPECT_THAT(bm_specifics.parent_guid(),
Eq(bookmarks::BookmarkNode::kBookmarkBarNodeGuid));
EXPECT_THAT(bm_specifics.type(), Eq(sync_pb::BookmarkSpecifics::URL));
EXPECT_THAT(bm_specifics.legacy_canonicalized_title(), Eq(kTitle));
EXPECT_THAT(GURL(bm_specifics.url()), Eq(kUrl));
EXPECT_THAT(base::Time::FromDeltaSinceWindowsEpoch(
base::Microseconds(bm_specifics.creation_time_us())),
Eq(kTime));
EXPECT_TRUE(syncer::UniquePosition::FromProto(bm_specifics.unique_position())
.Equals(kUniquePosition));
for (const sync_pb::MetaInfo& meta_info : bm_specifics.meta_info()) {
std::string value;
node->GetMetaInfo(meta_info.key(), &value);
EXPECT_THAT(meta_info.value(), Eq(value));
}
}
TEST(BookmarkSpecificsConversionsTest,
ShouldCreateSpecificsFromBookmarkNodeWithIllegalTitle) {
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const std::vector<std::string> illegal_titles = {"", ".", ".."};
size_t index = 0;
for (const std::string& illegal_title : illegal_titles) {
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, index++, base::UTF8ToUTF16(illegal_title),
GURL("http://www.url.com"));
ASSERT_THAT(node, NotNull());
sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
node, model.get(), RandomUniquePosition(),
/*force_favicon_load=*/false);
// Legacy clients append a space to illegal titles.
EXPECT_THAT(specifics.bookmark().legacy_canonicalized_title(),
Eq(illegal_title + " "));
}
}
TEST(BookmarkSpecificsConversionsTest,
ShouldCreateSpecificsWithoutUrlFromFolderNode) {
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddFolder(
/*parent=*/bookmark_bar_node, /*index=*/0, u"Title");
ASSERT_THAT(node, NotNull());
sync_pb::EntitySpecifics specifics =
CreateSpecificsFromBookmarkNode(node, model.get(), RandomUniquePosition(),
/*force_favicon_load=*/false);
const sync_pb::BookmarkSpecifics& bm_specifics = specifics.bookmark();
EXPECT_FALSE(bm_specifics.has_url());
EXPECT_THAT(bm_specifics.type(), Eq(sync_pb::BookmarkSpecifics::FOLDER));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldLoadFaviconWhenCreatingSpecificsFromBookmarkNode) {
auto client = std::make_unique<TestBookmarkClient>();
TestBookmarkClient* client_ptr = client.get();
std::unique_ptr<bookmarks::BookmarkModel> model =
TestBookmarkClient::CreateModelWithClient(std::move(client));
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, u"Title",
GURL("http://www.url.com"));
ASSERT_THAT(node, NotNull());
ASSERT_FALSE(node->is_favicon_loaded());
ASSERT_FALSE(client_ptr->HasFaviconLoadTasks());
sync_pb::EntitySpecifics specifics =
CreateSpecificsFromBookmarkNode(node, model.get(), RandomUniquePosition(),
/*force_favicon_load=*/true);
EXPECT_TRUE(client_ptr->HasFaviconLoadTasks());
EXPECT_FALSE(specifics.bookmark().has_favicon());
EXPECT_FALSE(specifics.bookmark().has_icon_url());
}
TEST(BookmarkSpecificsConversionsTest,
ShouldNotLoadFaviconWhenCreatingSpecificsFromBookmarkNode) {
auto client = std::make_unique<TestBookmarkClient>();
TestBookmarkClient* client_ptr = client.get();
std::unique_ptr<bookmarks::BookmarkModel> model =
TestBookmarkClient::CreateModelWithClient(std::move(client));
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, u"Title",
GURL("http://www.url.com"));
ASSERT_THAT(node, NotNull());
ASSERT_FALSE(node->is_favicon_loaded());
ASSERT_FALSE(client_ptr->HasFaviconLoadTasks());
sync_pb::EntitySpecifics specifics = CreateSpecificsFromBookmarkNode(
node, model.get(), RandomUniquePosition(), /*force_favicon_load=*/false);
EXPECT_FALSE(client_ptr->HasFaviconLoadTasks());
EXPECT_FALSE(specifics.bookmark().has_favicon());
EXPECT_FALSE(specifics.bookmark().has_icon_url());
}
TEST(BookmarkSpecificsConversionsTest,
ShouldIncludeFaviconWhenCreatingSpecificsFromBookmarkNodeIfLoaded) {
const GURL kBookmarkUrl("http://www.url.com");
const GURL kIconUrl("http://www.icon-url.com");
const SkColor kColor = SK_ColorRED;
auto client = std::make_unique<TestBookmarkClient>();
TestBookmarkClient* client_ptr = client.get();
std::unique_ptr<bookmarks::BookmarkModel> model =
TestBookmarkClient::CreateModelWithClient(std::move(client));
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, u"Title", kBookmarkUrl);
ASSERT_THAT(node, NotNull());
ASSERT_FALSE(node->is_favicon_loaded());
// Complete the loading of the favicon as part of the test setup.
model->GetFavicon(node);
ASSERT_TRUE(client_ptr->HasFaviconLoadTasks());
client_ptr->SimulateFaviconLoaded(kBookmarkUrl, kIconUrl,
CreateTestImage(kColor));
ASSERT_TRUE(node->is_favicon_loaded());
sync_pb::EntitySpecifics specifics =
CreateSpecificsFromBookmarkNode(node, model.get(), RandomUniquePosition(),
/*force_favicon_load=*/false);
EXPECT_THAT(specifics.bookmark().favicon(), Not(IsEmpty()));
EXPECT_THAT(specifics.bookmark().icon_url(), Eq(kIconUrl));
// Verify that the |favicon| field is properly encoded.
const gfx::Image favicon = gfx::Image::CreateFrom1xPNGBytes(
reinterpret_cast<const unsigned char*>(
specifics.bookmark().favicon().data()),
specifics.bookmark().favicon().size());
EXPECT_THAT(favicon.Width(), Eq(16));
EXPECT_THAT(favicon.Height(), Eq(16));
EXPECT_THAT(favicon.AsBitmap().getColor(1, 1), Eq(kColor));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldOmitLargeFaviconUrlWhenCreatingSpecificsFromBookmarkNode) {
const GURL kBookmarkUrl("http://www.url.com");
const GURL kIconUrl(
base::StrCat({"http://www.icon-url.com/", std::string(5000, 'a')}));
const SkColor kColor = SK_ColorRED;
// This test uses a valid but very long icon URL, larger than
// |kMaxFaviconUrlSize|.
ASSERT_TRUE(kIconUrl.is_valid());
ASSERT_THAT(kIconUrl.spec().size(), Ge(5000u));
auto client = std::make_unique<TestBookmarkClient>();
TestBookmarkClient* client_ptr = client.get();
std::unique_ptr<bookmarks::BookmarkModel> model =
TestBookmarkClient::CreateModelWithClient(std::move(client));
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, u"Title", kBookmarkUrl);
ASSERT_THAT(node, NotNull());
ASSERT_FALSE(node->is_favicon_loaded());
// Complete the loading of the favicon as part of the test setup.
model->GetFavicon(node);
ASSERT_TRUE(client_ptr->HasFaviconLoadTasks());
client_ptr->SimulateFaviconLoaded(kBookmarkUrl, kIconUrl,
CreateTestImage(kColor));
ASSERT_TRUE(node->is_favicon_loaded());
sync_pb::EntitySpecifics specifics =
CreateSpecificsFromBookmarkNode(node, model.get(), RandomUniquePosition(),
/*force_favicon_load=*/false);
// The icon URL should be omitted (populated with the empty string).
EXPECT_TRUE(specifics.bookmark().has_icon_url());
EXPECT_THAT(specifics.bookmark().icon_url(), IsEmpty());
// The favicon image itself should be synced.
EXPECT_THAT(specifics.bookmark().favicon(), Not(IsEmpty()));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldCreateNonFolderBookmarkNodeFromSpecifics) {
const GURL kUrl("http://www.url.com");
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::string kTitle = "Title";
const base::Time kTime = base::Time::Now();
const GURL kIconUrl("http://www.icon-url.com");
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kUrl.spec());
bm_specifics.set_guid(kGuid.AsLowercaseString());
bm_specifics.set_icon_url(kIconUrl.spec());
bm_specifics.set_favicon("PNG");
bm_specifics.set_legacy_canonicalized_title(kTitle);
bm_specifics.set_creation_time_us(
kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
// Parent GUID and unique position are ignored by
// CreateBookmarkNodeFromSpecifics(), but are required here to pass DCHECKs.
bm_specifics.set_parent_guid(bookmarks::BookmarkNode::kBookmarkBarNodeGuid);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key(kKey1);
meta_info1->set_value(kValue1);
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key(kKey2);
meta_info2->set_value(kValue2);
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<favicon::MockFaviconService> favicon_service;
EXPECT_CALL(favicon_service,
AddPageNoVisitForBookmark(kUrl, base::UTF8ToUTF16(kTitle)));
EXPECT_CALL(favicon_service, MergeFavicon(kUrl, kIconUrl, _, _, _));
base::HistogramTester histogram_tester;
const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
bm_specifics,
/*parent=*/model->bookmark_bar_node(), /*index=*/0, model.get(),
&favicon_service);
ASSERT_THAT(node, NotNull());
EXPECT_THAT(node->guid(), Eq(kGuid));
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kTitle)));
EXPECT_FALSE(node->is_folder());
EXPECT_THAT(node->url(), Eq(kUrl));
EXPECT_THAT(node->date_added(), Eq(kTime));
std::string value1;
node->GetMetaInfo(kKey1, &value1);
EXPECT_THAT(value1, Eq(kValue1));
std::string value2;
node->GetMetaInfo(kKey2, &value2);
EXPECT_THAT(value2, Eq(kValue2));
histogram_tester.ExpectUniqueSample(
"Sync.BookmarkSpecificsExcludingFoldersContainFavicon",
/*sample=*/true,
/*expected_bucket_count=*/1);
}
TEST(BookmarkSpecificsConversionsTest, ShouldCreateFolderFromSpecifics) {
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::string kTitle = "Title";
const base::Time kTime = base::Time::Now();
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_guid(kGuid.AsLowercaseString());
bm_specifics.set_legacy_canonicalized_title(kTitle);
bm_specifics.set_creation_time_us(
kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
// Parent GUID and unique position are ignored by
// CreateBookmarkNodeFromSpecifics(), but are required here to pass DCHECKs.
bm_specifics.set_parent_guid(bookmarks::BookmarkNode::kBookmarkBarNodeGuid);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key(kKey1);
meta_info1->set_value(kValue1);
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key(kKey2);
meta_info2->set_value(kValue2);
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<favicon::MockFaviconService> favicon_service;
EXPECT_CALL(favicon_service, AddPageNoVisitForBookmark).Times(0);
EXPECT_CALL(favicon_service, MergeFavicon).Times(0);
EXPECT_CALL(favicon_service, DeleteFaviconMappings).Times(0);
base::HistogramTester histogram_tester;
const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
bm_specifics,
/*parent=*/model->bookmark_bar_node(), /*index=*/0, model.get(),
&favicon_service);
ASSERT_THAT(node, NotNull());
EXPECT_THAT(node->guid(), Eq(kGuid));
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kTitle)));
EXPECT_TRUE(node->is_folder());
// TODO(crbug.com/1214840): Folders should propagate the creation time into
// BookmarkModel, just like non-folders.
EXPECT_THAT(node->date_added(), Ge(kTime));
std::string value1;
node->GetMetaInfo(kKey1, &value1);
EXPECT_THAT(value1, Eq(kValue1));
std::string value2;
node->GetMetaInfo(kKey2, &value2);
EXPECT_THAT(value2, Eq(kValue2));
// The histogram should not be recorded for folders.
histogram_tester.ExpectTotalCount(
"Sync.BookmarkSpecificsExcludingFoldersContainFavicon", 0);
}
TEST(BookmarkSpecificsConversionsTest,
ShouldPreferFullTitleOnCreatingBookmarkNodeFromSpecifics) {
const GURL kUrl("http://www.url.com");
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::string kTitle = "Title";
const std::string kFullTitle = "Title Long Version";
const base::Time kTime = base::Time::Now();
const GURL kIconUrl("http://www.icon-url.com");
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kUrl.spec());
bm_specifics.set_guid(kGuid.AsLowercaseString());
bm_specifics.set_icon_url(kIconUrl.spec());
bm_specifics.set_favicon("PNG");
bm_specifics.set_legacy_canonicalized_title(kTitle);
bm_specifics.set_full_title(kFullTitle);
bm_specifics.set_creation_time_us(
kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
// Parent GUID and unique position are ignored by
// CreateBookmarkNodeFromSpecifics(), but are required here to pass DCHECKs.
bm_specifics.set_parent_guid(bookmarks::BookmarkNode::kBookmarkBarNodeGuid);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key(kKey1);
meta_info1->set_value(kValue1);
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key(kKey2);
meta_info2->set_value(kValue2);
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<favicon::MockFaviconService> favicon_service;
EXPECT_CALL(favicon_service,
AddPageNoVisitForBookmark(kUrl, base::UTF8ToUTF16(kFullTitle)));
EXPECT_CALL(favicon_service, MergeFavicon(kUrl, kIconUrl, _, _, _));
const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
bm_specifics,
/*parent=*/model->bookmark_bar_node(), /*index=*/0, model.get(),
&favicon_service);
ASSERT_THAT(node, NotNull());
EXPECT_THAT(node->guid(), Eq(kGuid));
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kFullTitle)));
EXPECT_THAT(node->url(), Eq(kUrl));
EXPECT_THAT(node->date_added(), Eq(kTime));
std::string value1;
node->GetMetaInfo(kKey1, &value1);
EXPECT_THAT(value1, Eq(kValue1));
std::string value2;
node->GetMetaInfo(kKey2, &value2);
EXPECT_THAT(value2, Eq(kValue2));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldCreateBookmarkNodeFromSpecificsWithIllegalTitle) {
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<favicon::MockFaviconService> favicon_service;
const std::vector<std::string> illegal_titles = {"", ".", ".."};
size_t index = 0;
for (const std::string& illegal_title : illegal_titles) {
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url("http://www.url.com");
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
// Legacy clients append an extra space to illegal clients.
bm_specifics.set_legacy_canonicalized_title(illegal_title + " ");
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
// Parent GUID and unique position are ignored by
// CreateBookmarkNodeFromSpecifics(), but are required here to pass DCHECKs.
bm_specifics.set_parent_guid(bookmarks::BookmarkNode::kBookmarkBarNodeGuid);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
const bookmarks::BookmarkNode* node =
CreateBookmarkNodeFromSpecifics(bm_specifics,
/*parent=*/model->bookmark_bar_node(),
index++, model.get(), &favicon_service);
ASSERT_THAT(node, NotNull());
// The node should be created without the extra space.
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(illegal_title)));
}
}
TEST(BookmarkSpecificsConversionsTest,
ShouldCreateBookmarkNodeFromSpecificsWithFaviconAndWithoutIconUrl) {
const GURL kUrl("http://www.url.com");
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::string kTitle = "Title";
const GURL kIconUrl("http://www.icon-url.com");
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kUrl.spec());
bm_specifics.set_guid(kGuid.AsLowercaseString());
bm_specifics.set_favicon("PNG");
bm_specifics.set_legacy_canonicalized_title(kTitle);
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
// Parent GUID and unique position are ignored by
// CreateBookmarkNodeFromSpecifics(), but are required here to pass DCHECKs.
bm_specifics.set_parent_guid(bookmarks::BookmarkNode::kBookmarkBarNodeGuid);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
testing::NiceMock<favicon::MockFaviconService> favicon_service;
// The favicon service should be called with page url since the icon url is
// missing.
EXPECT_CALL(favicon_service, MergeFavicon(kUrl, kUrl, _, _, _));
const bookmarks::BookmarkNode* node = CreateBookmarkNodeFromSpecifics(
bm_specifics,
/*parent=*/model->bookmark_bar_node(), /*index=*/0, model.get(),
&favicon_service);
EXPECT_THAT(node, NotNull());
}
TEST(BookmarkSpecificsConversionsTest, ShouldUpdateBookmarkNodeFromSpecifics) {
const GURL kUrl("http://www.url.com");
const std::string kTitle = "Title";
const base::Time kTime = base::Time::Now();
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
ASSERT_THAT(node, NotNull());
model->SetNodeMetaInfo(node, kKey1, kValue1);
model->SetNodeMetaInfo(node, kKey2, kValue2);
const GURL kNewUrl("http://www.new-url.com");
const std::string kNewTitle = "NewTitle";
const GURL kNewIconUrl("http://www.new-icon-url.com");
const std::string kNewValue1 = "new-value1";
const std::string kNewValue2 = "new-value2";
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kNewUrl.spec());
bm_specifics.set_guid(node->guid().AsLowercaseString());
bm_specifics.set_icon_url(kNewIconUrl.spec());
bm_specifics.set_favicon("PNG");
bm_specifics.set_legacy_canonicalized_title(kNewTitle);
bm_specifics.set_creation_time_us(
kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key(kKey1);
meta_info1->set_value(kNewValue1);
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key(kKey2);
meta_info2->set_value(kNewValue2);
testing::NiceMock<favicon::MockFaviconService> favicon_service;
EXPECT_CALL(favicon_service,
AddPageNoVisitForBookmark(kNewUrl, base::UTF8ToUTF16(kNewTitle)));
EXPECT_CALL(favicon_service, MergeFavicon(kNewUrl, kNewIconUrl, _, _, _));
UpdateBookmarkNodeFromSpecifics(bm_specifics, node, model.get(),
&favicon_service);
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kNewTitle)));
EXPECT_THAT(node->url(), Eq(kNewUrl));
std::string value1;
node->GetMetaInfo(kKey1, &value1);
EXPECT_THAT(value1, Eq(kNewValue1));
std::string value2;
node->GetMetaInfo(kKey2, &value2);
EXPECT_THAT(value2, Eq(kNewValue2));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldPreferFullTitleOnUpdateBookmarkNodeFromSpecifics) {
const GURL kUrl("http://www.url.com");
const std::string kTitle = "Title";
const base::Time kTime = base::Time::Now();
const std::string kKey1 = "key1";
const std::string kValue1 = "value1";
const std::string kKey2 = "key2";
const std::string kValue2 = "value2";
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
ASSERT_THAT(node, NotNull());
model->SetNodeMetaInfo(node, kKey1, kValue1);
model->SetNodeMetaInfo(node, kKey2, kValue2);
const GURL kNewUrl("http://www.new-url.com");
const std::string kNewTitle = "NewTitle";
const std::string kNewFullTitle = "NewTitle Long Version";
const GURL kNewIconUrl("http://www.new-icon-url.com");
const std::string kNewValue1 = "new-value1";
const std::string kNewValue2 = "new-value2";
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kNewUrl.spec());
bm_specifics.set_guid(node->guid().AsLowercaseString());
bm_specifics.set_icon_url(kNewIconUrl.spec());
bm_specifics.set_favicon("PNG");
bm_specifics.set_legacy_canonicalized_title(kNewTitle);
bm_specifics.set_full_title(kNewFullTitle);
bm_specifics.set_creation_time_us(
kTime.ToDeltaSinceWindowsEpoch().InMicroseconds());
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key(kKey1);
meta_info1->set_value(kNewValue1);
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key(kKey2);
meta_info2->set_value(kNewValue2);
testing::NiceMock<favicon::MockFaviconService> favicon_service;
EXPECT_CALL(favicon_service, AddPageNoVisitForBookmark(
kNewUrl, base::UTF8ToUTF16(kNewFullTitle)));
EXPECT_CALL(favicon_service, MergeFavicon(kNewUrl, kNewIconUrl, _, _, _));
UpdateBookmarkNodeFromSpecifics(bm_specifics, node, model.get(),
&favicon_service);
EXPECT_THAT(node->GetTitle(), Eq(base::UTF8ToUTF16(kNewFullTitle)));
EXPECT_THAT(node->url(), Eq(kNewUrl));
std::string value1;
node->GetMetaInfo(kKey1, &value1);
EXPECT_THAT(value1, Eq(kNewValue1));
std::string value2;
node->GetMetaInfo(kKey2, &value2);
EXPECT_THAT(value2, Eq(kNewValue2));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldUpdateBookmarkNodeFromSpecificsWithFaviconAndWithoutIconUrl) {
const GURL kUrl("http://www.url.com");
const std::string kTitle = "Title";
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const bookmarks::BookmarkNode* node = model->AddURL(
/*parent=*/bookmark_bar_node, /*index=*/0, base::UTF8ToUTF16(kTitle),
GURL(kUrl));
ASSERT_THAT(node, NotNull());
const GURL kNewUrl("http://www.new-url.com");
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url(kNewUrl.spec());
bm_specifics.set_guid(node->guid().AsLowercaseString());
bm_specifics.set_favicon("PNG");
testing::NiceMock<favicon::MockFaviconService> favicon_service;
// The favicon service should be called with page url since the icon url is
// missing.
EXPECT_CALL(favicon_service, MergeFavicon(kNewUrl, kNewUrl, _, _, _));
UpdateBookmarkNodeFromSpecifics(bm_specifics, node, model.get(),
&favicon_service);
}
TEST(BookmarkSpecificsConversionsTest, ShouldBeValidBookmarkSpecifics) {
sync_pb::BookmarkSpecifics bm_specifics;
// URL is irrelevant for a folder.
bm_specifics.set_url("INVALID_URL");
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_parent_guid(
base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
EXPECT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
ASSERT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
bm_specifics.set_url("http://www.valid-url.com");
EXPECT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeValidBookmarkSpecificsWithFaviconAndWithoutIconUrl) {
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url("http://www.valid-url.com");
bm_specifics.set_favicon("PNG");
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_parent_guid(
base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
EXPECT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeInvalidBookmarkSpecificsWithoutFaviconAndWithIconUrl) {
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_url("http://www.valid-url.com");
bm_specifics.set_icon_url("http://www.valid-icon-url.com");
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
base::HistogramTester histogram_tester;
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kIconURLWithoutFavicon,
/*expected_count=*/1);
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeInvalidBookmarkSpecificsWithInvalidGUID) {
base::HistogramTester histogram_tester;
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
bm_specifics.set_parent_guid(
base::GUID::GenerateRandomV4().AsLowercaseString());
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
// No GUID.
bm_specifics.clear_guid();
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidGUID,
/*expected_count=*/1);
// Add empty GUID.
bm_specifics.set_guid("");
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidGUID,
/*expected_count=*/2);
// Add invalid GUID.
bm_specifics.set_guid("INVALID GUID");
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidGUID,
/*expected_count=*/3);
// Add valid GUID.
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
ASSERT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeInvalidBookmarkSpecificsWithInvalidParentGUID) {
base::HistogramTester histogram_tester;
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
// No parent GUID.
bm_specifics.clear_parent_guid();
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidParentGUID,
/*expected_count=*/1);
// Add invalid parent GUID.
bm_specifics.set_parent_guid("INVALID GUID");
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidParentGUID,
/*expected_count=*/2);
// Add valid GUID.
bm_specifics.set_parent_guid(
base::GUID::GenerateRandomV4().AsLowercaseString());
ASSERT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeInvalidBookmarkSpecificsDueToInvalidUniquePosition) {
sync_pb::BookmarkSpecifics bm_specifics;
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
// Leave |unique_position| field populated but empty.
bm_specifics.mutable_unique_position();
base::HistogramTester histogram_tester;
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidUniquePosition,
/*expected_count=*/1);
}
TEST(BookmarkSpecificsConversionsTest,
ShouldBeInvalidBookmarkSpecificsWithBannedGUID) {
ASSERT_THAT(bookmarks::BookmarkNode::kBannedGuidDueToPastSyncBug,
Eq(InferGuidFromLegacyOriginatorId(
/*originator_cache_guid=*/"",
/*originator_client_item_id=*/"")
.AsLowercaseString()));
base::HistogramTester histogram_tester;
sync_pb::EntitySpecifics specifics;
sync_pb::BookmarkSpecifics* bm_specifics = specifics.mutable_bookmark();
bm_specifics->set_type(sync_pb::BookmarkSpecifics::FOLDER);
*bm_specifics->mutable_unique_position() = RandomUniquePosition();
bm_specifics->set_guid(bookmarks::BookmarkNode::kBannedGuidDueToPastSyncBug);
EXPECT_FALSE(IsValidBookmarkSpecifics(*bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kBannedGUID,
/*expected_count=*/1);
}
TEST(BookmarkSpecificsConversionsTest, ShouldBeInvalidBookmarkSpecifics) {
sync_pb::BookmarkSpecifics bm_specifics;
// Empty specifics.
{
base::HistogramTester histogram_tester;
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kEmptySpecifics,
/*expected_count=*/1);
}
{
base::HistogramTester histogram_tester;
bm_specifics.set_type(sync_pb::BookmarkSpecifics::FOLDER);
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidGUID,
/*expected_count=*/1);
}
// Populate the required fields.
bm_specifics.set_guid(base::GUID::GenerateRandomV4().AsLowercaseString());
bm_specifics.set_parent_guid(
base::GUID::GenerateRandomV4().AsLowercaseString());
*bm_specifics.mutable_unique_position() = RandomUniquePosition();
ASSERT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
{
base::HistogramTester histogram_tester;
bm_specifics.set_type(sync_pb::BookmarkSpecifics::URL);
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidURL,
/*expected_count=*/1);
}
// Add invalid url.
{
base::HistogramTester histogram_tester;
bm_specifics.set_url("INVALID_URL");
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kInvalidURL,
/*expected_count=*/1);
}
// Add a valid url.
bm_specifics.set_url("http://www.valid-url.com");
ASSERT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
sync_pb::MetaInfo* meta_info1 = bm_specifics.add_meta_info();
meta_info1->set_key("key");
meta_info1->set_value("value1");
ASSERT_TRUE(IsValidBookmarkSpecifics(bm_specifics));
// Add redudant keys in meta_info.
base::HistogramTester histogram_tester;
sync_pb::MetaInfo* meta_info2 = bm_specifics.add_meta_info();
meta_info2->set_key("key");
meta_info2->set_value("value2");
EXPECT_FALSE(IsValidBookmarkSpecifics(bm_specifics));
histogram_tester.ExpectBucketCount(
"Sync.InvalidBookmarkSpecifics",
/*sample=*/InvalidBookmarkSpecificsError::kNonUniqueMetaInfoKeys,
/*expected_count=*/1);
}
TEST(BookmarkSpecificsConversionsTest, ReplaceUrlNodeWithUpdatedGUID) {
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::u16string kTitle = u"bar";
const GURL kUrl = GURL("http://foo.com");
const base::Time kCreationTime = base::Time::Now();
auto meta_info_map = std::make_unique<bookmarks::BookmarkNode::MetaInfoMap>();
const std::string kKey = "key";
const std::string kValue = "value";
(*meta_info_map)[kKey] = kValue;
// Add a bookmark URL.
const bookmarks::BookmarkNode* original_url = model->AddURL(
bookmark_bar_node, 0, kTitle, kUrl, meta_info_map.get(), kCreationTime);
// Replace url1.
const bookmarks::BookmarkNode* new_url =
ReplaceBookmarkNodeGUID(original_url, kGuid, model.get());
// All data except for the GUID should be the same.
EXPECT_EQ(kGuid, new_url->guid());
EXPECT_EQ(kTitle, new_url->GetTitle());
EXPECT_EQ(bookmark_bar_node, new_url->parent());
EXPECT_EQ(0, bookmark_bar_node->GetIndexOf(new_url));
EXPECT_EQ(kUrl, new_url->url());
EXPECT_EQ(kCreationTime, new_url->date_added());
std::string out_value_url;
EXPECT_TRUE(new_url->GetMetaInfo(kKey, &out_value_url));
EXPECT_EQ(kValue, out_value_url);
}
TEST(BookmarkSpecificsConversionsTest, ReplaceFolderNodeWithUpdatedGUID) {
std::unique_ptr<bookmarks::BookmarkModel> model =
bookmarks::TestBookmarkClient::CreateModel();
const bookmarks::BookmarkNode* bookmark_bar_node = model->bookmark_bar_node();
const base::GUID kGuid = base::GUID::GenerateRandomV4();
const std::u16string kTitle = u"foobar";
auto meta_info_map = std::make_unique<bookmarks::BookmarkNode::MetaInfoMap>();
const std::string kKey = "key";
const std::string kValue = "value";
(*meta_info_map)[kKey] = kValue;
// Add a folder with child URLs.
const bookmarks::BookmarkNode* original_folder =
model->AddFolder(bookmark_bar_node, 0, kTitle, meta_info_map.get());
const bookmarks::BookmarkNode* url1 =
model->AddURL(original_folder, 0, u"bar", GURL("http://bar.com"));
const bookmarks::BookmarkNode* url2 =
model->AddURL(original_folder, 1, u"foo", GURL("http://foo.com"));
// Replace folder1.
const bookmarks::BookmarkNode* new_folder =
ReplaceBookmarkNodeGUID(original_folder, kGuid, model.get());
// All data except for the GUID should be the same.
EXPECT_EQ(kGuid, new_folder->guid());
EXPECT_EQ(kTitle, new_folder->GetTitle());
EXPECT_EQ(bookmark_bar_node, new_folder->parent());
EXPECT_EQ(0, bookmark_bar_node->GetIndexOf(new_folder));
std::string out_value_folder;
EXPECT_TRUE(new_folder->GetMetaInfo(kKey, &out_value_folder));
EXPECT_EQ(kValue, out_value_folder);
EXPECT_EQ(2u, new_folder->children().size());
EXPECT_EQ(0, new_folder->GetIndexOf(url1));
EXPECT_EQ(1, new_folder->GetIndexOf(url2));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldConsiderValidBookmarkGuidIfMatchesClientTag) {
const std::string kGuid = base::GUID::GenerateRandomV4().AsLowercaseString();
sync_pb::BookmarkSpecifics specifics;
specifics.set_guid(kGuid);
EXPECT_TRUE(HasExpectedBookmarkGuid(
specifics, syncer::ClientTagHash::FromUnhashed(syncer::BOOKMARKS, kGuid),
/*originator_cache_guid=*/"",
/*originator_client_item_id=*/""));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldConsiderValidBookmarkGuidIfMatchesOriginator) {
const std::string kGuid = base::GUID::GenerateRandomV4().AsLowercaseString();
sync_pb::BookmarkSpecifics specifics;
specifics.set_guid(kGuid);
EXPECT_TRUE(HasExpectedBookmarkGuid(specifics, syncer::ClientTagHash(),
/*originator_cache_guid=*/"",
/*originator_client_item_id=*/kGuid));
}
TEST(BookmarkSpecificsConversionsTest,
ShouldConsiderInvalidBookmarkGuidIfEmptyOriginator) {
const std::string kGuid = InferGuidFromLegacyOriginatorId(
/*originator_cache_guid=*/"",
/*=originator_client_item_id=*/"")
.AsLowercaseString();
sync_pb::BookmarkSpecifics specifics;
specifics.set_guid(kGuid);
EXPECT_FALSE(HasExpectedBookmarkGuid(specifics,
syncer::ClientTagHash::FromHashed("foo"),
/*originator_cache_guid=*/"",
/*originator_client_item_id=*/""));
}
} // namespace
} // namespace sync_bookmarks