// Copyright 2013 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/profile_resetter/profile_resetter.h"

#include <stddef.h>

#include <functional>
#include <map>
#include <memory>
#include <string>
#include <utility>

#include "base/byte_size.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_path_override.h"
#include "base/test/with_feature_override.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/google/google_brand.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"
#include "chrome/browser/profile_resetter/profile_reset_report.pb.h"
#include "chrome/browser/profile_resetter/profile_resetter_test_base.h"
#include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
#include "chrome/browser/search/background/ntp_custom_background_service.h"
#include "chrome/browser/search/background/ntp_custom_background_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_test_util.h"
#include "chrome/browser/themes/test/theme_service_changed_waiter.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/webdata_services/web_data_service_factory.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/browser/content_settings_info.h"
#include "components/content_settings/core/browser/content_settings_registry.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/content_settings/core/browser/website_settings_info.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/search_engines/enterprise/enterprise_search_manager.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_storage_partition.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest_constants.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_WIN)
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/process/process_handle.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/shortcut.h"
#endif

using extensions::mojom::ManifestLocation;

namespace {

const char kDistributionConfig[] =
    "{"
    " \"homepage\" : \"http://www.foo.com\","
    " \"homepage_is_newtabpage\" : false,"
    " \"browser\" : {"
    "   \"show_home_button\" : true"
    "  },"
    " \"session\" : {"
    "   \"restore_on_startup\" : 4,"
    "   \"startup_urls\" : [\"http://goo.gl\", \"http://foo.de\"]"
    "  },"
    " \"extensions\" : {"
    "   \"settings\" : {"
    "     \"placeholder_for_id\": {"
    "      }"
    "    }"
    "  }"
    "}";

const char kDistributionConfigWithSearch[] =
    "{"
    " \"homepage\" : \"http://www.foo.com\","
    " \"homepage_is_newtabpage\" : false,"
    " \"browser\" : {"
    "   \"show_home_button\" : true"
    "  },"
    " \"session\" : {"
    "   \"restore_on_startup\" : 4,"
    "   \"startup_urls\" : [\"http://goo.gl\", \"http://foo.de\"]"
    "  },"
    " \"search_provider_overrides\" : ["
    "    {"
    "      \"name\" : \"first\","
    "      \"keyword\" : \"firstkey\","
    "      \"search_url\" : \"http://www.foo.com/s?q={searchTerms}\","
    "      \"favicon_url\" : \"http://www.foo.com/favicon.ico\","
    "      \"suggest_url\" : \"http://www.foo.com/s?q={searchTerms}\","
    "      \"encoding\" : \"UTF-8\","
    "      \"id\" : 1001"
    "    }"
    "  ],"
    " \"extensions\" : {"
    "   \"settings\" : {"
    "     \"placeholder_for_id\": {"
    "      }"
    "    }"
    "  }"
    "}";

const char kXmlConfig[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
    "<response protocol=\"3.0\" server=\"prod\">"
      "<app appid=\"{8A69D345-D564-463C-AFF1-A69D9E530F96}\" status=\"ok\">"
        "<data index=\"skipfirstrunui-importsearch-defaultbrowser\" "
          "name=\"install\" status=\"ok\">"
          "placeholder_for_data"
        "</data>"
      "</app>"
    "</response>";

using extensions::Extension;
using extensions::Manifest;

class FakeNtpCustomBackgroundService : public NtpCustomBackgroundService {
 public:
  using NtpCustomBackgroundService::NtpCustomBackgroundService;
  void FetchCustomBackgroundAndExtractBackgroundColor(
      const GURL& image_url,
      const GURL& fetch_url) override {}
};

std::unique_ptr<KeyedService> CreateFakeNtpCustomBackgroundService(
    content::BrowserContext* context) {
  Profile* profile = Profile::FromBrowserContext(context);
  return std::make_unique<FakeNtpCustomBackgroundService>(profile);
}

// ProfileResetterTest --------------------------------------------------------

// ProfileResetterTest sets up the extension, WebData and TemplateURL services.
class ProfileResetterTest : public extensions::ExtensionServiceTestBase,
                            public ProfileResetterTestBase {
 public:
  ProfileResetterTest();
  ~ProfileResetterTest() override;

 protected:
  void SetUp() override;

 private:
#if BUILDFLAG(IS_WIN)
  base::ScopedPathOverride user_desktop_override_;
  base::ScopedPathOverride app_dir_override_;
  base::ScopedPathOverride start_menu_override_;
  base::ScopedPathOverride taskbar_pins_override_;
  base::win::ScopedCOMInitializer com_init_;
#endif
};

ProfileResetterTest::ProfileResetterTest()
#if BUILDFLAG(IS_WIN)
    : user_desktop_override_(base::DIR_USER_DESKTOP),
      app_dir_override_(base::DIR_ROAMING_APP_DATA),
      start_menu_override_(base::DIR_START_MENU),
      taskbar_pins_override_(base::DIR_TASKBAR_PINS)
#endif
{}

ProfileResetterTest::~ProfileResetterTest() = default;

void ProfileResetterTest::SetUp() {
  extensions::ExtensionServiceTestBase::SetUp();
  ExtensionServiceInitParams params;
  params.testing_factories = {
      TestingProfile::TestingFactory(
          NtpCustomBackgroundServiceFactory::GetInstance(),
          base::BindRepeating(&CreateFakeNtpCustomBackgroundService)),
      TestingProfile::TestingFactory(
          TemplateURLServiceFactory::GetInstance(),
          TemplateURLServiceTestUtil::GetTemplateURLServiceTestingFactory()),
  };

  InitializeExtensionService(std::move(params));

  google_brand::BrandForTesting brand_for_testing("");
  resetter_ = std::make_unique<ProfileResetter>(profile());
}

// ConfigParserTest -----------------------------------------------------------

class ConfigParserTest : public testing::Test {
 protected:
  ConfigParserTest();
  ~ConfigParserTest() override;

  std::unique_ptr<BrandcodeConfigFetcher> WaitForRequest(const GURL& url);

  network::TestURLLoaderFactory& test_url_loader_factory() {
    return test_url_loader_factory_;
  }

 private:
  MOCK_METHOD0(Callback, void(void));

  content::BrowserTaskEnvironment task_environment_;
  network::TestURLLoaderFactory test_url_loader_factory_;
  data_decoder::test::InProcessDataDecoder data_decoder_;
};

ConfigParserTest::ConfigParserTest()
    : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP) {}

ConfigParserTest::~ConfigParserTest() = default;

std::unique_ptr<BrandcodeConfigFetcher> ConfigParserTest::WaitForRequest(
    const GURL& url) {
  EXPECT_CALL(*this, Callback());
  std::string upload_data;
  test_url_loader_factory_.SetInterceptor(
      base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
        upload_data = network::GetUploadData(request);
      }));
  std::unique_ptr<BrandcodeConfigFetcher> fetcher(new BrandcodeConfigFetcher(
      &test_url_loader_factory_,
      base::BindOnce(&ConfigParserTest::Callback, base::Unretained(this)), url,
      "ABCD"));
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(fetcher->IsActive());
  // Look for the brand code in the request.
  EXPECT_NE(std::string::npos, upload_data.find("ABCD"));
  return fetcher;
}

