chromium/chrome/browser/ui/views/relaunch_notification/relaunch_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 "chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller.h"

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

#include "ash/public/cpp/update_types.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/power_monitor_test.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/chromeos_buildflags.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.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 "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/shell.h"
#include "ash/test/ash_test_helper.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ui/ash/system/system_tray_client_impl.h"
#include "components/user_manager/scoped_user_manager.h"
#include "content/public/test/browser_task_environment.h"
#include "ui/display/manager/display_configurator.h"
#include "ui/display/manager/test/action_logger.h"
#include "ui/display/manager/test/test_native_display_delegate.h"
#else
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/frame/test_with_browser_view.h"
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

_;
Eq;
ResultOf;
Return;

namespace {

// A delegate interface for handling the actions taken by the controller.
class ControllerDelegate {};

// A fake controller that asks a delegate to do work.
class FakeRelaunchNotificationController
    : public RelaunchNotificationController {};

// A mock delegate for testing.
class MockControllerDelegate : public ControllerDelegate {};

// A fake UpgradeDetector.
class FakeUpgradeDetector : public UpgradeDetector {};

}  // namespace

// A test harness that provides facilities for manipulating the relaunch
// notification policy setting and for broadcasting upgrade notifications.
class RelaunchNotificationControllerTest : public ::testing::Test {};

TEST_F(RelaunchNotificationControllerTest, CreateDestroy) {}

// Without the browser.relaunch_notification preference set, the controller
// should not be observing the UpgradeDetector, and should therefore never
// attempt to show any notifications.

// TODO(crbug.com/40099078) Disabled due to race condition.
#if defined(THREAD_SANATIZER)
#define MAYBE_PolicyUnset
#else
#define MAYBE_PolicyUnset
#endif
TEST_F(RelaunchNotificationControllerTest, MAYBE_PolicyUnset) {}

// With the browser.relaunch_notification preference set to 1, the controller
// should be observing the UpgradeDetector and should show "Requested"
// notifications on each level change above "very low".
TEST_F(RelaunchNotificationControllerTest, RecommendedByPolicy) {}

// With the browser.relaunch_notification preference set to 2, the controller
// should be observing the UpgradeDetector and should show "Required"
// notifications on each level change.
TEST_F(RelaunchNotificationControllerTest, RequiredByPolicy) {}

// Flipping the policy should have no effect when at level NONE or VERY_LOW.
TEST_F(RelaunchNotificationControllerTest, PolicyChangesNoUpgrade) {}

// Policy changes at an elevated level should show the appropriate notification.
TEST_F(RelaunchNotificationControllerTest, PolicyChangesWithUpgrade) {}

// Relaunch is forced when the deadline is reached.
TEST_F(RelaunchNotificationControllerTest, RequiredDeadlineReached) {}

// No forced relaunch if the dialog is closed.
TEST_F(RelaunchNotificationControllerTest, RequiredDeadlineReachedNoPolicy) {}

// NotificationPeriod changes should do nothing at any policy setting when the
// annoyance level is at none.
TEST_F(RelaunchNotificationControllerTest, NonePeriodChange) {}

// NotificationPeriod changes should do nothing at any policy setting when the
// annoyance level is at very low.
TEST_F(RelaunchNotificationControllerTest, VeryLowPeriodChange) {}

// NotificationPeriod changes impact reshows of the relaunch recommended bubble.
TEST_F(RelaunchNotificationControllerTest, PeriodChangeRecommended) {}

// NotificationPeriod changes impact reshows of the relaunch required dialog.
TEST_F(RelaunchNotificationControllerTest, PeriodChangeRequired) {}

// Test that grace period is given to the user to relaunch if the deadline is
// shortened to be in the past due to change in notification period.
TEST_F(RelaunchNotificationControllerTest, DeadlineShortenGracePeriod) {}

// Test that grace period is given to the user to relaunch if the device goes to
// sleep beyond the deadline before showing the notification.
TEST_F(RelaunchNotificationControllerTest, DeviceSleepBeforeNotification) {}

// Test that the deadline is extended by the grace period when the
// notification is potentially seen.
TEST_F(RelaunchNotificationControllerTest, DeferredRequired) {}

