text: // Copyright 2015 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chrome/installer/util/beacons.h"
  
  #include <memory>
  #include <tuple>
  
  #include "base/test/test_reg_util_win.h"
  #include "base/test/test_timeouts.h"
  #include "base/threading/platform_thread.h"
  #include "base/win/registry.h"
  #include "base/win/win_util.h"
  #include "build/branding_buildflags.h"
  #include "chrome/install_static/install_details.h"
  #include "chrome/install_static/install_modes.h"
  #include "chrome/install_static/test/scoped_install_details.h"
  #include "chrome/installer/util/install_util.h"
  #include "chrome/installer/util/util_constants.h"
  #include "testing/gtest/include/gtest/gtest.h"
  
  using ::testing::Bool;
  using ::testing::Combine;
  using ::testing::Values;
  using BeaconType = installer_util::Beacon::BeaconType;
  using BeaconScope = installer_util::Beacon::BeaconScope;
  
  namespace installer_util {
  
  // A test fixture that exercises a beacon.
  class BeaconTest : public ::testing::TestWithParam<
                         ::testing::tuple<BeaconType, BeaconScope, bool>> {
   protected:
    static const base::char16 kBeaconName[];
  
    BeaconTest()
        : beacon_type_(::testing::get<0>(GetParam())),
          beacon_scope_(::testing::get<1>(GetParam())),
          system_install_(::testing::get<2>(GetParam())),
          scoped_install_details_(system_install_),
          beacon_(kBeaconName, beacon_type_, beacon_scope_) {}
  
    void SetUp() override {
      // Override the registry so that tests can freely push state to it.
      ASSERT_NO_FATAL_FAILURE(
          registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
      ASSERT_NO_FATAL_FAILURE(
          registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
    }
  
    BeaconType beacon_type() const { return beacon_type_; }
    BeaconScope beacon_scope() const { return beacon_scope_; }
    bool system_install() const { return system_install_; }
    Beacon* beacon() { return &beacon_; }
  
   private:
    BeaconType beacon_type_;
    BeaconScope beacon_scope_;
    bool system_install_;
    install_static::ScopedInstallDetails scoped_install_details_;
    Beacon beacon_;
    registry_util::RegistryOverrideManager registry_override_manager_;
  };
  
  // static
  const base::char16 BeaconTest::kBeaconName[] = L"TestBeacon";
  
  // Nothing in the regsitry, so the beacon should not exist.
  TEST_P(BeaconTest, GetNonExistent) {
    ASSERT_TRUE(beacon()->Get().is_null());
  }
  
  // Updating and then getting the beacon should return a value, and that it is
  // within range.
  TEST_P(BeaconTest, UpdateAndGet) {
    base::Time before(base::Time::Now());
    beacon()->Update();
    base::Time after(base::Time::Now());
    base::Time beacon_time(beacon()->Get());
    ASSERT_FALSE(beacon_time.is_null());
    ASSERT_LE(before, beacon_time);
    ASSERT_GE(after, beacon_time);
  }
  
  // Tests that updating a first beacon only updates it the first time, but doing
  // so for a last beacon always updates.
  TEST_P(BeaconTest, UpdateTwice) {
    beacon()->Update();
    base::Time beacon_time(beacon()->Get());
  
    base::PlatformThread::Sleep(TestTimeouts::tiny_timeout());
  
    beacon()->Update();
    if (beacon_type() == BeaconType::FIRST) {
      ASSERT_EQ(beacon_time, beacon()->Get());
    } else {
      ASSERT_NE(beacon_time, beacon()->Get());
    }
  }
  
  // Tests that the beacon is written into the proper location in the registry.
  TEST_P(BeaconTest, Location) {
    beacon()->Update();
    const install_static::InstallDetails& install_details =
        install_static::InstallDetails::Get();
    HKEY right_root = system_install() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
    HKEY wrong_root = system_install() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE;
    base::string16 right_key;
    base::string16 wrong_key;
    base::string16 value_name;
  
    if (beacon_scope() == BeaconScope::PER_INSTALL || !system_install()) {
      value_name = kBeaconName;
      right_key = install_details.GetClientStateKeyPath();
      wrong_key = install_details.GetClientStateMediumKeyPath();
    } else {
      ASSERT_TRUE(base::win::GetUserSidString(&value_name));
      right_key =
          install_details.GetClientStateMediumKeyPath() + L"\\" + kBeaconName;
      wrong_key = install_details.GetClientStateKeyPath();
    }
  
  #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
    // Keys should not exist in the wrong root or in the right root but wrong key.
    ASSERT_FALSE(
        base::win::RegKey(wrong_root, right_key.c_str(), KEY_READ).Valid())
        << right_key;
    ASSERT_FALSE(
        base::win::RegKey(wrong_root, wrong_key.c_str(), KEY_READ).Valid())
        << wrong_key;
    ASSERT_FALSE(
        base::win::RegKey(right_root, wrong_key.c_str(), KEY_READ).Valid())
        << wrong_key;
  #else
    // The tests above are skipped for Chromium builds because they fail for two
    // reasons:
    // - ClientState and ClientStateMedium are both Software\Chromium.
    // - the registry override manager does its virtualization into
    //   Software\Chromium, so it always exists.
  
    // Silence unused variable warnings.
    ignore_result(wrong_root);
  #endif
  
    // The right key should exist.
    base::win::RegKey key(right_root, right_key.c_str(), KEY_READ);
    ASSERT_TRUE(key.Valid()) << right_key;
    // And should have the value.
    ASSERT_TRUE(key.HasValue(value_name.c_str())) << value_name;
  }
  
  // Run the tests for all combinations of beacon type, scope, and install level.
  INSTANTIATE_TEST_SUITE_P(BeaconTest,
                           BeaconTest,
                           Combine(Values(BeaconType::FIRST, BeaconType::LAST),
                                   Values(BeaconScope::PER_USER,
                                          BeaconScope::PER_INSTALL),
                                   Bool()));
  
  class DefaultBrowserBeaconTest
      : public ::testing::TestWithParam<
            std::tuple<install_static::InstallConstantIndex, const char*>> {
   protected:
    using Super = ::testing::TestWithParam<
        std::tuple<install_static::InstallConstantIndex, const char*>>;
  
    void SetUp() override {
      Super::SetUp();
  
      install_static::InstallConstantIndex mode_index;
      const char* level;
      std::tie(mode_index, level) = GetParam();
  
      system_install_ = (std::string(level) != "user");
  
      // Configure InstallDetails for the test.
      scoped_install_details_ =
          std::make_unique<install_static::ScopedInstallDetails>(system_install_,
                                                                 mode_index);
      // Override the registry so that tests can freely push state to it.
      ASSERT_NO_FATAL_FAILURE(
          registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
      ASSERT_NO_FATAL_FAILURE(
          registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
    }
  
    bool system_install_ = false;
  
   private:
    std::unique_ptr<install_static::ScopedInstallDetails> scoped_install_details_;
    registry_util::RegistryOverrideManager registry_override_manager_;
  };
  
  // Tests that the default browser beacons work as expected.
  TEST_P(DefaultBrowserBeaconTest, All) {
    std::unique_ptr<Beacon> last_was_default(MakeLastWasDefaultBeacon());
    std::unique_ptr<Beacon> first_not_default(MakeFirstNotDefaultBeacon());
  
    ASSERT_TRUE(last_was_default->Get().is_null());
    ASSERT_TRUE(first_not_default->Get().is_null());
  
    // Chrome is not default.
    UpdateDefaultBrowserBeaconWithState(ShellUtil::NOT_DEFAULT);
    ASSERT_TRUE(last_was_default->Get().is_null());
    ASSERT_FALSE(first_not_default->Get().is_null());
  
    // Then it is.
    UpdateDefaultBrowserBeaconWithState(ShellUtil::IS_DEFAULT);
    ASSERT_FALSE(last_was_default->Get().is_null());
    ASSERT_TRUE(first_not_default->Get().is_null());
  
    // It still is.
    UpdateDefaultBrowserBeaconWithState(ShellUtil::IS_DEFAULT);
    ASSERT_FALSE(last_was_default->Get().is_null());
    ASSERT_TRUE(first_not_default->Get().is_null());
  
    // Now it's not again.
    UpdateDefaultBrowserBeaconWithState(ShellUtil::NOT_DEFAULT);
    ASSERT_FALSE(last_was_default->Get().is_null());
    ASSERT_FALSE(first_not_default->Get().is_null());
  
    // And it still isn't.
    UpdateDefaultBrowserBeaconWithState(ShellUtil::NOT_DEFAULT);
    ASSERT_FALSE(last_was_default->Get().is_null());
    ASSERT_FALSE(first_not_default->Get().is_null());
  }
  
  #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
  // Stable supports user and system levels.
  INSTANTIATE_TEST_SUITE_P(
      Stable,
      DefaultBrowserBeaconTest,
      testing::Combine(testing::Values(install_static::STABLE_INDEX),
                       testing::Values("user", "system")));
  // Beta supports user and system levels.
  INSTANTIATE_TEST_SUITE_P(
      Beta,
      DefaultBrowserBeaconTest,
      testing::Combine(testing::Values(install_static::BETA_INDEX),
                       testing::Values("user", "system")));
  // Dev supports user and system levels.
  INSTANTIATE_TEST_SUITE_P(
      Dev,
      DefaultBrowserBeaconTest,
      testing::Combine(testing::Values(install_static::DEV_INDEX),
                       testing::Values("user", "system")));
  // Canary is only at user level.
  INSTANTIATE_TEST_SUITE_P(
      Canary,
      DefaultBrowserBeaconTest,
      testing::Combine(testing::Values(install_static::CANARY_INDEX),
                       testing::Values("user")));
  #else   // BUILDFLAG(GOOGLE_CHROME_BRANDING)
  // Chromium supports user and system levels.
  INSTANTIATE_TEST_SUITE_P(
      Chromium,
      DefaultBrowserBeaconTest,
      testing::Combine(testing::Values(install_static::CHROMIUM_INDEX),
                       testing::Values("user", "system")));
  #endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
  
  }  // namespace installer_util
  