chromium/chrome/browser/ui/webui/ash/settings/pages/apps/app_notification_handler_unittest.cc

// Copyright 2021 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/webui/ash/settings/pages/apps/app_notification_handler.h"

#include <memory>
#include <utility>
#include <vector>

#include "ash/public/cpp/message_center_ash.h"
#include "ash/public/cpp/test/test_new_window_delegate.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/app_service_test.h"
#include "chrome/browser/ui/webui/ash/settings/pages/apps/mojom/app_notification_handler.mojom.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/testing_profile.h"
#include "components/services/app_service/public/cpp/app_types.h"
#include "components/services/app_service/public/cpp/permission.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"

namespace ash::settings {

namespace {

class FakeMessageCenterAsh : public MessageCenterAsh {
 public:
  FakeMessageCenterAsh() = default;
  ~FakeMessageCenterAsh() override = default;

  // MessageCenterAsh override:
  void SetQuietMode(bool in_quiet_mode) override {
    if (in_quiet_mode != in_quiet_mode_) {
      in_quiet_mode_ = in_quiet_mode;
      NotifyOnQuietModeChanged(in_quiet_mode);
    }
  }

  bool IsQuietMode() const override { return in_quiet_mode_; }

 private:
  bool in_quiet_mode_ = false;
};

class AppNotificationHandlerTestObserver
    : public app_notification::mojom::AppNotificationsObserver {
 public:
  AppNotificationHandlerTestObserver() {}
  ~AppNotificationHandlerTestObserver() override {}

  void OnNotificationAppChanged(app_notification::mojom::AppPtr app) override {
    recently_updated_app_ = std::move(app);
    app_list_changed_++;
  }

  void OnQuietModeChanged(bool enabled) override {
    is_quiet_mode_ = enabled;
    quiet_mode_changed_++;
  }

  mojo::PendingRemote<app_notification::mojom::AppNotificationsObserver>
  GenerateRemote() {
    return receiver_.BindNewPipeAndPassRemote();
  }

  const std::vector<app_notification::mojom::AppPtr>& apps() { return apps_; }
  const app_notification::mojom::AppPtr& recently_updated_app() {
    return recently_updated_app_;
  }

  bool is_quiet_mode() { return is_quiet_mode_; }

  int app_list_changed() { return app_list_changed_; }
  int quiet_mode_changed() { return quiet_mode_changed_; }

 private:
  std::vector<app_notification::mojom::AppPtr> apps_;
  app_notification::mojom::AppPtr recently_updated_app_;
  bool is_quiet_mode_ = false;

  int app_list_changed_ = 0;
  int quiet_mode_changed_ = 0;

  mojo::Receiver<app_notification::mojom::AppNotificationsObserver> receiver_{
      this};
};

class MockNewWindowDelegate : public testing::NiceMock<TestNewWindowDelegate> {
 public:
  // TestNewWindowDelegate:
  MOCK_METHOD(void,
              OpenUrl,
              (const GURL& url, OpenUrlFrom from, Disposition disposition),
              (override));
};

}  // namespace

class AppNotificationHandlerTest : public testing::Test {
 public:
  AppNotificationHandlerTest()
      : task_environment_(content::BrowserTaskEnvironment::REAL_IO_THREAD),
        profile_(std::make_unique<TestingProfile>()) {}
  ~AppNotificationHandlerTest() override = default;

  void SetUp() override {
    MessageCenterAsh::SetForTesting(&message_center_ash_);
    app_service_proxy_ =
        apps::AppServiceProxyFactory::GetForProfile(profile_.get());
    apps::WaitForAppServiceProxyReady(app_service_proxy_);
    handler_ =
        std::make_unique<AppNotificationHandler>(app_service_proxy_.get());

    observer_ = std::make_unique<AppNotificationHandlerTestObserver>();
    handler_->AddObserver(observer_->GenerateRemote());

    auto instance = std::make_unique<MockNewWindowDelegate>();
    auto primary = std::make_unique<MockNewWindowDelegate>();
    new_window_delegate_primary_ = primary.get();
    new_window_provider_ = std::make_unique<TestNewWindowDelegateProvider>(
        std::move(instance), std::move(primary));
  }

  void TearDown() override {
    new_window_provider_.reset();
    handler_.reset();
    MessageCenterAsh::SetForTesting(nullptr);
  }

 protected:
  raw_ptr<MockNewWindowDelegate, DanglingUntriaged>
      new_window_delegate_primary_;

  AppNotificationHandlerTestObserver* observer() { return observer_.get(); }

  void SetQuietModeState(bool quiet_mode_enabled) {
    handler_->SetQuietMode(quiet_mode_enabled);
  }

  void OpenBrowserNotificationSettings() {
    handler_->OpenBrowserNotificationSettings();
  }

  void CreateAndStoreFakeApp(std::string fake_id,
                             apps::AppType app_type,
                             apps::PermissionType permission_type,
                             bool permission_value = true) {
    apps::Permission::PermissionValue fake_permission_value = permission_value;
    apps::PermissionPtr fake_permission = std::make_unique<apps::Permission>(
        permission_type, std::move(fake_permission_value),
        /*is_managed=*/false);
    std::vector<apps::PermissionPtr> fake_permissions;
    fake_permissions.push_back(std::move(fake_permission));

    std::vector<apps::AppPtr> fake_apps;
    apps::AppPtr fake_app = std::make_unique<apps::App>(app_type, fake_id);
    fake_app->show_in_management = true;
    fake_app->readiness = apps::Readiness::kReady;
    fake_app->permissions = std::move(fake_permissions);

    fake_apps.push_back(std::move(fake_app));

    UpdateAppRegistryCache(fake_apps, app_type);
  }

