chromium/chrome/browser/upgrade_detector/upgrade_detector_chromeos_unittest.cc

// Copyright 2018 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/upgrade_detector/upgrade_detector_chromeos.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/environment.h"
#include "base/memory/raw_ptr.h"
#include "base/test/task_environment.h"
#include "base/time/clock.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/upgrade_detector/upgrade_observer.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/dbus/update_engine/fake_update_engine_client.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "components/prefs/testing_pref_service.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

class TestUpgradeDetectorChromeos : public UpgradeDetectorChromeos {
 public:
  explicit TestUpgradeDetectorChromeos(const base::Clock* clock,
                                       const base::TickClock* tick_clock)
      : UpgradeDetectorChromeos(clock, tick_clock) {}

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

  ~TestUpgradeDetectorChromeos() override = default;

  // Exposed for testing.
  using UpgradeDetector::AdjustDeadline;
  using UpgradeDetector::GetDefaultRelaunchWindow;
  using UpgradeDetectorChromeos::UPGRADE_AVAILABLE_REGULAR;

  base::TimeDelta GetHighAnnoyanceLevelDelta() {
    return GetAnnoyanceLevelDeadline(UpgradeDetector::UPGRADE_ANNOYANCE_HIGH) -
           GetAnnoyanceLevelDeadline(
               UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);
  }
};

class MockUpgradeObserver : public UpgradeObserver {
 public:
  explicit MockUpgradeObserver(UpgradeDetector* upgrade_detector)
      : upgrade_detector_(upgrade_detector) {
    upgrade_detector_->AddObserver(this);
  }

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

  ~MockUpgradeObserver() override { upgrade_detector_->RemoveObserver(this); }
  MOCK_METHOD0(OnUpdateOverCellularAvailable, void());
  MOCK_METHOD0(OnUpdateOverCellularOneTimePermissionGranted, void());
  MOCK_METHOD0(OnUpgradeRecommended, void());
  MOCK_METHOD0(OnCriticalUpgradeInstalled, void());
  MOCK_METHOD0(OnOutdatedInstall, void());
  MOCK_METHOD0(OnOutdatedInstallNoAutoUpdate, void());
  MOCK_METHOD1(OnRelaunchOverriddenToRequired, void(bool overridden));

 private:
  const raw_ptr<UpgradeDetector> upgrade_detector_;
};

}  // namespace

class UpgradeDetectorChromeosTest : public ::testing::Test {
 public:
  UpgradeDetectorChromeosTest(const UpgradeDetectorChromeosTest&) = delete;
  UpgradeDetectorChromeosTest& operator=(const UpgradeDetectorChromeosTest&) =
      delete;

