blob: 761d302fbf3428d4830dd41fc1ad299e2055a0b6 [file] [log] [blame]
// Copyright 2015 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/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.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_wrapper.h"
#include "content/common/service_worker/service_worker_types.h"
#include "content/public/browser/notification_database_data.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 "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/mojom/service_worker/service_worker_registration.mojom.h"
#include "url/gurl.h"
namespace content {
// Fake Service Worker registration id to use in tests requiring one.
const int64_t kFakeServiceWorkerRegistrationId = 42;
class NotificationBrowserClient : public TestContentBrowserClient {
public:
NotificationBrowserClient()
: platform_notification_service_(
std::make_unique<MockPlatformNotificationService>()) {}
PlatformNotificationService* GetPlatformNotificationService() override {
return platform_notification_service_.get();
}
private:
std::unique_ptr<PlatformNotificationService> platform_notification_service_;
};
class PlatformNotificationContextTest : public ::testing::Test {
public:
PlatformNotificationContextTest()
: thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), success_(false) {}
// Callback to provide when reading a single notification from the database.
void DidReadNotificationData(bool success,
const NotificationDatabaseData& database_data) {
success_ = success;
database_data_ = database_data;
}
// Callback to provide when writing a notification to the database.
void DidWriteNotificationData(bool success,
const std::string& notification_id) {
success_ = success;
notification_id_ = notification_id;
}
// Callback to provide when deleting notification data from the database.
void DidDeleteNotificationData(bool success) { success_ = success; }
// Callback to provide when registering a Service Worker with a Service
// Worker Context. Will write the registration id to |store_registration_id|.
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;
}
// Callback to provide when unregistering a Service Worker. Will write the
// resulting status code to |store_status|.
void DidUnregisterServiceWorker(blink::ServiceWorkerStatusCode* store_status,
blink::ServiceWorkerStatusCode status) {
DCHECK(store_status);
*store_status = status;
}
// Callback to provide when reading multiple notifications from the database.
// Will store the success value in the class member, and write the read
// notification datas to |store_notification_datas|.
void DidReadAllNotificationDatas(
std::vector<NotificationDatabaseData>* store_notification_datas,
bool success,
const std::vector<NotificationDatabaseData>& notification_datas) {
DCHECK(store_notification_datas);
success_ = success;
*store_notification_datas = notification_datas;
}
protected:
// Creates a new PlatformNotificationContextImpl instance. When using this
// method, the underlying database will always be created in memory.
scoped_refptr<PlatformNotificationContextImpl>
CreatePlatformNotificationContext() {
auto context = base::MakeRefCounted<PlatformNotificationContextImpl>(
base::FilePath(), &browser_context_, nullptr);
context->Initialize();
OverrideTaskRunnerForTesting(context.get());
return context;
}
// Overrides the task runner in |context| with the current message loop
// proxy, to reduce the number of threads involved in the tests.
void OverrideTaskRunnerForTesting(PlatformNotificationContextImpl* context) {
context->SetTaskRunnerForTesting(base::ThreadTaskRunnerHandle::Get());
}
// Returns the testing browsing context that can be used for this test.
BrowserContext* browser_context() { return &browser_context_; }
// Returns whether the last invoked callback finished successfully.
bool success() const { return success_; }
// Returns the next persistent notification id for tests.
int64_t next_persistent_notification_id() {
return next_persistent_notification_id_++;
}
// Returns the NotificationDatabaseData associated with the last invoked
// ReadNotificationData callback.
const NotificationDatabaseData& database_data() const {
return database_data_;
}
// Returns the notification id of the notification last written.
const std::string& notification_id() const { return notification_id_; }
private:
TestBrowserThreadBundle thread_bundle_; // Must be first member
TestBrowserContext browser_context_;
bool success_;
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::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The read operation should have failed, as it does not exist.
ASSERT_FALSE(success());
}
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::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The write operation should have succeeded with a notification id.
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The read operation should have succeeded, with the right notification.
ASSERT_TRUE(success());
const NotificationDatabaseData& read_database_data = database_data();
EXPECT_EQ(notification_database_data.origin, read_database_data.origin);
}
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 =
base::ASCIIToUTF16("First");
notification_database_data.notification_data.tag = tag;
// Write the first notification with the given |tag|.
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
std::string read_notification_id = notification_id();
// The write operation should have succeeded with a notification id.
ASSERT_TRUE(success());
EXPECT_FALSE(read_notification_id.empty());
notification_database_data.notification_data.title =
base::ASCIIToUTF16("Second");
// Write the second notification with the given |tag|.
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
ASSERT_FALSE(notification_id().empty());
ASSERT_EQ(notification_id(), read_notification_id);
// Reading the notifications should only yield the second, replaced one.
std::vector<NotificationDatabaseData> notification_database_datas;
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::Bind(&PlatformNotificationContextTest::DidReadAllNotificationDatas,
base::Unretained(this), &notification_database_datas));
base::RunLoop().RunUntilIdle();
// The read operation should have succeeded, with the right notification.
ASSERT_TRUE(success());
ASSERT_EQ(1u, notification_database_datas.size());
EXPECT_EQ(tag, notification_database_datas[0].notification_data.tag);
EXPECT_EQ(base::ASCIIToUTF16("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"),
base::Bind(&PlatformNotificationContextTest::DidDeleteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The notification may not have existed, but since the goal of deleting data
// is to make sure that it's gone, the goal has been satisfied. As such,
// deleting a non-existent notification is considered to be a success.
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::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The write operation should have succeeded with a notification id.
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
context->DeleteNotificationData(
notification_id(), origin,
base::Bind(&PlatformNotificationContextTest::DidDeleteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The notification existed, so it should have been removed successfully.
ASSERT_TRUE(success());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The notification was removed, so we shouldn't be able to read it from
// the database anymore.
EXPECT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, ServiceWorkerUnregistered) {
std::unique_ptr<EmbeddedWorkerTestHelper> embedded_worker_test_helper(
new EmbeddedWorkerTestHelper(base::FilePath()));
// Manually create the PlatformNotificationContextImpl so that the Service
// Worker context wrapper can be passed in.
scoped_refptr<PlatformNotificationContextImpl> notification_context(
new PlatformNotificationContextImpl(
base::FilePath(), browser_context(),
embedded_worker_test_helper->context_wrapper()));
notification_context->Initialize();
OverrideTaskRunnerForTesting(notification_context.get());
GURL origin("https://example.com");
GURL script_url("https://example.com/worker.js");
int64_t service_worker_registration_id =
blink::mojom::kInvalidServiceWorkerRegistrationId;
// Register a Service Worker to get a valid registration id.
blink::mojom::ServiceWorkerRegistrationOptions options;
options.scope = origin;
embedded_worker_test_helper->context()->RegisterServiceWorker(
script_url, options,
base::BindOnce(&PlatformNotificationContextTest::DidRegisterServiceWorker,
base::Unretained(this), &service_worker_registration_id));
base::RunLoop().RunUntilIdle();
ASSERT_NE(service_worker_registration_id,
blink::mojom::kInvalidServiceWorkerRegistrationId);
NotificationDatabaseData notification_database_data;
// Create a notification for that Service Worker registration.
notification_context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
blink::ServiceWorkerStatusCode unregister_status;
// Now drop the Service Worker registration which owns that notification.
embedded_worker_test_helper->context()->UnregisterServiceWorker(
origin, base::BindOnce(
&PlatformNotificationContextTest::DidUnregisterServiceWorker,
base::Unretained(this), &unregister_status));
base::RunLoop().RunUntilIdle();
ASSERT_EQ(blink::ServiceWorkerStatusCode::kOk, unregister_status);
// And verify that the associated notification has indeed been dropped.
notification_context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
}
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::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The write operation should have succeeded with a notification id.
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
// Call the OnStorageWiped override from the ServiceWorkerContextCoreObserver,
// which indicates that the database should go away entirely.
context->OnStorageWiped();
// Verify that reading notification data fails because the data does not
// exist anymore. Deliberately omit RunUntilIdle(), since this is unlikely to
// be the case when OnStorageWiped gets called in production.
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin, PlatformNotificationContext::Interaction::NONE,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(success());
}
TEST_F(PlatformNotificationContextTest, DestroyOnDiskDatabase) {
base::ScopedTempDir database_dir;
ASSERT_TRUE(database_dir.CreateUniqueTempDir());
// Manually construct the PlatformNotificationContextImpl because this test
// requires the database to be created on the filesystem.
scoped_refptr<PlatformNotificationContextImpl> context(
new PlatformNotificationContextImpl(database_dir.GetPath(),
browser_context(), nullptr));
OverrideTaskRunnerForTesting(context.get());
// Trigger a read-operation to force creating the database.
context->ReadNotificationDataAndRecordInteraction(
"invalid-notification-id", GURL("https://example.com"),
PlatformNotificationContext::Interaction::NONE,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(IsDirectoryEmpty(database_dir.GetPath()));
EXPECT_FALSE(success());
// Blow away the database by faking a Service Worker Context wipe-out.
context->OnStorageWiped();
base::RunLoop().RunUntilIdle();
// The database's directory should be empty at this point.
EXPECT_TRUE(IsDirectoryEmpty(database_dir.GetPath()));
}
TEST_F(PlatformNotificationContextTest, ReadAllServiceWorkerDataEmpty) {
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
GURL origin("https://example.com");
std::vector<NotificationDatabaseData> notification_database_datas;
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::Bind(&PlatformNotificationContextTest::DidReadAllNotificationDatas,
base::Unretained(this), &notification_database_datas));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(success());
EXPECT_EQ(0u, notification_database_datas.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;
// Insert ten notifications into the database belonging to origin and the
// test Service Worker Registration id.
for (int i = 0; i < 10; ++i) {
context->WriteNotificationData(
next_persistent_notification_id(), kFakeServiceWorkerRegistrationId,
origin, notification_database_data,
base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
}
// Now read the notifications from the database again. There should be ten,
// all set with the correct origin and Service Worker Registration id.
std::vector<NotificationDatabaseData> notification_database_datas;
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::Bind(&PlatformNotificationContextTest::DidReadAllNotificationDatas,
base::Unretained(this), &notification_database_datas));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
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) {
NotificationBrowserClient notification_browser_client;
SetBrowserClientForTesting(&notification_browser_client);
scoped_refptr<PlatformNotificationContextImpl> context =
CreatePlatformNotificationContext();
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::Bind(&PlatformNotificationContextTest::DidWriteNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
EXPECT_FALSE(notification_id().empty());
PlatformNotificationService* service =
notification_browser_client.GetPlatformNotificationService();
service->DisplayPersistentNotification(browser_context(), notification_id(),
origin, origin, notification_data,
notification_resources);
std::vector<NotificationDatabaseData> notification_database_datas;
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::Bind(&PlatformNotificationContextTest::DidReadAllNotificationDatas,
base::Unretained(this), &notification_database_datas));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
ASSERT_EQ(1u, notification_database_datas.size());
// Delete the notification from the display service without removing it from
// the database. It should automatically synchronize on the next read.
service->ClosePersistentNotification(browser_context(), notification_id());
context->ReadAllNotificationDataForServiceWorkerRegistration(
origin, kFakeServiceWorkerRegistrationId,
base::Bind(&PlatformNotificationContextTest::DidReadAllNotificationDatas,
base::Unretained(this), &notification_database_datas));
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(success());
ASSERT_EQ(0u, notification_database_datas.size());
context->ReadNotificationDataAndRecordInteraction(
notification_id(), origin,
PlatformNotificationContext::Interaction::CLOSED,
base::Bind(&PlatformNotificationContextTest::DidReadNotificationData,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
// The notification was removed, so we shouldn't be able to read it from
// the database anymore.
EXPECT_FALSE(success());
}
} // namespace content