chromium/chrome/browser/notifications/mac/notification_platform_bridge_mac_unittest.cc

// Copyright 2016 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/notifications/mac/notification_platform_bridge_mac.h"

#include "base/functional/bind.h"
#include "base/i18n/number_formatting.h"
#include "base/mac/mac_util.h"
#include "base/memory/raw_ptr.h"
#include "base/process/process_handle.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/notifications/mac/notification_utils.h"
#include "chrome/browser/notifications/mac/stub_notification_dispatcher_mac.h"
#include "chrome/browser/notifications/notification_test_util.h"
#include "chrome/browser/web_applications/os_integration/mac/app_shim_registry.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "components/webapps/common/web_app_id.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "url/gurl.h"

using message_center::Notification;

namespace {

class MockNotificationDispatcherMac : public StubNotificationDispatcherMac {
 public:
  MOCK_METHOD(void, CloseAllNotifications, (), (override));
};

}  // namespace

class NotificationPlatformBridgeMacTest : public testing::Test {
 public:
  NotificationPlatformBridgeMacTest()
      : profile_manager_(TestingBrowserProcess::GetGlobal()) {}

  void SetUp() override {
    ASSERT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile("Default");
  }

 protected:
  std::unique_ptr<Notification> CreateBanner(
      const char* title,
      const char* subtitle,
      const char* origin,
      const char* button1,
      const char* button2,
      const webapps::AppId& web_app_id = "") {
    return CreateNotification(title, subtitle, origin, button1, button2,
                              /*require_interaction=*/false,
                              /*show_settings_button=*/true, web_app_id);
  }

  std::unique_ptr<Notification> CreateAlert(
      const char* title,
      const char* subtitle,
      const char* origin,
      const char* button1,
      const char* button2,
      const webapps::AppId& web_app_id = "") {
    return CreateNotification(title, subtitle, origin, button1, button2,
                              /*require_interaction=*/true,
                              /*show_settings_button=*/true, web_app_id);
  }

  std::unique_ptr<Notification> CreateNotification(
      const char* title,
      const char* subtitle,
      const char* origin,
      const char* button1,
      const char* button2,
      bool require_interaction,
      bool show_settings_button,
      const webapps::AppId& web_app_id = "") {
    message_center::RichNotificationData optional_fields;
    if (button1) {
      optional_fields.buttons.push_back(
          message_center::ButtonInfo(base::UTF8ToUTF16(button1)));
      if (button2) {
        optional_fields.buttons.push_back(
            message_center::ButtonInfo(base::UTF8ToUTF16(button2)));
      }
    }
    if (show_settings_button) {
      optional_fields.settings_button_handler =
          message_center::SettingsButtonHandler::DELEGATE;
    }

    GURL url = GURL(origin);

    auto notification = std::make_unique<Notification>(
        message_center::NOTIFICATION_TYPE_SIMPLE, "id1",
        base::UTF8ToUTF16(title), base::UTF8ToUTF16(subtitle), ui::ImageModel(),
        u"Notifier's Name", url,
        message_center::NotifierId(
            url, /*title=*/std::nullopt,
            web_app_id.empty() ? std::nullopt : std::make_optional(web_app_id)),
        optional_fields, new message_center::NotificationDelegate());
    if (require_interaction)
      notification->set_never_timeout(true);

    return notification;
  }

  std::unique_ptr<NotificationDispatcherMac> CreateBannerDispatcher() {
    auto banner_dispatcher = std::make_unique<StubNotificationDispatcherMac>();
    banner_dispatcher_ = banner_dispatcher->AsWeakPtr();
    return banner_dispatcher;
  }

  std::unique_ptr<NotificationDispatcherMac> CreateAlertDispatcher() {
    auto alert_dispatcher = std::make_unique<StubNotificationDispatcherMac>();
    alert_dispatcher_ = alert_dispatcher->AsWeakPtr();
    return alert_dispatcher;
  }