// A helper class to create/delete/check a Chrome desktop shortcut on Windows.
class ShortcutHandler {
 public:
  ShortcutHandler();

  ShortcutHandler(const ShortcutHandler&) = delete;
  ShortcutHandler& operator=(const ShortcutHandler&) = delete;

  ~ShortcutHandler();

  static bool IsSupported();
  ShortcutCommand CreateWithArguments(const std::wstring& name,
                                      const std::wstring& args);
  void CheckShortcutHasArguments(const std::wstring& desired_args) const;
  void Delete();
  void HideFile();
  bool IsFileHidden() const;

 private:
#if BUILDFLAG(IS_WIN)
  base::FilePath shortcut_path_;
#endif
};

#if BUILDFLAG(IS_WIN)
ShortcutHandler::ShortcutHandler() = default;

ShortcutHandler::~ShortcutHandler() {
  if (!shortcut_path_.empty())
    Delete();
}

// static
bool ShortcutHandler::IsSupported() {
  return true;
}

ShortcutCommand ShortcutHandler::CreateWithArguments(const std::wstring& name,
                                                     const std::wstring& args) {
  EXPECT_TRUE(shortcut_path_.empty());
  base::FilePath path_to_create;
  EXPECT_TRUE(base::PathService::Get(base::DIR_USER_DESKTOP, &path_to_create));
  path_to_create = path_to_create.Append(name);
  EXPECT_FALSE(base::PathExists(path_to_create)) << path_to_create.value();

  base::FilePath path_exe;
  EXPECT_TRUE(base::PathService::Get(base::FILE_EXE, &path_exe));
  base::win::ShortcutProperties shortcut_properties;
  shortcut_properties.set_target(path_exe);
  shortcut_properties.set_arguments(args);
  EXPECT_TRUE(base::win::CreateOrUpdateShortcutLink(
      path_to_create, shortcut_properties,
      base::win::ShortcutOperation::kCreateAlways))
      << path_to_create.value();
  shortcut_path_ = path_to_create;
  return ShortcutCommand(shortcut_path_, args);
}

void ShortcutHandler::CheckShortcutHasArguments(
    const std::wstring& desired_args) const {
  EXPECT_FALSE(shortcut_path_.empty());
  std::wstring args;
  EXPECT_TRUE(base::win::ResolveShortcut(shortcut_path_, NULL, &args));
  EXPECT_EQ(desired_args, args);
}

void ShortcutHandler::Delete() {
  EXPECT_FALSE(shortcut_path_.empty());
  EXPECT_TRUE(base::DeleteFile(shortcut_path_));
  shortcut_path_.clear();
}

void ShortcutHandler::HideFile() {
  DWORD attributes = ::GetFileAttributes(shortcut_path_.value().c_str());
  ASSERT_NE(attributes, INVALID_FILE_ATTRIBUTES);
  ASSERT_TRUE(::SetFileAttributes(shortcut_path_.value().c_str(),
                                  attributes | FILE_ATTRIBUTE_HIDDEN));
}

bool ShortcutHandler::IsFileHidden() const {
  DWORD attributes = ::GetFileAttributes(shortcut_path_.value().c_str());
  EXPECT_NE(attributes, INVALID_FILE_ATTRIBUTES);
  return attributes & FILE_ATTRIBUTE_HIDDEN;
}

#else
ShortcutHandler::ShortcutHandler() = default;

ShortcutHandler::~ShortcutHandler() = default;

// static
bool ShortcutHandler::IsSupported() {
  return false;
}

ShortcutCommand ShortcutHandler::CreateWithArguments(const std::wstring& name,
                                                     const std::wstring& args) {
  return ShortcutCommand();
}

void ShortcutHandler::CheckShortcutHasArguments(
    const std::wstring& desired_args) const {}

void ShortcutHandler::Delete() {
}

void ShortcutHandler::HideFile() {}

bool ShortcutHandler::IsFileHidden() const {
  return false;
}
#endif  // BUILDFLAG(IS_WIN)

// helper functions -----------------------------------------------------------

scoped_refptr<Extension> CreateExtension(const std::u16string& name,
                                         const base::FilePath& path,
                                         ManifestLocation location,
                                         extensions::Manifest::Type type,
                                         bool installed_by_default) {
  base::DictValue manifest;
  manifest.Set(extensions::manifest_keys::kVersion, "1.0.0.0");
  manifest.Set(extensions::manifest_keys::kName, name);
  manifest.Set(extensions::manifest_keys::kManifestVersion, 2);
  switch (type) {
    case extensions::Manifest::Type::kTheme:
      manifest.Set(extensions::manifest_keys::kTheme, base::DictValue());
      break;
    case extensions::Manifest::Type::kHostedApp:
      manifest.SetByDottedPath(extensions::manifest_keys::kLaunchWebURL,
                               "http://www.google.com");
      manifest.Set(extensions::manifest_keys::kUpdateURL,
                   "http://clients2.google.com/service/update2/crx");
      break;
    case extensions::Manifest::Type::kExtension:
      // do nothing
      break;
    default:
      NOTREACHED();
  }
  manifest.SetByDottedPath(extensions::manifest_keys::kOmniboxKeyword, name);
  std::u16string error;
  scoped_refptr<Extension> extension = Extension::Create(
      path,
      location,
      manifest,
      installed_by_default ? Extension::WAS_INSTALLED_BY_DEFAULT
                           : Extension::NO_FLAGS,
      &error);
  EXPECT_TRUE(extension.get() != nullptr) << error;
  return extension;
}

