| // 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 "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| // Fake Service Worker registration id to use in tests requiring one. |
| const int64_t kFakeServiceWorkerRegistrationId = 42; |
| |
| 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, |
| ServiceWorkerStatusCode status, |
| const std::string& status_message, |
| int64_t service_worker_registration_id) { |
| DCHECK(store_registration_id); |
| EXPECT_EQ(SERVICE_WORKER_OK, 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(ServiceWorkerStatusCode* store_status, |
| 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. |
| PlatformNotificationContextImpl* CreatePlatformNotificationContext() { |
| PlatformNotificationContextImpl* context = |
| new PlatformNotificationContextImpl(base::FilePath(), &browser_context_, |
| nullptr); |
| context->Initialize(); |
| |
| OverrideTaskRunnerForTesting(context); |
| 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 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_; |
| TestBrowserContext browser_context_; |
| |
| bool success_; |
| NotificationDatabaseData database_data_; |
| std::string notification_id_; |
| }; |
| |
| TEST_F(PlatformNotificationContextTest, ReadNonExistentNotification) { |
| scoped_refptr<PlatformNotificationContextImpl> context = |
| CreatePlatformNotificationContext(); |
| |
| context->ReadNotificationData( |
| "invalid-notification-id", GURL("https://example.com"), |
| 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( |
| 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->ReadNotificationData( |
| notification_id(), origin, |
| 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( |
| 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( |
| 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), ¬ification_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( |
| 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->ReadNotificationData( |
| notification_id(), origin, |
| 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 = kInvalidServiceWorkerRegistrationId; |
| |
| // Register a Service Worker to get a valid registration id. |
| embedded_worker_test_helper->context()->RegisterServiceWorker( |
| origin, script_url, nullptr /* provider_host */, |
| base::Bind(&PlatformNotificationContextTest::DidRegisterServiceWorker, |
| base::Unretained(this), &service_worker_registration_id)); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_NE(service_worker_registration_id, |
| kInvalidServiceWorkerRegistrationId); |
| |
| NotificationDatabaseData notification_database_data; |
| |
| // Create a notification for that Service Worker registration. |
| notification_context->WriteNotificationData( |
| origin, notification_database_data, |
| base::Bind(&PlatformNotificationContextTest::DidWriteNotificationData, |
| base::Unretained(this))); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| ASSERT_TRUE(success()); |
| EXPECT_FALSE(notification_id().empty()); |
| |
| ServiceWorkerStatusCode unregister_status; |
| |
| // Now drop the Service Worker registration which owns that notification. |
| embedded_worker_test_helper->context()->UnregisterServiceWorker( |
| origin, |
| base::Bind(&PlatformNotificationContextTest::DidUnregisterServiceWorker, |
| base::Unretained(this), &unregister_status)); |
| |
| base::RunLoop().RunUntilIdle(); |
| ASSERT_EQ(SERVICE_WORKER_OK, unregister_status); |
| |
| // And verify that the associated notification has indeed been dropped. |
| notification_context->ReadNotificationData( |
| notification_id(), origin, |
| 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( |
| 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 ServiceWorkerContextObserver, |
| // 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->ReadNotificationData( |
| notification_id(), origin, |
| 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->ReadNotificationData( |
| "invalid-notification-id", GURL("https://example.com"), |
| 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), ¬ification_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( |
| 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), ¬ification_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); |
| } |
| } |
| |
| } // namespace content |