chromium/ios/chrome/browser/bookmarks/model/bookmark_model_factory_unittest.cc

// 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 "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/no_destructor.h"
#include "base/path_service.h"
#include "base/test/metrics/histogram_tester.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/url_and_title.h"
#include "components/bookmarks/common/bookmark_constants.h"
#include "ios/chrome/browser/bookmarks/model/bookmark_ios_unit_test_support.h"
#include "ios/chrome/browser/bookmarks/model/bookmark_model_factory.h"
#include "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace ios {

namespace {

using testing::UnorderedElementsAre;

const char kLocalOrSyncableIdsReassignedMetricName[] =
    "Bookmarks.IdsReassigned.OnProfileLoad.LocalOrSyncable";
const char kAccountIdsReassignedMetricName[] =
    "Bookmarks.IdsReassigned.OnProfileLoad.Account";

MATCHER_P(HasUrl, expected_url, "") {
  return arg.url == expected_url;
}

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;
}

}  // namespace

class BookmarkModelFactoryTest : public BookmarkIOSUnitTestSupport {
 public:
  BookmarkModelFactoryTest() = default;
  ~BookmarkModelFactoryTest() override = default;
};

class BookmarkModelFactoryWithIdCollisionsWithinOneFileOnDiskTest
    : public BookmarkModelFactoryTest {
 protected:
  BookmarkModelFactoryWithIdCollisionsWithinOneFileOnDiskTest() {}
  ~BookmarkModelFactoryWithIdCollisionsWithinOneFileOnDiskTest() override {}

  void SetUpBrowserStateBeforeCreatingServices() override {
    BookmarkModelFactoryTest::SetUpBrowserStateBeforeCreatingServices();

    // Populate the JSON files on disk, in a way that the local-or-syncable file
    // has duplicate IDs that trigger the reassignment of bookmark IDs. This
    // setup is similar to the components/bookmarks test
    // ModelLoaderTest.LoadTwoFilesWhereFirstHasInternalIdCollisions.
    ASSERT_TRUE(base::CopyFile(
        GetTestDataDir().AppendASCII("bookmarks/model_with_duplicate_ids.json"),
        chrome_browser_state_->GetStatePath().Append(
            bookmarks::kLocalOrSyncableBookmarksFileName)));
    ASSERT_TRUE(base::CopyFile(GetTestDataDir().AppendASCII(
                                   "bookmarks/model_with_sync_metadata_2.json"),
                               chrome_browser_state_->GetStatePath().Append(
                                   bookmarks::kAccountBookmarksFileName)));
  }

  base::HistogramTester histogram_tester_;
};

TEST_F(BookmarkModelFactoryWithIdCollisionsWithinOneFileOnDiskTest,
       ReassignIdsAndLogMetrics) {
  histogram_tester_.ExpectUniqueSample(kAccountIdsReassignedMetricName,
                                       /*sample=*/false,
                                       /*expected_bucket_count=*/1);
  histogram_tester_.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
                                       /*sample=*/true,
                                       /*expected_bucket_count=*/1);
}

class BookmarkModelFactoryWithIdCollisionsAcrossTwoFilesOnDiskTest
    : public BookmarkModelFactoryTest {
 protected:
  BookmarkModelFactoryWithIdCollisionsAcrossTwoFilesOnDiskTest() {}
  ~BookmarkModelFactoryWithIdCollisionsAcrossTwoFilesOnDiskTest() override {}

  void SetUpBrowserStateBeforeCreatingServices() override {
    BookmarkModelFactoryTest::SetUpBrowserStateBeforeCreatingServices();

    // Populate the JSON files on disk, in a way that there are ID collisions
    // across, which is achieved by reusing the very same file content. This
    // setup is similar to the components/bookmarks test
    // ModelLoaderTest.LoadTwoFilesWithCollidingIdsAcross.
    ASSERT_TRUE(
        base::CopyFile(GetTestDataDir().AppendASCII(
                           "bookmarks/model_with_sync_metadata_1.json"),
                       chrome_browser_state_->GetStatePath().Append(
                           bookmarks::kLocalOrSyncableBookmarksFileName)));
    ASSERT_TRUE(base::CopyFile(GetTestDataDir().AppendASCII(
                                   "bookmarks/model_with_sync_metadata_1.json"),
                               chrome_browser_state_->GetStatePath().Append(
                                   bookmarks::kAccountBookmarksFileName)));
  }

  base::HistogramTester histogram_tester_;
};

TEST_F(BookmarkModelFactoryWithIdCollisionsAcrossTwoFilesOnDiskTest,
       ReassignIdsAndLogMetrics) {
  histogram_tester_.ExpectUniqueSample(kAccountIdsReassignedMetricName,
                                       /*sample=*/false,
                                       /*expected_bucket_count=*/1);
  // The ID collisions across two files are detected and reassigned.
  histogram_tester_.ExpectUniqueSample(kLocalOrSyncableIdsReassignedMetricName,
                                       /*sample=*/1,
                                       /*expected_bucket_count=*/1);
}

}  // namespace ios