// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// Unit tests for initial preferences related methods.

#include "chrome/installer/util/initial_preferences.h"

#include <stddef.h>

#include <memory>

#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/installer/util/initial_preferences_constants.h"
#include "chrome/installer/util/util_constants.h"
#include "rlz/buildflags/buildflags.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using ::testing::Optional;

// A helper class to set the "GoogleUpdateIsMachine" environment variable.
class ScopedGoogleUpdateIsMachine {
 public:
  explicit ScopedGoogleUpdateIsMachine(const char* value)
      : env_(base::Environment::Create()) {
    env_->SetVar("GoogleUpdateIsMachine", value);
  }

  ~ScopedGoogleUpdateIsMachine() { env_->UnSetVar("GoogleUpdateIsMachine"); }

 private:
  std::unique_ptr<base::Environment> env_;
};

class InitialPreferencesTest : public testing::Test {
 protected:
  void SetUp() override {
    ASSERT_TRUE(base::CreateTemporaryFile(&prefs_file_));
  }

  void TearDown() override { EXPECT_TRUE(base::DeleteFile(prefs_file_)); }

  const base::FilePath& prefs_file() const { return prefs_file_; }

 private:
  base::FilePath prefs_file_;
};

// Used to specify an expected value for a set boolean preference variable.
struct ExpectedBooleans {
  const char* name;
  bool expected_value;
};

}  // namespace

TEST_F(InitialPreferencesTest, NoFileToParse) {
  EXPECT_TRUE(base::DeleteFile(prefs_file()));
  installer::InitialPreferences prefs(prefs_file());
  EXPECT_FALSE(prefs.read_from_file());
}

TEST_F(InitialPreferencesTest, ParseDistroParams) {
  const char text[] = R"({
    "distribution": {
      "show_welcome_page": true,
      "import_bookmarks_from_file": "c:\\foo",
      "do_not_create_any_shortcuts": true,
      "do_not_create_desktop_shortcut": true,
      "do_not_create_quick_launch_shortcut": true,
      "do_not_create_taskbar_shortcut": true,
      "do_not_launch_chrome": true,
      "make_chrome_default_for_user": true,
      "program_files_dir": "c:\\bar",
      "system_level": true,
      "verbose_logging": true,
      "require_eula": true
    },
    "blah": {
      "show_welcome_page": false
    }
  })";

  EXPECT_TRUE(base::WriteFile(prefs_file(), text));
  installer::InitialPreferences prefs(prefs_file());
  EXPECT_TRUE(prefs.read_from_file());

  const char* const expected_true[] = {
      installer::initial_preferences::kDoNotCreateAnyShortcuts,
      installer::initial_preferences::kDoNotCreateDesktopShortcut,
      installer::initial_preferences::kDoNotCreateQuickLaunchShortcut,
      installer::initial_preferences::kDoNotCreateTaskbarShortcut,
      installer::initial_preferences::kDoNotLaunchChrome,
      installer::initial_preferences::kMakeChromeDefaultForUser,
      installer::initial_preferences::kSystemLevel,
      installer::initial_preferences::kVerboseLogging,
      installer::initial_preferences::kRequireEula,
  };

  for (const char* pref_name : expected_true) {
    bool value = false;
    EXPECT_TRUE(prefs.GetBool(pref_name, &value));
    EXPECT_TRUE(value) << pref_name;
  }

  std::string str_value;
  EXPECT_TRUE(prefs.GetString(
      installer::initial_preferences::kDistroImportBookmarksFromFilePref,
      &str_value));
  EXPECT_STREQ("c:\\foo", str_value.c_str());

  base::FilePath path;
  EXPECT_TRUE(
      prefs.GetPath(installer::initial_preferences::kProgramFilesDir, &path));
  EXPECT_EQ(base::FilePath(FILE_PATH_LITERAL("c:\\bar")), path);
}

