blob: d3b69911bc7ae6644f3469493495ff710db7a41b [file]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/bookmarks/browser/model_loader.h"
#include <memory>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_file_util.h"
#include "base/test/test_future.h"
#include "components/bookmarks/browser/bookmark_client.h"
#include "components/bookmarks/browser/bookmark_load_details.h"
#include "components/bookmarks/common/bookmark_metrics.h"
#include "components/bookmarks/common/storage_file_encryption_type.h"
#include "components/bookmarks/test/bookmark_test_with_encryption_stages.h"
#include "components/os_crypt/async/browser/test_utils.h"
#include "components/os_crypt/async/common/encryptor.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace bookmarks {
namespace {
constexpr char kLocalOrSyncableIdsReassignedMetricName[] =
"Bookmarks.IdsReassigned2.OnProfileLoad.LocalOrSyncable";
constexpr char kAccountIdsReassignedMetricName[] =
"Bookmarks.IdsReassigned2.OnProfileLoad.Account";
constexpr char kUserFolderCountMetricName[] =
"Bookmarks.UserFolder.OnProfileLoad.Count";
constexpr char kUserFolderTopLevelCountMetricName[] =
"Bookmarks.UserFolder.OnProfileLoad.TopLevelCount";
constexpr char kUserFolderBookmarkBarTopLevelItemsMetricName[] =
"Bookmarks.UserFolder.OnProfileLoad.BookmarkBarTopLevelItems";
constexpr char kBookmarksFileLoadResultMetricName[] =
"Bookmarks.BookmarksFileLoadResult";
constexpr char kEncryptedBookmarksFileMatchesResultMetricName[] =
"Bookmarks.EncryptedBookmarksFileMatchesResult";
constexpr char kFallbackToClearTextFileOnLoadResultMetricName[] =
"Bookmarks.FallbackToClearTextFileOnLoadResult";
constexpr char kBookmarksStorageFileSizeAtStartupMetricName[] =
"Bookmarks.Storage.FileSizeAtStartup2";
constexpr char kBookmarksEncryptedStorageFileSizeAtStartupMetricName[] =
"Bookmarks.Storage.EncryptedFileSizeAtStartup";
constexpr char kBookmarksTimeToReadFileMetricName[] =
"Bookmarks.TimeToReadFile";
constexpr char kBookmarksAverageNodeSizeMetricName[] =
"Bookmarks.AverageNodeSize";
constexpr char kBookmarksDeleteFileMetricName[] =
"Bookmarks.DeleteClearTextFile";
constexpr base::FilePath::CharType kBackupExtension[] =
FILE_PATH_LITERAL("bak");
const base::FilePath& GetTestDataDir() {
static base::NoDestructor<base::FilePath> dir([]() {
base::FilePath dir;
base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &dir);
return dir.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data");
}());
return *dir;
}
const BookmarkNode* FindNodeByUuid(const UuidIndex& index,
const std::string& uuid_str) {
const base::Uuid uuid = base::Uuid::ParseLowercase(uuid_str);
CHECK(uuid.is_valid());
const auto it = index.find(uuid);
return it == index.end() ? nullptr : *it;
}
MATCHER(FileAndBackupFileExist, "") {
return base::PathExists(arg) &&
base::PathExists(arg.ReplaceExtension(kBackupExtension));
}
TEST(ModelLoaderTest, LoadEmptyModelFromInexistentFile) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("inexistent_bookmarks_file.json");
ASSERT_FALSE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails>& details = details_future.Get();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(4, details->max_id());
EXPECT_EQ(0u, details->bb_node()->children().size());
EXPECT_EQ(0u, details->other_folder_node()->children().size());
EXPECT_EQ(0u, details->mobile_folder_node()->children().size());
EXPECT_EQ("", details->local_or_syncable_sync_metadata_str());
// Permanent node ID's are subject to change, but expectations are listed
// below for the purpose of documenting the current behavior.
EXPECT_EQ(1u, details->bb_node()->id());
EXPECT_EQ(2u, details->other_folder_node()->id());
EXPECT_EQ(3u, details->mobile_folder_node()->id());
histogram_tester.ExpectTotalCount(kLocalOrSyncableIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kAccountIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kFileMissing,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
}
TEST(ModelLoaderTest, LoadEmptyModelFromInvalidJson) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_invalid_json.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails>& details = details_future.Get();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(4, details->max_id());
EXPECT_EQ(0u, details->bb_node()->children().size());
EXPECT_EQ(0u, details->other_folder_node()->children().size());
EXPECT_EQ(0u, details->mobile_folder_node()->children().size());
EXPECT_EQ("", details->local_or_syncable_sync_metadata_str());
// Permanent node ID's are subject to change, but expectations are listed
// below for the purpose of documenting the current behavior.
EXPECT_EQ(1u, details->bb_node()->id());
EXPECT_EQ(2u, details->other_folder_node()->id());
EXPECT_EQ(3u, details->mobile_folder_node()->id());
histogram_tester.ExpectTotalCount(kLocalOrSyncableIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kAccountIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/
metrics::BookmarksFileLoadResult::kJSONParsingFailed,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
}
TEST(ModelLoaderTest, LoadEmptyFromImproperlyEncodedJSON) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_without_version.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails>& details = details_future.Get();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(4, details->max_id());
EXPECT_EQ(0u, details->bb_node()->children().size());
EXPECT_EQ(0u, details->other_folder_node()->children().size());
EXPECT_EQ(0u, details->mobile_folder_node()->children().size());
EXPECT_EQ("", details->local_or_syncable_sync_metadata_str());
// Permanent node ID's are subject to change, but expectations are listed
// below for the purpose of documenting the current behavior.
EXPECT_EQ(1u, details->bb_node()->id());
EXPECT_EQ(2u, details->other_folder_node()->id());
EXPECT_EQ(3u, details->mobile_folder_node()->id());
histogram_tester.ExpectTotalCount(kLocalOrSyncableIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kAccountIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/0, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/
metrics::BookmarksFileLoadResult::kBookmarkCodecDecodingFailed,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
}
TEST(ModelLoaderTest, LoadNonEmptyModel) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(11, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
const UuidIndex uuid_index = details->owned_local_or_syncable_uuid_index();
// Sanity-check the presence of one node.
const BookmarkNode* folder_b1 =
FindNodeByUuid(uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, folder_b1);
EXPECT_EQ(u"Folder B1", folder_b1->GetTitle());
EXPECT_EQ(4, folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectTotalCount(kAccountIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/3, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/3, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/1, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kSuccess,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
}
TEST(ModelLoaderTest, LoadNonEmptyModelFromOneFileWithInternalIdCollisions) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
EXPECT_TRUE(details->required_recovery());
EXPECT_TRUE(details->ids_reassigned());
EXPECT_EQ(12, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
// Permanent node ID's are subject to change, but expectations are listed
// below for the purpose of documenting the current behavior. Note that in
// this case some permanent folders get non-standard IDs assigned.
EXPECT_EQ(1u, details->bb_node()->id());
EXPECT_EQ(4u, details->other_folder_node()->id());
EXPECT_EQ(10u, details->mobile_folder_node()->id());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
const UuidIndex uuid_index = details->owned_local_or_syncable_uuid_index();
// Sanity-check the presence of one node.
const BookmarkNode* folder_b1 =
FindNodeByUuid(uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, folder_b1);
EXPECT_EQ(u"Folder B1", folder_b1->GetTitle());
EXPECT_EQ(11, folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectTotalCount(kAccountIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kSuccess,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
}
TEST(ModelLoaderTest, LoadTwoFilesWithNonCollidingIds) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file1 =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json");
const base::FilePath test_file2 =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json");
ASSERT_TRUE(base::PathExists(test_file1));
ASSERT_TRUE(base::PathExists(test_file2));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file1,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file2,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(24, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-2", details->account_sync_metadata_str());
const UuidIndex local_or_syncable_uuid_index =
details->owned_local_or_syncable_uuid_index();
const UuidIndex account_uuid_index = details->owned_account_uuid_index();
// Sanity-check the presence of one node. The UUID should not have changed.
const BookmarkNode* local_or_syncable_folder_b1 = FindNodeByUuid(
local_or_syncable_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, local_or_syncable_folder_b1);
EXPECT_EQ(u"Folder B1", local_or_syncable_folder_b1->GetTitle());
EXPECT_EQ(4, local_or_syncable_folder_b1->id());
const BookmarkNode* account_folder_b1 = FindNodeByUuid(
account_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, account_folder_b1);
EXPECT_EQ(u"Folder B1", account_folder_b1->GetTitle());
EXPECT_EQ(23, account_folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/6, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/6, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/1, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kSuccess,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kSuccess,
/*expected_count=*/1);
}
TEST(ModelLoaderTest, LoadTwoFilesWithCollidingIdsAcross) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
// ID collisions should have triggered recovery and reassignment of IDs.
EXPECT_TRUE(details->required_recovery());
EXPECT_TRUE(details->ids_reassigned());
EXPECT_EQ(20, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-1", details->account_sync_metadata_str());
const UuidIndex local_or_syncable_uuid_index =
details->owned_local_or_syncable_uuid_index();
const UuidIndex account_uuid_index = details->owned_account_uuid_index();
// Sanity-check the presence of one node. The UUID should not have changed.
const BookmarkNode* account_folder_b1 = FindNodeByUuid(
account_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, account_folder_b1);
EXPECT_EQ(u"Folder B1", account_folder_b1->GetTitle());
EXPECT_EQ(4, account_folder_b1->id());
const BookmarkNode* local_or_syncable_folder_b1 = FindNodeByUuid(
local_or_syncable_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, local_or_syncable_folder_b1);
EXPECT_EQ(u"Folder B1", local_or_syncable_folder_b1->GetTitle());
// The local-or-syncable node ID gets reassigned. The precise value isn't
// important, but it is added here as overly-strict requirement to document
// the behavior.
EXPECT_EQ(15, local_or_syncable_folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
}
TEST(ModelLoaderTest, LoadTwoFilesWhereFirstHasInternalIdCollisions) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file1 =
GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json");
const base::FilePath test_file2 =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json");
ASSERT_TRUE(base::PathExists(test_file1));
ASSERT_TRUE(base::PathExists(test_file2));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file1,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file2,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
// ID collisions should have triggered recovery and reassignment of some IDs.
EXPECT_TRUE(details->required_recovery());
EXPECT_TRUE(details->ids_reassigned());
EXPECT_EQ(25, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-2", details->account_sync_metadata_str());
const UuidIndex local_or_syncable_uuid_index =
details->owned_local_or_syncable_uuid_index();
const UuidIndex account_uuid_index = details->owned_account_uuid_index();
// Sanity-check the presence of one node. The UUID should not have changed.
const BookmarkNode* account_folder_b1 = FindNodeByUuid(
account_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, account_folder_b1);
EXPECT_EQ(u"Folder B1", account_folder_b1->GetTitle());
// The node ID for account bookmarks stay unchanged. This isn't particularly
// important, but it is added here as overly-strict requirement to document
// the behavior.
EXPECT_EQ(23, account_folder_b1->id());
const BookmarkNode* local_or_syncable_folder_b1 = FindNodeByUuid(
local_or_syncable_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, local_or_syncable_folder_b1);
EXPECT_EQ(u"Folder B1", local_or_syncable_folder_b1->GetTitle());
// The node ID gets reassigned. The precise value isn't important, but it is
// added here as overly-strict requirement to document the behavior.
EXPECT_EQ(24, local_or_syncable_folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
}
TEST(ModelLoaderTest, LoadTwoFilesWhereSecondHasInternalIdCollisions) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file1 =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json");
const base::FilePath test_file2 =
GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json");
ASSERT_TRUE(base::PathExists(test_file1));
ASSERT_TRUE(base::PathExists(test_file2));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file1,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file2,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
// ID collisions should have triggered recovery and reassignment of some IDs.
EXPECT_TRUE(details->required_recovery());
EXPECT_TRUE(details->ids_reassigned());
EXPECT_EQ(25, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-2",
details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-1", details->account_sync_metadata_str());
const UuidIndex local_or_syncable_uuid_index =
details->owned_local_or_syncable_uuid_index();
const UuidIndex account_uuid_index = details->owned_account_uuid_index();
// Sanity-check the presence of one node. The UUID should not have changed.
const BookmarkNode* account_folder_b1 = FindNodeByUuid(
account_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, account_folder_b1);
EXPECT_EQ(u"Folder B1", account_folder_b1->GetTitle());
// The node ID gets reassigned. The precise value isn't important, but it is
// added here as overly-strict requirement to document the behavior.
EXPECT_EQ(11, account_folder_b1->id());
const BookmarkNode* local_or_syncable_folder_b1 = FindNodeByUuid(
local_or_syncable_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, local_or_syncable_folder_b1);
EXPECT_EQ(u"Folder B1", local_or_syncable_folder_b1->GetTitle());
// The node ID gets reassigned. The precise value isn't important, but it is
// added here as overly-strict requirement to document the behavior.
EXPECT_EQ(23, local_or_syncable_folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
}
TEST(ModelLoaderTest, LoadTwoFilesWhereBothHaveInternalIdCollisions) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file1 =
GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json");
const base::FilePath test_file2 =
GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json");
ASSERT_TRUE(base::PathExists(test_file1));
ASSERT_TRUE(base::PathExists(test_file2));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file1,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file2,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
// ID collisions should have triggered recovery and reassignment of some IDs.
EXPECT_TRUE(details->required_recovery());
EXPECT_TRUE(details->ids_reassigned());
EXPECT_EQ(21, details->max_id());
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_EQ("dummy-sync-metadata-1",
details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-1", details->account_sync_metadata_str());
const UuidIndex local_or_syncable_uuid_index =
details->owned_local_or_syncable_uuid_index();
const UuidIndex account_uuid_index = details->owned_account_uuid_index();
// Sanity-check the presence of one node. The UUID should not have changed.
const BookmarkNode* account_folder_b1 = FindNodeByUuid(
account_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, account_folder_b1);
EXPECT_EQ(u"Folder B1", account_folder_b1->GetTitle());
// The node ID gets reassigned. The precise value isn't important, but it is
// added here as overly-strict requirement to document the behavior.
EXPECT_EQ(11, account_folder_b1->id());
const BookmarkNode* local_or_syncable_folder_b1 = FindNodeByUuid(
local_or_syncable_uuid_index, "da47f36f-050f-4ac9-aa35-ab0d93d39f95");
ASSERT_NE(nullptr, local_or_syncable_folder_b1);
EXPECT_EQ(u"Folder B1", local_or_syncable_folder_b1->GetTitle());
// The node ID gets reassigned. The precise value isn't important, but it is
// added here as overly-strict requirement to document the behavior.
EXPECT_EQ(16, local_or_syncable_folder_b1->id());
histogram_tester.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/true,
/*expected_bucket_count=*/1);
}
TEST(ModelLoaderTest, LoadTwoFilesWhereTheLocalOrSyncableFileDoesNotExist) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file1 =
GetTestDataDir().AppendASCII("bookmarks/inexistent_file.json");
const base::FilePath test_file2 =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json");
ASSERT_FALSE(base::PathExists(test_file1));
ASSERT_TRUE(base::PathExists(test_file2));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file1,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/test_file2,
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
ASSERT_NE(nullptr, details->bb_node());
ASSERT_NE(nullptr, details->other_folder_node());
ASSERT_NE(nullptr, details->mobile_folder_node());
ASSERT_NE(nullptr, details->account_bb_node());
ASSERT_NE(nullptr, details->account_other_folder_node());
ASSERT_NE(nullptr, details->account_mobile_folder_node());
// The JSON file used to load account nodes uses specific IDs that are
// actually important for this test. One behavior being tested here is that
// local-or-syncable bookmarks (inexistent file) will not be treated as
// collision.
ASSERT_EQ(1, details->account_bb_node()->id());
ASSERT_EQ(2, details->account_other_folder_node()->id());
ASSERT_EQ(10, details->account_mobile_folder_node()->id());
EXPECT_FALSE(details->required_recovery());
EXPECT_FALSE(details->ids_reassigned());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
EXPECT_TRUE(details->bb_node()->children().empty());
EXPECT_TRUE(details->other_folder_node()->children().empty());
EXPECT_TRUE(details->mobile_folder_node()->children().empty());
// Local-or-syncable permanent nodes should have been assigned new IDs that
// do not conflict with account nodes.
EXPECT_EQ(11, details->bb_node()->id());
EXPECT_EQ(12, details->other_folder_node()->id());
EXPECT_EQ(13, details->mobile_folder_node()->id());
EXPECT_EQ(14, details->max_id());
EXPECT_EQ("", details->local_or_syncable_sync_metadata_str());
EXPECT_EQ("dummy-sync-metadata-1", details->account_sync_metadata_str());
histogram_tester.ExpectTotalCount(kLocalOrSyncableIdsReassignedMetricName,
/*expected_count=*/0);
histogram_tester.ExpectUniqueSample(kAccountIdsReassignedMetricName,
/*sample=*/false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kFileMissing,
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*sample=*/metrics::BookmarksFileLoadResult::kSuccess,
/*expected_count=*/1);
}
TEST(ModelLoaderTest, LoadModelWithNestedUserFolders) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment;
const base::FilePath test_file =
GetTestDataDir().AppendASCII("bookmarks/model_nested_user_folders.json");
ASSERT_TRUE(base::PathExists(test_file));
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
/*encryptor=*/nullptr,
/*local_or_syncable_file_path=*/test_file,
/*encrypted_local_or_syncable_file_path=*/base::FilePath(),
/*account_file_path=*/base::FilePath(),
/*encrypted_account_file_path=*/base::FilePath(),
LoadManagedNodeCallback(),
/*save_local_or_syncable_secondary_file_callback=*/base::DoNothing(),
/*save_account_secondary_file_callback=*/base::DoNothing(),
details_future.GetCallback());
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
histogram_tester.ExpectTotalCount(kUserFolderCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderCountMetricName,
/*sample=*/7, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(kUserFolderTopLevelCountMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(kUserFolderTopLevelCountMetricName,
/*sample=*/3, /*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*expected_count=*/1);
histogram_tester.ExpectBucketCount(
kUserFolderBookmarkBarTopLevelItemsMetricName,
/*sample=*/2, /*expected_count=*/1);
}
void VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
base::HistogramTester& histogram_tester,
std::string_view primary_histogram_suffix,
std::string_view secondary_histogram_suffix,
metrics::BookmarksFileLoadResult secondary_result) {
// Primary file load succeeds
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
primary_histogram_suffix}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".Account",
primary_histogram_suffix}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
secondary_histogram_suffix}),
secondary_result,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".Account",
secondary_histogram_suffix}),
secondary_result,
/*expected_bucket_count=*/1);
}
base::FilePath CreateCopyWithBackup(const base::FilePath& filepath,
const std::string_view& file_name) {
const base::FilePath temp_dir = base::CreateUniqueTempDirectoryScopedToTest();
const base::FilePath copy_filepath = temp_dir.AppendASCII(file_name);
base::CopyFile(filepath, copy_filepath);
base::CopyFile(filepath, copy_filepath.ReplaceExtension(kBackupExtension));
return copy_filepath;
}
std::optional<base::FilePath> CreateTempEncryptedFile(
const base::FilePath& file,
const std::string_view& file_name,
const scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>&
encryptor) {
std::string file_content;
if (!base::ReadFileToString(file, &file_content)) {
return std::nullopt;
}
std::string encrypted_file_content;
if (!encryptor->data.EncryptString(file_content, &encrypted_file_content)) {
return std::nullopt;
}
const base::FilePath temp_dir = base::CreateUniqueTempDirectoryScopedToTest();
const base::FilePath encrypted_file = temp_dir.AppendASCII(file_name);
if (!base::WriteFile(encrypted_file, encrypted_file_content)) {
return std::nullopt;
}
return encrypted_file;
}
class ModelLoaderWithSecondayFileTest
: public testing::TestWithParam<BookmarkEncryptionStage> {
protected:
ModelLoaderWithSecondayFileTest() {
test::InitFeaturesForBookmarkTestEncryptionStage(feature_list_, GetParam());
}
bool IsEncryptedFilePrimary() {
return GetParam() == BookmarkEncryptionStage::kWriteBothReadPreferEncrypted;
}
StorageFileEncryptionType GetSecondaryStorageFileEncryptionType() {
return IsEncryptedFilePrimary() ? StorageFileEncryptionType::kClearText
: StorageFileEncryptionType::kEncrypted;
}
base::FilePath CreateTempEncryptedFileOrClearTextCopy(
bool primary_content,
std::string_view existing_file_name,
std::string_view new_file_name) {
const base::FilePath existing_file_path =
GetTestDataDir().AppendASCII(existing_file_name);
if ((primary_content && IsEncryptedFilePrimary()) ||
(!primary_content && !IsEncryptedFilePrimary())) {
std::optional<base::FilePath> encrypted_file_path =
CreateTempEncryptedFile(existing_file_path,
base::StrCat({"Encrypted", new_file_name}),
encryptor_);
return encrypted_file_path.value();
}
return CreateCopyWithBackup(existing_file_path, new_file_name);
}
scoped_refptr<ModelLoader> CreateModelLoader(
const base::FilePath& primary_local_or_syncable_file_path,
const base::FilePath& secondary_local_or_syncable_file_path,
const base::FilePath& primary_account_file_path,
const base::FilePath& secondary_account_file_path,
ModelLoader::SaveSingleFileCallback
save_local_or_syncable_single_file_callback,
ModelLoader::SaveSingleFileCallback save_account_single_file_callback) {
if (IsEncryptedFilePrimary()) {
return ModelLoader::Create(
encryptor_, secondary_local_or_syncable_file_path,
primary_local_or_syncable_file_path, secondary_account_file_path,
primary_account_file_path, LoadManagedNodeCallback(),
std::move(save_local_or_syncable_single_file_callback),
std::move(save_account_single_file_callback),
/*callback=*/base::DoNothing());
}
return ModelLoader::Create(
encryptor_, primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path, primary_account_file_path,
secondary_account_file_path, LoadManagedNodeCallback(),
std::move(save_local_or_syncable_single_file_callback),
std::move(save_account_single_file_callback),
/*callback=*/base::DoNothing());
}
std::string GetPrimaryEncryptionHistogramSuffix() {
return IsEncryptedFilePrimary() ? ".Encrypted" : ".ClearText";
}
std::string GetSecondaryEncryptionHistogramSuffix() {
return IsEncryptedFilePrimary() ? ".ClearText" : ".Encrypted";
}
const base::FilePath& GetClearTextFilePath(
const base::FilePath& primary_file_path,
const base::FilePath& secondary_file_path) {
return IsEncryptedFilePrimary() ? secondary_file_path : primary_file_path;
}
void VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
base::HistogramTester& histogram_tester,
metrics::BookmarksFileLoadResult secondary_result) {
bookmarks::VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
histogram_tester, GetPrimaryEncryptionHistogramSuffix(),
GetSecondaryEncryptionHistogramSuffix(), secondary_result);
}
base::test::ScopedFeatureList feature_list_;
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor_ = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place,
os_crypt_async::GetTestEncryptorForTesting());
};
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldCreateSecondaryFilesWhenMissing) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath secondary_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath primary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath secondary_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
histogram_tester,
/*secondary_result=*/metrics::BookmarksFileLoadResult::kFileMissing);
// Verify that the save encrypted file callback is called for both files.
EXPECT_EQ(GetSecondaryStorageFileEncryptionType(),
save_local_or_syncable_bookmark_future.Get());
EXPECT_EQ(GetSecondaryStorageFileEncryptionType(),
save_account_bookmark_future.Get());
}
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldReportContentMismatch) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath secondary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath primary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath secondary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
histogram_tester,
/*secondary_result=*/metrics::BookmarksFileLoadResult::kSuccess);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".LocalOrSyncable"}),
false,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".Account"}),
false,
/*expected_bucket_count=*/1);
// Verify that saving of the encrypted files is scheduled.
EXPECT_EQ(GetSecondaryStorageFileEncryptionType(),
save_local_or_syncable_bookmark_future.Get());
EXPECT_EQ(GetSecondaryStorageFileEncryptionType(),
save_account_bookmark_future.Get());
EXPECT_THAT(GetClearTextFilePath(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path),
FileAndBackupFileExist());
EXPECT_THAT(GetClearTextFilePath(primary_account_file_path,
secondary_account_file_path),
FileAndBackupFileExist());
}
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldReportContentMatches) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath secondary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath primary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath secondary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
histogram_tester,
/*secondary_result=*/metrics::BookmarksFileLoadResult::kSuccess);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".LocalOrSyncable"}),
true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".Account"}),
true,
/*expected_bucket_count=*/1);
// Verify that the save encrypted file callback hasn't been called.
EXPECT_FALSE(save_local_or_syncable_bookmark_future.IsReady());
EXPECT_FALSE(save_account_bookmark_future.IsReady());
EXPECT_THAT(GetClearTextFilePath(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path),
FileAndBackupFileExist());
EXPECT_THAT(GetClearTextFilePath(primary_account_file_path,
secondary_account_file_path),
FileAndBackupFileExist());
}
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldCreateSecondaryForAccountNodeWhenOnlyOneMissing) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath secondary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath primary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath secondary_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
// Local or syncable reads succeed
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
GetSecondaryEncryptionHistogramSuffix()}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".LocalOrSyncable"}),
true,
/*expected_bucket_count=*/1);
EXPECT_FALSE(save_local_or_syncable_bookmark_future.IsReady());
// Account reads fail
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".Account",
GetSecondaryEncryptionHistogramSuffix()}),
metrics::BookmarksFileLoadResult::kFileMissing,
/*expected_bucket_count=*/1);
EXPECT_EQ(save_account_bookmark_future.Get(),
GetSecondaryStorageFileEncryptionType());
}
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldRecordSizeAndReadTimeForSecondaryFiles) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath secondary_local_or_syncable_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_1.json",
/*new_file_name=*/"TestBookmarks1");
const base::FilePath primary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/true,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
const base::FilePath secondary_account_file_path =
CreateTempEncryptedFileOrClearTextCopy(
/*primary_content=*/false,
/*existing_file_name=*/"bookmarks/model_with_sync_metadata_2.json",
/*new_file_name=*/"TestBookmarks2");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
histogram_tester.ExpectTotalCount(
kBookmarksStorageFileSizeAtStartupMetricName,
/*expected_count=*/2);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksAverageNodeSizeMetricName, ".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
kBookmarksEncryptedStorageFileSizeAtStartupMetricName,
/*expected_count=*/2);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksAverageNodeSizeMetricName, ".Encrypted"}),
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksTimeToReadFileMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksTimeToReadFileMetricName, ".LocalOrSyncable",
".Encrypted"}),
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksTimeToReadFileMetricName, ".Account", ".ClearText"}),
/*expected_count=*/1);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksTimeToReadFileMetricName, ".Account", ".Encrypted"}),
/*expected_count=*/1);
}
TEST_P(ModelLoaderWithSecondayFileTest,
LoadBookmarks_ShouldNotCreateSecondaryFilesWhenPrimaryFilesAreMissing) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath primary_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath secondary_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
const base::FilePath primary_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_3.json");
const base::FilePath secondary_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_4.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
scoped_refptr<ModelLoader> loader =
CreateModelLoader(primary_local_or_syncable_file_path,
secondary_local_or_syncable_file_path,
primary_account_file_path, secondary_account_file_path,
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
EXPECT_FALSE(save_local_or_syncable_bookmark_future.IsReady());
EXPECT_FALSE(save_account_bookmark_future.IsReady());
// We tried to read the primary files but fail.
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
GetPrimaryEncryptionHistogramSuffix()}),
metrics::BookmarksFileLoadResult::kFileMissing,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".Account",
GetPrimaryEncryptionHistogramSuffix()}),
metrics::BookmarksFileLoadResult::kFileMissing,
/*expected_bucket_count=*/1);
// We never try to read the secondary file.
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
GetSecondaryEncryptionHistogramSuffix()}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".Account",
GetSecondaryEncryptionHistogramSuffix()}),
/*expected_count=*/0);
}
INSTANTIATE_TEST_SUITE_P(
ModelLoaderWithSecondayFileTest,
ModelLoaderWithSecondayFileTest,
testing::Values(BookmarkEncryptionStage::kWriteBothReadOnlyClear,
BookmarkEncryptionStage::kWriteBothReadPreferEncrypted));
TEST(ModelLoaderTest, LoadBookmarks_ShouldReportDecryptionFailed) {
base::test::ScopedFeatureList features;
test::InitFeaturesForBookmarkTestEncryptionStage(
features, BookmarkEncryptionStage::kWriteBothReadOnlyClear);
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
const base::FilePath local_or_syncable_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json"),
"Bookmarks");
const base::FilePath account_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json"),
"AccountBookmarks");
// These files aren't encrypted, so they will will fail decryption.
const base::FilePath encrypted_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json");
const base::FilePath encrypted_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(), details_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
VerifyPrimaryLoadCorrectlySecondaryHasGivenResult(
histogram_tester, /*primary_histogram_suffix=*/".ClearText",
/*secondary_histogram_suffix=*/".Encrypted",
/*secondary_result=*/metrics::BookmarksFileLoadResult::kDecryptionFailed);
// Verify that saving of the encrypted files is scheduled.
EXPECT_EQ(save_local_or_syncable_bookmark_future.Get(),
StorageFileEncryptionType::kEncrypted);
EXPECT_EQ(save_account_bookmark_future.Get(),
StorageFileEncryptionType::kEncrypted);
EXPECT_THAT(local_or_syncable_file_path, FileAndBackupFileExist());
EXPECT_THAT(account_file_path, FileAndBackupFileExist());
}
class ModelLoaderWithEncryptionFileAsPrimaryTest
: public testing::TestWithParam<BookmarkEncryptionStage> {
protected:
ModelLoaderWithEncryptionFileAsPrimaryTest() {
test::InitFeaturesForBookmarkTestEncryptionStage(feature_list_, GetParam());
}
base::test::ScopedFeatureList feature_list_;
};
TEST_P(ModelLoaderWithEncryptionFileAsPrimaryTest,
LoadBookmarks_ShouldLoadDetailsCorrectlyFromEncryptedFiles) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
const base::FilePath local_or_syncable_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json"),
"Bookmarks");
const base::FilePath encrypted_local_or_syncable_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_1.json"),
"EncryptedBookmarks", encryptor)
.value();
const base::FilePath account_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json"),
"AccountBookmarks");
const base::FilePath encrypted_account_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_2.json"),
"EncryptedAccountBookmarks", encryptor)
.value();
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(), details_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
// Clear text file is successfully used as primary file fallback.
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
}
TEST_P(ModelLoaderWithEncryptionFileAsPrimaryTest,
LoadBookmarks_ShouldSaveEncryptedFileIfMissingAndFallBackToClearText) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
const base::FilePath local_or_syncable_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json"),
"Bookmarks");
const base::FilePath account_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json"),
"AccountBookmarks");
const base::FilePath encrypted_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath encrypted_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(), details_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
// Clear text file is successfully used as primary file fallback.
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kFallbackToClearTextFileOnLoadResultMetricName, ".LocalOrSyncable"}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kFallbackToClearTextFileOnLoadResultMetricName, ".Account"}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
// Encrypted file isn't read.
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".Encrypted"}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".Encrypted"}),
/*expected_count=*/0);
// Verify that the save encrypted file callback is called for both files.
EXPECT_EQ(StorageFileEncryptionType::kEncrypted,
save_local_or_syncable_bookmark_future.Get());
EXPECT_EQ(StorageFileEncryptionType::kEncrypted,
save_account_bookmark_future.Get());
EXPECT_THAT(local_or_syncable_file_path, FileAndBackupFileExist());
EXPECT_THAT(account_file_path, FileAndBackupFileExist());
}
TEST_P(ModelLoaderWithEncryptionFileAsPrimaryTest,
LoadBookmarks_ShouldSaveEncryptedFileEvenIfClearTextFallbackFails) {
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
const base::FilePath local_or_syncable_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_invalid_json.json"),
"Bookmarks");
const base::FilePath account_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_invalid_json.json"),
"AccountBookmarks");
const base::FilePath encrypted_local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath encrypted_account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(), details_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
// Clear text file fails to load.
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
EXPECT_EQ(0u, details->bb_node()->children().size());
EXPECT_EQ(0u, details->other_folder_node()->children().size());
EXPECT_EQ(0u, details->mobile_folder_node()->children().size());
EXPECT_EQ(nullptr, details->account_bb_node());
EXPECT_EQ(nullptr, details->account_other_folder_node());
EXPECT_EQ(nullptr, details->account_mobile_folder_node());
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kFallbackToClearTextFileOnLoadResultMetricName, ".LocalOrSyncable"}),
metrics::BookmarksFileLoadResult::kJSONParsingFailed,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kFallbackToClearTextFileOnLoadResultMetricName, ".Account"}),
metrics::BookmarksFileLoadResult::kJSONParsingFailed,
/*expected_bucket_count=*/1);
// Encrypted file isn't read.
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".Encrypted"}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".Encrypted"}),
/*expected_count=*/0);
// Verify that the save encrypted file callback is called for both files.
EXPECT_EQ(StorageFileEncryptionType::kEncrypted,
save_local_or_syncable_bookmark_future.Get());
EXPECT_EQ(StorageFileEncryptionType::kEncrypted,
save_account_bookmark_future.Get());
EXPECT_THAT(local_or_syncable_file_path, FileAndBackupFileExist());
EXPECT_THAT(account_file_path, FileAndBackupFileExist());
}
INSTANTIATE_TEST_SUITE_P(
ModelLoaderWithEncryptionFileAsPrimaryTest,
ModelLoaderWithEncryptionFileAsPrimaryTest,
testing::Values(
BookmarkEncryptionStage::kWriteBothReadPreferEncrypted,
BookmarkEncryptionStage::kWriteOnlyEncryptedReadPreferEncrypted));
TEST(ModelLoaderWithEncryptionWriteOnly,
LoadBookmarks_ShouldNeverLoadOrSaveClearTextFile) {
base::test::ScopedFeatureList features;
test::InitFeaturesForBookmarkTestEncryptionStage(
features,
BookmarkEncryptionStage::kWriteOnlyEncryptedReadPreferEncrypted);
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath encrypted_local_or_syncable_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_1.json"),
"TestEncryptedBookmarks1", encryptor)
.value();
const base::FilePath account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
const base::FilePath encrypted_account_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_2.json"),
"TestEncryptedBookmarks2", encryptor)
.value();
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
base::test::TestFuture<std::unique_ptr<BookmarkLoadDetails>> details_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(),
/*callback=*/details_future.GetCallback());
task_environment.FastForwardUntilNoTasksRemain();
// Encrypted file read succeeds for both local or syncable and account files.
const std::unique_ptr<BookmarkLoadDetails> details = details_future.Take();
ASSERT_NE(nullptr, details);
EXPECT_EQ(1u, details->bb_node()->children().size());
EXPECT_EQ(1u, details->other_folder_node()->children().size());
EXPECT_EQ(1u, details->mobile_folder_node()->children().size());
EXPECT_EQ(1u, details->account_bb_node()->children().size());
EXPECT_EQ(1u, details->account_other_folder_node()->children().size());
EXPECT_EQ(1u, details->account_mobile_folder_node()->children().size());
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".Encrypted"}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".Encrypted"}),
metrics::BookmarksFileLoadResult::kSuccess,
/*expected_bucket_count=*/1);
// No clear text file save are requested.
EXPECT_FALSE(save_local_or_syncable_bookmark_future.IsReady());
EXPECT_FALSE(save_account_bookmark_future.IsReady());
// No clear text file load results are logged.
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksFileLoadResultMetricName, ".LocalOrSyncable",
".ClearText"}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kBookmarksFileLoadResultMetricName, ".Account", ".ClearText"}),
/*expected_count=*/0);
// No comparison with clear text file are logged.
histogram_tester.ExpectTotalCount(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".LocalOrSyncable"}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat(
{kEncryptedBookmarksFileMatchesResultMetricName, ".Account"}),
/*expected_count=*/0);
}
TEST(ModelLoaderWithEncryptionWriteOnly,
LoadBookmarks_ShouldRecordOnlyEncryptedFileSizes) {
base::test::ScopedFeatureList features;
test::InitFeaturesForBookmarkTestEncryptionStage(
features,
BookmarkEncryptionStage::kWriteOnlyEncryptedReadPreferEncrypted);
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath local_or_syncable_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_1.json");
const base::FilePath encrypted_local_or_syncable_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_1.json"),
"TestEncryptedBookmarks1", encryptor)
.value();
const base::FilePath account_file_path =
GetTestDataDir().AppendASCII("bookmarks/missing_file_2.json");
const base::FilePath encrypted_account_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_2.json"),
"TestEncryptedBookmarks2", encryptor)
.value();
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(),
/*callback=*/base::DoNothing());
task_environment.FastForwardUntilNoTasksRemain();
// Only record encrypted file sizes
histogram_tester.ExpectTotalCount(
kBookmarksStorageFileSizeAtStartupMetricName,
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksAverageNodeSizeMetricName, ".ClearText"}),
/*expected_count=*/0);
histogram_tester.ExpectTotalCount(
kBookmarksEncryptedStorageFileSizeAtStartupMetricName,
/*expected_count=*/2);
histogram_tester.ExpectTotalCount(
base::StrCat({kBookmarksAverageNodeSizeMetricName, ".Encrypted"}),
/*expected_count=*/1);
}
TEST(ModelLoaderWithEncryptionWriteOnly,
LoadBookmarks_ShouldDeleteClearTextFilesWhenEncryptedReadSucceeds) {
base::test::ScopedFeatureList features;
test::InitFeaturesForBookmarkTestEncryptionStage(
features,
BookmarkEncryptionStage::kWriteOnlyEncryptedReadPreferEncrypted);
scoped_refptr<base::RefCountedData<const os_crypt_async::Encryptor>>
encryptor = base::MakeRefCounted<
base::RefCountedData<const os_crypt_async::Encryptor>>(
std::in_place, os_crypt_async::GetTestEncryptorForTesting());
base::HistogramTester histogram_tester;
base::test::TaskEnvironment task_environment{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
const base::FilePath local_or_syncable_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_1.json"),
"TestBookmarks1");
const base::FilePath encrypted_local_or_syncable_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_1.json"),
"TestEncryptedBookmarks1", encryptor)
.value();
const base::FilePath account_file_path = CreateCopyWithBackup(
GetTestDataDir().AppendASCII("bookmarks/model_with_sync_metadata_2.json"),
"TestBookmarks2");
const base::FilePath encrypted_account_file_path =
CreateTempEncryptedFile(GetTestDataDir().AppendASCII(
"bookmarks/model_with_sync_metadata_2.json"),
"TestEncryptedBookmarks2", encryptor)
.value();
base::test::TestFuture<StorageFileEncryptionType>
save_local_or_syncable_bookmark_future;
base::test::TestFuture<StorageFileEncryptionType>
save_account_bookmark_future;
scoped_refptr<ModelLoader> loader = ModelLoader::Create(
encryptor, local_or_syncable_file_path,
encrypted_local_or_syncable_file_path, account_file_path,
encrypted_account_file_path, LoadManagedNodeCallback(),
save_local_or_syncable_bookmark_future.GetCallback(),
save_account_bookmark_future.GetCallback(),
/*callback=*/base::DoNothing());
task_environment.FastForwardUntilNoTasksRemain();
// Clear text files are deleted.
EXPECT_FALSE(base::PathExists(local_or_syncable_file_path));
EXPECT_FALSE(base::PathExists(account_file_path));
EXPECT_FALSE(base::PathExists(
local_or_syncable_file_path.ReplaceExtension(kBackupExtension)));
EXPECT_FALSE(
base::PathExists(account_file_path.ReplaceExtension(kBackupExtension)));
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksDeleteFileMetricName, ".LocalOrSyncable"}),
/*sample=*/true,
/*expected_bucket_count=*/1);
histogram_tester.ExpectUniqueSample(
base::StrCat({kBookmarksDeleteFileMetricName, ".Account"}),
/*sample=*/true,
/*expected_bucket_count=*/1);
// Encrypted files still exist.
EXPECT_TRUE(base::PathExists(encrypted_local_or_syncable_file_path));
EXPECT_TRUE(base::PathExists(encrypted_account_file_path));
}
} // namespace
} // namespace bookmarks