#include "chrome/browser/ui/safety_hub/menu_notification_service.h"
#include <ctime>
#include <memory>
#include <optional>
#include <string>
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/password_manager/password_manager_test_util.h"
#include "chrome/browser/permissions/notifications_engagement_service_factory.h"
#include "chrome/browser/ui/safety_hub/menu_notification.h"
#include "chrome/browser/ui/safety_hub/menu_notification_service_factory.h"
#include "chrome/browser/ui/safety_hub/notification_permission_review_service_factory.h"
#include "chrome/browser/ui/safety_hub/revoked_permissions_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_constants.h"
#include "chrome/browser/ui/safety_hub/safety_hub_prefs.h"
#include "chrome/browser/ui/safety_hub/safety_hub_test_util.h"
#include "chrome/browser/ui/safety_hub/unused_site_permissions_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/constants.h"
#include "components/permissions/pref_names.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/extensions/cws_info_service_factory.h"
#include "chrome/browser/password_manager/password_manager_test_util.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service.h"
#include "chrome/browser/ui/safety_hub/password_status_check_service_factory.h"
#include "chrome/browser/ui/safety_hub/safety_hub_hats_service.h"
#include "chrome/browser/ui/safety_hub/safety_hub_hats_service_factory.h"
#include "components/password_manager/core/browser/password_store/test_password_store.h"
#endif
using ::testing::_;
class SafetyHubMenuNotificationServiceTest
: public ChromeRenderViewHostTestHarness {
public:
SafetyHubMenuNotificationServiceTest()
: ChromeRenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
prefs()->SetBoolean(
safety_hub_prefs::kUnusedSitePermissionsRevocationEnabled, true);
safety_hub_test_util::CreateRevokedPermissionsService(profile());
safety_hub_test_util::CreateNotificationPermissionsReviewService(profile());
}
void TearDown() override {
RunUntilIdle();
ChromeRenderViewHostTestHarness::TearDown();
}
void RunUntilIdle() { task_environment()->RunUntilIdle(); }
protected:
void CreateMockNotificationPermissionEntry() {
const GURL url = GURL("https://example.com:443");
hcsm()->SetContentSettingDefaultScope(
url, GURL(), ContentSettingsType::NOTIFICATIONS, CONTENT_SETTING_ALLOW);
auto* notifications_engagement_service =
NotificationsEngagementServiceFactory::GetForProfile(profile());
notifications_engagement_service->RecordNotificationDisplayed(url, 7);
safety_hub_test_util::UpdateSafetyHubServiceAsync(
notification_permissions_service());
}
void CreateMockUnusedSitePermissionsEntry(const std::string url) {
auto dict = base::Value::Dict().Set(
permissions::kRevokedKey,
base::Value::List().Append(
UnusedSitePermissionsManager::ConvertContentSettingsTypeToKey(
ContentSettingsType::GEOLOCATION)));
hcsm()->SetWebsiteSettingDefaultScope(
GURL(url), GURL(url),
ContentSettingsType::REVOKED_UNUSED_SITE_PERMISSIONS,
base::Value(dict.Clone()));
safety_hub_test_util::UpdateSafetyHubServiceAsync(
revoked_permissions_service());
}
void ShowNotificationEnoughTimes(
int remainingImpressionCount =
kSafetyHubMenuNotificationMinImpressionCount) {
std::optional<MenuNotificationEntry> notification;
AdvanceClockBy(base::Days(90));
for (int i = 0; i < remainingImpressionCount; ++i) {
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
}
AdvanceClockBy(kSafetyHubMenuNotificationMinNotificationDuration);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
RevokedPermissionsService* revoked_permissions_service() {
return RevokedPermissionsServiceFactory::GetForProfile(profile());
}
NotificationPermissionsReviewService* notification_permissions_service() {
return NotificationPermissionsReviewServiceFactory::GetForProfile(
profile());
}
SafetyHubMenuNotificationService* menu_notification_service() {
return SafetyHubMenuNotificationServiceFactory::GetForProfile(profile());
}
sync_preferences::TestingPrefServiceSyncable* prefs() {
return profile()->GetTestingPrefService();
}
HostContentSettingsMap* hcsm() {
return HostContentSettingsMapFactory::GetForProfile(profile());
}
void AdvanceClockBy(base::TimeDelta delta) {
task_environment()->AdvanceClock(delta);
}
void ExpectPluralString(int string_id,
int count,
std::u16string notification_string) {
EXPECT_EQ(l10n_util::GetPluralStringFUTF16(string_id, count),
notification_string);
}
};
TEST_F(SafetyHubMenuNotificationServiceTest, GetNotificationToShowNoResult) {
std::optional<MenuNotificationEntry> notification =
menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, SingleNotificationToShow) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification =
menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification.value().label);
EXPECT_EQ(IDC_OPEN_SAFETY_HUB, notification.value().command);
}
TEST_F(SafetyHubMenuNotificationServiceTest, TwoNotificationsIncremental) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification;
for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) {
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
}
AdvanceClockBy(kSafetyHubMenuNotificationMinNotificationDuration);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
const base::TimeDelta kNotificationIntervalUnusedSitePermissions =
base::Days(10);
AdvanceClockBy(kNotificationIntervalUnusedSitePermissions);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
CreateMockUnusedSitePermissionsEntry("https://example2.com:443");
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, TwoNotificationsSequentially) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification;
for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) {
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
}
AdvanceClockBy(kSafetyHubMenuNotificationMinNotificationDuration);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
CreateMockNotificationPermissionEntry();
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, TwoNotificationsNoOverride) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification;
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
CreateMockNotificationPermissionEntry();
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount - 2; ++i) {
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
}
AdvanceClockBy(kSafetyHubMenuNotificationMinNotificationDuration);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVIEW_NOTIFICATION_PERMISSIONS_MENU_NOTIFICATION,
1, notification->label);
for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount - 1; ++i) {
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVIEW_NOTIFICATION_PERMISSIONS_MENU_NOTIFICATION,
1, notification->label);
}
AdvanceClockBy(kSafetyHubMenuNotificationMinNotificationDuration);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, SafeBrowsingOverride) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification;
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification->label);
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
AdvanceClockBy(base::Days(1));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
EXPECT_EQ(l10n_util::GetStringUTF16(
IDS_SETTINGS_SAFETY_HUB_SAFE_BROWSING_MENU_NOTIFICATION),
notification.value().label);
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, SafeBrowsingTriggerLogic) {
std::optional<MenuNotificationEntry> notification;
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
AdvanceClockBy(base::Hours(12));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
AdvanceClockBy(base::Hours(12));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ShowNotificationEnoughTimes(kSafetyHubMenuNotificationMinImpressionCount - 1);
AdvanceClockBy(base::Days(90));
ShowNotificationEnoughTimes();
AdvanceClockBy(base::Days(90));
ShowNotificationEnoughTimes();
AdvanceClockBy(base::Days(90));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, true);
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
AdvanceClockBy(base::Days(1));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
}
#if BUILDFLAG(IS_ANDROID)
TEST_F(SafetyHubMenuNotificationServiceTest, PasswordOverride) {
std::optional<MenuNotificationEntry> notification;
prefs()->SetBoolean(prefs::kSafeBrowsingEnabled, false);
notification = menu_notification_service()->GetNotificationToShow();
AdvanceClockBy(base::Days(1));
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 1);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_COMPROMISED_PASSWORDS_MENU_NOTIFICATION, 1,
notification.value().label);
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 0);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
TEST_F(SafetyHubMenuNotificationServiceTest, PasswordTrigger) {
std::optional<MenuNotificationEntry> notification;
prefs()->SetInteger(prefs::kBreachedCredentialsCount, -1);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 2);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_COMPROMISED_PASSWORDS_MENU_NOTIFICATION, 2,
notification.value().label);
menu_notification_service()->DismissActiveNotificationOfModule(
safety_hub::SafetyHubModuleType::PASSWORDS);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 1);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 3);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_COMPROMISED_PASSWORDS_MENU_NOTIFICATION, 3,
notification.value().label);
prefs()->SetInteger(prefs::kBreachedCredentialsCount, 0);
notification = menu_notification_service()->GetNotificationToShow();
EXPECT_FALSE(notification.has_value());
}
#endif
TEST_F(SafetyHubMenuNotificationServiceTest, DismissNotifications) {
CreateMockUnusedSitePermissionsEntry("https://example1.com:443");
std::optional<MenuNotificationEntry> notification =
menu_notification_service()->GetNotificationToShow();
EXPECT_TRUE(notification.has_value());
ExpectPluralString(
IDS_SETTINGS_SAFETY_HUB_REVOKED_PERMISSIONS_MENU_NOTIFICATION, 1,
notification.value().label);
menu_notification_service()->DismissActiveNotification();
EXPECT_FALSE(
menu_notification_service()->GetNotificationToShow().has_value());
}