chromium/ash/system/update/update_notification_controller_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 "ash/system/update/update_notification_controller.h"

#include <optional>

#include "ash/public/cpp/login_types.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/public/cpp/update_types.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/system/model/enterprise_domain_model.h"
#include "ash/system/model/system_tray_model.h"
#include "ash/system/session/shutdown_confirmation_dialog.h"
#include "ash/system/system_notification_controller.h"
#include "ash/test/ash_test_base.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "build/branding_buildflags.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/vector_icons/vector_icons.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/color/color_id.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/message_center_observer.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/message_center/public/cpp/notification_types.h"

namespace ash {
namespace {

const char kNotificationId[] = "chrome://update";
const char* kDomain = "example.com";
const char* kDeviceDomain = "example.org";

// Waits for the notification to be added. Needed because the controller
// posts a task to check for slow boot request before showing the
// notification.
class AddNotificationWaiter : public message_center::MessageCenterObserver {
 public:
  AddNotificationWaiter() {
    message_center::MessageCenter::Get()->AddObserver(this);
  }
  ~AddNotificationWaiter() override {
    message_center::MessageCenter::Get()->RemoveObserver(this);
  }

  void Wait() { run_loop_.Run(); }

  // message_center::MessageCenterObserver:
  void OnNotificationAdded(const std::string& notification_id) override {
    if (notification_id == kNotificationId)
      run_loop_.Quit();
  }

  base::RunLoop run_loop_;
};

void ShowDefaultUpdateNotification() {
  Shell::Get()->system_tray_model()->ShowUpdateIcon(
      UpdateSeverity::kLow, /*factory_reset_required=*/false,
      /*rollback=*/false);
}

}  // namespace

class UpdateNotificationControllerTest : public AshTestBase {
 public:
  UpdateNotificationControllerTest() = default;

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

  ~UpdateNotificationControllerTest() override = default;

  void SetUp() override {
    AshTestBase::SetUp();

    system_app_name_ =
        l10n_util::GetStringUTF16(IDS_ASH_MESSAGE_CENTER_SYSTEM_APP_NAME);

    EnterpriseDomainModel* enterprise_domain =
        Shell::Get()->system_tray_model()->enterprise_domain();
    enterprise_domain->SetEnterpriseAccountDomainInfo(kDomain);
    enterprise_domain->SetDeviceEnterpriseInfo(
        DeviceEnterpriseInfo{kDeviceDomain, ManagementDeviceMode::kNone});
  }

 protected:
  message_center::Notification* GetNotification() {
    return message_center::MessageCenter::Get()->FindVisibleNotificationById(
        kNotificationId);
  }

  bool HasNotification() { return GetNotification(); }

  std::string GetNotificationTitle() {
    return base::UTF16ToUTF8(GetNotification()->title());
  }

  std::string GetNotificationMessage() {
    return base::UTF16ToUTF8(GetNotification()->message());
  }

  std::string GetNotificationButtonText(int index) {
    return base::UTF16ToUTF8(GetNotification()->buttons().at(index).title);
  }

  int GetNotificationButtonCount() {
    return GetNotification()->buttons().size();
  }

  int GetNotificationPriority() { return GetNotification()->priority(); }

  const gfx::VectorIcon& GetNotificationIcon() {
    return GetNotification()->vector_small_image();
  }

  bool GetNotificationNeverTimeout() {
    return GetNotification()->never_timeout();
  }

  void AddSlowBootFilePath(const base::FilePath& file_path) {
    bool success = base::WriteFile(file_path, "1\n");
    EXPECT_TRUE(success);
    Shell::Get()
        ->system_notification_controller()
        ->update_->slow_boot_file_path_ = file_path;
  }

  ShutdownConfirmationDialog* GetSlowBootConfirmationDialog() {
    return Shell::Get()
        ->system_notification_controller()
        ->update_->confirmation_dialog_;
  }

  void CompareNotificationColor(SkColor expected_color,
                                ui::ColorId expected_color_id_for_jelly) {
    const auto color_id = GetNotification()->accent_color_id();
    const auto color = GetNotification()->accent_color();

    if (color_id.has_value()) {
      // We use `ui::ColorId` for Jelly.
      EXPECT_EQ(expected_color_id_for_jelly, color_id);
    } else if (color.has_value()) {
      EXPECT_EQ(expected_color, color);
    }
  }