// Call to override the current relaunch notification type should override it to
// required and policy change should not affect it.
TEST_F(RelaunchNotificationControllerTest, OverriddenToRequired) {}

// Tests that the required notification is shown all three times when the clock
// moves along with the elevations.
TEST_F(RelaunchNotificationControllerTest, NotifyAllWithShortestPeriod) {}

#if BUILDFLAG(IS_CHROMEOS_ASH)

class RelaunchNotificationControllerPlatformImplTest : public ::testing::Test {
 protected:
  RelaunchNotificationControllerPlatformImplTest()
      : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}

  ~RelaunchNotificationControllerPlatformImplTest() override = default;

  void SetUp() override {
    ash::AshTestHelper::InitParams init_params;
    init_params.start_session = false;
    ash_test_helper_.SetUp(std::move(init_params));

    user_manager_ = new ash::FakeChromeUserManager();
    scoped_user_manager_ = std::make_unique<user_manager::ScopedUserManager>(
        base::WrapUnique(user_manager_.get()));

    const char test_user_email[] = "[email protected]";
    const AccountId test_account_id(AccountId::FromUserEmail(test_user_email));
    user_manager_->AddUser(test_account_id);
    user_manager_->LoginUser(test_account_id);

    // SessionManager is created by
    // |AshTestHelper::bluetooth_config_test_helper()|.
    session_manager::SessionManager::Get()->CreateSession(
        test_account_id, test_user_email, false);
    session_manager::SessionManager::Get()->SetSessionState(
        session_manager::SessionState::ACTIVE);

    logger_ = std::make_unique<display::test::ActionLogger>();
    native_display_delegate_ =
        new display::test::TestNativeDisplayDelegate(logger_.get());
    ash::Shell::Get()->display_configurator()->SetDelegateForTesting(
        std::unique_ptr<display::NativeDisplayDelegate>(
            native_display_delegate_));
  }

  void LockScreen() {
    session_manager::SessionManager::Get()->SetSessionState(
        session_manager::SessionState::LOCKED);
  }

  void UnLockScreen() {
    session_manager::SessionManager::Get()->SetSessionState(
        session_manager::SessionState::ACTIVE);
  }

  void TurnDisplayOff() {
    ash::Shell::Get()->display_configurator()->SetDisplayPower(
        chromeos::DISPLAY_POWER_ALL_OFF, 0, base::DoNothing());
  }

  void TurnDisplayOn() {
    ash::Shell::Get()->display_configurator()->SetDisplayPower(
        chromeos::DISPLAY_POWER_ALL_ON, 0, base::DoNothing());
  }

  RelaunchNotificationControllerPlatformImpl& platform_impl() { return impl_; }

  // Returns the TaskEnvironment's MockClock.
  const base::Clock* GetMockClock() { return task_environment_.GetMockClock(); }

 private:
  content::BrowserTaskEnvironment task_environment_;
  RelaunchNotificationControllerPlatformImpl impl_;
  ash::AshTestHelper ash_test_helper_;
  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> user_manager_;
  std::unique_ptr<user_manager::ScopedUserManager> scoped_user_manager_;
  std::unique_ptr<display::test::ActionLogger> logger_;
  raw_ptr<display::NativeDisplayDelegate> native_display_delegate_;
};

// SynchronousNotification
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       SynchronousNotification) {
  UnLockScreen();
  TurnDisplayOn();

  // Expect the platform_impl to query for the deadline synchronously.
  ::testing::StrictMock<base::MockOnceCallback<base::Time()>> callback;
  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());
  ::testing::Mock::VerifyAndClearExpectations(&callback);
}

// Deferred Notification when the display is off then on.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       DeferredNotificationDisplayOff) {
  TurnDisplayOff();

  ::testing::StrictMock<base::MockOnceCallback<base::Time()>> callback;

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());
  ::testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, Run());
  TurnDisplayOn();
  ::testing::Mock::VerifyAndClearExpectations(&callback);
}

