| // Copyright 2018 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 "content/browser/notifications/notification_storage.h" |
| |
| #include "base/bind.h" |
| #include "base/guid.h" |
| #include "base/run_loop.h" |
| #include "content/browser/service_worker/embedded_worker_test_helper.h" |
| #include "content/browser/service_worker/service_worker_registration.h" |
| #include "content/common/service_worker/service_worker_types.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 "third_party/blink/public/common/service_worker/service_worker_status_code.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| class NotificationStorageTest : public ::testing::Test { |
| public: |
| NotificationStorageTest() |
| : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
| origin_(GURL("https://example.com")), |
| success_(false), |
| service_worker_registration_id_( |
| blink::mojom::kInvalidServiceWorkerRegistrationId) { |
| helper_ = std::make_unique<EmbeddedWorkerTestHelper>(base::FilePath()); |
| storage_ = |
| std::make_unique<NotificationStorage>(helper_->context_wrapper()); |
| } |
| |
| void DidRegisterServiceWorker(base::OnceClosure quit_closure, |
| blink::ServiceWorkerStatusCode status, |
| const std::string& status_message, |
| int64_t service_worker_registration_id) { |
| DCHECK(service_worker_registration_id_); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) << status_message; |
| |
| service_worker_registration_id_ = service_worker_registration_id; |
| |
| std::move(quit_closure).Run(); |
| } |
| |
| void DidFindServiceWorkerRegistration( |
| scoped_refptr<ServiceWorkerRegistration>* out_service_worker_registration, |
| base::OnceClosure quit_closure, |
| blink::ServiceWorkerStatusCode status, |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration) { |
| DCHECK(out_service_worker_registration); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) |
| << blink::ServiceWorkerStatusToString(status); |
| |
| *out_service_worker_registration = service_worker_registration; |
| |
| std::move(quit_closure).Run(); |
| } |
| |
| // Registers a Service Worker for the testing origin and returns its |
| // |service_worker_registration_id|. If registration failed, this will be |
| // blink::mojom::kInvalidServiceWorkerRegistrationId. The |
| // ServiceWorkerRegistration will be kept alive for the test's lifetime. |
| int64_t RegisterServiceWorker() { |
| GURL script_url = origin_; |
| |
| { |
| blink::mojom::ServiceWorkerRegistrationOptions options; |
| options.scope = origin_; |
| base::RunLoop run_loop; |
| helper_->context()->RegisterServiceWorker( |
| script_url, options, |
| base::BindOnce(&NotificationStorageTest::DidRegisterServiceWorker, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| if (service_worker_registration_id_ == |
| blink::mojom::kInvalidServiceWorkerRegistrationId) { |
| ADD_FAILURE() << "Could not obtain a valid Service Worker registration"; |
| return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| } |
| |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration; |
| |
| { |
| base::RunLoop run_loop; |
| helper_->context()->storage()->FindRegistrationForId( |
| service_worker_registration_id_, origin_, |
| base::BindOnce( |
| &NotificationStorageTest::DidFindServiceWorkerRegistration, |
| base::Unretained(this), &service_worker_registration, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // Wait for the worker to be activated. |
| thread_bundle_.RunUntilIdle(); |
| |
| if (!service_worker_registration) { |
| ADD_FAILURE() << "Could not find the new Service Worker registration."; |
| return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| } |
| |
| service_worker_registrations_.push_back( |
| std::move(service_worker_registration)); |
| |
| return service_worker_registration_id_; |
| } |
| |
| void DidWriteNotificationDataSynchronous(base::OnceClosure quit_closure, |
| bool success, |
| const std::string& notification_id) { |
| success_ = success; |
| notification_id_ = notification_id; |
| std::move(quit_closure).Run(); |
| } |
| |
| void WriteNotificationDataSynchronous(const NotificationDatabaseData& data) { |
| base::RunLoop run_loop; |
| storage_->WriteNotificationData( |
| data, base::Bind( |
| &NotificationStorageTest::DidWriteNotificationDataSynchronous, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| void DidReadNotificationDataAndRecordInteractionSynchronous( |
| base::OnceClosure quit_closure, |
| bool success, |
| const NotificationDatabaseData& data) { |
| success_ = success; |
| out_data_ = data; |
| std::move(quit_closure).Run(); |
| } |
| |
| NotificationDatabaseData ReadNotificationDataAndRecordInteractionSynchronous( |
| int64_t service_worker_registration_id, |
| const std::string& notification_id, |
| PlatformNotificationContext::Interaction interaction) { |
| base::RunLoop run_loop; |
| storage_->ReadNotificationDataAndRecordInteraction( |
| service_worker_registration_id, notification_id, interaction, |
| base::Bind(&NotificationStorageTest:: |
| DidReadNotificationDataAndRecordInteractionSynchronous, |
| base::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| return out_data_; |
| } |
| |
| // Generates a random notification ID. The format of the ID is opaque. |
| std::string GenerateNotificationId() { return base::GenerateGUID(); } |
| |
| protected: |
| TestBrowserThreadBundle thread_bundle_; // Must be first member |
| std::unique_ptr<EmbeddedWorkerTestHelper> helper_; |
| GURL origin_; |
| TestBrowserContext browser_context_; |
| bool success_; |
| int64_t service_worker_registration_id_; |
| NotificationDatabaseData out_data_; |
| |
| private: |
| std::unique_ptr<NotificationStorage> storage_; |
| std::string notification_id_; |
| |
| // Vector of ServiceWorkerRegistration instances that have to be kept alive |
| // for the lifetime of this test. |
| std::vector<scoped_refptr<ServiceWorkerRegistration>> |
| service_worker_registrations_; |
| }; |
| |
| TEST_F(NotificationStorageTest, WriteReadNotification) { |
| NotificationDatabaseData data; |
| data.notification_id = GenerateNotificationId(); |
| data.origin = origin_; |
| data.service_worker_registration_id = RegisterServiceWorker(); |
| ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| data.service_worker_registration_id); |
| WriteNotificationDataSynchronous(data); |
| ASSERT_TRUE(success_); |
| |
| NotificationDatabaseData read_data = |
| ReadNotificationDataAndRecordInteractionSynchronous( |
| data.service_worker_registration_id, data.notification_id, |
| PlatformNotificationContext::Interaction::NONE); |
| ASSERT_TRUE(success_); |
| EXPECT_EQ(data.origin, read_data.origin); |
| EXPECT_EQ(data.notification_id, read_data.notification_id); |
| EXPECT_EQ(data.service_worker_registration_id, |
| read_data.service_worker_registration_id); |
| } |
| |
| TEST_F(NotificationStorageTest, ReadInvalidNotification) { |
| int64_t service_worker_registration_id = RegisterServiceWorker(); |
| ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| service_worker_registration_id); |
| ReadNotificationDataAndRecordInteractionSynchronous( |
| service_worker_registration_id, "bad_id", |
| PlatformNotificationContext::Interaction::NONE); |
| ASSERT_FALSE(success_); |
| } |
| |
| TEST_F(NotificationStorageTest, ReadAndUpdateInteraction) { |
| NotificationDatabaseData data, read_data; |
| data.notification_id = GenerateNotificationId(); |
| data.origin = origin_; |
| data.service_worker_registration_id = RegisterServiceWorker(); |
| ASSERT_NE(blink::mojom::kInvalidServiceWorkerRegistrationId, |
| data.service_worker_registration_id); |
| |
| WriteNotificationDataSynchronous(data); |
| ASSERT_TRUE(success_); |
| |
| // Check that the time deltas have not yet been set. |
| EXPECT_FALSE(read_data.time_until_first_click_millis.has_value()); |
| EXPECT_FALSE(read_data.time_until_last_click_millis.has_value()); |
| EXPECT_FALSE(read_data.time_until_close_millis.has_value()); |
| |
| // Check that when a notification has an interaction, the appropriate field is |
| // updated on the read. |
| read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| data.service_worker_registration_id, data.notification_id, |
| PlatformNotificationContext::Interaction::CLICKED); |
| ASSERT_TRUE(success_); |
| EXPECT_EQ(1, read_data.num_clicks); |
| |
| read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| data.service_worker_registration_id, data.notification_id, |
| PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED); |
| ASSERT_TRUE(success_); |
| EXPECT_EQ(1, read_data.num_action_button_clicks); |
| |
| read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| data.service_worker_registration_id, data.notification_id, |
| PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED); |
| ASSERT_TRUE(success_); |
| EXPECT_EQ(2, read_data.num_action_button_clicks); |
| |
| // Check that the click timestamps are correctly updated. |
| EXPECT_TRUE(read_data.time_until_first_click_millis.has_value()); |
| EXPECT_TRUE(read_data.time_until_last_click_millis.has_value()); |
| |
| // Check that when a read with a CLOSED interaction occurs, the correct |
| // field is updated. |
| read_data = ReadNotificationDataAndRecordInteractionSynchronous( |
| data.service_worker_registration_id, data.notification_id, |
| PlatformNotificationContext::Interaction::CLOSED); |
| ASSERT_TRUE(success_); |
| EXPECT_EQ(true, read_data.time_until_close_millis.has_value()); |
| } |
| |
| } // namespace content |