  std::u16string system_app_name_;
};

// Tests that the update icon becomes visible when an update becomes available.
TEST_F(UpdateNotificationControllerTest, VisibilityAfterUpdate) {
  ShowDefaultUpdateNotification();

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ("Update device", GetNotificationTitle());
  EXPECT_EQ("Learn more about the latest " +
                base::UTF16ToUTF8(system_app_name_) + " update",
            GetNotificationMessage());
  EXPECT_EQ("Restart", GetNotificationButtonText(0));

  // Click the restart button.
  message_center::MessageCenter::Get()->ClickOnNotificationButton(
      kNotificationId, 0);

  // Restart was requested.
  EXPECT_EQ(1,
            GetSessionControllerClient()->request_restart_for_update_count());
}

// Tests that the update icon becomes visible when an update becomes
// available.
TEST_F(UpdateNotificationControllerTest, VisibilityAfterUpdateWithSlowReboot) {
  // Add a slow boot file.
  base::ScopedTempDir tmp_dir;
  ASSERT_TRUE(tmp_dir.CreateUniqueTempDir());
  AddSlowBootFilePath(tmp_dir.GetPath().Append("slow_boot_required"));

  ShowDefaultUpdateNotification();

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ("Update device", GetNotificationTitle());
  EXPECT_EQ("Learn more about the latest " +
                base::UTF16ToUTF8(system_app_name_) +
                " update. This Chromebook needs to restart to apply an update. "
                "This can take up to 1 minute.",
            GetNotificationMessage());
  EXPECT_EQ("Restart", GetNotificationButtonText(0));

  // Ensure Slow Boot Dialog is not open.
  EXPECT_FALSE(GetSlowBootConfirmationDialog());

  // Trigger Click on "Restart to Update" button in Notification.
  message_center::MessageCenter::Get()->ClickOnNotificationButton(
      kNotificationId, 0);

  // Ensure Slow Boot Dialog is open and notification is removed.
  ASSERT_TRUE(GetSlowBootConfirmationDialog());
  EXPECT_FALSE(HasNotification());

  // Click the cancel button on Slow Boot Confirmation Dialog.
  GetSlowBootConfirmationDialog()->CancelDialog();

  // Ensure that the Slow Boot Dialog is closed and notification is visible.
  EXPECT_FALSE(GetSlowBootConfirmationDialog());
  EXPECT_TRUE(HasNotification());
}

// Tests that the update icon's visibility after an update becomes
// available for downloading over cellular connection.
TEST_F(UpdateNotificationControllerTest,
       VisibilityAfterUpdateOverCellularAvailable) {
  // Simulate an update available for downloading over cellular connection.
  Shell::Get()->system_tray_model()->SetUpdateOverCellularAvailableIconVisible(
      true);

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ("Update device", GetNotificationTitle());
  EXPECT_EQ("Learn more about the latest " +
                base::UTF16ToUTF8(system_app_name_) + " update",
            GetNotificationMessage());
  EXPECT_EQ(0, GetNotificationButtonCount());

  // Simulate the user's one time permission on downloading the update is
  // granted.
  Shell::Get()->system_tray_model()->SetUpdateOverCellularAvailableIconVisible(
      false);

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  // The notification disappears.
  EXPECT_FALSE(HasNotification());
}

TEST_F(UpdateNotificationControllerTest,
       VisibilityAfterUpdateRequiringFactoryReset) {
  // Simulate an update that requires factory reset.
  Shell::Get()->system_tray_model()->ShowUpdateIcon(UpdateSeverity::kLow, true,
                                                    false);

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  const std::u16string chrome_os_device_name = ui::GetChromeOSDeviceName();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_TITLE),
            GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_UPDATE_NOTIFICATION_MESSAGE_POWERWASH,
                                      chrome_os_device_name, system_app_name_),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, NoUpdateNotification) {
  // The system starts with no update pending, so the notification isn't
  // visible.
  EXPECT_FALSE(HasNotification());
}