TEST_F(InitialPreferencesTest, ParseMissingDistroParams) {
  const char text[] = R"({
    "distribution": {
      "import_bookmarks_from_file": "",
      "do_not_create_desktop_shortcut": true,
      "do_not_create_quick_launch_shortcut": true,
      "do_not_launch_chrome": false
    }
  })";

  EXPECT_TRUE(base::WriteFile(prefs_file(), text));
  installer::InitialPreferences prefs(prefs_file());
  EXPECT_TRUE(prefs.read_from_file());

  ExpectedBooleans expected_bool[] = {
      {installer::initial_preferences::kDoNotCreateDesktopShortcut, true},
      {installer::initial_preferences::kDoNotCreateQuickLaunchShortcut, true},
      {installer::initial_preferences::kDoNotLaunchChrome, false},
  };

  bool value = false;
  for (const auto& expected : expected_bool) {
    EXPECT_TRUE(prefs.GetBool(expected.name, &value));
    EXPECT_EQ(value, expected.expected_value) << expected.name;
  }

  const char* const missing_bools[] = {
      installer::initial_preferences::kDoNotRegisterForUpdateLaunch,
      installer::initial_preferences::kMakeChromeDefaultForUser,
  };

  for (const char* missing_bool : missing_bools) {
    EXPECT_FALSE(prefs.GetBool(missing_bool, &value)) << missing_bool;
  }

  std::string str_value;
  EXPECT_FALSE(prefs.GetString(
      installer::initial_preferences::kDistroImportBookmarksFromFilePref,
      &str_value));
}

TEST_F(InitialPreferencesTest, FirstRunTabs) {
  const char text[] = R"({
    "distribution": {
      "something here": true
    },
    "first_run_tabs": [
      "http://google.com/f1",
      "https://google.com/f2",
      "new_tab_page"
    ]
  })";

  EXPECT_TRUE(base::WriteFile(prefs_file(), text));
  installer::InitialPreferences prefs(prefs_file());
  typedef std::vector<std::string> TabsVector;
  TabsVector tabs = prefs.GetFirstRunTabs();
  ASSERT_EQ(3u, tabs.size());
  EXPECT_EQ("http://google.com/f1", tabs[0]);
  EXPECT_EQ("https://google.com/f2", tabs[1]);
  EXPECT_EQ("new_tab_page", tabs[2]);
}

// Test the parsing of the initial_extensions block from initial preferences.
TEST_F(InitialPreferencesTest, ParseInitialExtensionsWithProviderName) {
  constexpr char kTestExtensionId1[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
  constexpr char kTestExtensionId2[] = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
  constexpr char kProvider[] = "GTest";
  constexpr std::string_view kInitialExtensions = R"({
    "initial_extensions": {
      "provider_name": "GTest",
      "list": [
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
      ]
    }
  })";

  ASSERT_TRUE(base::WriteFile(prefs_file(), kInitialExtensions));
  installer::InitialPreferences prefs(prefs_file());
  ASSERT_TRUE(prefs.read_from_file());

  const base::ListValue* extensions = prefs.GetInitialExtensionsList();
  ASSERT_NE(extensions, nullptr);
  ASSERT_EQ(extensions->size(), 2u);

  const std::string* id1 = (*extensions)[0].GetIfString();
  ASSERT_NE(id1, nullptr);
  EXPECT_EQ(*id1, kTestExtensionId1);

  const std::string* id2 = (*extensions)[1].GetIfString();
  ASSERT_NE(id2, nullptr);
  EXPECT_EQ(*id2, kTestExtensionId2);

  EXPECT_EQ(prefs.GetInitialExtensionsProviderName(), std::string(kProvider));
}

// Test parsing when initial_extensions.list exists but provider_name is
// omitted.
TEST_F(InitialPreferencesTest, ParseInitialExtensionsWithoutProviderName) {
  constexpr char kTestExtensionId1[] = "cccccccccccccccccccccccccccccccc";
  constexpr char kTestExtensionId2[] = "dddddddddddddddddddddddddddddddd";
  constexpr std::string_view kInitialExtensions = R"({
    "initial_extensions": {
      "list": [
        "cccccccccccccccccccccccccccccccc",
        "dddddddddddddddddddddddddddddddd"
      ]
    }
  })";

  ASSERT_TRUE(base::WriteFile(prefs_file(), kInitialExtensions));
  installer::InitialPreferences prefs(prefs_file());
  ASSERT_TRUE(prefs.read_from_file());

  const base::ListValue* extensions = prefs.GetInitialExtensionsList();
  ASSERT_NE(extensions, nullptr);
  ASSERT_EQ(extensions->size(), 2u);

  const std::string* id1 = (*extensions)[0].GetIfString();
  ASSERT_NE(id1, nullptr);
  EXPECT_EQ(*id1, kTestExtensionId1);

  const std::string* id2 = (*extensions)[1].GetIfString();
  ASSERT_NE(id2, nullptr);
  EXPECT_EQ(*id2, kTestExtensionId2);

  // Provider name should be empty when not present.
  EXPECT_TRUE(prefs.GetInitialExtensionsProviderName().empty());
}

