chromium/chrome/browser/ash/policy/remote_commands/device_command_reboot_job_with_scheduled_reboot_policy_test.cc

// Copyright 2023 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/ash/policy/remote_commands/device_command_reboot_job.h"

#include <memory>
#include <utility>

#include "ash/constants/ash_pref_names.h"
#include "ash/constants/ash_switches.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_command_line.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/browser/ash/policy/remote_commands/device_command_reboot_job_test_util.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/device_scheduled_reboot_handler.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/scoped_wake_lock.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/test/fake_scheduled_task_executor.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/test/scheduled_task_test_util.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "services/device/public/cpp/test/test_wake_lock_provider.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace policy {

namespace {

constexpr base::TimeDelta kUserSessionRebootDelay = base::Minutes(5);

constexpr base::TimeDelta kScheduledRebootNotificationDelay = base::Hours(1);
constexpr base::TimeDelta kScheduledRebootDialogDelay = base::Minutes(5);

constexpr char kRebootTaskTimeFieldName[] = "reboot_time";

std::unique_ptr<user_manager::FakeUserManager>
CreateFakeUserManagerForRegularSession() {
  auto manager = std::make_unique<user_manager::FakeUserManager>();

  const AccountId id = AccountId::FromUserEmail("[email protected]");
  manager->AddUser(id);
  manager->UserLoggedIn(id, /*username_hash=*/id.GetUserEmail(),
                        /*browser_restart=*/false, /*is_child=*/false);

  return manager;
}

class ScopedTestWakeLockProvider {
 public:
  ScopedTestWakeLockProvider() {
    ScopedWakeLock::OverrideWakeLockProviderBinderForTesting(
        base::BindRepeating(&device::TestWakeLockProvider::BindReceiver,
                            base::Unretained(&test_wake_lock_provider_)));
  }

  ~ScopedTestWakeLockProvider() {
    ScopedWakeLock::OverrideWakeLockProviderBinderForTesting(
        base::NullCallback());
  }

 private:
  device::TestWakeLockProvider test_wake_lock_provider_;
};

}  // namespace

// Tests interaction between the reboot command and DeviceScheduledReboot
// policy.
class DeviceCommandRebootJobWithScheduledRebootPolicyTest
    : public DeviceCommandRebootJobTestBase,
      public testing::Test {
 public:
  DeviceCommandRebootJobWithScheduledRebootPolicyTest(
      const DeviceCommandRebootJobWithScheduledRebootPolicyTest&) = delete;
  DeviceCommandRebootJobWithScheduledRebootPolicyTest& operator=(
      const DeviceCommandRebootJobWithScheduledRebootPolicyTest&) = delete;

 protected:
  DeviceCommandRebootJobWithScheduledRebootPolicyTest() {
    scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
        ash::switches::kScheduledRebootGracePeriodInSecondsForTesting,
        base::NumberToString(0));
  }

  ~DeviceCommandRebootJobWithScheduledRebootPolicyTest() override = default;

  std::unique_ptr<DeviceScheduledRebootHandler> CreateScheduledRebootHandler(
      std::unique_ptr<FakeScheduledTaskExecutor> fake_task_executor) {
    auto device_scheduled_reboot_handler =
        std::make_unique<DeviceScheduledRebootHandler>(
            ash::CrosSettings::Get(), std::move(fake_task_executor),
            fake_notifications_scheduler_.get());
    device_scheduled_reboot_handler->SetRebootDelayForTest(base::TimeDelta());
    return device_scheduled_reboot_handler;
  }

  std::unique_ptr<DeviceCommandRebootJob> CreateAndInitializeCommand() {
    return DeviceCommandRebootJobTestBase::CreateAndInitializeCommand(
        /*age_of_command=*/base::TimeDelta(), kUserSessionRebootDelay);
  }

  ash::ScopedTestingCrosSettings scoped_cros_settings_;
  base::test::ScopedCommandLine scoped_command_line_;

 private:
  user_manager::ScopedUserManager scoped_user_manager_{
      CreateFakeUserManagerForRegularSession()};
  ScopedLoginState scoped_login_state_{ScopedLoginState::CreateRegularUser()};
  ScopedTestWakeLockProvider scoped_test_wake_lock_provider_;
};

