1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/notifications/notification_database.h"
7 #include "base/files/scoped_temp_dir.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "content/public/browser/notification_database_data.h"
11 #include "content/public/common/platform_notification_data.h"
12 #include "testing/gtest/include/gtest/gtest.h"
13 #include "third_party/leveldatabase/src/include/leveldb/db.h"
14 #include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
19 const int kExampleServiceWorkerRegistrationId
= 42;
23 int64_t service_worker_registration_id
;
24 } kExampleNotificationData
[] = {
25 { "https://example.com", 0 },
26 { "https://example.com", kExampleServiceWorkerRegistrationId
},
27 { "https://example.com", kExampleServiceWorkerRegistrationId
},
28 { "https://example.com", kExampleServiceWorkerRegistrationId
+ 1 },
29 { "https://chrome.com", 0 },
30 { "https://chrome.com", 0 },
31 { "https://chrome.com", kExampleServiceWorkerRegistrationId
}
34 class NotificationDatabaseTest
: public ::testing::Test
{
36 // Creates a new NotificationDatabase instance in memory.
37 NotificationDatabase
* CreateDatabaseInMemory() {
38 return new NotificationDatabase(base::FilePath());
41 // Creates a new NotificationDatabase instance in |path|.
42 NotificationDatabase
* CreateDatabaseOnFileSystem(
43 const base::FilePath
& path
) {
44 return new NotificationDatabase(path
);
47 // Creates a new notification for |service_worker_registration_id| belonging
48 // to |origin| and writes it to the database. The written notification id
49 // will be stored in |notification_id|.
50 void CreateAndWriteNotification(NotificationDatabase
* database
,
52 int64_t service_worker_registration_id
,
53 int64_t* notification_id
) {
54 NotificationDatabaseData database_data
;
55 database_data
.origin
= origin
;
56 database_data
.service_worker_registration_id
=
57 service_worker_registration_id
;
59 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
60 database
->WriteNotificationData(origin
,
65 // Populates |database| with a series of example notifications that differ in
66 // their origin and Service Worker registration id.
67 void PopulateDatabaseWithExampleData(NotificationDatabase
* database
) {
68 int64_t notification_id
;
69 for (size_t i
= 0; i
< arraysize(kExampleNotificationData
); ++i
) {
70 ASSERT_NO_FATAL_FAILURE(CreateAndWriteNotification(
72 GURL(kExampleNotificationData
[i
].origin
),
73 kExampleNotificationData
[i
].service_worker_registration_id
,
78 // Returns if |database| has been opened.
79 bool IsDatabaseOpen(NotificationDatabase
* database
) {
80 return database
->IsOpen();
83 // Returns if |database| is an in-memory only database.
84 bool IsInMemoryDatabase(NotificationDatabase
* database
) {
85 return database
->IsInMemoryDatabase();
88 // Writes a LevelDB key-value pair directly to the LevelDB backing the
89 // notification database in |database|.
90 void WriteLevelDBKeyValuePair(NotificationDatabase
* database
,
91 const std::string
& key
,
92 const std::string
& value
) {
93 leveldb::Status status
=
94 database
->GetDBForTesting()->Put(leveldb::WriteOptions(), key
, value
);
95 ASSERT_TRUE(status
.ok());
99 TEST_F(NotificationDatabaseTest
, OpenCloseMemory
) {
100 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
102 // Should return false because the database does not exist in memory.
103 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
104 database
->Open(false /* create_if_missing */));
106 // Should return true, indicating that the database could be created.
107 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
108 database
->Open(true /* create_if_missing */));
110 EXPECT_TRUE(IsDatabaseOpen(database
.get()));
111 EXPECT_TRUE(IsInMemoryDatabase(database
.get()));
113 // Verify that in-memory databases do not persist when being re-created.
114 database
.reset(CreateDatabaseInMemory());
116 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
117 database
->Open(false /* create_if_missing */));
120 TEST_F(NotificationDatabaseTest
, OpenCloseFileSystem
) {
121 base::ScopedTempDir database_dir
;
122 ASSERT_TRUE(database_dir
.CreateUniqueTempDir());
124 scoped_ptr
<NotificationDatabase
> database(
125 CreateDatabaseOnFileSystem(database_dir
.path()));
127 // Should return false because the database does not exist on the file system.
128 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
129 database
->Open(false /* create_if_missing */));
131 // Should return true, indicating that the database could be created.
132 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
133 database
->Open(true /* create_if_missing */));
135 EXPECT_TRUE(IsDatabaseOpen(database
.get()));
136 EXPECT_FALSE(IsInMemoryDatabase(database
.get()));
138 // Close the database, and re-open it without attempting to create it because
139 // the files on the file system should still exist as expected.
140 database
.reset(CreateDatabaseOnFileSystem(database_dir
.path()));
141 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
142 database
->Open(false /* create_if_missing */));
145 TEST_F(NotificationDatabaseTest
, DestroyDatabase
) {
146 base::ScopedTempDir database_dir
;
147 ASSERT_TRUE(database_dir
.CreateUniqueTempDir());
149 scoped_ptr
<NotificationDatabase
> database(
150 CreateDatabaseOnFileSystem(database_dir
.path()));
152 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
153 database
->Open(true /* create_if_missing */));
154 EXPECT_TRUE(IsDatabaseOpen(database
.get()));
156 // Destroy the database. This will immediately close it as well.
157 ASSERT_EQ(NotificationDatabase::STATUS_OK
, database
->Destroy());
158 EXPECT_FALSE(IsDatabaseOpen(database
.get()));
160 // Try to re-open the database (but not re-create it). This should fail as
161 // the files associated with the database should have been blown away.
162 database
.reset(CreateDatabaseOnFileSystem(database_dir
.path()));
163 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
164 database
->Open(false /* create_if_missing */));
167 TEST_F(NotificationDatabaseTest
, NotificationIdIncrements
) {
168 base::ScopedTempDir database_dir
;
169 ASSERT_TRUE(database_dir
.CreateUniqueTempDir());
171 scoped_ptr
<NotificationDatabase
> database(
172 CreateDatabaseOnFileSystem(database_dir
.path()));
174 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
175 database
->Open(true /* create_if_missing */));
177 GURL
origin("https://example.com");
179 int64_t notification_id
= 0;
181 // Verify that getting two ids on the same database instance results in
182 // incrementing values. Notification ids will start at 1.
183 ASSERT_NO_FATAL_FAILURE(CreateAndWriteNotification(
184 database
.get(), origin
, 0 /* sw_registration_id */, ¬ification_id
));
185 EXPECT_EQ(notification_id
, 1);
187 ASSERT_NO_FATAL_FAILURE(CreateAndWriteNotification(
188 database
.get(), origin
, 0 /* sw_registration_id */, ¬ification_id
));
189 EXPECT_EQ(notification_id
, 2);
191 database
.reset(CreateDatabaseOnFileSystem(database_dir
.path()));
192 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
193 database
->Open(false /* create_if_missing */));
195 // Verify that the next notification id was stored in the database, and
196 // continues where we expect it to be, even after closing and opening it.
197 ASSERT_NO_FATAL_FAILURE(CreateAndWriteNotification(
198 database
.get(), origin
, 0 /* sw_registration_id */, ¬ification_id
));
199 EXPECT_EQ(notification_id
, 3);
202 TEST_F(NotificationDatabaseTest
, NotificationIdIncrementsStorage
) {
203 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
204 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
205 database
->Open(true /* create_if_missing */));
207 GURL
origin("https://example.com");
209 NotificationDatabaseData database_data
;
210 database_data
.notification_id
= -1;
212 int64_t notification_id
= 0;
213 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
214 database
->WriteNotificationData(origin
,
218 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
219 database
->ReadNotificationData(notification_id
,
223 EXPECT_EQ(notification_id
, database_data
.notification_id
);
226 TEST_F(NotificationDatabaseTest
, NotificationIdCorruption
) {
227 base::ScopedTempDir database_dir
;
228 ASSERT_TRUE(database_dir
.CreateUniqueTempDir());
230 scoped_ptr
<NotificationDatabase
> database(
231 CreateDatabaseOnFileSystem(database_dir
.path()));
233 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
234 database
->Open(true /* create_if_missing */));
236 GURL
origin("https://example.com");
238 NotificationDatabaseData database_data
;
239 int64_t notification_id
= 0;
241 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
242 database
->WriteNotificationData(origin
,
245 EXPECT_EQ(notification_id
, 1);
247 // Deliberately write an invalid value as the next notification id. When
248 // re-opening the database, the Open() method should realize that an invalid
249 // value is being read, and mark the database as corrupted.
250 ASSERT_NO_FATAL_FAILURE(WriteLevelDBKeyValuePair(database
.get(),
251 "NEXT_NOTIFICATION_ID",
254 database
.reset(CreateDatabaseOnFileSystem(database_dir
.path()));
255 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_CORRUPTED
,
256 database
->Open(false /* create_if_missing */));
259 TEST_F(NotificationDatabaseTest
, ReadInvalidNotificationData
) {
260 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
261 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
262 database
->Open(true /* create_if_missing */));
264 NotificationDatabaseData database_data
;
266 // Reading the notification data for a notification that does not exist should
267 // return the ERROR_NOT_FOUND status code.
268 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
269 database
->ReadNotificationData(9001,
270 GURL("https://chrome.com"),
274 TEST_F(NotificationDatabaseTest
, ReadNotificationDataDifferentOrigin
) {
275 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
276 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
277 database
->Open(true /* create_if_missing */));
279 int64_t notification_id
= 0;
280 GURL
origin("https://example.com");
282 NotificationDatabaseData database_data
, read_database_data
;
283 database_data
.notification_data
.title
= base::UTF8ToUTF16("My Notification");
285 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
286 database
->WriteNotificationData(origin
,
290 // Reading the notification from the database when given a different origin
291 // should return the ERROR_NOT_FOUND status code.
292 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
293 database
->ReadNotificationData(notification_id
,
294 GURL("https://chrome.com"),
295 &read_database_data
));
297 // However, reading the notification from the database with the same origin
298 // should return STATUS_OK and the associated notification data.
299 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
300 database
->ReadNotificationData(notification_id
,
302 &read_database_data
));
304 EXPECT_EQ(database_data
.notification_data
.title
,
305 read_database_data
.notification_data
.title
);
308 TEST_F(NotificationDatabaseTest
, ReadNotificationDataReflection
) {
309 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
310 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
311 database
->Open(true /* create_if_missing */));
313 int64_t notification_id
= 0;
315 GURL
origin("https://example.com");
317 PlatformNotificationData notification_data
;
318 notification_data
.title
= base::UTF8ToUTF16("My Notification");
319 notification_data
.direction
=
320 PlatformNotificationData::DIRECTION_RIGHT_TO_LEFT
;
321 notification_data
.lang
= "nl-NL";
322 notification_data
.body
= base::UTF8ToUTF16("Hello, world!");
323 notification_data
.tag
= "replace id";
324 notification_data
.icon
= GURL("https://example.com/icon.png");
325 notification_data
.silent
= true;
327 NotificationDatabaseData database_data
;
328 database_data
.notification_id
= notification_id
;
329 database_data
.origin
= origin
;
330 database_data
.service_worker_registration_id
= 42;
331 database_data
.notification_data
= notification_data
;
333 // Write the constructed notification to the database, and then immediately
334 // read it back from the database again as well.
335 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
336 database
->WriteNotificationData(origin
,
340 NotificationDatabaseData read_database_data
;
341 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
342 database
->ReadNotificationData(notification_id
,
344 &read_database_data
));
346 // Verify that all members retrieved from the database are exactly the same
347 // as the ones that were written to it. This tests the serialization behavior.
349 EXPECT_EQ(notification_id
, read_database_data
.notification_id
);
351 EXPECT_EQ(database_data
.origin
, read_database_data
.origin
);
352 EXPECT_EQ(database_data
.service_worker_registration_id
,
353 read_database_data
.service_worker_registration_id
);
355 const PlatformNotificationData
& read_notification_data
=
356 read_database_data
.notification_data
;
358 EXPECT_EQ(notification_data
.title
, read_notification_data
.title
);
359 EXPECT_EQ(notification_data
.direction
, read_notification_data
.direction
);
360 EXPECT_EQ(notification_data
.lang
, read_notification_data
.lang
);
361 EXPECT_EQ(notification_data
.body
, read_notification_data
.body
);
362 EXPECT_EQ(notification_data
.tag
, read_notification_data
.tag
);
363 EXPECT_EQ(notification_data
.icon
, read_notification_data
.icon
);
364 EXPECT_EQ(notification_data
.silent
, read_notification_data
.silent
);
367 TEST_F(NotificationDatabaseTest
, ReadWriteMultipleNotificationData
) {
368 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
369 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
370 database
->Open(true /* create_if_missing */));
372 GURL
origin("https://example.com");
373 int64_t notification_id
= 0;
375 // Write ten notifications to the database, each with a unique title and
376 // notification id (it is the responsibility of the user to increment this).
377 for (int i
= 1; i
<= 10; ++i
) {
378 ASSERT_NO_FATAL_FAILURE(CreateAndWriteNotification(
379 database
.get(), origin
, i
/* sw_registration_id */, ¬ification_id
));
380 EXPECT_EQ(notification_id
, i
);
383 NotificationDatabaseData database_data
;
385 // Read the ten notifications from the database, and verify that the titles
386 // of each of them matches with how they were created.
387 for (int i
= 1; i
<= 10; ++i
) {
388 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
389 database
->ReadNotificationData(i
/* notification_id */,
393 EXPECT_EQ(i
, database_data
.service_worker_registration_id
);
397 TEST_F(NotificationDatabaseTest
, DeleteInvalidNotificationData
) {
398 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
399 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
400 database
->Open(true /* create_if_missing */));
402 // Deleting non-existing notifications is not considered to be a failure.
403 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
404 database
->DeleteNotificationData(9001,
405 GURL("https://chrome.com")));
408 TEST_F(NotificationDatabaseTest
, DeleteNotificationDataSameOrigin
) {
409 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
410 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
411 database
->Open(true /* create_if_missing */));
413 int64_t notification_id
= 0;
415 NotificationDatabaseData database_data
;
416 GURL
origin("https://example.com");
418 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
419 database
->WriteNotificationData(origin
,
423 // Reading a notification after writing one should succeed.
424 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
425 database
->ReadNotificationData(notification_id
,
429 // Delete the notification which was just written to the database, and verify
430 // that reading it again will fail.
431 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
432 database
->DeleteNotificationData(notification_id
, origin
));
433 EXPECT_EQ(NotificationDatabase::STATUS_ERROR_NOT_FOUND
,
434 database
->ReadNotificationData(notification_id
,
439 TEST_F(NotificationDatabaseTest
, DeleteNotificationDataDifferentOrigin
) {
440 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
441 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
442 database
->Open(true /* create_if_missing */));
444 int64_t notification_id
= 0;
446 NotificationDatabaseData database_data
;
447 GURL
origin("https://example.com");
449 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
450 database
->WriteNotificationData(origin
,
454 // Attempting to delete the notification with a different origin, but with the
455 // same |notification_id|, should not return an error (the notification could
456 // not be found, but that's not considered a failure). However, it should not
457 // remove the notification either.
458 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
459 database
->DeleteNotificationData(notification_id
,
460 GURL("https://chrome.com")));
462 EXPECT_EQ(NotificationDatabase::STATUS_OK
,
463 database
->ReadNotificationData(notification_id
,
468 TEST_F(NotificationDatabaseTest
, ReadAllNotificationData
) {
469 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
470 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
471 database
->Open(true /* create_if_missing */));
473 ASSERT_NO_FATAL_FAILURE(PopulateDatabaseWithExampleData(database
.get()));
475 std::vector
<NotificationDatabaseData
> notifications
;
476 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
477 database
->ReadAllNotificationData(¬ifications
));
479 EXPECT_EQ(arraysize(kExampleNotificationData
), notifications
.size());
482 TEST_F(NotificationDatabaseTest
, ReadAllNotificationDataEmpty
) {
483 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
484 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
485 database
->Open(true /* create_if_missing */));
487 std::vector
<NotificationDatabaseData
> notifications
;
488 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
489 database
->ReadAllNotificationData(¬ifications
));
491 EXPECT_EQ(0u, notifications
.size());
494 TEST_F(NotificationDatabaseTest
, ReadAllNotificationDataForOrigin
) {
495 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
496 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
497 database
->Open(true /* create_if_missing */));
499 ASSERT_NO_FATAL_FAILURE(PopulateDatabaseWithExampleData(database
.get()));
501 GURL
origin("https://example.com");
503 std::vector
<NotificationDatabaseData
> notifications
;
504 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
505 database
->ReadAllNotificationDataForOrigin(origin
, ¬ifications
));
507 EXPECT_EQ(4u, notifications
.size());
510 TEST_F(NotificationDatabaseTest
,
511 ReadAllNotificationDataForServiceWorkerRegistration
) {
512 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
513 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
514 database
->Open(true /* create_if_missing */));
516 ASSERT_NO_FATAL_FAILURE(PopulateDatabaseWithExampleData(database
.get()));
518 GURL
origin("https://example.com:443");
520 std::vector
<NotificationDatabaseData
> notifications
;
521 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
522 database
->ReadAllNotificationDataForServiceWorkerRegistration(
523 origin
, kExampleServiceWorkerRegistrationId
, ¬ifications
));
525 EXPECT_EQ(2u, notifications
.size());
528 TEST_F(NotificationDatabaseTest
, DeleteAllNotificationDataForOrigin
) {
529 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
530 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
531 database
->Open(true /* create_if_missing */));
533 ASSERT_NO_FATAL_FAILURE(PopulateDatabaseWithExampleData(database
.get()));
535 GURL
origin("https://example.com:443");
537 std::set
<int64_t> deleted_notification_set
;
538 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
539 database
->DeleteAllNotificationDataForOrigin(
540 origin
, &deleted_notification_set
));
542 EXPECT_EQ(4u, deleted_notification_set
.size());
544 std::vector
<NotificationDatabaseData
> notifications
;
545 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
546 database
->ReadAllNotificationDataForOrigin(origin
, ¬ifications
));
548 EXPECT_EQ(0u, notifications
.size());
551 TEST_F(NotificationDatabaseTest
, DeleteAllNotificationDataForOriginEmpty
) {
552 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
553 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
554 database
->Open(true /* create_if_missing */));
556 GURL
origin("https://example.com");
558 std::set
<int64_t> deleted_notification_set
;
559 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
560 database
->DeleteAllNotificationDataForOrigin(
561 origin
, &deleted_notification_set
));
563 EXPECT_EQ(0u, deleted_notification_set
.size());
566 TEST_F(NotificationDatabaseTest
,
567 DeleteAllNotificationDataForServiceWorkerRegistration
) {
568 scoped_ptr
<NotificationDatabase
> database(CreateDatabaseInMemory());
569 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
570 database
->Open(true /* create_if_missing */));
572 ASSERT_NO_FATAL_FAILURE(PopulateDatabaseWithExampleData(database
.get()));
574 GURL
origin("https://example.com:443");
575 std::set
<int64_t> deleted_notification_set
;
576 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
577 database
->DeleteAllNotificationDataForServiceWorkerRegistration(
579 kExampleServiceWorkerRegistrationId
,
580 &deleted_notification_set
));
582 EXPECT_EQ(2u, deleted_notification_set
.size());
584 std::vector
<NotificationDatabaseData
> notifications
;
585 ASSERT_EQ(NotificationDatabase::STATUS_OK
,
586 database
->ReadAllNotificationDataForServiceWorkerRegistration(
587 origin
, kExampleServiceWorkerRegistrationId
, ¬ifications
));
589 EXPECT_EQ(0u, notifications
.size());
592 } // namespace content