// Test that GetInitialExtensionsList returns null when the block is absent.
TEST_F(InitialPreferencesTest, MissingInitialExtensionsBlock) {
  constexpr std::string_view kDistribution = R"({
  "distribution": {
      "verbose_logging": true
    }
  })";

  ASSERT_TRUE(base::WriteFile(prefs_file(), kDistribution));
  installer::InitialPreferences prefs(prefs_file());
  ASSERT_TRUE(prefs.read_from_file());

  const base::ListValue* extensions = prefs.GetInitialExtensionsList();
  EXPECT_EQ(extensions, nullptr);
}

// Test the parsing of bookmarks block from initial preferences.
TEST_F(InitialPreferencesTest, ValidateBookmarksJSON) {
  constexpr char bookmarks_json_string[] = R"({
    "bookmarks": {
      "first_run_bookmarks": {
        "children": [
          {
            "name": "ABC",
            "type": "url",
            "url": "https://google.com"
          },
          {
            "name": "Folder1",
            "type": "folder",
            "children": [
              {
                "name": "ABC",
                "type": "url",
                "url": "https://google.com"
              },
              {
                "name": "XYZ",
                "type": "url",
                "url": "https://facebook.com"
              }
            ]
          }
        ]
      }
    }
  })";
  ASSERT_TRUE(base::WriteFile(prefs_file(), bookmarks_json_string));

  installer::InitialPreferences prefs(prefs_file());

  const base::DictValue* bookmarks = prefs.GetBookmarksBlock();
  ASSERT_TRUE(bookmarks);

  ASSERT_TRUE(bookmarks->FindDict("first_run_bookmarks"));

  const base::ListValue* children =
      bookmarks->FindListByDottedPath("first_run_bookmarks.children");
  ASSERT_TRUE(children);
  ASSERT_EQ(children->size(), 2u);

  const base::DictValue* first_child = (*children)[0].GetIfDict();
  ASSERT_TRUE(first_child);
  EXPECT_EQ(*first_child->FindString("name"), "ABC");
  EXPECT_EQ(*first_child->FindString("type"), "url");
  EXPECT_EQ(*first_child->FindString("url"), "https://google.com");

  const base::DictValue* second_child = (*children)[1].GetIfDict();
  ASSERT_TRUE(second_child);
  EXPECT_EQ(*second_child->FindString("name"), "Folder1");
  EXPECT_EQ(*second_child->FindString("type"), "folder");
  EXPECT_EQ(second_child->FindList("children")->size(), 2u);
}