  NotificationPlatformBridgeMac::WebAppDispatcherFactory
  CreateWebAppDispatcherFactory() {
    return base::BindLambdaForTesting(
        [&](const webapps::AppId& web_app_id)
            -> std::unique_ptr<NotificationDispatcherMac> {
          auto dispatcher = std::make_unique<StubNotificationDispatcherMac>();
          web_app_dispatchers_[web_app_id] = dispatcher->AsWeakPtr();
          return dispatcher;
        });
  }

  StubNotificationDispatcherMac* banner_dispatcher() {
    return banner_dispatcher_.get();
  }

  StubNotificationDispatcherMac* alert_dispatcher() {
    return alert_dispatcher_.get();
  }

  StubNotificationDispatcherMac* dispatcher_for_web_app(
      const webapps::AppId& web_app_id) {
    auto it = web_app_dispatchers_.find(web_app_id);
    if (it == web_app_dispatchers_.end()) {
      return nullptr;
    }
    return it->second.get();
  }

  TestingProfile* profile() { return profile_; }

 protected:
  content::BrowserTaskEnvironment task_environment_;
  TestingProfileManager profile_manager_;
  raw_ptr<TestingProfile> profile_ = nullptr;
  base::WeakPtr<StubNotificationDispatcherMac> banner_dispatcher_;
  base::WeakPtr<StubNotificationDispatcherMac> alert_dispatcher_;
  std::map<webapps::AppId, base::WeakPtr<StubNotificationDispatcherMac>>
      web_app_dispatchers_;
};

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayNoButtons) {
  std::unique_ptr<Notification> notification =
      CreateBanner("Title", "Context", "https://gmail.com", nullptr, nullptr);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  const auto& notifications = banner_dispatcher()->notifications();

  EXPECT_EQ(1u, notifications.size());

  const auto& delivered_notification = notifications[0];
  EXPECT_EQ(u"Title", delivered_notification->title);
  EXPECT_EQ(u"Context", delivered_notification->body);
  EXPECT_EQ(u"gmail.com", delivered_notification->subtitle);
  EXPECT_TRUE(delivered_notification->buttons.empty());
  EXPECT_TRUE(delivered_notification->show_settings_button);
}

TEST_F(NotificationPlatformBridgeMacTest, TestIncognitoProfile) {
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  std::unique_ptr<Notification> notification =
      CreateBanner("Title", "Context", "https://gmail.com", nullptr, nullptr);

  TestingProfile::Builder profile_builder;
  profile_builder.SetPath(profile()->GetPath());
  profile_builder.SetProfileName(profile()->GetProfileUserName());
  Profile* incogito_profile = profile_builder.BuildIncognito(profile());

  // Show two notifications with the same id from different profiles.
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, /*metadata=*/nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, incogito_profile,
                  *notification, /*metadata=*/nullptr);
  EXPECT_EQ(2u, banner_dispatcher()->notifications().size());

  // Close the one for the incognito profile.
  bridge->Close(incogito_profile, "id1");
  const auto& notifications = banner_dispatcher()->notifications();
  ASSERT_EQ(1u, notifications.size());

  // Expect that the remaining notification is for the regular profile.
  const auto& remaining_notification = notifications[0];
  EXPECT_EQ(false, remaining_notification->meta->id->profile->incognito);

  // Close the one for the regular profile.
  bridge->Close(profile(), "id1");
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayNoSettings) {
  std::unique_ptr<Notification> notification = CreateNotification(
      "Title", "Context", "https://gmail.com", nullptr, nullptr,
      /*require_interaction=*/false, /*show_settings_button=*/false);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  const auto& notifications = banner_dispatcher()->notifications();

  EXPECT_EQ(1u, notifications.size());

  const auto& delivered_notification = notifications[0];
  EXPECT_EQ(u"Title", delivered_notification->title);
  EXPECT_EQ(u"Context", delivered_notification->body);
  EXPECT_EQ(u"gmail.com", delivered_notification->subtitle);
  EXPECT_TRUE(delivered_notification->buttons.empty());
  EXPECT_FALSE(delivered_notification->show_settings_button);
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayOneButton) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);

  const auto& notifications = banner_dispatcher()->notifications();
  EXPECT_EQ(1u, notifications.size());
  const auto& delivered_notification = notifications[0];
  EXPECT_EQ(u"Title", delivered_notification->title);
  EXPECT_EQ(u"Context", delivered_notification->body);
  EXPECT_EQ(u"gmail.com", delivered_notification->subtitle);
  EXPECT_EQ(1u, delivered_notification->buttons.size());
  EXPECT_TRUE(delivered_notification->show_settings_button);
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayProgress) {
  std::unique_ptr<Notification> notification =
      CreateBanner("Title", "Context", "https://gmail.com", nullptr, nullptr);
  const int kSamplePercent = 10;

  notification->set_progress(kSamplePercent);
  notification->set_type(message_center::NOTIFICATION_TYPE_PROGRESS);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);

  // Progress notifications are considered alerts
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
  const auto& displayed_alerts = alert_dispatcher()->notifications();
  ASSERT_EQ(1u, displayed_alerts.size());

  const auto& delivered_notification = displayed_alerts[0];
  std::u16string expected = base::FormatPercent(kSamplePercent) + u" - Title";
  EXPECT_EQ(expected, delivered_notification->title);
}