// Tests that the reboot command issued before the kDeviceScheduledReboot policy
// timestamp, triggers reboot instead of the policy.
TEST_F(DeviceCommandRebootJobWithScheduledRebootPolicyTest,
       RebootsWhenScheduledRebootPolicyIsAfterTimeoutInSession) {
  // A     B      C         D         E
  // 0min  1h     1h54min   1h59min   2h
  // --------------------------------------
  // ^     ^      ^         ^         ^
  // |     |      |         |         |
  // boot  |   command      |        scheduled reboot
  //     notification     reboot

  // A. Boot time and setup.
  auto fake_task_executor = std::make_unique<FakeScheduledTaskExecutor>(
      task_environment_.GetMockClock());

  // Schedule reboot policy to reboot in 2 hours.
  const base::TimeDelta delay_till_policy_reboot = base::Hours(2);
  auto [policy_value, reboot_time] = scheduled_task_test_util::CreatePolicy(
      fake_task_executor->GetTimeZone(), Now(), delay_till_policy_reboot,
      ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);

  std::unique_ptr<DeviceScheduledRebootHandler>
      device_scheduled_reboot_handler =
          CreateScheduledRebootHandler(std::move(fake_task_executor));

  scoped_cros_settings_.device_settings()->Set(ash::kDeviceScheduledReboot,
                                               std::move(policy_value));

  // Check that nothing happened yet.
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 0);

  // B. Fastforward to the point when the policy shows reboot notification.
  task_environment_.FastForwardBy(delay_till_policy_reboot -
                                  kScheduledRebootNotificationDelay);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // C. Fastforward closer to the scheduled reboot. Leave some time for the
  // command.
  task_environment_.FastForwardBy(kScheduledRebootNotificationDelay -
                                  kUserSessionRebootDelay - base::Minutes(1));

  auto command = CreateAndInitializeCommand();
  base::test::TestFuture<void> future;
  command->Run(Now(), NowTicks(), future.GetCallback());

  // Check that reboot notification is shown after timer starts. Fast forward
  // without an actual time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 2);

  // D. Fastforward till the user session timeout expires.
  task_environment_.FastForwardBy(kUserSessionRebootDelay);
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(command->status(), RemoteCommandJob::SUCCEEDED);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
  EXPECT_TRUE(prefs_->GetBoolean(ash::prefs::kShowPostRebootNotification));
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 3);
}

// Tests that the reboot command issued before scheduled reboot resets both
// the reboot notification and dialog.
TEST_F(DeviceCommandRebootJobWithScheduledRebootPolicyTest,
       TakesOverNotificationAndDialog) {
  // A     B      C         D         E
  // 0min  1h     1h55min   1h55min   1h59min
  // -----------------------------------------
  // ^     ^      ^         ^         ^
  // |     |      |         |         |
  // boot  |   dialog       |        command reboot
  //     notification      command

  // Use smaller user session delay for reboot command so it's triggered
  // before the scheduled reboot.
  constexpr base::TimeDelta kSmallUserSessionDelay = base::Minutes(4);
  scoped_command_line_.GetProcessCommandLine()->AppendSwitchASCII(
      ash::switches::kRemoteRebootCommandDelayInSecondsForTesting,
      base::NumberToString(kSmallUserSessionDelay.InSeconds()));

  // A. Boot time and setup.
  auto fake_task_executor = std::make_unique<FakeScheduledTaskExecutor>(
      task_environment_.GetMockClock());

  // Schedule reboot policy to reboot in 2 hours.
  const base::TimeDelta delay_till_policy_reboot = base::Hours(2);
  auto [policy_value, reboot_time] = scheduled_task_test_util::CreatePolicy(
      fake_task_executor->GetTimeZone(), Now(), delay_till_policy_reboot,
      ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);

  std::unique_ptr<DeviceScheduledRebootHandler>
      device_scheduled_reboot_handler =
          CreateScheduledRebootHandler(std::move(fake_task_executor));

  scoped_cros_settings_.device_settings()->Set(ash::kDeviceScheduledReboot,
                                               std::move(policy_value));

  // Check that nothing happened yet.
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 0);

  // C. Fastforward to the point when the policy shows reboot dialog.
  task_environment_.FastForwardBy(delay_till_policy_reboot -
                                  kScheduledRebootDialogDelay);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // D. Issue the command. Check it takes over the notification and the dialog.
  auto command = CreateAndInitializeCommand();
  base::test::TestFuture<void> future;
  command->Run(Now(), NowTicks(), future.GetCallback());

  // Check that reboot notification is shown after timer starts. Fast forward
  // without an actual time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 2);

  // E. Fastforward till the user session timeout expires.
  task_environment_.FastForwardBy(kSmallUserSessionDelay);
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(command->status(), RemoteCommandJob::SUCCEEDED);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
  EXPECT_TRUE(prefs_->GetBoolean(ash::prefs::kShowPostRebootNotification));
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 3);
}

