| // Copyright 2023 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/safety_hub/menu_notification.h" |
| |
| #include <memory> |
| |
| #include "base/json/values_util.h" |
| #include "base/time/time.h" |
| #include "base/values.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/ui/safety_hub/revoked_permissions_service.h" |
| #include "chrome/browser/ui/safety_hub/safety_hub_constants.h" |
| #include "chrome/browser/ui/safety_hub/safety_hub_service.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/content_settings/core/browser/content_settings_registry.h" |
| #include "components/content_settings/core/common/content_settings_constraints.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| constexpr char kUrl1[] = "https://example1.com:443"; |
| const base::TimeDelta kLifetime = base::Days(60); |
| const base::Time kPastTime = base::Time::Now() - kLifetime; |
| |
| // TODO(crbug.com/40267370): Use a mock result instead. |
| std::unique_ptr<RevokedPermissionsResult> CreateRevokedPermissionsResult( |
| base::Value::List urls) { |
| auto result = std::make_unique<RevokedPermissionsResult>(); |
| PermissionsData permissions_data; |
| for (base::Value& url_val : urls) { |
| permissions_data.primary_pattern = |
| ContentSettingsPattern::FromString(url_val.GetString()); |
| permissions_data.permission_types = {ContentSettingsType::GEOLOCATION}; |
| permissions_data.constraints = |
| content_settings::ContentSettingConstraints(kPastTime); |
| permissions_data.constraints.set_lifetime(kLifetime); |
| result->AddRevokedPermission(permissions_data); |
| } |
| return result; |
| } |
| } // namespace |
| |
| class SafetyHubMenuNotificationTest : public testing::Test { |
| public: |
| SafetyHubMenuNotificationTest() { |
| testing_profile_manager_ = std::make_unique<TestingProfileManager>( |
| TestingBrowserProcess::GetGlobal()); |
| EXPECT_TRUE(testing_profile_manager_->SetUp()); |
| profile_ = |
| testing_profile_manager_->CreateTestingProfile("user@example.com"); |
| EXPECT_TRUE(profile_); |
| } |
| ~SafetyHubMenuNotificationTest() override = default; |
| |
| void SetUp() override { |
| // Content settings registry needs to be reset to ensure that it has loaded |
| // the correct permission types. |
| auto* registry = content_settings::ContentSettingsRegistry::GetInstance(); |
| registry->ResetForTest(); |
| service_ = std::make_unique<RevokedPermissionsService>( |
| profile(), profile()->GetPrefs()); |
| } |
| |
| protected: |
| void FastForwardBy(base::TimeDelta delta) { |
| task_environment_.FastForwardBy(delta); |
| } |
| |
| RevokedPermissionsService* service() { return service_.get(); } |
| TestingProfile* profile() { return profile_.get(); } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| std::unique_ptr<TestingProfileManager> testing_profile_manager_; |
| raw_ptr<TestingProfile> profile_ = nullptr; |
| std::unique_ptr<RevokedPermissionsService> service_; |
| }; |
| |
| // TODO(crbug.com/364523673): This test is flaking on android pie builder. |
| TEST_F(SafetyHubMenuNotificationTest, DISABLED_ToFromDictValue) { |
| // Creating a mock menu notification. |
| base::Time last = kPastTime + base::Days(30); |
| auto notification = std::make_unique<SafetyHubMenuNotification>( |
| safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS); |
| notification->is_currently_active_ = true; |
| notification->impression_count_ = 42; |
| notification->first_impression_time_ = kPastTime; |
| notification->last_impression_time_ = last; |
| notification->current_result_ = |
| CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1)); |
| |
| // When transforming the notification to a Dict, the properties of the |
| // notification should be correct. |
| base::Value::Dict dict = notification->ToDictValue(); |
| EXPECT_TRUE( |
| dict.FindBool(safety_hub::kSafetyHubMenuNotificationActiveKey).value()); |
| EXPECT_EQ(42, dict.FindInt( |
| safety_hub::kSafetyHubMenuNotificationImpressionCountKey)); |
| EXPECT_EQ( |
| base::TimeToValue(kPastTime), |
| *dict.Find(safety_hub::kSafetyHubMenuNotificationFirstImpressionKey)); |
| EXPECT_EQ( |
| base::TimeToValue(last), |
| *dict.Find(safety_hub::kSafetyHubMenuNotificationLastImpressionKey)); |
| EXPECT_TRUE(dict.contains(safety_hub::kSafetyHubMenuNotificationResultKey)); |
| // The properties of the contained result should also be correct. |
| auto* result_dict = |
| dict.FindDict(safety_hub::kSafetyHubMenuNotificationResultKey); |
| EXPECT_TRUE(result_dict->contains(kRevokedPermissionsResultKey)); |
| EXPECT_EQ(1U, result_dict->FindList(kRevokedPermissionsResultKey)->size()); |
| base::Value::List& revoked_origins = |
| *result_dict->FindList(kRevokedPermissionsResultKey); |
| EXPECT_EQ(kUrl1, revoked_origins.front()); |
| |
| // Using the dict from before, we can create another menu notification object |
| // that should have the same properties as when it was initially created. |
| auto new_notification = std::make_unique<SafetyHubMenuNotification>( |
| std::move(dict), |
| safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS); |
| EXPECT_TRUE(new_notification->is_currently_active_); |
| EXPECT_EQ(42, new_notification->impression_count_); |
| EXPECT_EQ(kPastTime, new_notification->first_impression_time_); |
| EXPECT_EQ(last, new_notification->last_impression_time_); |
| EXPECT_FALSE(new_notification->prev_stored_result_.empty()); |
| EXPECT_EQ(kUrl1, new_notification->prev_stored_result_ |
| .FindList(kRevokedPermissionsResultKey) |
| ->front() |
| .GetString()); |
| } |
| |
| // TODO(crbug.com/364523673): This test is flaking on android pie builder. |
| TEST_F(SafetyHubMenuNotificationTest, DISABLED_ShouldBeShown) { |
| base::TimeDelta interval = base::Days(30); |
| auto notification = std::make_unique<SafetyHubMenuNotification>( |
| safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS); |
| |
| // No result associated with notification, so should not be shown. |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // The notification is updated with a new result that is a trigger. The |
| // notification has never been shown, so should be shown. |
| std::unique_ptr<RevokedPermissionsResult> result = |
| CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1)); |
| notification->UpdateResult(std::move(result)); |
| ASSERT_TRUE(notification->ShouldBeShown(interval)); |
| |
| // Showing the notification once should make it still be shown after. |
| notification->Show(); |
| ASSERT_TRUE(notification->ShouldBeShown(interval)); |
| |
| // Showing the notification the minimum number of times, but not the minimum |
| // period of time, should make it still be shown. |
| for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) { |
| notification->Show(); |
| FastForwardBy(base::Hours(1)); |
| } |
| ASSERT_TRUE(notification->ShouldBeShown(interval)); |
| |
| // Moving past the minimum duration should now make the notification no longer |
| // be shown. |
| FastForwardBy(kSafetyHubMenuNotificationMinNotificationDuration); |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // The notification can be dismissed now, and it should still not be shown. |
| notification->Dismiss(); |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // If the result does not change, the notification should not be shown. |
| FastForwardBy(interval); |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // When updating to a similar result, the notification should still not be |
| // shown. |
| std::unique_ptr<RevokedPermissionsResult> similar_result = |
| CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1)); |
| notification->UpdateResult(std::move(similar_result)); |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // When updating to a new result, the notification should be shown. |
| std::unique_ptr<RevokedPermissionsResult> new_result = |
| CreateRevokedPermissionsResult( |
| base::Value::List().Append("https://other.com:443")); |
| notification->UpdateResult(std::move(new_result)); |
| ASSERT_TRUE(notification->ShouldBeShown(interval)); |
| |
| // Notification should still be shown after showing once. |
| notification->Show(); |
| ASSERT_TRUE(notification->ShouldBeShown(interval)); |
| |
| // Dimissing an active notification should make it not be shown. Note: showing |
| // notification once is a prerequisite for dismissing it. |
| notification->Dismiss(); |
| ASSERT_FALSE(notification->ShouldBeShown(interval)); |
| |
| // Create new notification and new associated result to test passing time but |
| // not the count. |
| result = CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1)); |
| auto other_notification = std::make_unique<SafetyHubMenuNotification>( |
| safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS); |
| other_notification->UpdateResult(std::move(result)); |
| other_notification->Show(); |
| |
| // Go past the minimum duration that the notification should be shown. It |
| // should still be shown because it was only shown once. |
| FastForwardBy(kSafetyHubMenuNotificationMinNotificationDuration + |
| base::Days(2)); |
| ASSERT_TRUE(other_notification->ShouldBeShown(interval)); |
| |
| // The notification should no longer be shown if it has already been shown a |
| // sufficient number of times. |
| for (int i = 0; i < kSafetyHubMenuNotificationMinImpressionCount; ++i) { |
| other_notification->Show(); |
| FastForwardBy(base::Hours(1)); |
| } |
| ASSERT_FALSE(other_notification->ShouldBeShown(interval)); |
| } |
| |
| // TODO(crbug.com/364523673): This test is flaking on android pie builder. |
| TEST_F(SafetyHubMenuNotificationTest, DISABLED_IsCurrentlyActive) { |
| auto notification = std::make_unique<SafetyHubMenuNotification>( |
| safety_hub::SafetyHubModuleType::UNUSED_SITE_PERMISSIONS); |
| |
| // A notification is not active when it new or when it has not been shown yet. |
| ASSERT_FALSE(notification->IsCurrentlyActive()); |
| std::unique_ptr<RevokedPermissionsResult> result = |
| CreateRevokedPermissionsResult(base::Value::List().Append(kUrl1)); |
| notification->UpdateResult(std::move(result)); |
| ASSERT_FALSE(notification->IsCurrentlyActive()); |
| |
| // Showing a notification makes it active. |
| notification->Show(); |
| ASSERT_TRUE(notification->IsCurrentlyActive()); |
| |
| // Dismissing the nofication makes it not active any more. |
| notification->Dismiss(); |
| ASSERT_FALSE(notification->IsCurrentlyActive()); |
| } |
| |
| // TODO(crbug.com/40267370): Add tests for other types of Safety Hub services |
| // and Safety Hub results. |