| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <array> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/permissions/notifications_engagement_service_factory.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/site_engagement/content/site_engagement_service.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace permissions { |
| constexpr char kEngagementKey[] = "click_count"; |
| constexpr char kDisplayedKey[] = "display_count"; |
| |
| class NotificationsEngagementServiceTest : public testing::Test { |
| public: |
| NotificationsEngagementServiceTest() |
| : profile_(std::make_unique<TestingProfile>()) {} |
| |
| void SetUp() override; |
| |
| NotificationsEngagementServiceTest( |
| const NotificationsEngagementServiceTest&) = delete; |
| NotificationsEngagementServiceTest& operator=( |
| const NotificationsEngagementServiceTest&) = delete; |
| |
| protected: |
| NotificationsEngagementService* service() { |
| return NotificationsEngagementServiceFactory::GetForProfile(profile()); |
| } |
| |
| TestingProfile* profile() { return profile_.get(); } |
| |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| private: |
| std::unique_ptr<TestingProfile> profile_; |
| }; |
| |
| void NotificationsEngagementServiceTest::SetUp() { |
| testing::Test::SetUp(); |
| } |
| |
| TEST_F(NotificationsEngagementServiceTest, |
| NotificationsEngagementContentSetting) { |
| auto hosts = std::to_array<GURL>( |
| {GURL("https://google.com/"), GURL("https://www.youtube.com/")}); |
| |
| // Otherwise the test time and the service time will be out of sync and cause |
| // the tests to fail. |
| task_environment_.FastForwardBy(base::Days(2)); |
| |
| // Record initial display date to enable comparing dictionaries. |
| std::string displayedDate = service()->GetBucketLabel(base::Time::Now()); |
| |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| // Testing the dictionary of URLS |
| ContentSettingsForOneType notifications_engagement_setting = |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_EQ(0U, notifications_engagement_setting.size()); |
| |
| // Test that a new Dict entry is added when no entry existed for the given |
| // URL. |
| service()->RecordNotificationDisplayed(hosts[0]); |
| service()->RecordNotificationDisplayed(hosts[0]); |
| notifications_engagement_setting = |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_EQ(1U, notifications_engagement_setting.size()); |
| |
| // Advance time to set same URL entries in different dates. |
| task_environment_.FastForwardBy(base::Days(8)); |
| std::string displayedDateLater = service()->GetBucketLabel(base::Time::Now()); |
| |
| // Test that the same URL entry is not duplicated. |
| service()->RecordNotificationDisplayed(hosts[0]); |
| service()->RecordNotificationInteraction(hosts[0]); |
| notifications_engagement_setting = |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_EQ(1U, notifications_engagement_setting.size()); |
| |
| // Test that different entries are created for different URL. |
| service()->RecordNotificationDisplayed(hosts[1]); |
| notifications_engagement_setting = |
| host_content_settings_map->GetSettingsForOneType( |
| ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_EQ(2U, notifications_engagement_setting.size()); |
| |
| // Verify the contents of the |notifications_engagement_setting| for |
| // hosts[0]. |
| base::Value::Dict engagement_dict1; |
| base::Value::Dict dict1_entry1; |
| dict1_entry1.Set(kDisplayedKey, 2); |
| base::Value::Dict dict1_entry2; |
| dict1_entry2.Set(kDisplayedKey, 1); |
| dict1_entry2.Set(kEngagementKey, 1); |
| engagement_dict1.Set(displayedDate, std::move(dict1_entry1)); |
| engagement_dict1.Set(displayedDateLater, std::move(dict1_entry2)); |
| base::Value engagement_dict_value1 = base::Value(std::move(engagement_dict1)); |
| |
| ASSERT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[0]), |
| notifications_engagement_setting.at(0).primary_pattern); |
| ASSERT_EQ(ContentSettingsPattern::Wildcard(), |
| notifications_engagement_setting.at(0).secondary_pattern); |
| ASSERT_EQ(engagement_dict_value1, |
| notifications_engagement_setting.at(0).setting_value); |
| |
| // Verify the contents of the |notifications_engagement_setting| for |
| // hosts[1]. |
| base::Value::Dict engagement_dict2; |
| base::Value::Dict dict2_entry1; |
| dict2_entry1.Set(kDisplayedKey, 1); |
| engagement_dict2.Set(displayedDateLater, std::move(dict2_entry1)); |
| base::Value engagement_dict_value2 = base::Value(std::move(engagement_dict2)); |
| |
| ASSERT_EQ(ContentSettingsPattern::FromURLNoWildcard(hosts[1]), |
| notifications_engagement_setting.at(1).primary_pattern); |
| ASSERT_EQ(ContentSettingsPattern::Wildcard(), |
| notifications_engagement_setting.at(1).secondary_pattern); |
| ASSERT_EQ(engagement_dict_value2, |
| notifications_engagement_setting.at(1).setting_value); |
| } |
| |
| TEST_F(NotificationsEngagementServiceTest, |
| RecordNotificationDisplayedAndInteraction) { |
| GURL url1("https://www.google.com/"); |
| GURL url2("https://www.youtube.com/"); |
| GURL url3("https://www.permissions.site.com/"); |
| |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| // Test notifications sent within the same day constitute one date entry. |
| |
| task_environment_.FastForwardBy(base::Days(2)); |
| service()->RecordNotificationDisplayed(url1); |
| service()->RecordNotificationDisplayed(url1); |
| |
| base::Value website_engagement_value1 = |
| host_content_settings_map->GetWebsiteSetting( |
| url1, GURL(), ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_TRUE(website_engagement_value1.is_dict()); |
| base::Value::Dict& website_engagement_dict1 = |
| website_engagement_value1.GetDict(); |
| |
| ASSERT_EQ(1U, website_engagement_dict1.size()); |
| |
| // Test notifications sent in different days constitute different entries. |
| service()->RecordNotificationDisplayed(url2); |
| |
| task_environment_.FastForwardBy(base::Days(14)); |
| service()->RecordNotificationDisplayed(url2); |
| |
| task_environment_.FastForwardBy(base::Days(7)); |
| service()->RecordNotificationDisplayed(url2); |
| |
| base::Value website_engagement_value2 = |
| host_content_settings_map->GetWebsiteSetting( |
| url2, GURL(), ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_TRUE(website_engagement_value2.is_dict()); |
| base::Value::Dict& website_engagement_dict2 = |
| website_engagement_value2.GetDict(); |
| |
| ASSERT_EQ(3U, website_engagement_dict2.size()); |
| |
| // Test that display_count and click_count are incremented correctly. |
| service()->RecordNotificationDisplayed(url3); |
| service()->RecordNotificationDisplayed(url3); |
| |
| service()->RecordNotificationInteraction(url3); |
| service()->RecordNotificationInteraction(url3); |
| service()->RecordNotificationInteraction(url3); |
| |
| base::Value website_engagement_value3 = |
| host_content_settings_map->GetWebsiteSetting( |
| url3, GURL(), ContentSettingsType::NOTIFICATION_INTERACTIONS); |
| ASSERT_TRUE(website_engagement_value3.is_dict()); |
| base::Value::Dict& website_engagement_dict3 = |
| website_engagement_value3.GetDict(); |
| |
| std::string displayedDate = service()->GetBucketLabel(base::Time::Now()); |
| base::Value* entryForDate = website_engagement_dict3.Find(displayedDate); |
| |
| ASSERT_EQ(2, entryForDate->GetDict().FindInt(kDisplayedKey).value()); |
| ASSERT_EQ(3, entryForDate->GetDict().FindInt(kEngagementKey).value()); |
| } |
| |
| TEST_F(NotificationsEngagementServiceTest, |
| RecordNotificationDisplayedAndInteractionReportsUMA) { |
| GURL url1("https://url1.test/"); |
| GURL url2("https://url2.test/"); |
| GURL url3("https://url3.test/"); |
| GURL url4("https://url4.test/"); |
| GURL url5("https://url5.test/"); |
| GURL url6("https://url6.test/"); |
| GURL url7("https://url7.test/"); |
| GURL url8("https://url8.test/"); |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url1); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume0", 0, 1); |
| } |
| |
| { |
| site_engagement::SiteEngagementService* site_engagement_service = |
| site_engagement::SiteEngagementService::Get(profile()); |
| site_engagement_service->AddPointsForTesting(url2, 4); |
| |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url2); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume0", |
| site_engagement_service->GetScore(url2) * 2, 1); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url3, 7); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume1", 0, 7); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url4, 5 * 7); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume5", 0, 5 * 7); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url5, 10 * 7); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume10", 0, 10 * 7); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url6, 20 * 7); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.Volume20", 0, 20 * 7); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationDisplayed(url7, 30 * 7); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Displayed.VolumeAbove20", 0, 30 * 7); |
| } |
| |
| { |
| base::HistogramTester histogram_tester; |
| service()->RecordNotificationInteraction(url8); |
| histogram_tester.ExpectUniqueSample( |
| "Notifications.Engagement.Clicked.Volume0", 0, 1); |
| } |
| } |
| |
| TEST_F(NotificationsEngagementServiceTest, EraseStaleEntries) { |
| GURL url("https://www.google.com/"); |
| |
| // Test that only entries older than 30 days are deleted. |
| task_environment_.FastForwardBy(base::Days(10950)); |
| service()->RecordNotificationDisplayed(url); |
| task_environment_.FastForwardBy(base::Days(31)); |
| service()->RecordNotificationDisplayed(url); |
| |
| task_environment_.FastForwardBy(base::Days(8)); |
| service()->RecordNotificationDisplayed(url); |
| |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile()); |
| |
| base::Value::Dict website_engagement = |
| host_content_settings_map |
| ->GetWebsiteSetting(url, GURL(), |
| ContentSettingsType::NOTIFICATION_INTERACTIONS) |
| .GetDict() |
| .Clone(); |
| |
| // Stale entries should be erased, when a new display/interaction is |
| // recorded. |
| ASSERT_EQ(2u, website_engagement.size()); |
| } |
| |
| // Disabled, due to http://go/crb/1407635. |
| TEST_F(NotificationsEngagementServiceTest, DISABLED_GetBucketLabel) { |
| base::Time date1, date2, date3, date4; |
| base::Time expected_date1, expected_date2, expected_date3, expected_date4; |
| |
| ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:30 GMT", &date1)); |
| ASSERT_TRUE( |
| base::Time::FromString("2009-10-23 00:00:00.000 GMT", &expected_date1)); |
| ASSERT_EQ(NotificationsEngagementService::GetBucketLabel(date1), |
| base::NumberToString(expected_date1.base::Time::ToTimeT())); |
| std::string label1 = NotificationsEngagementService::GetBucketLabel(date1); |
| ASSERT_EQ(label1, base::NumberToString(expected_date1.base::Time::ToTimeT())); |
| std::optional<base::Time> begin1 = |
| NotificationsEngagementService::ParsePeriodBeginFromBucketLabel(label1); |
| ASSERT_TRUE(begin1.has_value()); |
| EXPECT_EQ(label1, |
| NotificationsEngagementService::GetBucketLabel(begin1.value())); |
| |
| ASSERT_TRUE(base::Time::FromString("Mon Oct 15 12:45 PDT 2007", &date2)); |
| ASSERT_TRUE( |
| base::Time::FromString("2007-10-15 00:00:00.000 GMT", &expected_date2)); |
| ASSERT_EQ(NotificationsEngagementService::GetBucketLabel(date2), |
| base::NumberToString(expected_date2.base::Time::ToTimeT())); |
| std::string label2 = NotificationsEngagementService::GetBucketLabel(date2); |
| ASSERT_EQ(label2, base::NumberToString(expected_date2.base::Time::ToTimeT())); |
| std::optional<base::Time> begin2 = |
| NotificationsEngagementService::ParsePeriodBeginFromBucketLabel(label2); |
| ASSERT_TRUE(begin2.has_value()); |
| EXPECT_EQ(label2, |
| NotificationsEngagementService::GetBucketLabel(begin2.value())); |
| |
| ASSERT_TRUE(base::Time::FromString("Mon Jan 01 13:59:58 +0100 1973", &date3)); |
| ASSERT_TRUE( |
| base::Time::FromString("1973-01-01 00:00:00.000 GMT", &expected_date3)); |
| ASSERT_EQ(NotificationsEngagementService::GetBucketLabel(date3), |
| base::NumberToString(expected_date3.base::Time::ToTimeT())); |
| std::string label3 = NotificationsEngagementService::GetBucketLabel(date3); |
| ASSERT_EQ(label3, base::NumberToString(expected_date3.base::Time::ToTimeT())); |
| std::optional<base::Time> begin3 = |
| NotificationsEngagementService::ParsePeriodBeginFromBucketLabel(label3); |
| ASSERT_TRUE(begin3.has_value()); |
| EXPECT_EQ(label3, |
| NotificationsEngagementService::GetBucketLabel(begin3.value())); |
| |
| ASSERT_TRUE(base::Time::FromString("Wed Mar 23 04:38:58 -1000 2022", &date4)); |
| ASSERT_TRUE( |
| base::Time::FromString("2022-03-23 00:00:00.000 GMT", &expected_date4)); |
| std::string label4 = NotificationsEngagementService::GetBucketLabel(date4); |
| ASSERT_EQ(label4, base::NumberToString(expected_date4.base::Time::ToTimeT())); |
| std::optional<base::Time> begin4 = |
| NotificationsEngagementService::ParsePeriodBeginFromBucketLabel(label4); |
| ASSERT_TRUE(begin4.has_value()); |
| EXPECT_EQ(label4, |
| NotificationsEngagementService::GetBucketLabel(begin4.value())); |
| } |
| } // namespace permissions |