// Test that we are parsing initial preferences correctly.
TEST_F(InitialPreferencesTest, GetInstallPreferencesTest) {
  // Create a temporary prefs file.
  base::FilePath prefs_file;
  ASSERT_TRUE(base::CreateTemporaryFile(&prefs_file));
  const char text[] = R"({
    "distribution": {
      "do_not_create_desktop_shortcut": false,
      "do_not_create_quick_launch_shortcut": false,
      "do_not_launch_chrome": true,
      "system_level": true,
      "verbose_logging": false
    }
  })";
  EXPECT_TRUE(base::WriteFile(prefs_file, text));

  // Make sure command line values override the values in initial preferences.
  std::wstring cmd_str(L"setup.exe --installerdata=\"" + prefs_file.value() +
                       L"\"");
  cmd_str.append(L" --do-not-launch-chrome");
  base::CommandLine cmd_line = base::CommandLine::FromString(cmd_str);
  installer::InitialPreferences prefs(cmd_line);

  // Check prefs that do not have any equivalent command line option.
  ExpectedBooleans expected_bool[] = {
      {installer::initial_preferences::kDoNotLaunchChrome, true},
      {installer::initial_preferences::kSystemLevel, true},
      {installer::initial_preferences::kVerboseLogging, false},
  };

  // Now check that prefs got merged correctly.
  bool value = false;
  for (const auto& expected : expected_bool) {
    EXPECT_TRUE(prefs.GetBool(expected.name, &value));
    EXPECT_EQ(value, expected.expected_value) << expected.name;
  }

  // Delete temporary prefs file.
  EXPECT_TRUE(base::DeleteFile(prefs_file));

  // Check that if initial prefs doesn't exist, we can still parse the common
  // prefs.
  cmd_str = L"setup.exe --do-not-launch-chrome";
  cmd_line.ParseFromString(cmd_str);
  installer::InitialPreferences prefs2(cmd_line);
  ExpectedBooleans expected_bool2[] = {
      {installer::initial_preferences::kDoNotLaunchChrome, true},
  };

  for (const auto& expected : expected_bool2) {
    EXPECT_TRUE(prefs2.GetBool(expected.name, &value));
    EXPECT_EQ(value, expected.expected_value) << expected.name;
  }

  EXPECT_FALSE(
      prefs2.GetBool(installer::initial_preferences::kSystemLevel, &value));
  EXPECT_FALSE(
      prefs2.GetBool(installer::initial_preferences::kVerboseLogging, &value));
}

TEST_F(InitialPreferencesTest, TestDefaultInstallConfig) {
  std::wstringstream chrome_cmd;
  chrome_cmd << "setup.exe";

  base::CommandLine chrome_install(
      base::CommandLine::FromString(chrome_cmd.str()));

  installer::InitialPreferences pref_chrome(chrome_install);
}

TEST_F(InitialPreferencesTest, EnforceLegacyPreferences) {
  static const char kLegacyPrefs[] = R"({
    "distribution": {
      "create_all_shortcuts": false,
      "import_bookmarks": true,
      "import_history": true,
      "import_home_page": true,
      "import_search_engine": true,
      "ping_delay": 40
    }
  })";

  installer::InitialPreferences prefs(kLegacyPrefs);

  bool do_not_create_desktop_shortcut = false;
  bool do_not_create_quick_launch_shortcut = false;
  bool do_not_create_taskbar_shortcut = false;
  prefs.GetBool(installer::initial_preferences::kDoNotCreateDesktopShortcut,
                &do_not_create_desktop_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateQuickLaunchShortcut,
                &do_not_create_quick_launch_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateTaskbarShortcut,
                &do_not_create_taskbar_shortcut);
  // create_all_shortcuts is a legacy preference that should only enforce
  // do_not_create_desktop_shortcut and do_not_create_quick_launch_shortcut
  // when set to false.
  EXPECT_TRUE(do_not_create_desktop_shortcut);
  EXPECT_TRUE(do_not_create_quick_launch_shortcut);
  EXPECT_FALSE(do_not_create_taskbar_shortcut);

  EXPECT_THAT(prefs.initial_dictionary().FindBool(prefs::kImportBookmarks),
              Optional(true));
  EXPECT_THAT(prefs.initial_dictionary().FindBool(prefs::kImportHistory),
              Optional(true));
  EXPECT_THAT(prefs.initial_dictionary().FindBool(prefs::kImportHomepage),
              Optional(true));
  EXPECT_THAT(prefs.initial_dictionary().FindBool(prefs::kImportSearchEngine),
              Optional(true));

#if BUILDFLAG(ENABLE_RLZ)
  std::optional<int> rlz_ping_delay =
      prefs.initial_dictionary().FindInt(prefs::kRlzPingDelaySeconds);
  EXPECT_TRUE(rlz_ping_delay);
  EXPECT_GT(rlz_ping_delay, 0);
  EXPECT_EQ(40, rlz_ping_delay);
#endif  // BUILDFLAG(ENABLE_RLZ)
}

