#include <stdint.h>
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/notifications/platform_notification_context_impl.h"
#include "content/browser/service_worker/embedded_worker_test_helper.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/notification_database_data.h"
#include "content/public/browser/permission_result.h"
#include "content/public/common/content_client.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/mock_permission_manager.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 "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration_options.mojom.h"
#include "third_party/leveldatabase/leveldb_chrome.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
using ::testing::Return;
MATCHER_P(PermissionTypeMatcher, id, "") {
return ::testing::Matches(::testing::Eq(id))(
blink::PermissionDescriptorToPermissionType(arg));
}
namespace content {
const int64_t kFakeServiceWorkerRegistrationId = 42;
class PlatformNotificationContextTest : public ::testing::Test {
public:
PlatformNotificationContextTest() = default;
void SetUp() override {
permission_manager_ = new ::testing::NiceMock<MockPermissionManager>();
browser_context_.SetPermissionControllerDelegate(
base::WrapUnique(permission_manager_.get()));
browser_context_.SetPlatformNotificationService(
std::make_unique<MockPlatformNotificationService>(&browser_context_));
}
void DidReadNotificationData(bool success,
const NotificationDatabaseData& database_data) {
success_ = success;
database_data_ = database_data;
}
void DidWriteNotificationData(bool success,
const std::string& notification_id) {
success_ = success;
notification_id_ = notification_id;
}
void DidDeleteNotificationData(bool success) { success_ = success; }
void DidDeleteAllNotificationData(bool success, size_t deleted_count) {
success_ = success;
deleted_count_ = deleted_count;
}
void DidRegisterServiceWorker(int64_t* store_registration_id,
blink::ServiceWorkerStatusCode status,
const std::string& status_message,
int64_t service_worker_registration_id) {
DCHECK(store_registration_id);
EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status);
*store_registration_id = service_worker_registration_id;
}
void DidUnregisterServiceWorker(blink::ServiceWorkerStatusCode* store_status,
blink::ServiceWorkerStatusCode status) {
DCHECK(store_status);
*store_status = status;
}
protected:
scoped_refptr<PlatformNotificationContextImpl>
CreatePlatformNotificationContext(base::FilePath path = {}) {
auto context = base::MakeRefCounted<PlatformNotificationContextImpl>(
path, &browser_context_, nullptr);
OverrideTaskRunnerForTesting(context.get());
context->Initialize();
base::RunLoop().RunUntilIdle();
return context;
}
void OverrideTaskRunnerForTesting(PlatformNotificationContextImpl* context) {
context->SetTaskRunnerForTesting(
base::SingleThreadTaskRunner::GetCurrentDefault());
}
std::set<std::string> GetDisplayedNotificationsSync(
PlatformNotificationService* service) {
std::set<std::string> displayed_notification_ids;
base::RunLoop run_loop;
service->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;
}
std::vector<NotificationDatabaseData> GetStoredNotificationsSync(
PlatformNotificationContextImpl* context,
const GURL& origin) {
std::vector<NotificationDatabaseData> notification_database_datas;
base::RunLoop run_loop;
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::BindLambdaForTesting(
[&](bool success, const std::vector<NotificationDatabaseData>&
notification_datas) {
DCHECK(success);
notification_database_datas = notification_datas;
run_loop.Quit();
}));
base::RunLoop().RunUntilIdle();
return notification_database_datas;
}
bool ReadNotificationResourcesSync(PlatformNotificationContextImpl* context,
const std::string& notification_id,
const GURL& origin) {
bool result = false;
base::RunLoop run_loop;
context->ReadNotificationResources(
notification_id, origin,
base::BindLambdaForTesting(
[&](bool success,
const blink::NotificationResources& notification_resources) {
result = success;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
bool WriteNotificationResourcesSync(
PlatformNotificationContextImpl* context,
std::vector<NotificationResourceData> resources) {
bool result = false;
base::RunLoop run_loop;
context->WriteNotificationResources(
std::move(resources), base::BindLambdaForTesting([&](bool success) {
result = success;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
std::string WriteNotificationDataSync(
PlatformNotificationContextImpl* context,
const GURL& origin,
const NotificationDatabaseData& data) {
std::string notification_id;
base::RunLoop run_loop;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, data,
base::BindLambdaForTesting([&](bool success, const std::string& id) {
DCHECK(success);
notification_id = id;
run_loop.Quit();
}));
run_loop.Run();
DCHECK(!notification_id.empty());
return notification_id;
}
bool WriteNotificationMetadataSync(PlatformNotificationContextImpl* context,
const std::string& notification_id,
const GURL& origin,
const std::string& metadata_key,
const std::string& metadata_value) {
bool result = false;
base::RunLoop run_loop;
context->WriteNotificationMetadata(
notification_id, origin, metadata_key, metadata_value,
base::BindLambdaForTesting([&](bool success) {
result = success;
run_loop.Quit();
}));
run_loop.Run();
return result;
}
void SetPermissionStatus(const GURL& origin,
blink::mojom::PermissionStatus permission_status) {
ON_CALL(*permission_manager_,
GetPermissionResultForOriginWithoutContext(
PermissionTypeMatcher(blink::PermissionType::NOTIFICATIONS),
url::Origin::Create(origin), url::Origin::Create(origin)))
.WillByDefault(Return(content::PermissionResult(
permission_status, PermissionStatusSource::UNSPECIFIED)));
}
base::FilePath GetDatabaseFilePath(PlatformNotificationContextImpl* context) {
return context->GetDatabasePath();
}
BrowserContext* browser_context() { return &browser_context_; }
bool success() const { return success_; }
int64_t next_persistent_notification_id() {
return next_persistent_notification_id_++;
}
const NotificationDatabaseData& database_data() const {
return database_data_;
}
const std::string& notification_id() const { return notification_id_; }
BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
private:
TestBrowserContext browser_context_;
raw_ptr<MockPermissionManager> permission_manager_ = nullptr;
bool success_ = false;
size_t deleted_count_ = 0;
NotificationDatabaseData database_data_;
std::string notification_id_;
int64_t next_persistent_notification_id_ = 1;
};
TEST_F(PlatformNotificationContextTest, ReadNonExistentNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
context->ReadNotificationDataAndRecordInteraction(
"invalid-notification-id", GURL("https://example.com"),
PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, InitializeIsLazy) {
GURL origin("https://example.com");
base::ScopedTempDir database_dir;
ASSERT_TRUE(database_dir.CreateUniqueTempDir());
base::FilePath database_path = database_dir.GetPath();
auto context = CreatePlatformNotificationContext(database_path);
base::FilePath db_path = GetDatabaseFilePath(context.get());
EXPECT_FALSE(base::PathExists(db_path));
GetStoredNotificationsSync(context.get(), origin);
EXPECT_TRUE(base::PathExists(db_path));
}
TEST_F(PlatformNotificationContextTest, WriteReadNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
const NotificationDatabaseData& read_database_data = database_data();
EXPECT_EQ(notification_database_data.origin, read_database_data.origin);
}
TEST_F(PlatformNotificationContextTest, ReadNotificationsFromBrowser) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData data;
data.origin = origin;
data.service_worker_registration_id = kFakeServiceWorkerRegistrationId;
data.is_shown_by_browser = false;
WriteNotificationDataSync(context.get(), origin, data);
data.is_shown_by_browser = true;
WriteNotificationDataSync(context.get(), origin, data);
EXPECT_EQ(1u, GetStoredNotificationsSync(context.get(), origin).size());
}
TEST_F(PlatformNotificationContextTest, WriteReadReplacedNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
const GURL origin("https://example.com");
const std::string tag = "foo";
NotificationDatabaseData notification_database_data;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
notification_database_data.origin = origin;
notification_database_data.notification_data.title = u"First";
notification_database_data.notification_data.tag = tag;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
std::string read_notification_id = notification_id();
ASSERT_TRUE(success());
EXPECT_FALSE(read_notification_id.empty());
notification_database_data.notification_data.title = u"Second";
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
ASSERT_FALSE(notification_id().empty());
ASSERT_EQ(notification_id(), read_notification_id);
std::vector<NotificationDatabaseData> notification_database_datas =
GetStoredNotificationsSync(context.get(), origin);
ASSERT_EQ(1u, notification_database_datas.size());
EXPECT_EQ(tag, notification_database_datas[0].notification_data.tag);
EXPECT_EQ(u"Second", notification_database_datas[0].notification_data.title);
}
TEST_F(PlatformNotificationContextTest, DeleteInvalidNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
context->DeleteNotificationData(
"invalid-notification-id", GURL("https://example.com"),
false,
base::BindOnce(
&PlatformNotificationContextTest::DidDeleteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(success());
}
TEST_F(PlatformNotificationContextTest, DeleteNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
context->DeleteNotificationData(
notification_id(), origin,
false,
base::BindOnce(
&PlatformNotificationContextTest::DidDeleteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, DeleteClosesNotification) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_EQ(1u, GetDisplayedNotificationsSync(service).size());
context->DeleteNotificationData(
notification_id(), origin,
true,
base::BindOnce(
&PlatformNotificationContextTest::DidDeleteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_EQ(0u, GetDisplayedNotificationsSync(service).size());
}
TEST_F(PlatformNotificationContextTest,
DeleteAllNotificationDataForBlockedOrigins) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
GURL origin1("https://example1.com");
GURL origin2("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
notification_database_data.origin = origin1;
for (size_t i = 0; i < 3; ++i) {
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin1, notification_database_data,
base::BindOnce(
&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
}
notification_database_data.origin = origin2;
for (size_t i = 0; i < 5; ++i) {
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin2, notification_database_data,
base::BindOnce(
&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
}
base::RunLoop().RunUntilIdle();
EXPECT_EQ(3u, GetStoredNotificationsSync(context.get(), origin1).size());
EXPECT_EQ(5u, GetStoredNotificationsSync(context.get(), origin2).size());
EXPECT_EQ(8u, GetDisplayedNotificationsSync(service).size());
SetPermissionStatus(origin1, blink::mojom::PermissionStatus::GRANTED);
SetPermissionStatus(origin2, blink::mojom::PermissionStatus::DENIED);
context->DeleteAllNotificationDataForBlockedOrigins(base::BindOnce(
&PlatformNotificationContextTest::DidDeleteAllNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_EQ(3u, GetStoredNotificationsSync(context.get(), origin1).size());
EXPECT_EQ(0u, GetStoredNotificationsSync(context.get(), origin2).size());
EXPECT_EQ(3u, GetDisplayedNotificationsSync(service).size());
}
TEST_F(PlatformNotificationContextTest, ServiceWorkerUnregistered) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
std::unique_ptr<EmbeddedWorkerTestHelper> embedded_worker_test_helper(
new EmbeddedWorkerTestHelper(base::FilePath()));
scoped_refptr<PlatformNotificationContextImpl> notification_context(
new PlatformNotificationContextImpl(
base::FilePath(), browser_context(),
embedded_worker_test_helper->context_wrapper()));
OverrideTaskRunnerForTesting(notification_context.get());
notification_context->Initialize();
base::RunLoop().RunUntilIdle();
GURL origin("https://example.com");
GURL script_url("https://example.com/worker.js");
const blink::StorageKey key =
blink::StorageKey::CreateFirstParty(url::Origin::Create(origin));
int64_t service_worker_registration_id =
blink::mojom::kInvalidServiceWorkerRegistrationId;
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = origin;
embedded_worker_test_helper->context()->RegisterServiceWorker(
script_url, key, options, blink::mojom::FetchClientSettingsObject::New(),
base::BindOnce(&PlatformNotificationContextTest::DidRegisterServiceWorker,
base::Unretained(this), &service_worker_registration_id),
GlobalRenderFrameHostId(),
PolicyContainerPolicies());
base::RunLoop().RunUntilIdle();
ASSERT_NE(service_worker_registration_id,
blink::mojom::kInvalidServiceWorkerRegistrationId);
NotificationDatabaseData notification_database_data;
notification_context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
ASSERT_EQ(1u, GetDisplayedNotificationsSync(service).size());
blink::ServiceWorkerStatusCode unregister_status;
embedded_worker_test_helper->context()->UnregisterServiceWorker(
origin, key,
false, ServiceWorkerRegistration::DeleteInitiator::kTest,
base::BindOnce(
&PlatformNotificationContextTest::DidUnregisterServiceWorker,
base::Unretained(this), &unregister_status));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, unregister_status);
notification_context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
ASSERT_EQ(0u, GetDisplayedNotificationsSync(service).size());
}
TEST_F(PlatformNotificationContextTest, DestroyDatabaseOnStorageWiped) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
context->OnStorageWiped();
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, DestroyOnDiskDatabase) {
base::ScopedTempDir database_dir;
ASSERT_TRUE(database_dir.CreateUniqueTempDir());
auto context = CreatePlatformNotificationContext(database_dir.GetPath());
context->ReadNotificationDataAndRecordInteraction(
"invalid-notification-id", GURL("https://example.com"),
PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(IsDirectoryEmpty(database_dir.GetPath()));
EXPECT_FALSE(success());
context->OnStorageWiped();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(IsDirectoryEmpty(database_dir.GetPath()));
}
TEST_F(PlatformNotificationContextTest, DestroyCorruptedDatabase) {
base::ScopedTempDir database_dir;
ASSERT_TRUE(database_dir.CreateUniqueTempDir());
GURL origin("https://example.com");
NotificationDatabaseData data;
data.origin = origin;
data.service_worker_registration_id = kFakeServiceWorkerRegistrationId;
auto context = CreatePlatformNotificationContext(database_dir.GetPath());
base::FilePath db_path = GetDatabaseFilePath(context.get());
WriteNotificationDataSync(context.get(), origin, data);
EXPECT_FALSE(IsDirectoryEmpty(db_path));
EXPECT_EQ(1u, GetStoredNotificationsSync(context.get(), origin).size());
context.reset();
context = CreatePlatformNotificationContext(database_dir.GetPath());
EXPECT_EQ(1u, GetStoredNotificationsSync(context.get(), origin).size());
context.reset();
EXPECT_TRUE(leveldb_chrome::CorruptClosedDBForTesting(db_path));
context = CreatePlatformNotificationContext(database_dir.GetPath());
EXPECT_TRUE(IsDirectoryEmpty(db_path));
EXPECT_EQ(0u, GetStoredNotificationsSync(context.get(), origin).size());
EXPECT_FALSE(IsDirectoryEmpty(db_path));
}
TEST_F(PlatformNotificationContextTest, ReadAllServiceWorkerDataEmpty) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
ASSERT_EQ(0u, GetStoredNotificationsSync(context.get(), origin).size());
}
TEST_F(PlatformNotificationContextTest, ReadAllServiceWorkerDataFilled) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
for (int i = 0; i < 10; ++i) {
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(
&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
}
std::vector<NotificationDatabaseData> notification_database_datas =
GetStoredNotificationsSync(context.get(), origin);
ASSERT_EQ(10u, notification_database_datas.size());
for (int i = 0; i < 10; ++i) {
EXPECT_EQ(origin, notification_database_datas[i].origin);
EXPECT_EQ(kFakeServiceWorkerRegistrationId,
notification_database_datas[i].service_worker_registration_id);
}
}
TEST_F(PlatformNotificationContextTest, SynchronizeNotifications) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
base::RunLoop().RunUntilIdle();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
blink::PlatformNotificationData notification_data;
blink::NotificationResources notification_resources;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
ASSERT_EQ(1u, GetStoredNotificationsSync(context.get(), origin).size());
task_environment_.FastForwardBy(base::Seconds(1));
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
service->ClosePersistentNotification(notification_id());
ASSERT_EQ(0u, GetStoredNotificationsSync(context.get(), origin).size());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin,
PlatformNotificationContext::Interaction::CLOSED,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, DeleteOldNotifications) {
#if BUILDFLAG(IS_MAC)
if (base::mac::MacOSMajorVersion() == 26) {
GTEST_SKIP() << "Disabled on macOS Tahoe.";
}
#endif
base::HistogramTester histogram_tester;
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
base::RunLoop().RunUntilIdle();
GURL origin("https://example.com");
NotificationDatabaseData data;
data.service_worker_registration_id = kFakeServiceWorkerRegistrationId;
WriteNotificationDataSync(context.get(), origin, data);
task_environment_.FastForwardBy(base::Days(5));
context->TriggerNotifications();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, GetDisplayedNotificationsSync(service).size());
ASSERT_EQ(1u, GetStoredNotificationsSync(context.get(), origin).size());
NotificationDatabaseData data_2;
data_2.service_worker_registration_id = kFakeServiceWorkerRegistrationId;
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data_2);
EXPECT_EQ(2u, GetDisplayedNotificationsSync(service).size());
ASSERT_EQ(2u, GetStoredNotificationsSync(context.get(), origin).size());
task_environment_.FastForwardBy(base::Days(2));
context->TriggerNotifications();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1u, GetDisplayedNotificationsSync(service).size());
std::vector<NotificationDatabaseData> notification_database_datas =
GetStoredNotificationsSync(context.get(), origin);
ASSERT_EQ(1u, notification_database_datas.size());
EXPECT_EQ(notification_id, notification_database_datas[0].notification_id);
histogram_tester.ExpectBucketCount(
"Notifications.Database.ExpiredNotificationCount",
1, 1);
}
TEST_F(PlatformNotificationContextTest, WriteDisplaysNotification) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::BindOnce(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
std::set<std::string> displayed_notification_ids =
GetDisplayedNotificationsSync(
browser_context()->GetPlatformNotificationService());
ASSERT_EQ(1u, displayed_notification_ids.size());
EXPECT_EQ(notification_id(), *displayed_notification_ids.begin());
}
TEST_F(PlatformNotificationContextTest, WriteReadNotificationResources) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData data;
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data);
ASSERT_FALSE(
ReadNotificationResourcesSync(context.get(), notification_id, origin));
std::vector<NotificationResourceData> resources;
resources.emplace_back(notification_id, origin,
blink::NotificationResources());
std::string invalid_id = "invalid-id";
resources.emplace_back(invalid_id, origin, blink::NotificationResources());
ASSERT_TRUE(
WriteNotificationResourcesSync(context.get(), std::move(resources)));
ASSERT_TRUE(
ReadNotificationResourcesSync(context.get(), notification_id, origin));
ASSERT_FALSE(
ReadNotificationResourcesSync(context.get(), invalid_id, origin));
}
TEST_F(PlatformNotificationContextTest, ReDisplayNotifications) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData data1;
data1.notification_resources = blink::NotificationResources();
data1.notification_data.show_trigger_timestamp =
base::Time::Now() + base::Days(10);
WriteNotificationDataSync(context.get(), origin, data1);
NotificationDatabaseData data2;
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data2);
std::vector<NotificationResourceData> resources;
resources.emplace_back(notification_id, origin,
blink::NotificationResources());
WriteNotificationResourcesSync(context.get(), std::move(resources));
NotificationDatabaseData data3;
WriteNotificationDataSync(context.get(), origin, data3);
ASSERT_EQ(2u, GetDisplayedNotificationsSync(service).size());
service->ClosePersistentNotification(notification_id);
ASSERT_EQ(1u, GetDisplayedNotificationsSync(service).size());
base::RunLoop run_loop;
context->ReDisplayNotifications(
{origin}, base::BindLambdaForTesting([&](size_t display_count) {
ASSERT_EQ(1u, display_count);
run_loop.Quit();
}));
run_loop.Run();
ASSERT_EQ(2u, GetDisplayedNotificationsSync(service).size());
ASSERT_FALSE(
ReadNotificationResourcesSync(context.get(), notification_id, origin));
}
TEST_F(PlatformNotificationContextTest, CountVisibleNotification) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
const GURL origin("https://example.com");
NotificationDatabaseData data;
data.origin = origin;
data.service_worker_registration_id = kFakeServiceWorkerRegistrationId;
data.is_shown_by_browser = true;
WriteNotificationDataSync(context.get(), origin, data);
data.is_shown_by_browser = false;
WriteNotificationDataSync(context.get(), origin, data);
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data);
data.notification_data.show_trigger_timestamp =
base::Time::Now() + base::Days(10);
WriteNotificationDataSync(context.get(), origin, data);
ASSERT_EQ(3u, GetDisplayedNotificationsSync(service).size());
service->ClosePersistentNotification(notification_id);
base::RunLoop run_loop;
context->CountVisibleNotificationsForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::BindLambdaForTesting([&](bool success, int count) {
EXPECT_TRUE(success);
EXPECT_EQ(2, count);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PlatformNotificationContextTest, DeleteNotificationsWithTag) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
const GURL origin("https://example.com");
const std::string tag = "foo";
NotificationDatabaseData data;
data.notification_data.tag = tag;
WriteNotificationDataSync(context.get(), GURL("https://a.com"), {});
WriteNotificationDataSync(context.get(), GURL("https://a.com"), data);
WriteNotificationDataSync(context.get(), origin, {});
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data);
ASSERT_EQ(4u, GetDisplayedNotificationsSync(service).size());
base::RunLoop run_loop;
context->DeleteAllNotificationDataWithTag(
tag, false, origin,
base::BindLambdaForTesting([&](bool success, size_t deleted_count) {
EXPECT_TRUE(success);
EXPECT_EQ(1u, deleted_count);
run_loop.Quit();
}));
run_loop.Run();
base::RunLoop().RunUntilIdle();
std::set<std::string> displayed_notifications =
GetDisplayedNotificationsSync(service);
EXPECT_EQ(3u, displayed_notifications.size());
EXPECT_EQ(0u, displayed_notifications.count(notification_id));
}
TEST_F(PlatformNotificationContextTest, DeleteNotificationsWithTagFromBrowser) {
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
const GURL origin("https://example.com");
const std::string tag = "foo";
NotificationDatabaseData data;
data.notification_data.tag = tag;
data.is_shown_by_browser = false;
WriteNotificationDataSync(context.get(), origin, data);
data.is_shown_by_browser = true;
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data);
ASSERT_EQ(2u, GetDisplayedNotificationsSync(service).size());
base::RunLoop run_loop;
context->DeleteAllNotificationDataWithTag(
tag, true, origin,
base::BindLambdaForTesting([&](bool success, size_t deleted_count) {
EXPECT_TRUE(success);
EXPECT_EQ(1u, deleted_count);
run_loop.Quit();
}));
run_loop.Run();
base::RunLoop().RunUntilIdle();
std::set<std::string> displayed_notifications =
GetDisplayedNotificationsSync(service);
EXPECT_EQ(1u, displayed_notifications.size());
EXPECT_EQ(0u, displayed_notifications.count(notification_id));
}
TEST_F(PlatformNotificationContextTest, GetOldestNotificationTime) {
#if BUILDFLAG(IS_MAC)
if (base::mac::MacOSMajorVersion() == 26) {
GTEST_SKIP() << "Disabled on macOS Tahoe.";
}
#endif
base::HistogramTester histogram_tester;
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
GURL origin("https://example.com");
base::Time oldest_notification_time = base::Time::Now();
for (size_t i = 0; i < 5; ++i) {
NotificationDatabaseData notification_database_data;
notification_database_data.service_worker_registration_id =
kFakeServiceWorkerRegistrationId;
notification_database_data.origin = origin;
std::string notification_id = WriteNotificationDataSync(
context.get(), origin, notification_database_data);
task_environment_.FastForwardBy(base::Days(1));
}
EXPECT_EQ(5u, GetStoredNotificationsSync(context.get(), origin).size());
EXPECT_EQ(5u, GetDisplayedNotificationsSync(service).size());
base::RunLoop run_loop;
context->CountVisibleNotificationsForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::BindLambdaForTesting([&](bool success, int count) {
EXPECT_TRUE(success);
EXPECT_EQ(5, count);
base::TimeDelta delta = base::Time::Now() - oldest_notification_time;
histogram_tester.ExpectUniqueSample(
"Notifications.Database.OldestNotificationTimeInMinutes",
delta.InMinutes(), 1);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PlatformNotificationContextTest,
GetOldestNotificationTimeForEmptyOrigin) {
base::HistogramTester histogram_tester;
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
PlatformNotificationService* service =
browser_context()->GetPlatformNotificationService();
GURL origin("https://example.com");
EXPECT_EQ(0u, GetDisplayedNotificationsSync(service).size());
base::RunLoop run_loop;
context->CountVisibleNotificationsForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::BindLambdaForTesting([&](bool success, int count) {
EXPECT_TRUE(success);
EXPECT_EQ(0, count);
histogram_tester.ExpectTotalCount(
"Notifications.Database.OldestNotificationTimeInMinutes", 0);
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(PlatformNotificationContextTest, WriteReadNotificationMetadata) {
base::HistogramTester histogram_tester;
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
NotificationDatabaseData data;
std::string notification_id =
WriteNotificationDataSync(context.get(), origin, data);
std::string metadata_key_1 = "content-detection";
std::string old_metadata_value_1 = "{\"bad\":\"value\"}";
ASSERT_TRUE(WriteNotificationMetadataSync(context.get(), notification_id,
origin, metadata_key_1,
old_metadata_value_1));
std::string new_metadata_value_1 = "{\"good\":\"value\"}";
ASSERT_TRUE(WriteNotificationMetadataSync(context.get(), notification_id,
origin, metadata_key_1,
new_metadata_value_1));
std::string metadata_key_2 = "other-type-of-metadata";
std::string metadata_value_2 = "{\"key\":\"other value\"}";
ASSERT_TRUE(WriteNotificationMetadataSync(context.get(), notification_id,
origin, metadata_key_2,
metadata_value_2));
context->ReadNotificationDataAndRecordInteraction(
notification_id, origin, PlatformNotificationContext::Interaction::NONE,
base::BindOnce(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
const NotificationDatabaseData& read_database_data = database_data();
EXPECT_EQ(origin, read_database_data.origin);
ASSERT_TRUE(read_database_data.serialized_metadata.contains(metadata_key_1));
EXPECT_EQ(new_metadata_value_1,
read_database_data.serialized_metadata.at(metadata_key_1));
ASSERT_TRUE(read_database_data.serialized_metadata.contains(metadata_key_2));
EXPECT_EQ(metadata_value_2,
read_database_data.serialized_metadata.at(metadata_key_2));
histogram_tester.ExpectBucketCount(
"Notifications.Database.WriteNotificationMetadataReadResult",
0, 3);
histogram_tester.ExpectBucketCount(
"Notifications.Database.WriteNotificationMetadataUpdateResult",
0, 3);
}
}