TEST_F(NotificationPlatformBridgeMacTest,
       TestDisplayUpdatesExistingNotification) {
  std::unique_ptr<Notification> notification = CreateNotification(
      "Title", "Context", "https://gmail.com", nullptr, nullptr,
      /*require_interaction=*/false, /*show_settings_button=*/false);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);

  {
    const auto& notifications = banner_dispatcher()->notifications();
    ASSERT_EQ(1u, notifications.size());
    EXPECT_TRUE(alert_dispatcher()->notifications().empty());
    const auto& delivered_notification = notifications[0];
    EXPECT_EQ(u"Title", delivered_notification->title);
  }

  notification = CreateNotification(
      "New Title", "Context", "https://gmail.com", nullptr, nullptr,
      /*require_interaction=*/false, /*show_settings_button=*/false);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  {
    const auto& notifications = banner_dispatcher()->notifications();
    ASSERT_EQ(1u, notifications.size());
    EXPECT_TRUE(alert_dispatcher()->notifications().empty());
    const auto& delivered_notification = notifications[0];
    EXPECT_EQ(u"New Title", delivered_notification->title);
  }

  notification = CreateNotification(
      "New Title", "Context", "https://gmail.com", nullptr, nullptr,
      /*require_interaction=*/true, /*show_settings_button=*/false);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  {
    const auto& notifications = alert_dispatcher()->notifications();
    ASSERT_EQ(1u, notifications.size());
    EXPECT_TRUE(banner_dispatcher()->notifications().empty());
    const auto& delivered_notification = notifications[0];
    EXPECT_EQ(u"New Title", delivered_notification->title);
  }
}

TEST_F(NotificationPlatformBridgeMacTest, TestCloseNotification) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  EXPECT_EQ(1u, banner_dispatcher()->notifications().size());

  bridge->Close(profile(), "id1");
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTest, TestGetDisplayed) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, nullptr);
  EXPECT_EQ(1u, banner_dispatcher()->notifications().size());

  {
    base::test::TestFuture<std::set<std::string>, bool> notifications;
    bridge->GetDisplayed(profile(), notifications.GetCallback());
    EXPECT_EQ(1u, notifications.Get<0>().size());
  }
  {
    base::test::TestFuture<std::set<std::string>, bool> notifications;
    bridge->GetDisplayedForOrigin(profile(), GURL("https://gmail.com"),
                                  notifications.GetCallback());
    EXPECT_EQ(1u, notifications.Get<0>().size());
  }
  {
    base::test::TestFuture<std::set<std::string>, bool> notifications;
    bridge->GetDisplayedForOrigin(profile(), GURL("https://example.com"),
                                  notifications.GetCallback());
    EXPECT_TRUE(notifications.Get<0>().empty());
  }
}