void ReplaceString(std::string* str,
                   const std::string& placeholder,
                   const std::string& substitution) {
  ASSERT_NE(static_cast<std::string*>(nullptr), str);
  size_t placeholder_pos = str->find(placeholder);
  ASSERT_NE(std::string::npos, placeholder_pos);
  str->replace(placeholder_pos, placeholder.size(), substitution);
}

/********************* Tests *********************/

TEST_F(ProfileResetterTest, ResetNothing) {
  // The callback should be called even if there is nothing to reset.
  ResetAndWait(0);
}

class ProfileResetterTestWithIgnoreSearchProviderOverrides
    : public base::test::WithFeatureOverride,
      public ProfileResetterTest {
 public:
  ProfileResetterTestWithIgnoreSearchProviderOverrides()
      : base::test::WithFeatureOverride(
            switches::kIgnoreSearchProviderOverrides) {}
};

TEST_P(ProfileResetterTestWithIgnoreSearchProviderOverrides,
       ResetDefaultSearchEngineNonOrganic) {
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE,
               kDistributionConfigWithSearch);

  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(profile());
  const TemplateURL* default_engine = model->GetDefaultSearchProvider();
  ASSERT_NE(static_cast<TemplateURL*>(nullptr), default_engine);

  if (IsParamFeatureEnabled()) {
    EXPECT_EQ(u"Google", default_engine->short_name());
    EXPECT_EQ(u"google.com", default_engine->keyword());
    EXPECT_NE(std::string::npos,
              default_engine->url().find("{google:baseURL}"));
  } else {
    EXPECT_EQ(u"first", default_engine->short_name());
    EXPECT_EQ(u"firstkey", default_engine->keyword());
    EXPECT_EQ("http://www.foo.com/s?q={searchTerms}", default_engine->url());
  }
}

INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(
    ProfileResetterTestWithIgnoreSearchProviderOverrides);

TEST_F(ProfileResetterTest, ResetDefaultSearchEnginePartially) {
  // Search engine's logic is tested by
  // TemplateURLServiceTest.RepairPrepopulatedSearchEngines.
  // Make sure TemplateURLService has loaded.
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(profile());
  TemplateURLService::TemplateURLVector urls = model->GetTemplateURLs();

  // The second call should produce no effect.
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  EXPECT_EQ(urls, model->GetTemplateURLs());
}

TEST_F(ProfileResetterTest, ResetDefaultSearchEngineRemovesCustomKeywords) {
  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(profile());
  ASSERT_TRUE(model);

  {
    TemplateURLData custom_engine_data;
    custom_engine_data.SetShortName(u"Custom Engine");
    custom_engine_data.SetKeyword(u"custom");
    custom_engine_data.SetURL("https://custom.search.com?q={searchTerms}");
    TemplateURL* custom_engine =
        model->Add(std::make_unique<TemplateURL>(custom_engine_data));
    ASSERT_TRUE(custom_engine);
    EXPECT_EQ(custom_engine, model->GetTemplateURLForKeyword(u"custom"));
  }

  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  EXPECT_FALSE(model->GetTemplateURLForKeyword(u"custom"));
}

TEST_F(ProfileResetterTest,
       ResetDefaultSearchEngine_RestoresPolicySiteSearchEngines) {
  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(profile());
  ASSERT_TRUE(model);
  model->Load();

  EnterpriseSearchManager::OwnedTemplateURLDataVector enterprise_search_engines;
  auto data = std::make_unique<TemplateURLData>();
  data->SetShortName(u"workname");
  data->SetKeyword(u"work");
  data->SetURL("https://work.com/q={searchTerms}");
  data->policy_origin = TemplateURLData::PolicyOrigin::kSiteSearch;
  data->enforced_by_policy = false;
  data->is_active = TemplateURLData::ActiveStatus::kTrue;
  data->favicon_url = GURL("https://work.com/favicon.ico");
  data->safe_for_autoreplace = false;
  enterprise_search_engines.push_back(std::move(data));

  SetManagedSearchSettingsPreference(enterprise_search_engines,
                                     static_cast<TestingProfile*>(profile()));

  TemplateURL* turl = model->GetTemplateURLForKeyword(u"work");
  ASSERT_TRUE(turl);
  EXPECT_TRUE(turl->CanPolicyBeOverridden());

  // Remove the recommended engine, causing its keyword to be marked as
  // overridden.
  model->Remove(turl);
  EXPECT_FALSE(model->GetTemplateURLForKeyword(u"work"));
  PrefService* prefs = profile()->GetPrefs();
  EXPECT_FALSE(prefs
                   ->GetList(EnterpriseSearchManager::
                                 kSiteSearchSettingsOverriddenKeywordsPrefName)
                   .empty());

  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  EXPECT_TRUE(prefs
                  ->GetList(EnterpriseSearchManager::
                                kSiteSearchSettingsOverriddenKeywordsPrefName)
                  .empty());
  EXPECT_TRUE(model->GetTemplateURLForKeyword(u"work"));
}