// Tests that the command issued close to the DeviceScheduledReboot policy
// timestamp does not trigger notifications and reboot, but the policy does.
TEST_F(DeviceCommandRebootJobWithScheduledRebootPolicyTest,
       RebootsWhenScheduledRebootPolicyIsBeforeTimeoutInSession) {
  // A     B         C         D      E
  // 0min  1h        1h56min   2h     2h1min
  // ---------------------------------------
  // ^     ^         ^         ^      ^
  // |     |         |         |      |
  // boot  |      command      |     command reboot
  //     notification       scheduled reboot

  // A. Boot time and setup.
  auto fake_task_executor = std::make_unique<FakeScheduledTaskExecutor>(
      task_environment_.GetMockClock());

  // Schedule reboot policy to reboot in 2 hours.
  const base::TimeDelta delay_till_policy_reboot = base::Hours(2);
  auto [policy_value, reboot_time] = scheduled_task_test_util::CreatePolicy(
      fake_task_executor->GetTimeZone(), Now(), delay_till_policy_reboot,
      ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);

  std::unique_ptr<DeviceScheduledRebootHandler>
      device_scheduled_reboot_handler =
          CreateScheduledRebootHandler(std::move(fake_task_executor));

  scoped_cros_settings_.device_settings()->Set(ash::kDeviceScheduledReboot,
                                               std::move(policy_value));

  // Check that nothing happened yet.
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 0);

  // B. Fastforward to the point when the policy shows reboot notification.
  task_environment_.FastForwardBy(delay_till_policy_reboot -
                                  kScheduledRebootNotificationDelay);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 0);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // C. Fastforward until reboot dialog is shown and a bit more so that the
  // command delay expires after the policy.
  task_environment_.FastForwardBy(kScheduledRebootNotificationDelay -
                                  kUserSessionRebootDelay + base::Minutes(1));

  // Check that the policy triggered the notification and the dialog.
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  auto command = CreateAndInitializeCommand();
  base::test::TestFuture<void> future;
  command->Run(Now(), NowTicks(), future.GetCallback());

  // Check that the command did not update notifications. Fast forward
  // without an actual time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // D. Fastforward until the policy triggers reboot.
  task_environment_.FastForwardBy(kUserSessionRebootDelay - base::Minutes(1));
  EXPECT_EQ(command->status(), RemoteCommandJob::RUNNING);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
  EXPECT_TRUE(prefs_->GetBoolean(ash::prefs::kShowPostRebootNotification));
}

// Tests that the DeviceScheduledReboot policy triggers the reboot when
// scheduled while the command is in progress
TEST_F(DeviceCommandRebootJobWithScheduledRebootPolicyTest,
       RebootsWhenScheduledRebootPolicyIsRescheduledBeforeTimeoutInSession) {
  // A      B        C                D
  // 0min   2h       2h3min           2h5min
  // ---------------------------------------
  // ^      ^        ^                ^
  // |      |        |                |
  // boot   command  |                command reboot
  //                 scheduled reboot

  // A. Boot time and setup.
  task_environment_.FastForwardBy(base::Hours(2));

  // B. Issue the reboot command. In session timeout starts.
  auto command = CreateAndInitializeCommand();
  base::test::TestFuture<void> future;
  command->Run(Now(), NowTicks(), future.GetCallback());

  // Check that the command showed notification. Fast forward without an actual
  // time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // Schedule reboot via policy within delay.
  auto fake_task_executor = std::make_unique<FakeScheduledTaskExecutor>(
      task_environment_.GetMockClock());
  const base::TimeDelta delay_till_policy_reboot =
      kUserSessionRebootDelay - base::Minutes(1);
  auto [policy_value, reboot_time] = scheduled_task_test_util::CreatePolicy(
      fake_task_executor->GetTimeZone(), Now(), delay_till_policy_reboot,
      ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);
  std::unique_ptr<DeviceScheduledRebootHandler>
      device_scheduled_reboot_handler =
          CreateScheduledRebootHandler(std::move(fake_task_executor));
  scoped_cros_settings_.device_settings()->Set(ash::kDeviceScheduledReboot,
                                               std::move(policy_value));

  // Check that the policy reset notification. Fast forward without an actual
  // time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 2);

  // C. Fastforward until the policy triggers reboot.
  task_environment_.FastForwardBy(delay_till_policy_reboot);
  EXPECT_EQ(command->status(), RemoteCommandJob::RUNNING);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
  EXPECT_TRUE(prefs_->GetBoolean(ash::prefs::kShowPostRebootNotification));
}