TEST_F(NotificationPlatformBridgeMacTest, TestQuitRemovesNotifications) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);
  {
    auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
        CreateBannerDispatcher(), CreateAlertDispatcher(),
        CreateWebAppDispatcherFactory());
    EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
    bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                    *notification, nullptr);
    EXPECT_EQ(1u, banner_dispatcher()->notifications().size());
  }
}

TEST_F(NotificationPlatformBridgeMacTest,
       TestProfileShutdownRemovesNotifications) {
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());

  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", "Button 2");

  TestingProfile::Builder profile_builder;
  profile_builder.SetPath(profile()->GetPath());
  profile_builder.SetProfileName(profile()->GetProfileUserName());
  Profile* incognito_profile = profile_builder.BuildIncognito(profile());

  // Show two notifications with the same id from different profiles.
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  *notification, /*metadata=*/nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, incognito_profile,
                  *notification, /*metadata=*/nullptr);
  EXPECT_EQ(2u, banner_dispatcher()->notifications().size());

  // Start shutdown of the incognito profile.
  bridge->DisplayServiceShutDown(incognito_profile);

  // Expect all notifications for that profile to be closed.
  const auto& notifications = banner_dispatcher()->notifications();
  ASSERT_EQ(1u, notifications.size());
  const auto& remaining_notification = notifications[0];
  EXPECT_EQ(false, remaining_notification->meta->id->profile->incognito);
}

// Regression test for crbug.com/1182795
TEST_F(NotificationPlatformBridgeMacTest, TestNullProfileShutdown) {
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  // Emulate shutdown of the null profile.
  bridge->DisplayServiceShutDown(/*profile=*/nullptr);
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayAlert) {
  std::unique_ptr<Notification> alert =
      CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(), *alert,
                  nullptr);
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
  EXPECT_EQ(1u, alert_dispatcher()->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayBannerAndAlert) {
  std::unique_ptr<Notification> alert =
      CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr);
  std::unique_ptr<Notification> banner = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id2", *alert), nullptr);
  EXPECT_EQ(1u, banner_dispatcher()->notifications().size());
  EXPECT_EQ(1u, alert_dispatcher()->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTest, TestCloseAlert) {
  std::unique_ptr<Notification> alert =
      CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  EXPECT_EQ(0u, alert_dispatcher()->notifications().size());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(), *alert,
                  nullptr);
  EXPECT_EQ(1u, alert_dispatcher()->notifications().size());

  bridge->Close(profile(), "id1");
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTest, TestQuitRemovesBannersAndAlerts) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", nullptr);
  std::unique_ptr<Notification> alert =
      CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr);

  auto banner_dispatcher = std::make_unique<MockNotificationDispatcherMac>();
  MockNotificationDispatcherMac* banner_dispatcher_ptr =
      banner_dispatcher.get();
  auto alert_dispatcher = std::make_unique<MockNotificationDispatcherMac>();
  MockNotificationDispatcherMac* alert_dispatcher_ptr = alert_dispatcher.get();
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      std::move(banner_dispatcher), std::move(alert_dispatcher),
      CreateWebAppDispatcherFactory());

  EXPECT_EQ(0u, banner_dispatcher_ptr->notifications().size());
  EXPECT_EQ(0u, alert_dispatcher_ptr->notifications().size());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *notification), nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id2", *alert), nullptr);
  EXPECT_EQ(1u, banner_dispatcher_ptr->notifications().size());
  EXPECT_EQ(1u, alert_dispatcher_ptr->notifications().size());

  // Destructing the bridge should close all alerts and banners.
  EXPECT_CALL(*banner_dispatcher_ptr, CloseAllNotifications());
  EXPECT_CALL(*alert_dispatcher_ptr, CloseAllNotifications());
  bridge.reset();
}