TEST_F(ProfileResetterTest,
       ResetDefaultSearchEngine_RestoresAllSearchEngineTypes) {
  TemplateURLService* model =
      TemplateURLServiceFactory::GetForProfile(profile());
  ASSERT_TRUE(model);
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  // 1. Setup Policy site search engine ("work") and remove it.
  EnterpriseSearchManager::OwnedTemplateURLDataVector enterprise_search_engines;
  auto data = std::make_unique<TemplateURLData>();
  data->SetShortName(u"workname");
  data->SetKeyword(u"work");
  data->SetURL("https://work.com/q={searchTerms}");
  data->policy_origin = TemplateURLData::PolicyOrigin::kSiteSearch;
  data->enforced_by_policy = false;
  data->is_active = TemplateURLData::ActiveStatus::kTrue;
  data->favicon_url = GURL("https://work.com/favicon.ico");
  data->safe_for_autoreplace = false;
  enterprise_search_engines.push_back(std::move(data));

  SetManagedSearchSettingsPreference(enterprise_search_engines,
                                     static_cast<TestingProfile*>(profile()));
  TemplateURL* policy_turl = model->GetTemplateURLForKeyword(u"work");
  ASSERT_TRUE(policy_turl);
  model->Remove(policy_turl);
  EXPECT_FALSE(model->GetTemplateURLForKeyword(u"work"));

  // 2. Remove a starter pack engine (e.g. "@history" or "@bookmarks").
  TemplateURL* starter_pack_turl =
      model->GetTemplateURLForKeyword(u"@bookmarks");
  if (!starter_pack_turl) {
    starter_pack_turl = model->GetTemplateURLForKeyword(u"@history");
  }
  ASSERT_TRUE(starter_pack_turl);
  std::u16string starter_pack_keyword = starter_pack_turl->keyword();
  model->Remove(starter_pack_turl);
  EXPECT_FALSE(model->GetTemplateURLForKeyword(starter_pack_keyword));

  // 3. Remove a prepopulated search engine (that is not default).
  TemplateURLService::TemplateURLVector template_urls =
      model->GetTemplateURLs();
  std::u16string prepopulated_keyword;
  for (TemplateURL* turl : template_urls) {
    if (turl->prepopulate_id() != 0 &&
        turl != model->GetDefaultSearchProvider()) {
      prepopulated_keyword = turl->keyword();
      model->Remove(turl);
      break;
    }
  }
  ASSERT_FALSE(prepopulated_keyword.empty());
  EXPECT_FALSE(model->GetTemplateURLForKeyword(prepopulated_keyword));

  // 4. Invoke ProfileResetter to reset search engines.
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE);

  // 5. Verify ALL THREE types of engines are restored!
  EXPECT_TRUE(model->GetTemplateURLForKeyword(u"work"));
  EXPECT_TRUE(model->GetTemplateURLForKeyword(starter_pack_keyword));
  EXPECT_TRUE(model->GetTemplateURLForKeyword(prepopulated_keyword));
}

TEST_F(ProfileResetterTest, ResetHomepageNonOrganic) {
  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);
  prefs->SetBoolean(prefs::kHomePageIsNewTabPage, true);
  prefs->SetString(prefs::kHomePage, "http://google.com");
  prefs->SetBoolean(prefs::kShowHomeButton, false);

  ResetAndWait(ProfileResetter::HOMEPAGE, kDistributionConfig);

  EXPECT_FALSE(prefs->GetBoolean(prefs::kHomePageIsNewTabPage));
  EXPECT_EQ("http://www.foo.com", prefs->GetString(prefs::kHomePage));
  EXPECT_TRUE(prefs->GetBoolean(prefs::kShowHomeButton));
}

TEST_F(ProfileResetterTest, ResetHomepagePartially) {
  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);
  prefs->SetBoolean(prefs::kHomePageIsNewTabPage, false);
  prefs->SetString(prefs::kHomePage, "http://www.foo.com");
  prefs->SetBoolean(prefs::kShowHomeButton, true);

  ResetAndWait(ProfileResetter::HOMEPAGE);

  EXPECT_TRUE(prefs->GetBoolean(prefs::kHomePageIsNewTabPage));
  EXPECT_EQ("http://www.foo.com", prefs->GetString(prefs::kHomePage));
  EXPECT_FALSE(prefs->GetBoolean(prefs::kShowHomeButton));
}

TEST_F(ProfileResetterTest, ResetContentSettings) {
  HostContentSettingsMap* host_content_settings_map =
      HostContentSettingsMapFactory::GetForProfile(profile());
  GURL url("http://example.org");
  std::map<ContentSettingsType, ContentSetting> default_settings;

  // TODO(raymes): Clean up this test so that we don't have such ugly iteration
  // over the content settings.
  content_settings::ContentSettingsRegistry* registry =
      content_settings::ContentSettingsRegistry::GetInstance();
  for (const content_settings::ContentSettingsInfo* info : *registry) {
    ContentSettingsType content_type = info->website_settings_info()->type();
    if (content_type == ContentSettingsType::MIXEDSCRIPT ||
        content_type == ContentSettingsType::PROTOCOL_HANDLERS) {
      // These types are excluded because one can't call
      // GetDefaultContentSetting() for them.
      continue;
    }
    ContentSetting default_setting =
        host_content_settings_map->GetDefaultContentSetting(content_type,
                                                            nullptr);
    default_settings[content_type] = default_setting;
    ContentSetting wildcard_setting = default_setting == CONTENT_SETTING_BLOCK
                                          ? CONTENT_SETTING_ALLOW
                                          : CONTENT_SETTING_BLOCK;
    ContentSetting site_setting = default_setting == CONTENT_SETTING_ALLOW
                                      ? CONTENT_SETTING_ALLOW
                                      : CONTENT_SETTING_BLOCK;
    if (info->IsSettingValid(wildcard_setting)) {
      host_content_settings_map->SetDefaultContentSetting(content_type,
                                                          wildcard_setting);
    }
    if (info->IsSettingValid(site_setting)) {
      host_content_settings_map->SetContentSettingDefaultScope(
          url, url, content_type, site_setting);
      ContentSettingsForOneType host_settings =
          host_content_settings_map->GetSettingsForOneType(content_type);
      EXPECT_EQ(2U, host_settings.size());
    }
  }

  ResetAndWait(ProfileResetter::CONTENT_SETTINGS);

  for (const content_settings::ContentSettingsInfo* info : *registry) {
    ContentSettingsType content_type = info->website_settings_info()->type();
    if (content_type == ContentSettingsType::MIXEDSCRIPT ||
        content_type == ContentSettingsType::PROTOCOL_HANDLERS)
      continue;
    ContentSetting default_setting =
        host_content_settings_map->GetDefaultContentSetting(content_type,
                                                            nullptr);
    EXPECT_TRUE(default_settings.count(content_type));
    EXPECT_EQ(default_settings[content_type], default_setting);
    ContentSetting site_setting = host_content_settings_map->GetContentSetting(
        GURL("example.org"), GURL(), content_type);
    EXPECT_EQ(default_setting, site_setting);

    ContentSettingsForOneType host_settings =
        host_content_settings_map->GetSettingsForOneType(content_type);
    EXPECT_EQ(1U, host_settings.size());
  }
}