// Tests that the reboot command triggers reboot when ScheduledReboot policy
// scheduled before command timeout is being rescheduled after the timeout.
TEST_F(DeviceCommandRebootJobWithScheduledRebootPolicyTest,
       RebootsWhenScheduledRebootPolicyIsRescheduledAfterTimeoutInSession) {
  // A      B        C                D               E
  // 0min   2h       2h4min           2h5min          2h6min
  // ---------------------------------------------------------
  // ^      ^        ^                ^               ^
  // |      |        |                |               |
  // boot   command  |                command reboot  |
  //                 first scheduled reboot           second scheduled reboot

  // A. Boot time and setup.
  task_environment_.FastForwardBy(base::Hours(2));

  // B. Issue the reboot command. In session timeout starts.
  auto command = CreateAndInitializeCommand();
  base::test::TestFuture<void> future;
  command->Run(Now(), NowTicks(), future.GetCallback());

  // Check that the command showed notification. Fast forward without an actual
  // time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 1);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 1);

  // Schedule reboot via policy within delay.
  auto fake_task_executor = std::make_unique<FakeScheduledTaskExecutor>(
      task_environment_.GetMockClock());
  auto* fake_task_executor_ptr = fake_task_executor.get();
  const base::TimeDelta delay_till_policy_reboot_before_command =
      kUserSessionRebootDelay - base::Minutes(1);
  auto [policy_value_before_command, reboot_time_before_command] =
      scheduled_task_test_util::CreatePolicy(
          fake_task_executor_ptr->GetTimeZone(), Now(),
          delay_till_policy_reboot_before_command,
          ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);
  std::unique_ptr<DeviceScheduledRebootHandler>
      device_scheduled_reboot_handler =
          CreateScheduledRebootHandler(std::move(fake_task_executor));
  scoped_cros_settings_.device_settings()->Set(
      ash::kDeviceScheduledReboot, std::move(policy_value_before_command));

  // Check that the policy reset notification. Fast forward without an actual
  // time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 2);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 2);

  // Reschedule the policy after the command timeout.
  const base::TimeDelta delay_till_policy_reboot_after_command =
      kUserSessionRebootDelay + base::Minutes(1);
  auto [policy_value_after_command, reboot_time_after_command] =
      scheduled_task_test_util::CreatePolicy(
          fake_task_executor_ptr->GetTimeZone(), Now(),
          delay_till_policy_reboot_after_command,
          ScheduledTaskExecutor::Frequency::kDaily, kRebootTaskTimeFieldName);
  scoped_cros_settings_.device_settings()->Set(
      ash::kDeviceScheduledReboot, std::move(policy_value_after_command));

  // Check that the command shows notification again. Fast forward without an
  // actual time so that the timer task is triggered.
  task_environment_.FastForwardBy(base::TimeDelta());
  EXPECT_EQ(fake_notifications_scheduler_->GetShowNotificationCalls(), 3);
  EXPECT_EQ(fake_notifications_scheduler_->GetShowDialogCalls(), 3);
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 3);

  // C. Fastforward until the initial scheduled reboot. Check that reboot does
  // not happen.
  task_environment_.FastForwardBy(delay_till_policy_reboot_before_command);
  EXPECT_EQ(command->status(), RemoteCommandJob::RUNNING);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 0);

  // D. Fastforward until the command timeout. Check that reboot happens.
  task_environment_.FastForwardBy(kUserSessionRebootDelay -
                                  delay_till_policy_reboot_before_command);
  ASSERT_TRUE(future.Wait());
  EXPECT_EQ(command->status(), RemoteCommandJob::SUCCEEDED);
  EXPECT_EQ(
      chromeos::FakePowerManagerClient::Get()->num_request_restart_calls(), 1);
  EXPECT_TRUE(prefs_->GetBoolean(ash::prefs::kShowPostRebootNotification));
  EXPECT_EQ(fake_notifications_scheduler_->GetCloseNotificationCalls(), 4);
}

}  // namespace policy