// Deferred Notification when the display is off then on.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       DeferredNotificationSessionLocked) {
  LockScreen();

  ::testing::StrictMock<base::MockOnceCallback<base::Time()>> callback;

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());
  ::testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, Run());
  UnLockScreen();
  ::testing::Mock::VerifyAndClearExpectations(&callback);
}

// Multiple screen on & off.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       RequiredDeadlineReachedAfterMultipleResume) {
  TurnDisplayOff();

  ::testing::StrictMock<base::MockOnceCallback<base::Time()>> callback;

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());
  ::testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, Run());
  TurnDisplayOn();

  TurnDisplayOff();
  TurnDisplayOn();
  TurnDisplayOff();
  TurnDisplayOn();
  ::testing::Mock::VerifyAndClearExpectations(&callback);
}

// Multiple session locks & unlocks.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       RequiredDeadlineReachedBeforeMultipleUnlock) {
  LockScreen();

  ::testing::StrictMock<base::MockOnceCallback<base::Time()>> callback;

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());
  ::testing::Mock::VerifyAndClearExpectations(&callback);

  EXPECT_CALL(callback, Run());
  UnLockScreen();

  LockScreen();
  UnLockScreen();
  LockScreen();
  UnLockScreen();
  ::testing::Mock::VerifyAndClearExpectations(&callback);
}

class MockSystemTrayClientImpl : public SystemTrayClientImpl {
 public:
  MockSystemTrayClientImpl() : SystemTrayClientImpl(this) {}
  MOCK_METHOD(void,
              SetRelaunchNotificationState,
              (const ash::RelaunchNotificationState&),
              (override));
};

// Correct relaunch notification state for required notification type.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       RelaunchNotificationStateRequired) {
  base::MockOnceCallback<base::Time()> callback;
  MockSystemTrayClientImpl system_tray_client_impl;
  ash::RelaunchNotificationState relaunch_notification_state;
  EXPECT_CALL(system_tray_client_impl, SetRelaunchNotificationState(_))
      .WillOnce(testing::SaveArg<0>(&relaunch_notification_state));

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/false,
      callback.Get());

  EXPECT_EQ(relaunch_notification_state.requirement_type,
            ash::RelaunchNotificationState::kRequired);
  EXPECT_EQ(relaunch_notification_state.policy_source,
            ash::RelaunchNotificationState::kUser);
  EXPECT_LE(relaunch_notification_state.rounded_time_until_reboot_required,
            base::Seconds(1));
}

// Correct relaunch notification state for required notification type with an
// override.
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       RelaunchNotificationStateRequiredWithOverride) {
  base::MockOnceCallback<base::Time()> callback;
  MockSystemTrayClientImpl system_tray_client_impl;
  ash::RelaunchNotificationState relaunch_notification_state;
  EXPECT_CALL(system_tray_client_impl, SetRelaunchNotificationState(_))
      .WillOnce(testing::SaveArg<0>(&relaunch_notification_state));

  platform_impl().NotifyRelaunchRequired(
      GetMockClock()->Now(), /*is_notification_type_overriden=*/true,
      callback.Get());

  EXPECT_EQ(relaunch_notification_state.requirement_type,
            ash::RelaunchNotificationState::kRequired);
  EXPECT_EQ(relaunch_notification_state.policy_source,
            ash::RelaunchNotificationState::kDevice);
  EXPECT_LE(relaunch_notification_state.rounded_time_until_reboot_required,
            base::Seconds(1));
}

#else  // BUILDFLAG(IS_CHROMEOS_ASH)

class RelaunchNotificationControllerPlatformImplTest
    : public TestWithBrowserView {};

// Flaky on all platforms: https://crbug.com/1294032
TEST_F(RelaunchNotificationControllerPlatformImplTest,
       DISABLED_SynchronousNotification) {}

//  Flaky on Mac: https://crbug.com/1312578
#if BUILDFLAG(IS_MAC)
#define MAYBE_DeferredDeadline
#else
#define MAYBE_DeferredDeadline
#endif
TEST_F(RelaunchNotificationControllerPlatformImplTest, MAYBE_DeferredDeadline) {}

#endif  // BUILDFLAG(IS_CHROMEOS_ASH)