  void UpdateAppRegistryCache(std::vector<apps::AppPtr>& fake_apps,
                              apps::AppType app_type) {
    app_service_proxy_->OnApps(std::move(fake_apps), app_type, false);
  }

  bool CheckIfFakeAppInList(std::string fake_id) {
    for (app_notification::mojom::AppPtr const& app : observer_->apps()) {
      if (app->id.compare(fake_id) == 0) {
        return true;
      }
    }
    return false;
  }

 private:
  std::unique_ptr<AppNotificationHandler> handler_;
  content::BrowserTaskEnvironment task_environment_;
  std::unique_ptr<TestingProfile> profile_;
  raw_ptr<apps::AppServiceProxy> app_service_proxy_;
  FakeMessageCenterAsh message_center_ash_;
  std::unique_ptr<AppNotificationHandlerTestObserver> observer_;
  std::unique_ptr<TestNewWindowDelegateProvider> new_window_provider_;
};

// Tests for update of in_quiet_mode_ variable by MessageCenterAsh observer
// OnQuietModeChange() after quiet mode state change between true and false.
TEST_F(AppNotificationHandlerTest, TestOnQuietModeChanged) {
  MessageCenterAsh::Get()->SetQuietMode(true);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(observer()->is_quiet_mode());
  EXPECT_EQ(observer()->quiet_mode_changed(), 1);

  MessageCenterAsh::Get()->SetQuietMode(false);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(observer()->is_quiet_mode());
  EXPECT_EQ(observer()->quiet_mode_changed(), 2);
}

// Tests for update of in_quiet_mode_ variable after setting state
// with MessageCenterAsh SetQuietMode() true and false.
TEST_F(AppNotificationHandlerTest, TestSetQuietMode) {
  SetQuietModeState(true);
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(observer()->is_quiet_mode());
  EXPECT_EQ(observer()->quiet_mode_changed(), 1);

  SetQuietModeState(false);
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(observer()->is_quiet_mode());
  EXPECT_EQ(observer()->quiet_mode_changed(), 2);
}

// Tests notifying observers with only kArc and kWeb apps that have the
// NOTIFICATIONS permission.
TEST_F(AppNotificationHandlerTest, TestAppListUpdated) {
  CreateAndStoreFakeApp("arcAppWithNotifications", apps::AppType::kArc,
                        apps::PermissionType::kNotifications,
                        /*permission_value=*/true);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 1);
  EXPECT_EQ("arcAppWithNotifications", observer()->recently_updated_app()->id);
  EXPECT_TRUE(absl::get<bool>(
      observer()->recently_updated_app()->notification_permission->value));

  CreateAndStoreFakeApp("webAppWithNotifications", apps::AppType::kWeb,
                        apps::PermissionType::kNotifications,
                        /*permission_value=*/true);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 2);
  EXPECT_EQ("webAppWithNotifications", observer()->recently_updated_app()->id);
  EXPECT_TRUE(absl::holds_alternative<bool>(
      observer()->recently_updated_app()->notification_permission->value));

  CreateAndStoreFakeApp("arcAppWithCamera", apps::AppType::kArc,
                        apps::PermissionType::kCamera);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 2);

  CreateAndStoreFakeApp("webAppWithGeolocation", apps::AppType::kWeb,
                        apps::PermissionType::kLocation);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 2);

  CreateAndStoreFakeApp("pluginVmAppWithPrinting", apps::AppType::kPluginVm,
                        apps::PermissionType::kPrinting);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 2);

  CreateAndStoreFakeApp("arcAppWithNotifications", apps::AppType::kArc,
                        apps::PermissionType::kNotifications,
                        /*permission_value=*/false);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 3);
  EXPECT_EQ("arcAppWithNotifications", observer()->recently_updated_app()->id);
  EXPECT_FALSE(absl::get<bool>(
      observer()->recently_updated_app()->notification_permission->value));

  CreateAndStoreFakeApp("webAppWithNotifications", apps::AppType::kWeb,
                        apps::PermissionType::kNotifications,
                        /*permission_value=*/false);

  base::RunLoop().RunUntilIdle();
  EXPECT_EQ(observer()->app_list_changed(), 4);
  EXPECT_EQ("webAppWithNotifications", observer()->recently_updated_app()->id);
  EXPECT_FALSE(absl::get<bool>(
      observer()->recently_updated_app()->notification_permission->value));
}

TEST_F(AppNotificationHandlerTest, TestOpenBrowserNotificationSettings) {
  EXPECT_CALL(*new_window_delegate_primary_,
              OpenUrl(GURL(chrome::kAppNotificationsBrowserSettingsURL),
                      ash::NewWindowDelegate::OpenUrlFrom::kUserInteraction,
                      ash::NewWindowDelegate::Disposition::kSwitchToTab));
  base::Value::List empty_args;
  OpenBrowserNotificationSettings();
}

}  // namespace ash::settings