TEST_F(ProfileResetterTest, ResetContentSettings_BluetoothAllowedDevicesMap) {
  content::StoragePartition* partition =
      profile()->GetDefaultStoragePartition();
  ASSERT_TRUE(partition);

  // Verifies that resetting content settings invokes
  // ClearBluetoothAllowedDevicesMap across loaded storage partitions
  // without crashing.
  ResetAndWait(ProfileResetter::CONTENT_SETTINGS);
}

TEST_F(ProfileResetterTest, ResetExtensionsByDisabling) {
  service()->Init();

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile());
  test::ThemeServiceChangedWaiter waiter(theme_service);

  scoped_refptr<Extension> theme = CreateExtension(
      u"example1", temp_dir.GetPath(), ManifestLocation::kUnpacked,
      extensions::Manifest::Type::kTheme, false);
  service()->FinishInstallationForTest(theme.get());
  waiter.WaitForThemeChanged();

  EXPECT_FALSE(theme_service->UsingDefaultTheme());

  scoped_refptr<Extension> ext2 = CreateExtension(
      u"example2", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext2.get());
  // Component extensions and policy-managed extensions shouldn't be disabled.
  scoped_refptr<Extension> ext3 = CreateExtension(
      u"example3", base::FilePath(FILE_PATH_LITERAL("//nonexistent2")),
      ManifestLocation::kComponent, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext3.get());
  scoped_refptr<Extension> ext4 = CreateExtension(
      u"example4", base::FilePath(FILE_PATH_LITERAL("//nonexistent3")),
      ManifestLocation::kExternalPolicyDownload,
      extensions::Manifest::Type::kExtension, false);
  registrar()->AddExtension(ext4.get());
  scoped_refptr<Extension> ext5 = CreateExtension(
      u"example5", base::FilePath(FILE_PATH_LITERAL("//nonexistent4")),
      ManifestLocation::kExternalComponent,
      extensions::Manifest::Type::kExtension, false);
  registrar()->AddExtension(ext5.get());
  scoped_refptr<Extension> ext6 = CreateExtension(
      u"example6", base::FilePath(FILE_PATH_LITERAL("//nonexistent5")),
      ManifestLocation::kExternalPolicy, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext6.get());
  EXPECT_EQ(6u, registry()->enabled_extensions().size());

  ResetAndWait(ProfileResetter::EXTENSIONS);
  EXPECT_EQ(4u, registry()->enabled_extensions().size());
  EXPECT_FALSE(registry()->enabled_extensions().Contains(theme->id()));
  EXPECT_FALSE(registry()->enabled_extensions().Contains(ext2->id()));
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext3->id()));
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext4->id()));
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext5->id()));
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext6->id()));
  EXPECT_TRUE(theme_service->UsingDefaultTheme());
}

TEST_F(ProfileResetterTest, ResetExtensionsByDisablingNonOrganic) {
  scoped_refptr<Extension> ext2 = CreateExtension(
      u"example2", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext2.get());
  // Components and external policy extensions shouldn't be deleted.
  scoped_refptr<Extension> ext3 = CreateExtension(
      u"example3", base::FilePath(FILE_PATH_LITERAL("//nonexistent2")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext3.get());
  EXPECT_EQ(2u, registry()->enabled_extensions().size());

  std::string master_prefs(kDistributionConfig);
  ReplaceString(&master_prefs, "placeholder_for_id", ext3->id());

  ResetAndWait(ProfileResetter::EXTENSIONS, master_prefs);

  EXPECT_EQ(1u, registry()->enabled_extensions().size());
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext3->id()));
}

TEST_F(ProfileResetterTest, ResetExtensionsAndDefaultApps) {
  service()->Init();

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  ThemeService* theme_service = ThemeServiceFactory::GetForProfile(profile());
  test::ThemeServiceChangedWaiter waiter(theme_service);

  scoped_refptr<Extension> ext1 = CreateExtension(
      u"example1", temp_dir.GetPath(), ManifestLocation::kUnpacked,
      extensions::Manifest::Type::kTheme, false);
  service()->FinishInstallationForTest(ext1.get());
  waiter.WaitForThemeChanged();

  EXPECT_FALSE(theme_service->UsingDefaultTheme());

  scoped_refptr<Extension> ext2 = CreateExtension(
      u"example2", base::FilePath(FILE_PATH_LITERAL("//nonexistent2")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  registrar()->AddExtension(ext2.get());

  scoped_refptr<Extension> ext3 = CreateExtension(
      u"example2", base::FilePath(FILE_PATH_LITERAL("//nonexistent3")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kHostedApp,
      true);
  registrar()->AddExtension(ext3.get());
  EXPECT_EQ(3u, registry()->enabled_extensions().size());

  ResetAndWait(ProfileResetter::EXTENSIONS);

  EXPECT_EQ(1u, registry()->enabled_extensions().size());
  EXPECT_FALSE(registry()->enabled_extensions().Contains(ext1->id()));
  EXPECT_FALSE(registry()->enabled_extensions().Contains(ext2->id()));
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext3->id()));
  EXPECT_TRUE(theme_service->UsingDefaultTheme());
}

TEST_F(ProfileResetterTest, ResetExtensionsByReenablingExternalComponents) {
  service()->Init();

  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  scoped_refptr<Extension> ext = CreateExtension(
      u"example", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
      ManifestLocation::kExternalComponent,
      extensions::Manifest::Type::kExtension, false);
  registrar()->AddExtension(ext.get());

  registrar()->DisableExtension(
      ext->id(), {extensions::disable_reason::DISABLE_USER_ACTION});
  EXPECT_FALSE(registry()->enabled_extensions().Contains(ext->id()));
  EXPECT_TRUE(registry()->disabled_extensions().Contains(ext->id()));

  ResetAndWait(ProfileResetter::EXTENSIONS);
  EXPECT_TRUE(registry()->enabled_extensions().Contains(ext->id()));
  EXPECT_FALSE(registry()->disabled_extensions().Contains(ext->id()));
}

TEST_F(ProfileResetterTest, ResetStartPageNonOrganic) {
  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);

  SessionStartupPref startup_pref(SessionStartupPref::LAST);
  SessionStartupPref::SetStartupPref(prefs, startup_pref);

  ResetAndWait(ProfileResetter::STARTUP_PAGES, kDistributionConfig);

  startup_pref = SessionStartupPref::GetStartupPref(prefs);
  EXPECT_EQ(SessionStartupPref::URLS, startup_pref.type);
  const GURL urls[] = {GURL("http://goo.gl"), GURL("http://foo.de")};
  EXPECT_EQ(std::vector<GURL>(std::begin(urls), std::end(urls)),
            startup_pref.urls);
}