 protected:
  UpgradeDetectorChromeosTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME),
        scoped_local_state_(TestingBrowserProcess::GetGlobal()),
        env_(base::Environment::Create()) {
    // Disable the detector's check to see if autoupdates are inabled.
    // Without this, tests put the detector into an invalid state by detecting
    // upgrades before the detection task completes.
    scoped_local_state_.Get()->SetUserPref(prefs::kAttemptedToEnableAutoupdate,
                                           std::make_unique<base::Value>(true));

    fake_update_engine_client_ =
        ash::UpdateEngineClient::InitializeFakeForTest();

    // Fast forward to set current time to local 2am . This is done to align the
    // relaunch deadline within the default relaunch window of 2am to 4am so
    // that it is not adjusted in tests.
    std::string env_tz;
    if (env_->GetVar("TZ", &env_tz))
      original_tz_ = env_tz;
    env_->SetVar("TZ", "UTC");
    tzset();
    FastForwardBy(base::Hours(2));
  }

  ~UpgradeDetectorChromeosTest() override {
    // Revert back to the original timezone.
    if (original_tz_) {
      env_->SetVar("TZ", original_tz_.value());
    } else {
      env_->UnSetVar("TZ");
    }
    tzset();

    ash::UpdateEngineClient::Shutdown();
  }

  const base::Clock* GetMockClock() { return task_environment_.GetMockClock(); }

  const base::TickClock* GetMockTickClock() {
    return task_environment_.GetMockTickClock();
  }

  void RunUntilIdle() { task_environment_.RunUntilIdle(); }

  void NotifyUpdateReadyToInstall(const std::string& version,
                                  bool is_rollback,
                                  bool will_powerwash) {
    update_engine::StatusResult status;
    if (!version.empty())
      status.set_new_version(version);
    status.set_is_enterprise_rollback(is_rollback);
    status.set_will_powerwash_after_reboot(will_powerwash);
    status.set_current_operation(update_engine::Operation::UPDATED_NEED_REBOOT);
    fake_update_engine_client_->set_default_status(status);
    fake_update_engine_client_->NotifyObserversThatStatusChanged(status);
  }

  void NotifyUpdateDownloading() {
    update_engine::StatusResult status;
    status.set_current_operation(update_engine::Operation::DOWNLOADING);
    fake_update_engine_client_->set_default_status(status);
    fake_update_engine_client_->NotifyObserversThatStatusChanged(status);
  }

  void NotifyStatusIdle() {
    update_engine::StatusResult status;
    status.set_current_operation(update_engine::Operation::IDLE);
    fake_update_engine_client_->set_default_status(status);
    fake_update_engine_client_->NotifyObserversThatStatusChanged(status);
  }

  // Sets the browser.relaunch_notification preference in Local State to
  // |value|.
  void SetIsRelaunchNotificationPolicyEnabled(bool enabled) {
    constexpr int kChromeMenuOnly = 0;     // Disabled.
    constexpr int kRecommendedBubble = 1;  // Enabled.

    scoped_local_state_.Get()->SetManagedPref(
        prefs::kRelaunchNotification,
        std::make_unique<base::Value>(enabled ? kRecommendedBubble
                                              : kChromeMenuOnly));
  }

  // Sets the browser.relaunch_notification_period preference in Local State to
  // |value|.
  void SetNotificationPeriodPref(base::TimeDelta value) {
    if (value.is_zero()) {
      scoped_local_state_.Get()->RemoveManagedPref(
          prefs::kRelaunchNotificationPeriod);
    } else {
      scoped_local_state_.Get()->SetManagedPref(
          prefs::kRelaunchNotificationPeriod,
          std::make_unique<base::Value>(
              base::saturated_cast<int>(value.InMilliseconds())));
    }
  }

  // Sets the browser.relaunch_heads_up_period preference in Local State to
  // |value|.
  void SetHeadsUpPeriodPref(base::TimeDelta value) {
    if (value.is_zero()) {
      scoped_local_state_.Get()->RemoveManagedPref(
          prefs::kRelaunchHeadsUpPeriod);
    } else {
      scoped_local_state_.Get()->SetManagedPref(
          prefs::kRelaunchHeadsUpPeriod,
          std::make_unique<base::Value>(
              base::saturated_cast<int>(value.InMilliseconds())));
    }
  }

  // Fast-forwards virtual time by |delta|.
  void FastForwardBy(base::TimeDelta delta) {
    task_environment_.FastForwardBy(delta);
  }

 private:
  base::test::TaskEnvironment task_environment_;
  ScopedTestingLocalState scoped_local_state_;
  std::unique_ptr<base::Environment> env_;
  std::optional<std::string> original_tz_;

  raw_ptr<ash::FakeUpdateEngineClient, DanglingUntriaged>
      fake_update_engine_client_;  // Not owned.
};

TEST_F(UpgradeDetectorChromeosTest, PolicyNotEnabled) {
  // RelaunchNotification policy is disabled.
  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();

  // The observer is expected to be notified that an upgrade is recommended.
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());

  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);

  upgrade_detector.Shutdown();
}

