blob: a0f5b5fee0dec1478642351aaa4479fa5278b225 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/notifications/metrics/mock_notification_metrics_logger.h"
#include "chrome/browser/notifications/metrics/notification_metrics_logger_factory.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/platform_notification_service_factory.h"
#include "chrome/browser/notifications/platform_notification_service_impl.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "extensions/buildflags/buildflags.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/common/notifications/platform_notification_data.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/value_builder.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
using blink::NotificationResources;
using blink::PlatformNotificationData;
using content::NotificationDatabaseData;
using message_center::Notification;
namespace {
const int kNotificationVibrationPattern[] = { 100, 200, 300 };
const char kNotificationId[] = "my-notification-id";
const char kClosedReason[] = "ClosedReason";
const char kDidReplaceAnotherNotification[] = "DidReplaceAnotherNotification";
const char kHasBadge[] = "HasBadge";
const char kHasImage[] = "HasImage";
const char kHasIcon[] = "HasIcon";
const char kHasRenotify[] = "HasRenotify";
const char kHasTag[] = "HasTag";
const char kIsSilent[] = "IsSilent";
const char kNumActions[] = "NumActions";
const char kNumClicks[] = "NumClicks";
const char kNumActionButtonClicks[] = "NumActionButtonClicks";
const char kRequireInteraction[] = "RequireInteraction";
const char kTimeUntilCloseMillis[] = "TimeUntilClose";
const char kTimeUntilFirstClickMillis[] = "TimeUntilFirstClick";
const char kTimeUntilLastClickMillis[] = "TimeUntilLastClick";
const GURL kOrigin = GURL("https://example.com");
} // namespace
class PlatformNotificationServiceTest : public testing::Test {
public:
void SetUp() override {
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(&profile_);
mock_logger_ = static_cast<MockNotificationMetricsLogger*>(
NotificationMetricsLoggerFactory::GetInstance()
->SetTestingFactoryAndUse(
&profile_,
base::BindRepeating(
&MockNotificationMetricsLogger::FactoryForTests)));
recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDown() override {
display_service_tester_.reset();
}
protected:
// Returns the Platform Notification Service these unit tests are for.
PlatformNotificationServiceImpl* service() {
return PlatformNotificationServiceFactory::GetForProfile(&profile_);
}
size_t GetNotificationCountForType(NotificationHandler::Type type) {
return display_service_tester_->GetDisplayedNotificationsForType(type)
.size();
}
Notification GetDisplayedNotificationForType(NotificationHandler::Type type) {
std::vector<Notification> notifications =
display_service_tester_->GetDisplayedNotificationsForType(type);
DCHECK_EQ(1u, notifications.size());
return notifications[0];
}
protected:
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
// Owned by the |profile_| as a keyed service.
MockNotificationMetricsLogger* mock_logger_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> recorder_;
};
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentThenClose) {
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My Notification");
data.body = base::ASCIIToUTF16("Hello, world!");
service()->DisplayNotification(kNotificationId, GURL("https://chrome.com/"),
data, NotificationResources());
EXPECT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
service()->CloseNotification(kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayPersistentThenClose) {
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
kNotificationId, GURL() /* service_worker_scope */,
GURL("https://chrome.com/"), data, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title",
base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!",
base::UTF16ToUTF8(notification.message()));
service()->ClosePersistentNotification(kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentPropertiesMatch) {
std::vector<int> vibration_pattern(
kNotificationVibrationPattern,
kNotificationVibrationPattern +
base::size(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
data.vibration_pattern = vibration_pattern;
data.silent = true;
service()->DisplayNotification(kNotificationId, GURL("https://chrome.com/"),
data, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_NON_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title",
base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!",
base::UTF16ToUTF8(notification.message()));
EXPECT_THAT(notification.vibration_pattern(),
testing::ElementsAreArray(kNotificationVibrationPattern));
EXPECT_TRUE(notification.silent());
}
TEST_F(PlatformNotificationServiceTest, DisplayPersistentPropertiesMatch) {
std::vector<int> vibration_pattern(
kNotificationVibrationPattern,
kNotificationVibrationPattern +
base::size(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
data.vibration_pattern = vibration_pattern;
data.silent = true;
data.actions.resize(2);
data.actions[0].type = blink::PLATFORM_NOTIFICATION_ACTION_TYPE_BUTTON;
data.actions[0].title = base::ASCIIToUTF16("Button 1");
data.actions[1].type = blink::PLATFORM_NOTIFICATION_ACTION_TYPE_TEXT;
data.actions[1].title = base::ASCIIToUTF16("Button 2");
NotificationResources notification_resources;
notification_resources.action_icons.resize(data.actions.size());
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
kNotificationId, GURL() /* service_worker_scope */,
GURL("https://chrome.com/"), data, notification_resources);
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title", base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!", base::UTF16ToUTF8(notification.message()));
EXPECT_THAT(notification.vibration_pattern(),
testing::ElementsAreArray(kNotificationVibrationPattern));
EXPECT_TRUE(notification.silent());
const auto& buttons = notification.buttons();
ASSERT_EQ(2u, buttons.size());
EXPECT_EQ("Button 1", base::UTF16ToUTF8(buttons[0].title));
EXPECT_FALSE(buttons[0].placeholder);
EXPECT_EQ("Button 2", base::UTF16ToUTF8(buttons[1].title));
EXPECT_TRUE(buttons[1].placeholder);
}
TEST_F(PlatformNotificationServiceTest, RecordNotificationUkmEvent) {
// Set up UKM recording conditions.
ASSERT_TRUE(profile_.CreateHistoryService(/* delete_file= */ true,
/* no_db= */ false));
auto* history_service = HistoryServiceFactory::GetForProfile(
&profile_, ServiceAccessType::EXPLICIT_ACCESS);
history_service->AddPage(kOrigin, base::Time::Now(), history::SOURCE_BROWSED);
NotificationDatabaseData data;
data.notification_id = "notification1";
data.origin = kOrigin;
data.closed_reason = NotificationDatabaseData::ClosedReason::USER;
data.replaced_existing_notification = true;
data.notification_data.icon = GURL("https://icon.com");
data.notification_data.image = GURL("https://image.com");
data.notification_data.renotify = false;
data.notification_data.tag = "tag";
data.notification_data.silent = true;
blink::PlatformNotificationAction action1, action2, action3;
data.notification_data.actions.push_back(action1);
data.notification_data.actions.push_back(action2);
data.notification_data.actions.push_back(action3);
data.notification_data.require_interaction = false;
data.num_clicks = 3;
data.num_action_button_clicks = 1;
data.time_until_close_millis = base::TimeDelta::FromMilliseconds(10000);
data.time_until_first_click_millis = base::TimeDelta::FromMilliseconds(2222);
data.time_until_last_click_millis = base::TimeDelta::FromMilliseconds(3333);
// Initially there are no UKM entries.
std::vector<const ukm::mojom::UkmEntry*> entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
EXPECT_EQ(0u, entries.size());
{
base::RunLoop run_loop;
service()->set_ukm_recorded_closure_for_testing(run_loop.QuitClosure());
service()->RecordNotificationUkmEvent(data);
run_loop.Run();
}
entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries[0];
recorder_->ExpectEntryMetric(
entry, kClosedReason,
static_cast<int>(NotificationDatabaseData::ClosedReason::USER));
recorder_->ExpectEntryMetric(entry, kDidReplaceAnotherNotification, true);
recorder_->ExpectEntryMetric(entry, kHasBadge, false);
recorder_->ExpectEntryMetric(entry, kHasIcon, 1);
recorder_->ExpectEntryMetric(entry, kHasImage, 1);
recorder_->ExpectEntryMetric(entry, kHasRenotify, false);
recorder_->ExpectEntryMetric(entry, kHasTag, true);
recorder_->ExpectEntryMetric(entry, kIsSilent, 1);
recorder_->ExpectEntryMetric(entry, kNumActions, 3);
recorder_->ExpectEntryMetric(entry, kNumActionButtonClicks, 1);
recorder_->ExpectEntryMetric(entry, kNumClicks, 3);
recorder_->ExpectEntryMetric(entry, kRequireInteraction, false);
recorder_->ExpectEntryMetric(entry, kTimeUntilCloseMillis, 10000);
recorder_->ExpectEntryMetric(entry, kTimeUntilFirstClickMillis, 2222);
recorder_->ExpectEntryMetric(entry, kTimeUntilLastClickMillis, 3333);
}
// Expect each call to ReadNextPersistentNotificationId to return a larger
// value.
TEST_F(PlatformNotificationServiceTest, NextPersistentNotificationId) {
int64_t first_id = service()->ReadNextPersistentNotificationId();
int64_t second_id = service()->ReadNextPersistentNotificationId();
EXPECT_LT(first_id, second_id);
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(PlatformNotificationServiceTest, DisplayNameForContextMessage) {
base::string16 display_name =
service()->DisplayNameForContextMessage(GURL("https://chrome.com/"));
EXPECT_TRUE(display_name.empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension")
.Build())
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(&profile_);
EXPECT_TRUE(registry->AddEnabled(extension));
display_name = service()->DisplayNameForContextMessage(
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"));
EXPECT_EQ("NotificationTest", base::UTF16ToUTF8(display_name));
}
TEST_F(PlatformNotificationServiceTest, CreateNotificationFromData) {
PlatformNotificationData notification_data;
notification_data.title = base::ASCIIToUTF16("My Notification");
notification_data.body = base::ASCIIToUTF16("Hello, world!");
GURL origin("https://chrome.com/");
Notification notification = service()->CreateNotificationFromData(
origin, "id", notification_data, NotificationResources());
EXPECT_TRUE(notification.context_message().empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension")
.Build())
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(&profile_);
EXPECT_TRUE(registry->AddEnabled(extension));
notification = service()->CreateNotificationFromData(
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"),
"id", notification_data, NotificationResources());
EXPECT_EQ("NotificationTest",
base::UTF16ToUTF8(notification.context_message()));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)