blob: 5ce507da6bf2455e4b1b24c15a8b9782e9dea1fb [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "content/browser/notifications/notification_trigger_constants.h"
#include "content/browser/notifications/platform_notification_context_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/mock_platform_notification_service.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using base::Time;
namespace content {
namespace {
// Fake Service Worker registration id to use in tests requiring one.
const int64_t kFakeServiceWorkerRegistrationId = 42;
} // namespace
class PlatformNotificationContextTriggerTest : public ::testing::Test {
public:
PlatformNotificationContextTriggerTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME),
success_(false) {
browser_context_.SetPlatformNotificationService(
std::make_unique<MockPlatformNotificationService>(&browser_context_));
}
void SetUp() override {
platform_notification_context_ =
base::MakeRefCounted<PlatformNotificationContextImpl>(
base::FilePath(), &browser_context_, nullptr);
platform_notification_context_->SetTaskRunnerForTesting(
base::SingleThreadTaskRunner::GetCurrentDefault());
platform_notification_context_->Initialize();
base::RunLoop().RunUntilIdle();
}
void TearDown() override {
// Destroy the context and allow its background tasks to run to close the
// database.
platform_notification_context_.reset();
task_environment_.RunUntilIdle();
}
// Callback to provide when writing a notification to the database.
void DidWriteNotificationData(bool success,
const std::string& notification_id) {
success_ = success;
}
protected:
void WriteNotificationData(const std::string& tag,
std::optional<base::Time> timestamp) {
ASSERT_TRUE(
TryWriteNotificationData("https://example.com", tag, timestamp));
}
bool TryWriteNotificationData(const std::string& url,
const std::string& tag,
std::optional<base::Time> timestamp) {
GURL origin(url);
NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
notification_database_data.notification_data.show_trigger_timestamp =
timestamp;
notification_database_data.notification_data.tag = tag;
platform_notification_context_->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(
&PlatformNotificationContextTriggerTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
return success_;
}
// Gets the currently displayed notifications from |browser_context_|
// synchronously.
std::set<std::string> GetDisplayedNotifications() {
std::set<std::string> displayed_notification_ids;
base::RunLoop run_loop;
browser_context_.GetPlatformNotificationService()
->GetDisplayedNotifications(base::BindLambdaForTesting(
[&](std::set<std::string> notification_ids, bool supports_sync) {
displayed_notification_ids = std::move(notification_ids);
run_loop.Quit();
}));
run_loop.Run();
return displayed_notification_ids;
}
void TriggerNotifications() {
platform_notification_context_->TriggerNotifications();
base::RunLoop().RunUntilIdle();
}
BrowserTaskEnvironment task_environment_; // Must be first member
private:
TestBrowserContext browser_context_;
scoped_refptr<PlatformNotificationContextImpl> platform_notification_context_;
// Returns the next persistent notification id for tests.
int64_t next_persistent_notification_id() {
return next_persistent_notification_id_++;
}
bool success_;
int64_t next_persistent_notification_id_ = 1;
};
TEST_F(PlatformNotificationContextTriggerTest, TriggerInFuture) {
WriteNotificationData("1", Time::Now() + base::Seconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
// Wait until the trigger timestamp is reached.
task_environment_.FastForwardBy(base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest, TriggerInPast) {
// Trigger timestamp in the past should immediately trigger.
WriteNotificationData("1", Time::Now() - base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteExistingTriggerInFuture) {
WriteNotificationData("1", Time::Now() + base::Seconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
task_environment_.FastForwardBy(base::Seconds(5));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
// Overwrites the scheduled notifications with a new trigger timestamp.
WriteNotificationData("1", Time::Now() + base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(5));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
task_environment_.FastForwardBy(base::Seconds(5));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest, OverwriteExistingTriggerToPast) {
WriteNotificationData("1", Time::Now() + base::Seconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
task_environment_.FastForwardBy(base::Seconds(5));
// Overwrites the scheduled notifications with a new trigger timestamp.
WriteNotificationData("1", Time::Now() - base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteDisplayedNotificationToPast) {
WriteNotificationData("1", Time::Now() + base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(10));
// Overwrites a displayed notification with a trigger timestamp in the past.
WriteNotificationData("1", Time::Now() - base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteDisplayedNotificationToFuture) {
WriteNotificationData("1", Time::Now() + base::Seconds(10));
task_environment_.FastForwardBy(base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
// Overwrites a displayed notification which hides it until the trigger
// timestamp is reached.
WriteNotificationData("1", Time::Now() + base::Seconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
task_environment_.FastForwardBy(base::Seconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
LimitsNumberOfScheduledNotificationsPerOrigin) {
for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
WriteNotificationData(base::NumberToString(i),
Time::Now() + base::Seconds(i));
}
ASSERT_FALSE(TryWriteNotificationData(
"https://example.com",
base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));
ASSERT_TRUE(TryWriteNotificationData(
"https://example2.com",
base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}
TEST_F(PlatformNotificationContextTriggerTest, EnforcesLimitOnUpdate) {
for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
WriteNotificationData(base::NumberToString(i),
Time::Now() + base::Seconds(i));
}
ASSERT_TRUE(TryWriteNotificationData(
"https://example.com",
base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
std::nullopt));
ASSERT_FALSE(TryWriteNotificationData(
"https://example.com",
base::NumberToString(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
base::Seconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}
TEST_F(PlatformNotificationContextTriggerTest, RecordDisplayDelay) {
base::HistogramTester histogram_tester;
base::TimeDelta trigger_delay = base::Seconds(10);
base::TimeDelta display_delay = base::Seconds(8);
WriteNotificationData("1", Time::Now() + trigger_delay);
ASSERT_EQ(0u, GetDisplayedNotifications().size());
// Forward time until after the expected trigger time.
task_environment_.FastForwardBy(trigger_delay + display_delay);
// Trigger notification |display_delay| after it should have been displayed.
TriggerNotifications();
}
} // namespace content