TEST_F(UpgradeDetectorChromeosTest, PolicyNotEnabledRollback) {
  // RelaunchNotification policy is disabled.
  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();

  // The observer is expected to be notified that an upgrade is recommended.
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());

  NotifyUpdateReadyToInstall("1.0.0.0", true /*is_rollback*/,
                             true /*will_powerwash*/);

  upgrade_detector.Shutdown();
}

TEST_F(UpgradeDetectorChromeosTest, PolicyEnabled) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  // For regular updates the notification is delayed if the policy is enabled.
  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();

  // The observer is not expected to be notified yet.
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(0);

  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);

  upgrade_detector.Shutdown();
}

TEST_F(UpgradeDetectorChromeosTest, PolicyEnabledRollback) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  // Notification should always appear for rollback.
  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();

  // The observer is expected to be notified that an upgrade is recommended.
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());

  NotifyUpdateReadyToInstall("1.0.0.0", true /*is_rollback*/,
                             true /*will_powerwash*/);

  upgrade_detector.Shutdown();
}

TEST_F(UpgradeDetectorChromeosTest, PolicyEnabledPowerwash) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  // Notification should always appear if the device is going to be powerwashed.
  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();

  // The observer is expected to be notified that an upgrade is recommended.
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());

  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             true /*will_powerwash*/);

  upgrade_detector.Shutdown();
}

