1 // Copyright 2014 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. 4 5 #include "google_apis/gcm/engine/gcm_store_impl.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/bind.h" 11 #include "base/command_line.h" 12 #include "base/files/file_path.h" 13 #include "base/files/scoped_temp_dir.h" 14 #include "base/memory/scoped_ptr.h" 15 #include "base/message_loop/message_loop.h" 16 #include "base/run_loop.h" 17 #include "base/strings/string_number_conversions.h" 18 #include "google_apis/gcm/base/fake_encryptor.h" 19 #include "google_apis/gcm/base/mcs_message.h" 20 #include "google_apis/gcm/base/mcs_util.h" 21 #include "google_apis/gcm/protocol/mcs.pb.h" 22 #include "testing/gtest/include/gtest/gtest.h" 23 24 namespace gcm { 25 26 namespace { 27 28 // Number of persistent ids to use in tests. 29 const int kNumPersistentIds = 10; 30 31 // Number of per-app messages in tests. 32 const int kNumMessagesPerApp = 20; 33 34 // App name for testing. 35 const char kAppName[] = "my_app"; 36 37 // Category name for testing. 38 const char kCategoryName[] = "my_category"; 39 40 const uint64 kDeviceId = 22; 41 const uint64 kDeviceToken = 55; 42 43 class GCMStoreImplTest : public testing::Test { 44 public: 45 GCMStoreImplTest(); 46 virtual ~GCMStoreImplTest(); 47 48 virtual void SetUp() OVERRIDE; 49 50 scoped_ptr<GCMStore> BuildGCMStore(); 51 52 std::string GetNextPersistentId(); 53 54 void PumpLoop(); 55 56 void LoadCallback(scoped_ptr<GCMStore::LoadResult>* result_dst, 57 scoped_ptr<GCMStore::LoadResult> result); 58 void UpdateCallback(bool success); 59 60 protected: 61 base::MessageLoop message_loop_; 62 base::ScopedTempDir temp_directory_; 63 bool expected_success_; 64 uint64 next_persistent_id_; 65 scoped_ptr<base::RunLoop> run_loop_; 66 }; 67 68 GCMStoreImplTest::GCMStoreImplTest() 69 : expected_success_(true), 70 next_persistent_id_(base::Time::Now().ToInternalValue()) { 71 EXPECT_TRUE(temp_directory_.CreateUniqueTempDir()); 72 run_loop_.reset(new base::RunLoop()); 73 } 74 75 GCMStoreImplTest::~GCMStoreImplTest() {} 76 77 void GCMStoreImplTest::SetUp() { 78 testing::Test::SetUp(); 79 } 80 81 scoped_ptr<GCMStore> GCMStoreImplTest::BuildGCMStore() { 82 return scoped_ptr<GCMStore>(new GCMStoreImpl( 83 temp_directory_.path(), 84 message_loop_.message_loop_proxy(), 85 make_scoped_ptr<Encryptor>(new FakeEncryptor))); 86 } 87 88 std::string GCMStoreImplTest::GetNextPersistentId() { 89 return base::Uint64ToString(next_persistent_id_++); 90 } 91 92 void GCMStoreImplTest::PumpLoop() { message_loop_.RunUntilIdle(); } 93 94 void GCMStoreImplTest::LoadCallback( 95 scoped_ptr<GCMStore::LoadResult>* result_dst, 96 scoped_ptr<GCMStore::LoadResult> result) { 97 ASSERT_TRUE(result->success); 98 *result_dst = result.Pass(); 99 run_loop_->Quit(); 100 run_loop_.reset(new base::RunLoop()); 101 } 102 103 void GCMStoreImplTest::UpdateCallback(bool success) { 104 ASSERT_EQ(expected_success_, success); 105 } 106 107 // Verify creating a new database and loading it. 108 TEST_F(GCMStoreImplTest, LoadNew) { 109 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 110 scoped_ptr<GCMStore::LoadResult> load_result; 111 gcm_store->Load(base::Bind( 112 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 113 PumpLoop(); 114 115 EXPECT_EQ(0U, load_result->device_android_id); 116 EXPECT_EQ(0U, load_result->device_security_token); 117 EXPECT_TRUE(load_result->incoming_messages.empty()); 118 EXPECT_TRUE(load_result->outgoing_messages.empty()); 119 EXPECT_TRUE(load_result->gservices_settings.empty()); 120 EXPECT_EQ(base::Time::FromInternalValue(0LL), load_result->last_checkin_time); 121 } 122 123 TEST_F(GCMStoreImplTest, DeviceCredentials) { 124 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 125 scoped_ptr<GCMStore::LoadResult> load_result; 126 gcm_store->Load(base::Bind( 127 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 128 PumpLoop(); 129 130 gcm_store->SetDeviceCredentials( 131 kDeviceId, 132 kDeviceToken, 133 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 134 PumpLoop(); 135 136 gcm_store = BuildGCMStore().Pass(); 137 gcm_store->Load(base::Bind( 138 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 139 PumpLoop(); 140 141 ASSERT_EQ(kDeviceId, load_result->device_android_id); 142 ASSERT_EQ(kDeviceToken, load_result->device_security_token); 143 } 144 145 TEST_F(GCMStoreImplTest, LastCheckinTime) { 146 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 147 scoped_ptr<GCMStore::LoadResult> load_result; 148 gcm_store->Load(base::Bind( 149 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 150 PumpLoop(); 151 152 base::Time last_checkin_time = base::Time::Now(); 153 154 gcm_store->SetLastCheckinTime( 155 last_checkin_time, 156 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 157 PumpLoop(); 158 159 gcm_store = BuildGCMStore().Pass(); 160 gcm_store->Load(base::Bind( 161 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 162 PumpLoop(); 163 ASSERT_EQ(last_checkin_time, load_result->last_checkin_time); 164 } 165 166 TEST_F(GCMStoreImplTest, GServicesSettings_ProtocolV2) { 167 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 168 scoped_ptr<GCMStore::LoadResult> load_result; 169 gcm_store->Load(base::Bind( 170 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 171 PumpLoop(); 172 173 std::map<std::string, std::string> settings; 174 settings["checkin_interval"] = "12345"; 175 settings["mcs_port"] = "438"; 176 settings["checkin_url"] = "http://checkin.google.com"; 177 std::string digest = "digest1"; 178 179 gcm_store->SetGServicesSettings( 180 settings, 181 digest, 182 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 183 PumpLoop(); 184 185 gcm_store = BuildGCMStore().Pass(); 186 gcm_store->Load(base::Bind( 187 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 188 PumpLoop(); 189 190 ASSERT_EQ(settings, load_result->gservices_settings); 191 ASSERT_EQ(digest, load_result->gservices_digest); 192 193 // Remove some, and add some. 194 settings.clear(); 195 settings["checkin_interval"] = "54321"; 196 settings["registration_url"] = "http://registration.google.com"; 197 digest = "digest2"; 198 199 gcm_store->SetGServicesSettings( 200 settings, 201 digest, 202 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 203 PumpLoop(); 204 205 gcm_store = BuildGCMStore().Pass(); 206 gcm_store->Load(base::Bind( 207 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 208 PumpLoop(); 209 210 ASSERT_EQ(settings, load_result->gservices_settings); 211 ASSERT_EQ(digest, load_result->gservices_digest); 212 } 213 214 TEST_F(GCMStoreImplTest, Registrations) { 215 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 216 scoped_ptr<GCMStore::LoadResult> load_result; 217 gcm_store->Load(base::Bind( 218 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 219 PumpLoop(); 220 221 // Add one registration with one sender. 222 linked_ptr<RegistrationInfo> registration1(new RegistrationInfo); 223 registration1->sender_ids.push_back("sender1"); 224 registration1->registration_id = "registration1"; 225 gcm_store->AddRegistration( 226 "app1", 227 registration1, 228 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 229 PumpLoop(); 230 231 // Add one registration with multiple senders. 232 linked_ptr<RegistrationInfo> registration2(new RegistrationInfo); 233 registration2->sender_ids.push_back("sender2_1"); 234 registration2->sender_ids.push_back("sender2_2"); 235 registration2->registration_id = "registration2"; 236 gcm_store->AddRegistration( 237 "app2", 238 registration2, 239 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 240 PumpLoop(); 241 242 gcm_store = BuildGCMStore().Pass(); 243 gcm_store->Load(base::Bind( 244 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 245 PumpLoop(); 246 247 ASSERT_EQ(2u, load_result->registrations.size()); 248 ASSERT_TRUE(load_result->registrations.find("app1") != 249 load_result->registrations.end()); 250 EXPECT_EQ(registration1->registration_id, 251 load_result->registrations["app1"]->registration_id); 252 ASSERT_EQ(1u, load_result->registrations["app1"]->sender_ids.size()); 253 EXPECT_EQ(registration1->sender_ids[0], 254 load_result->registrations["app1"]->sender_ids[0]); 255 ASSERT_TRUE(load_result->registrations.find("app2") != 256 load_result->registrations.end()); 257 EXPECT_EQ(registration2->registration_id, 258 load_result->registrations["app2"]->registration_id); 259 ASSERT_EQ(2u, load_result->registrations["app2"]->sender_ids.size()); 260 EXPECT_EQ(registration2->sender_ids[0], 261 load_result->registrations["app2"]->sender_ids[0]); 262 EXPECT_EQ(registration2->sender_ids[1], 263 load_result->registrations["app2"]->sender_ids[1]); 264 265 gcm_store->RemoveRegistration( 266 "app2", 267 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 268 PumpLoop(); 269 270 gcm_store = BuildGCMStore().Pass(); 271 gcm_store->Load(base::Bind( 272 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 273 PumpLoop(); 274 275 ASSERT_EQ(1u, load_result->registrations.size()); 276 ASSERT_TRUE(load_result->registrations.find("app1") != 277 load_result->registrations.end()); 278 EXPECT_EQ(registration1->registration_id, 279 load_result->registrations["app1"]->registration_id); 280 ASSERT_EQ(1u, load_result->registrations["app1"]->sender_ids.size()); 281 EXPECT_EQ(registration1->sender_ids[0], 282 load_result->registrations["app1"]->sender_ids[0]); 283 } 284 285 // Verify saving some incoming messages, reopening the directory, and then 286 // removing those incoming messages. 287 TEST_F(GCMStoreImplTest, IncomingMessages) { 288 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 289 scoped_ptr<GCMStore::LoadResult> load_result; 290 gcm_store->Load(base::Bind( 291 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 292 PumpLoop(); 293 294 std::vector<std::string> persistent_ids; 295 for (int i = 0; i < kNumPersistentIds; ++i) { 296 persistent_ids.push_back(GetNextPersistentId()); 297 gcm_store->AddIncomingMessage( 298 persistent_ids.back(), 299 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 300 PumpLoop(); 301 } 302 303 gcm_store = BuildGCMStore().Pass(); 304 gcm_store->Load(base::Bind( 305 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 306 PumpLoop(); 307 308 ASSERT_EQ(persistent_ids, load_result->incoming_messages); 309 ASSERT_TRUE(load_result->outgoing_messages.empty()); 310 311 gcm_store->RemoveIncomingMessages( 312 persistent_ids, 313 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 314 PumpLoop(); 315 316 gcm_store = BuildGCMStore().Pass(); 317 load_result->incoming_messages.clear(); 318 gcm_store->Load(base::Bind( 319 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 320 PumpLoop(); 321 322 ASSERT_TRUE(load_result->incoming_messages.empty()); 323 ASSERT_TRUE(load_result->outgoing_messages.empty()); 324 } 325 326 // Verify saving some outgoing messages, reopening the directory, and then 327 // removing those outgoing messages. 328 TEST_F(GCMStoreImplTest, OutgoingMessages) { 329 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 330 scoped_ptr<GCMStore::LoadResult> load_result; 331 gcm_store->Load(base::Bind( 332 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 333 PumpLoop(); 334 335 std::vector<std::string> persistent_ids; 336 const int kNumPersistentIds = 10; 337 for (int i = 0; i < kNumPersistentIds; ++i) { 338 persistent_ids.push_back(GetNextPersistentId()); 339 mcs_proto::DataMessageStanza message; 340 message.set_from(kAppName + persistent_ids.back()); 341 message.set_category(kCategoryName + persistent_ids.back()); 342 gcm_store->AddOutgoingMessage( 343 persistent_ids.back(), 344 MCSMessage(message), 345 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 346 PumpLoop(); 347 } 348 349 gcm_store = BuildGCMStore().Pass(); 350 gcm_store->Load(base::Bind( 351 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 352 PumpLoop(); 353 354 ASSERT_TRUE(load_result->incoming_messages.empty()); 355 ASSERT_EQ(load_result->outgoing_messages.size(), persistent_ids.size()); 356 for (int i = 0; i < kNumPersistentIds; ++i) { 357 std::string id = persistent_ids[i]; 358 ASSERT_TRUE(load_result->outgoing_messages[id].get()); 359 const mcs_proto::DataMessageStanza* message = 360 reinterpret_cast<mcs_proto::DataMessageStanza*>( 361 load_result->outgoing_messages[id].get()); 362 ASSERT_EQ(message->from(), kAppName + id); 363 ASSERT_EQ(message->category(), kCategoryName + id); 364 } 365 366 gcm_store->RemoveOutgoingMessages( 367 persistent_ids, 368 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 369 PumpLoop(); 370 371 gcm_store = BuildGCMStore().Pass(); 372 load_result->outgoing_messages.clear(); 373 gcm_store->Load(base::Bind( 374 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 375 PumpLoop(); 376 377 ASSERT_TRUE(load_result->incoming_messages.empty()); 378 ASSERT_TRUE(load_result->outgoing_messages.empty()); 379 } 380 381 // Verify incoming and outgoing messages don't conflict. 382 TEST_F(GCMStoreImplTest, IncomingAndOutgoingMessages) { 383 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 384 scoped_ptr<GCMStore::LoadResult> load_result; 385 gcm_store->Load(base::Bind( 386 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 387 PumpLoop(); 388 389 std::vector<std::string> persistent_ids; 390 const int kNumPersistentIds = 10; 391 for (int i = 0; i < kNumPersistentIds; ++i) { 392 persistent_ids.push_back(GetNextPersistentId()); 393 gcm_store->AddIncomingMessage( 394 persistent_ids.back(), 395 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 396 PumpLoop(); 397 398 mcs_proto::DataMessageStanza message; 399 message.set_from(kAppName + persistent_ids.back()); 400 message.set_category(kCategoryName + persistent_ids.back()); 401 gcm_store->AddOutgoingMessage( 402 persistent_ids.back(), 403 MCSMessage(message), 404 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 405 PumpLoop(); 406 } 407 408 gcm_store = BuildGCMStore().Pass(); 409 gcm_store->Load(base::Bind( 410 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 411 PumpLoop(); 412 413 ASSERT_EQ(persistent_ids, load_result->incoming_messages); 414 ASSERT_EQ(load_result->outgoing_messages.size(), persistent_ids.size()); 415 for (int i = 0; i < kNumPersistentIds; ++i) { 416 std::string id = persistent_ids[i]; 417 ASSERT_TRUE(load_result->outgoing_messages[id].get()); 418 const mcs_proto::DataMessageStanza* message = 419 reinterpret_cast<mcs_proto::DataMessageStanza*>( 420 load_result->outgoing_messages[id].get()); 421 ASSERT_EQ(message->from(), kAppName + id); 422 ASSERT_EQ(message->category(), kCategoryName + id); 423 } 424 425 gcm_store->RemoveIncomingMessages( 426 persistent_ids, 427 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 428 PumpLoop(); 429 gcm_store->RemoveOutgoingMessages( 430 persistent_ids, 431 base::Bind(&GCMStoreImplTest::UpdateCallback, base::Unretained(this))); 432 PumpLoop(); 433 434 gcm_store = BuildGCMStore().Pass(); 435 load_result->incoming_messages.clear(); 436 load_result->outgoing_messages.clear(); 437 gcm_store->Load(base::Bind( 438 &GCMStoreImplTest::LoadCallback, base::Unretained(this), &load_result)); 439 PumpLoop(); 440 441 ASSERT_TRUE(load_result->incoming_messages.empty()); 442 ASSERT_TRUE(load_result->outgoing_messages.empty()); 443 } 444 445 // Test that per-app message limits are enforced, persisted across restarts, 446 // and updated as messages are removed. 447 TEST_F(GCMStoreImplTest, PerAppMessageLimits) { 448 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 449 scoped_ptr<GCMStore::LoadResult> load_result; 450 gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback, 451 base::Unretained(this), 452 &load_result)); 453 454 // Add the initial (below app limit) messages. 455 for (int i = 0; i < kNumMessagesPerApp; ++i) { 456 mcs_proto::DataMessageStanza message; 457 message.set_from(kAppName); 458 message.set_category(kCategoryName); 459 EXPECT_TRUE(gcm_store->AddOutgoingMessage( 460 base::IntToString(i), 461 MCSMessage(message), 462 base::Bind(&GCMStoreImplTest::UpdateCallback, 463 base::Unretained(this)))); 464 PumpLoop(); 465 } 466 467 // Attempting to add some more should fail. 468 for (int i = 0; i < kNumMessagesPerApp; ++i) { 469 mcs_proto::DataMessageStanza message; 470 message.set_from(kAppName); 471 message.set_category(kCategoryName); 472 EXPECT_FALSE(gcm_store->AddOutgoingMessage( 473 base::IntToString(i + kNumMessagesPerApp), 474 MCSMessage(message), 475 base::Bind(&GCMStoreImplTest::UpdateCallback, 476 base::Unretained(this)))); 477 PumpLoop(); 478 } 479 480 // Tear down and restore the database. 481 gcm_store = BuildGCMStore().Pass(); 482 gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback, 483 base::Unretained(this), 484 &load_result)); 485 PumpLoop(); 486 487 // Adding more messages should still fail. 488 for (int i = 0; i < kNumMessagesPerApp; ++i) { 489 mcs_proto::DataMessageStanza message; 490 message.set_from(kAppName); 491 message.set_category(kCategoryName); 492 EXPECT_FALSE(gcm_store->AddOutgoingMessage( 493 base::IntToString(i + kNumMessagesPerApp), 494 MCSMessage(message), 495 base::Bind(&GCMStoreImplTest::UpdateCallback, 496 base::Unretained(this)))); 497 PumpLoop(); 498 } 499 500 // Remove the existing messages. 501 for (int i = 0; i < kNumMessagesPerApp; ++i) { 502 gcm_store->RemoveOutgoingMessage( 503 base::IntToString(i), 504 base::Bind(&GCMStoreImplTest::UpdateCallback, 505 base::Unretained(this))); 506 PumpLoop(); 507 } 508 509 // Successfully add new messages. 510 for (int i = 0; i < kNumMessagesPerApp; ++i) { 511 mcs_proto::DataMessageStanza message; 512 message.set_from(kAppName); 513 message.set_category(kCategoryName); 514 EXPECT_TRUE(gcm_store->AddOutgoingMessage( 515 base::IntToString(i + kNumMessagesPerApp), 516 MCSMessage(message), 517 base::Bind(&GCMStoreImplTest::UpdateCallback, 518 base::Unretained(this)))); 519 PumpLoop(); 520 } 521 } 522 523 // When the database is destroyed, all database updates should fail. At the 524 // same time, they per-app message counts should not go up, as failures should 525 // result in decrementing the counts. 526 TEST_F(GCMStoreImplTest, AddMessageAfterDestroy) { 527 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 528 scoped_ptr<GCMStore::LoadResult> load_result; 529 gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback, 530 base::Unretained(this), 531 &load_result)); 532 PumpLoop(); 533 gcm_store->Destroy(base::Bind(&GCMStoreImplTest::UpdateCallback, 534 base::Unretained(this))); 535 PumpLoop(); 536 537 expected_success_ = false; 538 for (int i = 0; i < kNumMessagesPerApp * 2; ++i) { 539 mcs_proto::DataMessageStanza message; 540 message.set_from(kAppName); 541 message.set_category(kCategoryName); 542 // Because all adds are failing, none should hit the per-app message limits. 543 EXPECT_TRUE(gcm_store->AddOutgoingMessage( 544 base::IntToString(i), 545 MCSMessage(message), 546 base::Bind(&GCMStoreImplTest::UpdateCallback, 547 base::Unretained(this)))); 548 PumpLoop(); 549 } 550 } 551 552 TEST_F(GCMStoreImplTest, ReloadAfterClose) { 553 scoped_ptr<GCMStore> gcm_store(BuildGCMStore()); 554 scoped_ptr<GCMStore::LoadResult> load_result; 555 gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback, 556 base::Unretained(this), 557 &load_result)); 558 PumpLoop(); 559 560 gcm_store->Close(); 561 PumpLoop(); 562 563 gcm_store->Load(base::Bind(&GCMStoreImplTest::LoadCallback, 564 base::Unretained(this), 565 &load_result)); 566 PumpLoop(); 567 } 568 569 } // namespace 570 571 } // namespace gcm 572