TEST_F(ProfileResetterTest, ResetStartPagePartially) {
  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);

  const GURL urls[] = {GURL("http://foo"), GURL("http://bar")};
  SessionStartupPref startup_pref(SessionStartupPref::URLS);
  startup_pref.urls.assign(std::begin(urls), std::end(urls));
  SessionStartupPref::SetStartupPref(prefs, startup_pref);

  ResetAndWait(ProfileResetter::STARTUP_PAGES, std::string());

  startup_pref = SessionStartupPref::GetStartupPref(prefs);
  EXPECT_EQ(SessionStartupPref::GetDefaultStartupType(), startup_pref.type);
  EXPECT_EQ(std::vector<GURL>(std::begin(urls), std::end(urls)),
            startup_pref.urls);
}

TEST_F(ProfileResetterTest, ResetShortcuts) {
  ShortcutHandler shortcut;
  ShortcutCommand command_line = shortcut.CreateWithArguments(
      L"chrome.lnk", L"--profile-directory=Default foo.com");
  shortcut.HideFile();
  shortcut.CheckShortcutHasArguments(L"--profile-directory=Default foo.com");
#if BUILDFLAG(IS_WIN)
  ASSERT_TRUE(shortcut.IsFileHidden());
#endif

  ResetAndWait(ProfileResetter::SHORTCUTS);

  shortcut.CheckShortcutHasArguments(L"--profile-directory=Default");
  EXPECT_FALSE(shortcut.IsFileHidden());
}

TEST_F(ProfileResetterTest, ResetFewFlags) {
  // mock_object_ is a StrictMock, so we verify that it is called only once.
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE |
               ProfileResetter::HOMEPAGE |
               ProfileResetter::CONTENT_SETTINGS);
}

// Tries to load unavailable config file.
TEST_F(ConfigParserTest, NoConnectivity) {
  const GURL url("http://test");
  test_url_loader_factory().AddResponse(
      url, network::mojom::URLResponseHead::New(), "",
      network::URLLoaderCompletionStatus(net::HTTP_INTERNAL_SERVER_ERROR));

  std::unique_ptr<BrandcodeConfigFetcher> fetcher = WaitForRequest(url);
  EXPECT_FALSE(fetcher->GetSettings());
}

// Tries to load available config file.
TEST_F(ConfigParserTest, ParseConfig) {
  const GURL url("http://test");
  std::string xml_config(kXmlConfig);
  ReplaceString(&xml_config, "placeholder_for_data", kDistributionConfig);
  ReplaceString(&xml_config,
                "placeholder_for_id",
                "abbaabbaabbaabbaabbaabbaabbaabba");
  auto head = network::mojom::URLResponseHead::New();
  std::string headers("HTTP/1.1 200 OK\nContent-type: text/xml\n\n");
  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(headers));
  head->mime_type = "text/xml";
  network::URLLoaderCompletionStatus status;
  status.decoded_body_length = base::ByteSize(xml_config.size());
  test_url_loader_factory().AddResponse(url, std::move(head), xml_config,
                                        status);

  std::unique_ptr<BrandcodeConfigFetcher> fetcher = WaitForRequest(url);
  std::unique_ptr<BrandcodedDefaultSettings> settings = fetcher->GetSettings();
  ASSERT_TRUE(settings);

  std::vector<std::string> extension_ids;
  EXPECT_TRUE(settings->GetExtensions(&extension_ids));
  EXPECT_EQ(1u, extension_ids.size());
  EXPECT_EQ("abbaabbaabbaabbaabbaabbaabbaabba", extension_ids[0]);

  std::string homepage;
  EXPECT_TRUE(settings->GetHomepage(&homepage));
  EXPECT_EQ("http://www.foo.com", homepage);

  std::optional<base::ListValue> startup_list(
      settings->GetUrlsToRestoreOnStartup());
  EXPECT_TRUE(startup_list.has_value());
  std::vector<std::string> startup_pages;
  for (const auto& entry : *startup_list) {
    ASSERT_TRUE(entry.is_string());
    startup_pages.push_back(entry.GetString());
  }
  ASSERT_EQ(2u, startup_pages.size());
  EXPECT_EQ("http://goo.gl", startup_pages[0]);
  EXPECT_EQ("http://foo.de", startup_pages[1]);
}

// Return an invalid response from the fetch request and delete the
// Fetcher object in the callback, which mimics how ResetSettingsHandler uses
// the class. See https://crbug.com/40935719.
TEST_F(ConfigParserTest, InvalidResponseDeleteFromCallback) {
  const GURL url("http://test");
  auto head = network::mojom::URLResponseHead::New();
  head->headers = base::MakeRefCounted<net::HttpResponseHeaders>(
      net::HttpUtil::AssembleRawHeaders(
          "HTTP/1.1 200 OK\nContent-type: application/custom\n\n"));
  head->mime_type = "application/custom";
  test_url_loader_factory().AddResponse(url, std::move(head),
                                        "Custom app data, not XML",
                                        network::URLLoaderCompletionStatus());

  base::RunLoop run_loop;
  std::unique_ptr<BrandcodeConfigFetcher> fetcher;
  auto callback = base::BindLambdaForTesting([&fetcher, &run_loop] {
    EXPECT_FALSE(fetcher->GetSettings());
    fetcher.reset();
    run_loop.Quit();
  });
  fetcher = std::make_unique<BrandcodeConfigFetcher>(&test_url_loader_factory(),
                                           std::move(callback), url, "ABCD");
  run_loop.Run();
}