TEST_F(InitialPreferencesTest, DontEnforceLegacyCreateAllShortcutsTrue) {
  static const char kCreateAllShortcutsFalsePrefs[] = R"({
    "distribution": {
      "create_all_shortcuts": true
    }
  })";

  installer::InitialPreferences prefs(kCreateAllShortcutsFalsePrefs);

  bool do_not_create_desktop_shortcut = false;
  bool do_not_create_quick_launch_shortcut = false;
  bool do_not_create_taskbar_shortcut = false;
  prefs.GetBool(installer::initial_preferences::kDoNotCreateDesktopShortcut,
                &do_not_create_desktop_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateQuickLaunchShortcut,
                &do_not_create_quick_launch_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateTaskbarShortcut,
                &do_not_create_taskbar_shortcut);
  EXPECT_FALSE(do_not_create_desktop_shortcut);
  EXPECT_FALSE(do_not_create_quick_launch_shortcut);
  EXPECT_FALSE(do_not_create_taskbar_shortcut);
}

TEST_F(InitialPreferencesTest,
       DontEnforceLegacyCreateAllShortcutsNotSpecified) {
  static const char kCreateAllShortcutsFalsePrefs[] = R"({
    "distribution": {
      "some_other_pref": true
    }
  })";

  installer::InitialPreferences prefs(kCreateAllShortcutsFalsePrefs);

  bool do_not_create_desktop_shortcut = false;
  bool do_not_create_quick_launch_shortcut = false;
  bool do_not_create_taskbar_shortcut = false;
  prefs.GetBool(installer::initial_preferences::kDoNotCreateDesktopShortcut,
                &do_not_create_desktop_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateQuickLaunchShortcut,
                &do_not_create_quick_launch_shortcut);
  prefs.GetBool(installer::initial_preferences::kDoNotCreateTaskbarShortcut,
                &do_not_create_taskbar_shortcut);
  EXPECT_FALSE(do_not_create_desktop_shortcut);
  EXPECT_FALSE(do_not_create_quick_launch_shortcut);
  EXPECT_FALSE(do_not_create_taskbar_shortcut);
}

TEST_F(InitialPreferencesTest, GoogleUpdateIsMachine) {
  {
    ScopedGoogleUpdateIsMachine env_setter("0");
    installer::InitialPreferences prefs(
        base::CommandLine(base::FilePath(FILE_PATH_LITERAL("setup.exe"))));
    bool value = false;
    prefs.GetBool(installer::initial_preferences::kSystemLevel, &value);
    EXPECT_FALSE(value);
  }
  {
    ScopedGoogleUpdateIsMachine env_setter("1");
    installer::InitialPreferences prefs(
        base::CommandLine(base::FilePath(FILE_PATH_LITERAL("setup.exe"))));
    bool value = false;
    prefs.GetBool(installer::initial_preferences::kSystemLevel, &value);
    EXPECT_TRUE(value);
  }
  {
    ScopedGoogleUpdateIsMachine env_setter("1bridgetoofar");
    installer::InitialPreferences prefs(
        base::CommandLine(base::FilePath(FILE_PATH_LITERAL("setup.exe"))));
    bool value = false;
    prefs.GetBool(installer::initial_preferences::kSystemLevel, &value);
    EXPECT_FALSE(value);
  }
  {
    ScopedGoogleUpdateIsMachine env_setter("2");
    installer::InitialPreferences prefs(
        base::CommandLine(base::FilePath(FILE_PATH_LITERAL("setup.exe"))));
    bool value = false;
    prefs.GetBool(installer::initial_preferences::kSystemLevel, &value);
    EXPECT_FALSE(value);
  }
}

#if !BUILDFLAG(IS_MAC)

TEST_F(InitialPreferencesTest, Path) {
  base::ScopedTempDir temp_dir;
  ASSERT_TRUE(temp_dir.CreateUniqueTempDir());

  auto initial_pref_path =
      temp_dir.GetPath().AppendASCII("initial_preferences");

  EXPECT_EQ(temp_dir.GetPath().AppendASCII("master_preferences"),
            installer::InitialPreferences::Path(temp_dir.GetPath()));
  EXPECT_EQ(initial_pref_path, installer::InitialPreferences::Path(
                                   temp_dir.GetPath(), /*for_read=*/false));

  base::File file(initial_pref_path, base::File::Flags::FLAG_CREATE);
  file.Close();

  EXPECT_EQ(initial_pref_path,
            installer::InitialPreferences::Path(temp_dir.GetPath()));
}

#endif  // !BUILDFLAG(IS_MAC)
