blob: 6283fee7dcc1ad7ff9e3bf628ce9533e4c517913 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/bookmarks/bookmarks_api.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/extensions/api/bookmarks/test/bookmarks_api_matchers.h"
#include "chrome/browser/extensions/bookmarks/bookmarks_error_constants.h"
#include "chrome/browser/extensions/bookmarks/bookmarks_features.h"
#include "chrome/browser/extensions/bookmarks/bookmarks_helpers.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/common/extensions/api/bookmarks.h"
#include "chrome/test/base/testing_profile.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/signin/public/base/signin_switches.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/event_router_factory.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/test_event_router_observer.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
using ::testing::Eq;
using ::testing::ExplainMatchResult;
using ::testing::Pointwise;
namespace extensions {
class BookmarksApiUnittest : public ExtensionServiceTestBase {
public:
BookmarksApiUnittest() = default;
BookmarksApiUnittest(const BookmarksApiUnittest&) = delete;
BookmarksApiUnittest& operator=(const BookmarksApiUnittest&) = delete;
void SetUp() override {
ExtensionServiceTestBase::SetUp();
ExtensionServiceInitParams params;
params.enable_bookmark_model = true;
InitializeExtensionService(std::move(params));
model_ = BookmarkModelFactory::GetForBrowserContext(profile());
bookmarks::test::WaitForBookmarkModelToLoad(model_);
folder_node_ = model_->AddFolder(model_->other_node(), 0, u"Empty folder");
subfolder_node_ = model_->AddFolder(folder_node_, 0, u"Empty subfolder");
url_node_ = model_->AddURL(model_->other_node(), 0, u"URL", url_);
folder_node_id_ = base::NumberToString(folder_node_->id());
}
raw_ptr<bookmarks::BookmarkModel> model() const { return model_; }
const bookmarks::BookmarkNode* folder_node() const { return folder_node_; }
std::string folder_node_id() const { return folder_node_id_; }
const bookmarks::BookmarkNode* subfolder_node() const {
return subfolder_node_;
}
const bookmarks::BookmarkNode* url_node() const { return url_node_; }
const GURL url() const { return url_; }
private:
raw_ptr<bookmarks::BookmarkModel> model_ = nullptr;
raw_ptr<const bookmarks::BookmarkNode> folder_node_ = nullptr;
raw_ptr<const bookmarks::BookmarkNode> subfolder_node_ = nullptr;
raw_ptr<const bookmarks::BookmarkNode> url_node_ = nullptr;
std::string folder_node_id_;
const GURL url_ = GURL("https://example.org");
};
// Tests that running updating a bookmark folder's url does not succeed.
// Regression test for https://crbug.com/818395.
TEST_F(BookmarksApiUnittest, Update) {
auto update_function = base::MakeRefCounted<BookmarksUpdateFunction>();
ASSERT_EQ(R"(Can't set URL of a bookmark folder.)",
api_test_utils::RunFunctionAndReturnError(
update_function.get(),
absl::StrFormat(R"(["%s", {"url": "https://example.com"}])",
folder_node_id().c_str()),
profile()));
}
// Tests that attempting to create a bookmark with no parent folder specified
// succeeds when only local/syncable bookmarks are available.
TEST_F(BookmarksApiUnittest, Create_NoParentLocalOnly) {
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
create_function.get(), R"([{"title": "New folder"}])", profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
// The new folder should be added as the last child of the local other node.
#if BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Update this test once the default visible
// bookmarks behavior is clarified.
const bookmarks::BookmarkNode* expected_parent = model()->mobile_node();
#else
const bookmarks::BookmarkNode* expected_parent = model()->other_node();
#endif
EXPECT_EQ(result_node.parent_id,
base::NumberToString(expected_parent->id()));
EXPECT_EQ(result_node.index, expected_parent->children().size() - 1);
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
// Tests that attempting to create a bookmark with no parent folder specified
// succeeds and uses the account bookmarks folder when the user is signed in
// with bookmarks in transport mode.
TEST_F(BookmarksApiUnittest, Create_NoParentAccount) {
base::test::ScopedFeatureList scoped_feature_list{
switches::kSyncEnableBookmarksInTransportMode};
model()->CreateAccountPermanentFolders();
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
create_function.get(), R"([{"title": "New folder"}])", profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
// The new folder should be added as the last child of the account other node.
EXPECT_EQ(result_node.parent_id,
base::NumberToString(model()->account_other_node()->id()));
EXPECT_EQ(result_node.index,
model()->account_other_node()->children().size() - 1);
}
#endif // !BUILDFLAG(IS_ANDROID)
// Tests creating a bookmark with a valid parent specified.
TEST_F(BookmarksApiUnittest, Create_ValidParent) {
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
create_function.get(),
absl::StrFormat(R"([{"parentId": "%lu", "title": "New folder"}])",
folder_node()->id()),
profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
// The new folder should be added as the last child of the parent folder.
EXPECT_EQ(result_node.parent_id, folder_node_id());
EXPECT_EQ(result_node.index, folder_node()->children().size() - 1);
}
// Tests creating a bookmark with a valid parent specified.
TEST_F(BookmarksApiUnittest,
Create_SucceedsInLocalParentWhenBothLocalAndAccountBookmarksExist) {
base::test::ScopedFeatureList scoped_feature_list{
switches::kSyncEnableBookmarksInTransportMode};
model()->CreateAccountPermanentFolders();
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
create_function.get(),
absl::StrFormat(R"([{"parentId": "%lu", "title": "New folder"}])",
folder_node()->id()),
profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
// The new folder should be added as the last child of the parent folder.
EXPECT_EQ(result_node.parent_id, folder_node_id());
EXPECT_EQ(result_node.index, folder_node()->children().size() - 1);
}
// Tests that attempting to creating a bookmark with a non-folder parent does
// not add the bookmark to that parent.
// Regression test for https://crbug.com/1441071.
TEST_F(BookmarksApiUnittest, Create_NonFolderParent) {
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
create_function.get(),
absl::StrFormat(R"([{"parentId": "%d"}])", url_node()->id()), profile());
ASSERT_EQ("Parameter 'parentId' does not specify a folder.", error);
const bookmarks::BookmarkNode* url_node =
model()->GetMostRecentlyAddedUserNodeForURL(url());
ASSERT_TRUE(url_node->children().empty());
}
// Tests that attempting to create a bookmark with a parent that is not
// visible fails.
TEST_F(BookmarksApiUnittest, Create_NonExistentParent) {
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
create_function.get(), R"([{"parentId": "1234"}])", profile());
EXPECT_EQ(error, bookmarks_errors::kNoParentError);
}
// Tests that attempting to create a bookmark with a parent that is not
// visible fails.
TEST_F(BookmarksApiUnittest, Create_NonVisibleParentNoVisibilityEnforcement) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kEnforceBookmarkVisibilityOnExtensionsAPI);
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
create_function.get(),
absl::StrFormat(R"([{"parentId": "%lu", "title": "New folder"}])",
model()->mobile_node()->id()),
profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
// The new folder should be added as the last child of the parent folder.
EXPECT_EQ(result_node.parent_id,
base::NumberToString(model()->mobile_node()->id()));
EXPECT_EQ(result_node.index, folder_node()->children().size() - 1);
// The mobile node is now visible, as it no longer empty.
ASSERT_TRUE(model()->mobile_node()->IsVisible());
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest, Create_NonVisibleParent) {
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto create_function = base::MakeRefCounted<BookmarksCreateFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
create_function.get(),
absl::StrFormat(R"([{"parentId": "%lu", "title": "New folder"}])",
model()->mobile_node()->id()),
profile());
EXPECT_EQ(error, bookmarks_errors::kNoParentError);
}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(BookmarksApiUnittest,
Get_SucceedsForLocalPermanentFolderWhenNoAccountFolders) {
auto get_function = base::MakeRefCounted<BookmarksGetFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->other_node()->id()), profile())
.value();
std::vector<const bookmarks::BookmarkNode*> expected_nodes = {
model()->other_node()};
EXPECT_THAT(result.GetList(), ResultMatchesNodes(expected_nodes));
}
TEST_F(BookmarksApiUnittest,
Get_SucceedsForNonEmptyLocalPermanentFolderWhenAccountFolders) {
base::test::ScopedFeatureList scoped_feature_list{
switches::kSyncEnableBookmarksInTransportMode};
model()->CreateAccountPermanentFolders();
auto get_function = base::MakeRefCounted<BookmarksGetFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->other_node()->id()), profile())
.value();
std::vector<const bookmarks::BookmarkNode*> expected_nodes = {
model()->other_node()};
EXPECT_THAT(result.GetList(), ResultMatchesNodes(expected_nodes));
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest,
Get_ReturnsEmptyForNonVisibleFolderNoVisibilityEnforcement) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kEnforceBookmarkVisibilityOnExtensionsAPI);
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()),
profile())
.value();
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
}
TEST_F(BookmarksApiUnittest, Get_ReturnsErrorForNonVisibleFolder) {
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()), profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(BookmarksApiUnittest, Get_FailsForNonExistentId) {
auto get_function = base::MakeRefCounted<BookmarksGetFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(), R"(["1233456"])", profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest,
GetChildren_ReturnsEmptyForNonVisibleFolderNoVisibilityEnforcement) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kEnforceBookmarkVisibilityOnExtensionsAPI);
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetChildrenFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()),
profile())
.value();
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
}
TEST_F(BookmarksApiUnittest, GetChildren_ReturnsErrorForNonVisibleFolder) {
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetChildrenFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()), profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(BookmarksApiUnittest, GetChildren_FailsForNonExistentId) {
auto get_function = base::MakeRefCounted<BookmarksGetChildrenFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(), R"(["1233456"])", profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest,
GetSubTree_ReturnsEmptyForNonVisibleFolderNoVisibilityEnforcement) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kEnforceBookmarkVisibilityOnExtensionsAPI);
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetSubTreeFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()),
profile())
.value();
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
}
TEST_F(BookmarksApiUnittest, GetSubTree_ReturnsErrorForNonVisibleFolder) {
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto get_function = base::MakeRefCounted<BookmarksGetSubTreeFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(),
absl::StrFormat(R"(["%lu"])", model()->mobile_node()->id()), profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(BookmarksApiUnittest, GetSubTree_FailsForNonExistentId) {
auto get_function = base::MakeRefCounted<BookmarksGetSubTreeFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
get_function.get(), R"(["1233456"])", profile());
EXPECT_EQ(error, extensions::bookmarks_errors::kNoNodeError);
}
TEST_F(BookmarksApiUnittest, Search_MatchesTitle) {
auto function = base::MakeRefCounted<BookmarksSearchFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), R"([{"title": "Empty folder"}])", profile())
.value();
std::vector<const bookmarks::BookmarkNode*> expected_nodes = {folder_node()};
EXPECT_THAT(result.GetList(), ResultMatchesNodes(expected_nodes));
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest, Search_NonVisibleFolderNotReturned) {
// Set the title of the folder node to a fixed value.
model()->SetTitle(model()->mobile_node(), u"Mobile Bookmarks",
bookmarks::metrics::BookmarkEditSource::kOther);
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto function = base::MakeRefCounted<BookmarksSearchFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
function.get(), R"([{"title": "Mobile Bookmarks"}])", profile())
.value();
EXPECT_THAT(result.GetList(), ::testing::IsEmpty());
}
TEST_F(BookmarksApiUnittest,
GetTree_SucceedsForLocalPermanentFolderWhenNoAccountFolders) {
auto get_tree_function = base::MakeRefCounted<BookmarksGetTreeFunction>();
const base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_tree_function.get(), R"([])", profile())
.value();
// The result should contain a single root node. Check that its children
// include the three permanent folders, plus the non-permanent folder/url.
ASSERT_EQ(result.GetList().size(), 1u);
auto root_node = extensions::api::bookmarks::BookmarkTreeNode::FromValue(
result.GetList()[0]);
EXPECT_EQ(root_node->id, "0");
ASSERT_EQ(root_node->children.value().size(), 2u);
EXPECT_EQ(root_node->children.value()[0].id,
base::NumberToString(model()->bookmark_bar_node()->id()));
EXPECT_EQ(root_node->children.value()[1].id,
base::NumberToString(model()->other_node()->id()));
auto& other_node = root_node->children.value()[1];
ASSERT_EQ(other_node.children.value().size(), 2u);
EXPECT_EQ(other_node.children.value()[0].id,
base::NumberToString(url_node()->id()));
EXPECT_EQ(other_node.children.value()[1].id,
base::NumberToString(folder_node()->id()));
}
TEST_F(BookmarksApiUnittest, GetTree_SucceedsWhenLocalAndAccountFolders) {
base::test::ScopedFeatureList scoped_feature_list{
switches::kSyncEnableBookmarksInTransportMode};
model()->CreateAccountPermanentFolders();
auto get_tree_function = base::MakeRefCounted<BookmarksGetTreeFunction>();
const base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
get_tree_function.get(), R"([])", profile())
.value();
// The result should contain a single root node. Check that its children
// include the three permanent folders, plus the non-permanent folder/url.
ASSERT_EQ(result.GetList().size(), 1u);
auto root_node = extensions::api::bookmarks::BookmarkTreeNode::FromValue(
result.GetList()[0]);
EXPECT_EQ(root_node->id, "0");
ASSERT_EQ(root_node->children.value().size(), 4u);
// TODO(crbug.com/382263783): the account folders should be returned before
// the local folders.
EXPECT_EQ(root_node->children.value()[0].id,
base::NumberToString(model()->bookmark_bar_node()->id()));
EXPECT_EQ(root_node->children.value()[0].index, 0);
EXPECT_EQ(root_node->children.value()[1].id,
base::NumberToString(model()->other_node()->id()));
auto& other_node = root_node->children.value()[1];
EXPECT_EQ(other_node.index, 1);
ASSERT_EQ(other_node.children.value().size(), 2u);
EXPECT_EQ(other_node.children.value()[0].id,
base::NumberToString(url_node()->id()));
EXPECT_EQ(other_node.children.value()[1].id,
base::NumberToString(folder_node()->id()));
EXPECT_EQ(root_node->children.value()[2].id,
base::NumberToString(model()->account_bookmark_bar_node()->id()));
EXPECT_EQ(root_node->children.value()[2].index, 2);
EXPECT_EQ(root_node->children.value()[3].id,
base::NumberToString(model()->account_other_node()->id()));
EXPECT_EQ(root_node->children.value()[3].index, 3);
}
// Tests that moving from local to account storage is allowed.
TEST_F(BookmarksApiUnittest, Move_LocalToAccount) {
base::test::ScopedFeatureList scoped_feature_list{
switches::kSyncEnableBookmarksInTransportMode};
model()->CreateAccountPermanentFolders();
ASSERT_TRUE(model()->IsLocalOnlyNode(*folder_node()));
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
move_function.get(),
absl::StrFormat(R"(["%lu", {"parentId": "%lu"}])",
folder_node()->id(),
model()->account_other_node()->id()),
profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
EXPECT_EQ(result_node.parent_id,
base::NumberToString(model()->account_other_node()->id()));
EXPECT_EQ(result_node.index, 0);
EXPECT_EQ(model()->account_other_node()->children()[0].get(), folder_node());
}
#endif // !BUILDFLAG(IS_ANDROID)
// Tests that attempting to move a bookmark to a non-folder parent does
// not add the bookmark to that parent.
// Regression test for https://crbug.com/1491227.
TEST_F(BookmarksApiUnittest, Move_NonFolderParent) {
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
move_function.get(),
absl::StrFormat(R"(["%d", {"parentId": "%d"}])", folder_node()->id(),
url_node()->id()),
profile());
ASSERT_EQ("Parameter 'parentId' does not specify a folder.", error);
const bookmarks::BookmarkNode* url_node =
model()->GetMostRecentlyAddedUserNodeForURL(url());
ASSERT_TRUE(url_node->children().empty());
}
// Tests that attempting to move a bookmark to a non existent parent returns an
// error.
TEST_F(BookmarksApiUnittest, Move_NonExistentParent) {
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
move_function.get(),
absl::StrFormat(R"(["%s", {"parentId": "1234"}])",
folder_node_id().c_str()),
profile());
EXPECT_EQ(error, bookmarks_errors::kNoParentError);
const bookmarks::BookmarkNode* url_node =
model()->GetMostRecentlyAddedUserNodeForURL(url());
ASSERT_TRUE(url_node->children().empty());
}
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/414844449): Port to desktop Android once default visible
// bookmarks behavior is clarified.
TEST_F(BookmarksApiUnittest, Move_NonVisibleParentNoVisibilityEnforcement) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kEnforceBookmarkVisibilityOnExtensionsAPI);
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
base::Value result =
extensions::api_test_utils::RunFunctionAndReturnSingleResult(
move_function.get(),
absl::StrFormat(R"(["%lu", {"parentId": "%lu"}])",
folder_node()->id(), model()->mobile_node()->id()),
profile())
.value();
api::bookmarks::BookmarkTreeNode result_node =
extensions::api::bookmarks::BookmarkTreeNode::FromValue(result).value();
EXPECT_EQ(result_node.parent_id,
base::NumberToString(model()->mobile_node()->id()));
EXPECT_EQ(result_node.index, 0);
EXPECT_EQ(model()->mobile_node()->children()[0].get(), folder_node());
ASSERT_TRUE(model()->mobile_node()->IsVisible());
}
TEST_F(BookmarksApiUnittest, Move_NonVisibleParent) {
// The mobile node is not visible, because it is empty.
ASSERT_FALSE(model()->mobile_node()->IsVisible());
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
move_function.get(),
absl::StrFormat(R"(["%lu", {"parentId": "%lu"}])", folder_node()->id(),
model()->mobile_node()->id()),
profile());
EXPECT_EQ(error, bookmarks_errors::kNoParentError);
}
#endif // !BUILDFLAG(IS_ANDROID)
// Tests that attempting to move a folder to itself returns an error.
TEST_F(BookmarksApiUnittest, Move_FolderToItself) {
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
move_function.get(),
absl::StrFormat(R"(["%s", {"parentId": "%s"}])", folder_node_id().c_str(),
folder_node_id().c_str()),
profile());
ASSERT_EQ("Can't move a folder to itself or its descendant.", error);
}
// Tests that attempting to move a folder to its descendant returns an error.
TEST_F(BookmarksApiUnittest, Move_FolderToDescendant) {
auto move_function = base::MakeRefCounted<BookmarksMoveFunction>();
std::string error = api_test_utils::RunFunctionAndReturnError(
move_function.get(),
absl::StrFormat(R"(["%d", {"parentId": "%d"}])", folder_node()->id(),
subfolder_node()->id()),
profile());
ASSERT_EQ("Can't move a folder to itself or its descendant.", error);
}
} // namespace extensions