class ProfileResetterTestForSnapshots
    : public ProfileResetterTestWithIgnoreSearchProviderOverrides {
 protected:
  void CheckSnapshots(std::string_view initial_prefs,
                      int expected_nonorganic_diff_fields,
                      std::string_view expected_dse_url) {
    ResettableSettingsSnapshot empty_snap(profile());
    EXPECT_EQ(0, empty_snap.FindDifferentFields(empty_snap));

    scoped_refptr<Extension> ext = CreateExtension(
        u"example", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
        ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
        false);
    ASSERT_TRUE(ext.get());
    registrar()->AddExtension(ext.get());

    std::string master_prefs(initial_prefs);
    std::string ext_id = ext->id();
    ReplaceString(&master_prefs, "placeholder_for_id", ext_id);

    // Reset to non organic defaults.
    ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE |
                     ProfileResetter::HOMEPAGE | ProfileResetter::STARTUP_PAGES,
                 master_prefs);
    ShortcutHandler shortcut_hijacked;
    ShortcutCommand command_line = shortcut_hijacked.CreateWithArguments(
        L"chrome1.lnk", L"--profile-directory=Default foo.com");
    shortcut_hijacked.CheckShortcutHasArguments(
        L"--profile-directory=Default foo.com");
    ShortcutHandler shortcut_ok;
    shortcut_ok.CreateWithArguments(L"chrome2.lnk",
                                    L"--profile-directory=Default1");

    ResettableSettingsSnapshot nonorganic_snap(profile());
    nonorganic_snap.RequestShortcuts(base::OnceClosure());
    // Let it enumerate shortcuts on a blockable task runner.
    content::RunAllTasksUntilIdle();
    int diff_fields = expected_nonorganic_diff_fields;
    if (!ShortcutHandler::IsSupported()) {
      diff_fields &= ~ResettableSettingsSnapshot::SHORTCUTS;
    }
    EXPECT_EQ(diff_fields, empty_snap.FindDifferentFields(nonorganic_snap));
    empty_snap.Subtract(nonorganic_snap);
    EXPECT_TRUE(empty_snap.startup_urls().empty());
    EXPECT_EQ(SessionStartupPref::GetDefaultStartupType(),
              empty_snap.startup_type());
    EXPECT_TRUE(empty_snap.homepage().empty());
    EXPECT_TRUE(empty_snap.homepage_is_ntp());
    EXPECT_FALSE(empty_snap.show_home_button());
    EXPECT_NE(std::string::npos, empty_snap.dse_url().find("{google:baseURL}"));
    EXPECT_EQ(ResettableSettingsSnapshot::ExtensionList(),
              empty_snap.enabled_extensions());
    EXPECT_EQ(std::vector<ShortcutCommand>(), empty_snap.shortcuts());

    // Reset to organic defaults.
    ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE |
                 ProfileResetter::HOMEPAGE | ProfileResetter::STARTUP_PAGES |
                 ProfileResetter::EXTENSIONS | ProfileResetter::SHORTCUTS);

    ResettableSettingsSnapshot organic_snap(profile());
    organic_snap.RequestShortcuts(base::OnceClosure());
    // Let it enumerate shortcuts on a blockable task runner.
    content::RunAllTasksUntilIdle();
    EXPECT_EQ(diff_fields, nonorganic_snap.FindDifferentFields(organic_snap));
    nonorganic_snap.Subtract(organic_snap);
    const GURL urls[] = {GURL("http://foo.de"), GURL("http://goo.gl")};
    EXPECT_EQ(std::vector<GURL>(std::begin(urls), std::end(urls)),
              nonorganic_snap.startup_urls());
    EXPECT_EQ(SessionStartupPref::URLS, nonorganic_snap.startup_type());
    EXPECT_EQ("http://www.foo.com", nonorganic_snap.homepage());
    EXPECT_FALSE(nonorganic_snap.homepage_is_ntp());
    EXPECT_TRUE(nonorganic_snap.show_home_button());
    EXPECT_EQ(expected_dse_url, nonorganic_snap.dse_url());
    EXPECT_EQ(ResettableSettingsSnapshot::ExtensionList(
                  1, std::make_pair(ext_id, "example")),
              nonorganic_snap.enabled_extensions());
    if (ShortcutHandler::IsSupported()) {
      std::vector<ShortcutCommand> shortcuts = nonorganic_snap.shortcuts();
      ASSERT_EQ(1u, shortcuts.size());
      EXPECT_EQ(command_line.first.value(), shortcuts[0].first.value());
      EXPECT_EQ(command_line.second, shortcuts[0].second);
    }
  }
};

TEST_P(ProfileResetterTestForSnapshots, CheckSnapshots) {
  CheckSnapshots(kDistributionConfig,
                 /*expected_nonorganic_diff_fields=*/
                 ResettableSettingsSnapshot::ALL_FIELDS &
                     ~ResettableSettingsSnapshot::DSE_URL,
                 TemplateURLPrepopulateData::google.search_url);
}

TEST_P(ProfileResetterTestForSnapshots, CheckSnapshots_WithSearch) {
  int expected_nonorganic_diff_fields;
  std::string expected_search_url;

  if (IsParamFeatureEnabled()) {
    expected_nonorganic_diff_fields = ResettableSettingsSnapshot::ALL_FIELDS &
                                      ~ResettableSettingsSnapshot::DSE_URL;
    expected_search_url = TemplateURLPrepopulateData::google.search_url;
  } else {
    expected_nonorganic_diff_fields = ResettableSettingsSnapshot::ALL_FIELDS;
    expected_search_url = "http://www.foo.com/s?q={searchTerms}";
  }

  CheckSnapshots(kDistributionConfigWithSearch, expected_nonorganic_diff_fields,
                 expected_search_url);
}

INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(ProfileResetterTestForSnapshots);

