blob: de2d611301b57230e150786db71d0857bfdd651d [file] [log] [blame]
// Copyright 2019 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 "base/bind.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_feature_list.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_features.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/test/mock_platform_notification_service.h"
#include "content/test/test_content_browser_client.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using base::Time;
using base::TimeDelta;
namespace content {
namespace {
// Fake Service Worker registration id to use in tests requiring one.
const int64_t kFakeServiceWorkerRegistrationId = 42;
class NotificationBrowserClient : public TestContentBrowserClient {
public:
explicit NotificationBrowserClient(BrowserContext* browser_context)
: platform_notification_service_(
std::make_unique<MockPlatformNotificationService>(
browser_context)) {}
PlatformNotificationService* GetPlatformNotificationService(
BrowserContext* browser_context) override {
return platform_notification_service_.get();
}
private:
std::unique_ptr<PlatformNotificationService> platform_notification_service_;
};
} // namespace
class PlatformNotificationContextTriggerTest : public ::testing::Test {
public:
PlatformNotificationContextTriggerTest()
: thread_bundle_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME,
base::test::ScopedTaskEnvironment::NowSource::
MAIN_THREAD_MOCK_TIME),
notification_browser_client_(&browser_context_),
success_(false) {
SetBrowserClientForTesting(&notification_browser_client_);
}
void SetUp() override {
// Advance time a little bit so TimeTicks::Now().is_null() becomes false.
thread_bundle_.FastForwardBy(base::TimeDelta::FromMilliseconds(1));
scoped_feature_list_.InitAndEnableFeature(features::kNotificationTriggers);
platform_notification_context_ =
base::MakeRefCounted<PlatformNotificationContextImpl>(
base::FilePath(), &browser_context_, nullptr);
platform_notification_context_->SetTaskRunnerForTesting(
base::ThreadTaskRunnerHandle::Get());
platform_notification_context_->Initialize();
base::RunLoop().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,
base::Optional<base::Time> timestamp) {
ASSERT_TRUE(
TryWriteNotificationData("https://example.com", tag, timestamp));
}
bool TryWriteNotificationData(const std::string& url,
const std::string& tag,
base::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
// |notification_browser_client_| synchronously.
std::set<std::string> GetDisplayedNotifications() {
std::set<std::string> displayed_notification_ids;
base::RunLoop run_loop;
notification_browser_client_
.GetPlatformNotificationService(&browser_context_)
->GetDisplayedNotifications(base::BindLambdaForTesting(
[&](std::set<std::string> notification_ids, bool supports_sync) {
displayed_notification_ids = std::move(notification_ids);
run_loop.QuitClosure().Run();
}));
run_loop.Run();
return displayed_notification_ids;
}
void TriggerNotifications() {
platform_notification_context_->TriggerNotifications();
base::RunLoop().RunUntilIdle();
}
TestBrowserThreadBundle thread_bundle_; // Must be first member
private:
base::test::ScopedFeatureList scoped_feature_list_;
TestBrowserContext browser_context_;
NotificationBrowserClient notification_browser_client_;
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() + TimeDelta::FromSeconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
// Wait until the trigger timestamp is reached.
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(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() - TimeDelta::FromSeconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteExistingTriggerInFuture) {
WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
// Overwrites the scheduled notifications with a new trigger timestamp.
WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest, OverwriteExistingTriggerToPast) {
WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(5));
// Overwrites the scheduled notifications with a new trigger timestamp.
WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteDisplayedNotificationToPast) {
WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(10));
// Overwrites a displayed notification with a trigger timestamp in the past.
WriteNotificationData("1", Time::Now() - TimeDelta::FromSeconds(10));
// This gets called by the notification scheduling system.
TriggerNotifications();
ASSERT_EQ(1u, GetDisplayedNotifications().size());
}
TEST_F(PlatformNotificationContextTriggerTest,
OverwriteDisplayedNotificationToFuture) {
WriteNotificationData("1", Time::Now() + TimeDelta::FromSeconds(10));
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(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() + TimeDelta::FromSeconds(10));
ASSERT_EQ(0u, GetDisplayedNotifications().size());
thread_bundle_.FastForwardBy(TimeDelta::FromSeconds(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(std::to_string(i),
Time::Now() + TimeDelta::FromSeconds(i));
}
ASSERT_FALSE(TryWriteNotificationData(
"https://example.com",
std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
ASSERT_TRUE(TryWriteNotificationData(
"https://example2.com",
std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}
TEST_F(PlatformNotificationContextTriggerTest, EnforcesLimitOnUpdate) {
for (int i = 1; i <= kMaximumScheduledNotificationsPerOrigin; ++i) {
WriteNotificationData(std::to_string(i),
Time::Now() + TimeDelta::FromSeconds(i));
}
ASSERT_TRUE(TryWriteNotificationData(
"https://example.com",
std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
base::nullopt));
ASSERT_FALSE(TryWriteNotificationData(
"https://example.com",
std::to_string(kMaximumScheduledNotificationsPerOrigin + 1),
Time::Now() +
TimeDelta::FromSeconds(kMaximumScheduledNotificationsPerOrigin + 1)));
}
} // namespace content