TEST_F(NotificationPlatformBridgeMacTest, TestDisplayETLDPlusOne) {
  std::unique_ptr<Notification> notification = CreateBanner(
      "Title", "Context", "https://overthelimit.hello.world.test.co.uk",
      "Button 1", nullptr);

  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *notification), nullptr);

  notification = CreateBanner("Title", "Context", "https://mail.appspot.com",
                              "Button 1", nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id2", *notification), nullptr);

  notification = CreateBanner("Title", "Context", "https://tests.peter.sh",
                              "Button 1", nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id3", *notification), nullptr);

  notification = CreateBanner(
      "Title", "Context",
      "https://somereallylongsubdomainthatactuallyisanaliasfortests.peter.sh/",
      "Button 1", nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id4", *notification), nullptr);

  notification = CreateBanner("Title", "Context", "http://localhost:8080",
                              "Button 1", nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id5", *notification), nullptr);

  notification = CreateBanner("Title", "Context", "https://93.186.186.172",
                              "Button 1", nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id6", *notification), nullptr);

  const auto& notifications = banner_dispatcher()->notifications();
  EXPECT_EQ(6u, notifications.size());
  EXPECT_EQ(u"test.co.uk", notifications[0]->subtitle);
  EXPECT_EQ(u"mail.appspot.com", notifications[1]->subtitle);
  EXPECT_EQ(u"tests.peter.sh", notifications[2]->subtitle);
  EXPECT_EQ(u"peter.sh", notifications[3]->subtitle);
  EXPECT_EQ(u"localhost:8080", notifications[4]->subtitle);
  EXPECT_EQ(u"93.186.186.172", notifications[5]->subtitle);
}

class NotificationPlatformBridgeMacTestWithNotificationAttribution
    : public NotificationPlatformBridgeMacTest {
 public:
  void SetUp() override {
    NotificationPlatformBridgeMacTest::SetUp();
    web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());
    installed_app_id_ = web_app::test::InstallDummyWebApp(
        profile(), "Web App Name", GURL("https://gmail.com"));
    // TODO(https://crbug.com/328437955): Remove this when OS integration
    // happens safely by default.
    AppShimRegistry::Get()->OnAppInstalledForProfile(installed_app_id_,
                                                     profile()->GetPath());
  }

 protected:
  webapps::AppId installed_app_id_;

 private:
  base::test::ScopedFeatureList scoped_feature_list_{
      features::kAppShimNotificationAttribution};
};

TEST_F(NotificationPlatformBridgeMacTestWithNotificationAttribution,
       BannersAndAlertsAreAttributed) {
  std::unique_ptr<Notification> alert =
      CreateAlert("Title", "Context", "https://gmail.com", "Button 1", nullptr,
                  installed_app_id_);
  std::unique_ptr<Notification> banner =
      CreateBanner("Title", "Context", "https://gmail.com", "Button 1", nullptr,
                   installed_app_id_);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id2", *alert), nullptr);
  EXPECT_EQ(0u, banner_dispatcher()->notifications().size());
  EXPECT_EQ(0u, alert_dispatcher()->notifications().size());
  auto* app_dispatcher = dispatcher_for_web_app(installed_app_id_);
  ASSERT_TRUE(app_dispatcher);
  EXPECT_EQ(2u, app_dispatcher->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTestWithNotificationAttribution,
       CloseNotificationInWebApp) {
  std::unique_ptr<Notification> banner =
      CreateBanner("Title", "Context", "https://gmail.com", "Button 1", nullptr,
                   installed_app_id_);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);
  auto* app_dispatcher = dispatcher_for_web_app(installed_app_id_);
  ASSERT_TRUE(app_dispatcher);
  EXPECT_EQ(1u, app_dispatcher->notifications().size());

  bridge->Close(profile(), "notification_id1");
  EXPECT_EQ(0u, app_dispatcher->notifications().size());
}

TEST_F(NotificationPlatformBridgeMacTestWithNotificationAttribution,
       DisplayMovesNotificationToWebApp) {
  std::unique_ptr<Notification> banner = CreateBanner(
      "Title", "Context", "https://gmail.com", "Button 1", /*button2=*/nullptr,
      /*web_app_id=*/"");
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);

  EXPECT_FALSE(dispatcher_for_web_app(installed_app_id_));
  EXPECT_TRUE(alert_dispatcher()->notifications().empty());
  EXPECT_EQ(1u, banner_dispatcher()->notifications().size());

  banner = CreateBanner("Title", "Context", "https://gmail.com", "Button 1",
                        /*button2=*/nullptr, installed_app_id_);
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);

  auto* app_dispatcher = dispatcher_for_web_app(installed_app_id_);
  ASSERT_TRUE(app_dispatcher);
  EXPECT_EQ(1u, app_dispatcher->notifications().size());
  EXPECT_TRUE(alert_dispatcher()->notifications().empty());
  EXPECT_TRUE(banner_dispatcher()->notifications().empty());
}

TEST_F(NotificationPlatformBridgeMacTestWithNotificationAttribution,
       GetDisplayed) {
  std::unique_ptr<Notification> banner =
      CreateBanner("Title", "Context", "https://gmail.com", "Button 1", nullptr,
                   installed_app_id_);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  bridge->Display(NotificationHandler::Type::WEB_PERSISTENT, profile(),
                  Notification("notification_id1", *banner), nullptr);
  auto* app_dispatcher = dispatcher_for_web_app(installed_app_id_);
  ASSERT_TRUE(app_dispatcher);
  EXPECT_EQ(1u, app_dispatcher->notifications().size());

  {
    base::test::TestFuture<std::set<std::string>, bool> future;
    bridge->GetDisplayed(profile(), future.GetCallback());
    auto [notifications, supports_synchronization] = future.Get();
    EXPECT_TRUE(notifications.empty());
    EXPECT_FALSE(supports_synchronization);
  }
  {
    base::test::TestFuture<std::set<std::string>, bool> future;
    bridge->GetDisplayedForOrigin(profile(), GURL("https://gmail.com"),
                                  future.GetCallback());
    auto [notifications, supports_synchronization] = future.Get();
    EXPECT_EQ(1u, notifications.size());
    EXPECT_TRUE(supports_synchronization);
  }
  {
    base::test::TestFuture<std::set<std::string>, bool> future;
    bridge->GetDisplayedForOrigin(profile(), GURL("https://example.com"),
                                  future.GetCallback());

    auto [notifications, supports_synchronization] = future.Get();
    EXPECT_TRUE(notifications.empty());
    EXPECT_TRUE(supports_synchronization);
  }
}

TEST_F(NotificationPlatformBridgeMacTestWithNotificationAttribution,
       GetDisplayedWithoutExistingDispatcher) {
  std::unique_ptr<Notification> banner =
      CreateBanner("Title", "Context", "https://gmail.com", "Button 1", nullptr,
                   installed_app_id_);
  auto bridge = std::make_unique<NotificationPlatformBridgeMac>(
      CreateBannerDispatcher(), CreateAlertDispatcher(),
      CreateWebAppDispatcherFactory());
  EXPECT_FALSE(dispatcher_for_web_app(installed_app_id_));

  {
    base::test::TestFuture<std::set<std::string>, bool> future;
    bridge->GetDisplayedForOrigin(profile(), GURL("https://gmail.com"),
                                  future.GetCallback());
    auto [notifications, supports_synchronization] = future.Get();
    EXPECT_EQ(0u, notifications.size());
    EXPECT_TRUE(supports_synchronization);
  }

  EXPECT_TRUE(dispatcher_for_web_app(installed_app_id_));
}