TEST_F(ProfileResetterTest, FeedbackSerializationAsProtoTest) {
  // Reset to non organic defaults.
  ResetAndWait(ProfileResetter::DEFAULT_SEARCH_ENGINE |
               ProfileResetter::HOMEPAGE |
               ProfileResetter::STARTUP_PAGES,
               kDistributionConfig);

  scoped_refptr<Extension> ext = CreateExtension(
      u"example", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  ASSERT_TRUE(ext.get());
  registrar()->AddExtension(ext.get());

  ShortcutHandler shortcut;
  ShortcutCommand command_line = shortcut.CreateWithArguments(
      L"chrome.lnk", L"--profile-directory=Default foo.com");

  ResettableSettingsSnapshot nonorganic_snap(profile());
  nonorganic_snap.RequestShortcuts(base::OnceClosure());
  // Let it enumerate shortcuts on a blockable task runner.
  content::RunAllTasksUntilIdle();

  static_assert(ResettableSettingsSnapshot::ALL_FIELDS == 31,
                "this test needs to be expanded");
  for (int field_mask = 0; field_mask <= ResettableSettingsSnapshot::ALL_FIELDS;
       ++field_mask) {
    std::unique_ptr<reset_report::ChromeResetReport> report =
        SerializeSettingsReportToProto(nonorganic_snap, field_mask);

    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::STARTUP_MODE),
              report->startup_url_path_size() > 0);
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::STARTUP_MODE),
              report->has_startup_type());
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::HOMEPAGE),
              report->has_homepage_path());
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::HOMEPAGE),
              report->has_homepage_is_new_tab_page());
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::HOMEPAGE),
              report->has_show_home_button());
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::DSE_URL),
              report->has_default_search_engine_path());
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::EXTENSIONS),
              report->enabled_extensions_size() > 0);
    EXPECT_EQ(!!(field_mask & ResettableSettingsSnapshot::SHORTCUTS) &&
                  ShortcutHandler::IsSupported(),
              report->shortcuts_size() > 0);
  }
}

struct FeedbackCapture {
  void SetFeedback(Profile* profile,
                   const ResettableSettingsSnapshot& snapshot) {
    list_ = GetReadableFeedbackForSnapshot(profile, snapshot);
    OnUpdatedList();
  }

  void Fail() {
    ADD_FAILURE() << "This method shouldn't be called.";
  }

  MOCK_METHOD0(OnUpdatedList, void(void));

  base::ListValue list_;
};

// Make sure GetReadableFeedback handles non-ascii letters.
TEST_F(ProfileResetterTest, GetReadableFeedback) {
  scoped_refptr<Extension> ext = CreateExtension(
      u"Tiësto", base::FilePath(FILE_PATH_LITERAL("//nonexistent")),
      ManifestLocation::kUnpacked, extensions::Manifest::Type::kExtension,
      false);
  ASSERT_TRUE(ext.get());
  registrar()->AddExtension(ext.get());

  PrefService* prefs = profile()->GetPrefs();
  DCHECK(prefs);
  // The URL is "http://россия.рф".
  std::wstring url(L"http://"
    L"\u0440\u043e\u0441\u0441\u0438\u044f.\u0440\u0444");
  prefs->SetBoolean(prefs::kHomePageIsNewTabPage, false);
  prefs->SetString(prefs::kHomePage, base::WideToUTF8(url));

  SessionStartupPref startup_pref(SessionStartupPref::URLS);
  startup_pref.urls.push_back(GURL(base::WideToUTF8(url)));
  SessionStartupPref::SetStartupPref(prefs, startup_pref);

  ShortcutHandler shortcut;
  ShortcutCommand command_line = shortcut.CreateWithArguments(
      L"chrome.lnk", L"--profile-directory=Default foo.com");

  FeedbackCapture capture;
  EXPECT_CALL(capture, OnUpdatedList());
  ResettableSettingsSnapshot snapshot(profile());
  snapshot.RequestShortcuts(base::BindOnce(&FeedbackCapture::SetFeedback,
                                           base::Unretained(&capture),
                                           profile(), std::cref(snapshot)));
  // Let it enumerate shortcuts on a blockable task runner.
  content::RunAllTasksUntilIdle();
  EXPECT_TRUE(snapshot.shortcuts_determined());
  ::testing::Mock::VerifyAndClearExpectations(&capture);
  // The homepage and the startup page are in punycode. They are unreadable.
  // Trying to find the extension name.
  base::ListValue list = std::move(capture.list_);
  bool checked_extensions = false;
  bool checked_shortcuts = false;
  for (const auto& entry : list) {
    const base::DictValue* dict = entry.GetIfDict();
    ASSERT_TRUE(dict);
    const std::string* value = dict->FindString("key");
    ASSERT_TRUE(value);
    if (*value == "Extensions") {
      const std::string* extensions = dict->FindString("value");
      ASSERT_TRUE(extensions);
      EXPECT_EQ(*extensions, "Tiësto");
      checked_extensions = true;
    } else if (*value == "Shortcut targets") {
      const std::string* targets = dict->FindString("value");
      ASSERT_TRUE(targets);
      EXPECT_NE(std::string::npos, targets->find("foo.com")) << *targets;
      checked_shortcuts = true;
    }
  }
  EXPECT_TRUE(checked_extensions);
  EXPECT_EQ(ShortcutHandler::IsSupported(), checked_shortcuts);
}

TEST_F(ProfileResetterTest, DestroySnapshotFast) {
  FeedbackCapture capture;
  std::unique_ptr<ResettableSettingsSnapshot> deleted_snapshot(
      new ResettableSettingsSnapshot(profile()));
  deleted_snapshot->RequestShortcuts(
      base::BindOnce(&FeedbackCapture::Fail, base::Unretained(&capture)));
  deleted_snapshot.reset();
  // Running remaining tasks shouldn't trigger the callback to be called as
  // |deleted_snapshot| was deleted before it could run.
  base::RunLoop().RunUntilIdle();
}

TEST_F(ProfileResetterTest, ResetNTPCustomizationsTest) {
  auto* ntp_custom_background_service =
      NtpCustomBackgroundServiceFactory::GetForProfile(profile());
  ntp_custom_background_service->AddValidBackdropUrlForTesting(
      GURL("https://background.com"));
  ntp_custom_background_service->SetCustomBackgroundInfo(
      /*background_url=*/GURL("https://background.com"),
      /*thumbnail_url=*/GURL("https://thumbnail.com"),
      /*attribution_line_1=*/"line 1",
      /*attribution_line_2=*/"line 2",
      /*action_url=*/GURL("https://action.com"),
      /*collection_id=*/"");
  EXPECT_TRUE(ntp_custom_background_service->GetCustomBackground().has_value());
  ResetAndWait(ProfileResetter::NTP_CUSTOMIZATIONS);
  EXPECT_FALSE(
      ntp_custom_background_service->GetCustomBackground().has_value());
}

}  // namespace