TEST_F(UpdateNotificationControllerTest, RollbackNotification) {
  Shell::Get()->system_tray_model()->ShowUpdateIcon(
      UpdateSeverity::kLow, /*factory_reset_required=*/true,
      /*rollback=*/true);

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  const std::u16string chrome_os_device_name = ui::GetChromeOSDeviceName();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(
      strcmp(kSystemMenuRollbackIcon.name, GetNotificationIcon().name) == 0);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ROLLBACK_NOTIFICATION_TITLE),
            GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_UPDATE_NOTIFICATION_MESSAGE_ROLLBACK,
                                      base::ASCIIToUTF16(kDomain),
                                      chrome_os_device_name),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, RollbackRecommendedNotification) {
  Shell::Get()->system_tray_model()->ShowUpdateIcon(
      UpdateSeverity::kLow, /*factory_reset_required=*/true,
      /*rollback=*/true);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRecommendedNotOverdue});

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  const std::u16string chrome_os_device_name = ui::GetChromeOSDeviceName();

  // Notification is the same as for a non-recommended rollback.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(
      strcmp(kSystemMenuRollbackIcon.name, GetNotificationIcon().name) == 0);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ROLLBACK_NOTIFICATION_TITLE),
            GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_UPDATE_NOTIFICATION_MESSAGE_ROLLBACK,
                                      base::ASCIIToUTF16(kDomain),
                                      chrome_os_device_name),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest,
       RollbackRecommendedOverdueNotification) {
  Shell::Get()->system_tray_model()->ShowUpdateIcon(
      UpdateSeverity::kLow, /*factory_reset_required=*/true,
      /*rollback=*/true);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRecommendedAndOverdue});

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  const std::u16string chrome_os_device_name = ui::GetChromeOSDeviceName();

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorCriticalWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysError);
  EXPECT_TRUE(
      strcmp(kSystemMenuRollbackIcon.name, GetNotificationIcon().name) == 0);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_ROLLBACK_OVERDUE_NOTIFICATION_TITLE),
            GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(
                IDS_UPDATE_NOTIFICATION_MESSAGE_ROLLBACK_OVERDUE,
                base::ASCIIToUTF16(kDomain), chrome_os_device_name),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, RollbackRequiredNotification) {
  Shell::Get()->system_tray_model()->ShowUpdateIcon(
      UpdateSeverity::kLow, /*factory_reset_required=*/true,
      /*rollback=*/true);

  constexpr base::TimeDelta remaining_time = base::Seconds(3);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .rounded_time_until_reboot_required = remaining_time,
  });

  const std::u16string chrome_os_device_name = ui::GetChromeOSDeviceName();

  // Showing Update Notification posts a task to check for slow boot request
  // and use the result of that check to generate appropriate notification. Wait
  // until everything is complete and then check if the notification is visible.
  task_environment()->RunUntilIdle();

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorCriticalWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysError);
  EXPECT_TRUE(
      strcmp(kSystemMenuRollbackIcon.name, GetNotificationIcon().name) == 0);
  EXPECT_EQ(
      l10n_util::GetPluralStringFUTF8(IDS_ROLLBACK_REQUIRED_TITLE_SECONDS, 3),
      GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(IDS_ROLLBACK_REQUIRED_BODY,
                                      base::ASCIIToUTF16(kDomain),
                                      chrome_os_device_name),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationRecommended) {
  ShowDefaultUpdateNotification();

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRecommendedNotOverdue});

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetStringUTF8(IDS_RELAUNCH_RECOMMENDED_TITLE);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_RECOMMENDED_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest,
       SetUpdateNotificationRecommendedOverdue) {
  ShowDefaultUpdateNotification();

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRecommendedAndOverdue});

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetStringUTF8(IDS_RELAUNCH_RECOMMENDED_OVERDUE_TITLE);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_RECOMMENDED_OVERDUE_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationRequiredDays) {
  ShowDefaultUpdateNotification();

  constexpr base::TimeDelta remaining_time = base::Days(3);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .rounded_time_until_reboot_required = remaining_time,
  });

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetPluralStringFUTF8(IDS_RELAUNCH_REQUIRED_TITLE_DAYS, 3);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_REQUIRED_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(message_center::SYSTEM_PRIORITY, GetNotificationPriority());
  EXPECT_EQ(true, GetNotificationNeverTimeout());
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest,
       SetUpdateNotificationRequiredWithDevicePolicySource) {
  ShowDefaultUpdateNotification();
  AddNotificationWaiter waiter;

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .policy_source = RelaunchNotificationState::kDevice,
  });

  waiter.Wait();

  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_REQUIRED_BODY, base::ASCIIToUTF16(kDeviceDomain));

  ASSERT_TRUE(HasNotification());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
}

TEST_F(UpdateNotificationControllerTest,
       SetUpdateNotificationWithoutAccountDomainManager) {
  ShowDefaultUpdateNotification();
  Shell::Get()
      ->system_tray_model()
      ->enterprise_domain()
      ->SetEnterpriseAccountDomainInfo(std::string());
  AddNotificationWaiter waiter;

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRequired});

  waiter.Wait();

  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_UPDATE_NOTIFICATION_MESSAGE_LEARN_MORE, system_app_name_);

  ASSERT_TRUE(HasNotification());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
}

TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationRequiredHours) {
  ShowDefaultUpdateNotification();

  constexpr base::TimeDelta remaining_time = base::Hours(3);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .rounded_time_until_reboot_required = remaining_time,
  });

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetPluralStringFUTF8(IDS_RELAUNCH_REQUIRED_TITLE_HOURS, 3);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_REQUIRED_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(message_center::SYSTEM_PRIORITY, GetNotificationPriority());
  EXPECT_EQ(true, GetNotificationNeverTimeout());
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationRequiredMinutes) {
  ShowDefaultUpdateNotification();

  constexpr base::TimeDelta remaining_time = base::Minutes(3);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .rounded_time_until_reboot_required = remaining_time,
  });

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetPluralStringFUTF8(IDS_RELAUNCH_REQUIRED_TITLE_MINUTES, 3);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_REQUIRED_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(message_center::SYSTEM_PRIORITY, GetNotificationPriority());
  EXPECT_EQ(true, GetNotificationNeverTimeout());
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest, SetUpdateNotificationRequiredSeconds) {
  ShowDefaultUpdateNotification();

  constexpr base::TimeDelta remaining_time = base::Seconds(3);

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({
      .requirement_type = RelaunchNotificationState::kRequired,
      .rounded_time_until_reboot_required = remaining_time,
  });

  task_environment()->RunUntilIdle();

  const std::string expected_notification_title =
      l10n_util::GetPluralStringFUTF8(IDS_RELAUNCH_REQUIRED_TITLE_SECONDS, 3);
  const std::string expected_notification_body = l10n_util::GetStringFUTF8(
      IDS_RELAUNCH_REQUIRED_BODY, base::ASCIIToUTF16(kDomain));

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorWarning,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysWarning);
  EXPECT_TRUE(strcmp(vector_icons::kBusinessIcon.name,
                     GetNotificationIcon().name) == 0);
  EXPECT_EQ(message_center::SYSTEM_PRIORITY, GetNotificationPriority());
  EXPECT_EQ(true, GetNotificationNeverTimeout());
  EXPECT_EQ(expected_notification_title, GetNotificationTitle());
  EXPECT_EQ(expected_notification_body, GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
}

// Simulates setting the notification back to the default after showing
// one for recommended updates.
TEST_F(UpdateNotificationControllerTest, SetBackToDefault) {
  ShowDefaultUpdateNotification();

  Shell::Get()->system_tray_model()->SetRelaunchNotificationState(
      {.requirement_type = RelaunchNotificationState::kRecommendedNotOverdue});

  task_environment()->RunUntilIdle();

  // Reset update state.
  Shell::Get()->system_tray_model()->SetRelaunchNotificationState({});

  task_environment()->RunUntilIdle();

  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_TITLE),
            GetNotificationTitle());
  EXPECT_EQ(l10n_util::GetStringFUTF8(
                IDS_UPDATE_NOTIFICATION_MESSAGE_LEARN_MORE, system_app_name_),
            GetNotificationMessage());
  EXPECT_EQ(l10n_util::GetStringUTF8(IDS_UPDATE_NOTIFICATION_RESTART_BUTTON),
            GetNotificationButtonText(0));
  EXPECT_NE(message_center::NotificationPriority::SYSTEM_PRIORITY,
            GetNotificationPriority());
}

TEST_F(UpdateNotificationControllerTest,
       VisibilityAfterDeferredUpdateShowNotification) {
  // Simulate a deferred update.
  Shell::Get()->system_tray_model()->SetUpdateDeferred(
      DeferredUpdateState::kShowNotification);

  // Wait until everything is complete and then check if the notification is
  // visible.
  task_environment()->RunUntilIdle();

  // The notification is now visible.
  ASSERT_TRUE(HasNotification());
  CompareNotificationColor(
      /*expected_color=*/kSystemNotificationColorNormal,
      /*expected_color_id_for_jelly=*/cros_tokens::kCrosSysPrimary);
  EXPECT_TRUE(strcmp(kSystemMenuUpdateIcon.name, GetNotificationIcon().name) ==
              0);
  EXPECT_EQ("Update device", GetNotificationTitle());
  EXPECT_EQ(
      "Get the latest features and security improvements. Updates happen in "
      "the background.",
      GetNotificationMessage());
  EXPECT_EQ("Restart", GetNotificationButtonText(0));
}

TEST_F(UpdateNotificationControllerTest,
       VisibilityAfterDeferredUpdateShowDialog) {
  // Simulate a deferred update.
  Shell::Get()->system_tray_model()->SetUpdateDeferred(
      DeferredUpdateState::kShowDialog);

  // Wait until everything is complete and then check if the notification is
  // not visible.
  task_environment()->RunUntilIdle();

  // The notification is not visible.
  ASSERT_FALSE(HasNotification());
}

TEST_F(UpdateNotificationControllerTest, VisibilityAfterDeferredUpdateOff) {
  // Simulate a deferred update.
  Shell::Get()->system_tray_model()->SetUpdateDeferred(
      DeferredUpdateState::kNone);

  // Wait until everything is complete and then check if the notification is
  // not visible.
  task_environment()->RunUntilIdle();

  // The notification is not visible.
  ASSERT_FALSE(HasNotification());
}

}  // namespace ash