TEST_F(UpgradeDetectorChromeosTest, TestHighAnnoyanceDeadline) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  // Observer should get some notifications about new version.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(testing::AtLeast(1));
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);

  const auto deadline = upgrade_detector.GetAnnoyanceLevelDeadline(
      UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);

  // Another new version of ChromeOS is ready to install after high
  // annoyance reached.
  FastForwardBy(upgrade_detector.GetDefaultHighAnnoyanceThreshold());
  ::testing::Mock::VerifyAndClear(&mock_observer);
  // New notification could be sent or not.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);

  // Deadline wasn't changed because of new upgrade detected.
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            deadline);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestHeadsUpPeriod) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  const auto notification_period = base::Days(7);
  const auto heads_up_period = base::Days(1);
  const auto grace_period = base::Hours(1);
  SetNotificationPeriodPref(notification_period);
  SetHeadsUpPeriodPref(heads_up_period);

  const auto no_notification_till =
      notification_period - heads_up_period - base::Minutes(1);
  const auto first_notification_at = notification_period - heads_up_period;
  const auto second_notification_at = notification_period - grace_period;
  const auto third_notification_at = notification_period;

  // Observer should not get notifications about new version till 6-th day.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(no_notification_till);
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  FastForwardBy(first_notification_at - no_notification_till);
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Notifications should arrive every 20 minutes after the first one with one
  // final notification at grace annoyance.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(second_notification_at - first_notification_at);
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_GRACE);

  // UPGRADE_ANNOYANCE_HIGH at the end of the period.
  // Notifications should arrive every 20 minutes after the first one with one
  // final notification at high annoyance.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(third_notification_at - second_notification_at);
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestGracePeriodChange) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  SetNotificationPeriodPref(base::Days(4));
  SetHeadsUpPeriodPref(base::Minutes(90));

  // Observer should not get notifications about new version first 3 days.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(base::Days(3));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Observer should get notifications because of elevated annoyance reached.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  FastForwardBy(base::Days(1) - base::Minutes(90));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Observer should get notifications because of grace annoyance reached which
  // is midway of elevated and high deadline.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(base::Minutes(45));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_GRACE);

  // Observer should get notifications as annoyance level is back to elevated
  // because of HeadsUpPeriod change.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetHeadsUpPeriodPref(base::Minutes(60));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Observer should get notifications as annoyance level moves to grace because
  // of HeadsUpPeriod change.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetHeadsUpPeriodPref(base::Minutes(120));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_GRACE);

  // Observer should get notifications as annoyance level moves to none because
  // of NotificationPeriod change.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(7));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // UPGRADE_ANNOYANCE_HIGH at the end of the period.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(base::Days(3) + base::Minutes(45));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestHeadsUpPeriodChange) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  SetNotificationPeriodPref(base::Days(7));
  SetHeadsUpPeriodPref(base::Days(1));

  // Observer should not get notifications about new version first 4 days.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(base::Days(4));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Observer should get notifications because of HeadsUpPeriod change.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetHeadsUpPeriodPref(base::Days(3));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // UPGRADE_ANNOYANCE_HIGH at the end of the period.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(base::Days(3));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestHeadsUpPeriodNotificationPeriodChange) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  SetNotificationPeriodPref(base::Days(7));
  SetHeadsUpPeriodPref(base::Days(1));

  // Observer should not get notifications about new version first 4 days.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(base::Days(4));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Observer should get notifications because of NotificationPeriod change.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(5));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // UPGRADE_ANNOYANCE_HIGH at the end of the period.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(base::Days(3));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest,
       TestHeadsUpPeriodOverflowNotificationPeriod) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  SetNotificationPeriodPref(base::Days(7));
  SetHeadsUpPeriodPref(base::Days(8));

  // Observer should get notification because HeadsUpPeriod bigger than
  // NotificationPeriod.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);
  EXPECT_EQ(upgrade_detector.GetHighAnnoyanceLevelDelta(), base::Days(7));

  // HighAnnoyanceLevelDelta becomes 8 days because period is increased.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(14));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);
  EXPECT_EQ(upgrade_detector.GetHighAnnoyanceLevelDelta(), base::Days(8));

  // Bring NotificationPeriod back, HighAnnoyanceLevelDelta becomes 7 days.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(7));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);
  EXPECT_EQ(upgrade_detector.GetHighAnnoyanceLevelDelta(), base::Days(7));

  // UPGRADE_ANNOYANCE_HIGH at the end of the period.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended())
      .Times(testing::AnyNumber());
  FastForwardBy(base::Days(7));
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);
  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, OnUpgradeRecommendedCalledOnce) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  SetNotificationPeriodPref(base::Days(7));
  SetHeadsUpPeriodPref(base::Days(7));

  // Observer should get notification because HeadsUpPeriod is the same as
  // NotificationPeriod.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);

  // Both periods are changed but OnUpgradeRecommended called only once.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(8));
  SetHeadsUpPeriodPref(base::Days(8));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, DeadlineAdjustmentDefaultWindow) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  const auto delta = base::Days(7);

  base::Time detect_time;
  ASSERT_TRUE(base::Time::FromString("1 Jan 2018 06:00", &detect_time));
  base::Time deadline, deadline_lower_border, deadline_upper_border;
  ASSERT_TRUE(
      base::Time::FromString("9 Jan 2018 02:00", &deadline_lower_border));
  ASSERT_TRUE(
      base::Time::FromString("9 Jan 2018 04:00", &deadline_upper_border));
  const auto relaunch_window = upgrade_detector.GetDefaultRelaunchWindow();
  deadline =
      upgrade_detector.AdjustDeadline(detect_time + delta, relaunch_window);
  EXPECT_GE(deadline, deadline_lower_border);
  EXPECT_LE(deadline, deadline_upper_border);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestOverrideThresholds) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  const auto notification_period = base::Days(7);
  const auto heads_up_period = base::Days(2);
  SetNotificationPeriodPref(notification_period);
  SetHeadsUpPeriodPref(heads_up_period);
  // Simulate update installed.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Overriding the thresholds should change the high annoyance deadline and the
  // notification stage accordingly and notify the observers.
  base::TimeDelta delta = base::Hours(2);
  base::Time deadline = upgrade_detector.upgrade_detected_time() + delta;
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  upgrade_detector.OverrideHighAnnoyanceDeadline(deadline);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            deadline);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);
  ::testing::Mock::VerifyAndClear(&mock_observer);

  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  upgrade_detector.ResetOverriddenDeadline();
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);
  ::testing::Mock::VerifyAndClear(&mock_observer);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestOverrideNotificationType) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Observer should get some notification about the overridden relaunch
  // notification style.
  EXPECT_CALL(mock_observer, OnRelaunchOverriddenToRequired(true));
  upgrade_detector.OverrideRelaunchNotificationToRequired(true);
  ::testing::Mock::VerifyAndClear(&mock_observer);

  // Observer should get some notification about the resetting the overridden
  // relaunch notification style.
  EXPECT_CALL(mock_observer, OnRelaunchOverriddenToRequired(false));
  upgrade_detector.OverrideRelaunchNotificationToRequired(false);
  ::testing::Mock::VerifyAndClear(&mock_observer);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestUpdateInProgress) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  // Finish the first update and set annoyance level to ELEVATED.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(testing::AtLeast(1));
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(upgrade_detector.GetDefaultElevatedAnnoyanceThreshold());
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Start a second update with a new version and make sure observers are
  // notified when annoyance level changes to NONE during download.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  NotifyUpdateDownloading();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Complete update and make sure observers are notified when the annoyance
  // level goes back to the previous state.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  NotifyUpdateReadyToInstall("2.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Change level to HIGH and make sure observers are notified.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(testing::AtLeast(1));
  FastForwardBy(upgrade_detector.GetHighAnnoyanceLevelDelta());
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_HIGH);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

TEST_F(UpgradeDetectorChromeosTest, TestInvalidateUpdate) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  upgrade_detector.Init();
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);

  // Finish the first update and set annoyance level to ELEVATED.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(testing::AtLeast(1));
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(upgrade_detector.GetDefaultElevatedAnnoyanceThreshold());
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  // Invalidate update by resetting the status. Annoyance level should go
  // back down to NONE.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  NotifyStatusIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_NONE);

  // Make sure any future updates complete successfully and observers are
  // notified.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended()).Times(testing::AtLeast(1));
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  FastForwardBy(upgrade_detector.GetDefaultElevatedAnnoyanceThreshold());
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.upgrade_notification_stage(),
            UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED);

  upgrade_detector.Shutdown();
  RunUntilIdle();
}

// Tests correct deadlines are set when an upgrade is detected.
TEST_F(UpgradeDetectorChromeosTest, AnnoyanceLevelDeadlines) {
  // Enable the relaunch notification policy.
  SetIsRelaunchNotificationPolicyEnabled(true /*enabled*/);

  TestUpgradeDetectorChromeos upgrade_detector(GetMockClock(),
                                               GetMockTickClock());
  ::testing::StrictMock<MockUpgradeObserver> mock_observer(&upgrade_detector);
  upgrade_detector.Init();

  // Deadline not set before upgrade detected.
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            base::Time());

  // Pretend that an upgrade was just detected now.
  NotifyUpdateReadyToInstall("1.0.0.0", false /*is_rollback*/,
                             false /*will_powerwash*/);
  base::Time detect_time = upgrade_detector.upgrade_detected_time();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            detect_time + base::Days(7));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_GRACE),
            detect_time + base::Days(7) - base::Hours(1));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED),
            detect_time + base::Days(4));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_VERY_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_NONE),
            detect_time);

  // Drop the period and notice change in the deadlines.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetNotificationPeriodPref(base::Days(1));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            detect_time + base::Hours(24));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_GRACE),
            detect_time + base::Hours(23));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_VERY_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_NONE),
            detect_time);

  // Set heads up period and notice change in the deadlines.
  EXPECT_CALL(mock_observer, OnUpgradeRecommended());
  SetHeadsUpPeriodPref(base::Hours(10));
  RunUntilIdle();
  ::testing::Mock::VerifyAndClear(&mock_observer);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_HIGH),
            detect_time + base::Hours(24));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_GRACE),
            detect_time + base::Hours(23));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_ELEVATED),
            detect_time + base::Hours(14));
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_VERY_LOW),
            detect_time);
  EXPECT_EQ(upgrade_detector.GetAnnoyanceLevelDeadline(
                UpgradeDetector::UPGRADE_ANNOYANCE_NONE),
            detect_time);

  upgrade_detector.Shutdown();
}