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
24namespace gcm {
25
26namespace {
27
28// Number of persistent ids to use in tests.
29const int kNumPersistentIds = 10;
30
31// Number of per-app messages in tests.
32const int kNumMessagesPerApp = 20;
33
34// App name for testing.
35const char kAppName[] = "my_app";
36
37// Category name for testing.
38const char kCategoryName[] = "my_category";
39
40const uint64 kDeviceId = 22;
41const uint64 kDeviceToken = 55;
42
43class 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
68GCMStoreImplTest::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
75GCMStoreImplTest::~GCMStoreImplTest() {}
76
77void GCMStoreImplTest::SetUp() {
78  testing::Test::SetUp();
79}
80
81scoped_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
88std::string GCMStoreImplTest::GetNextPersistentId() {
89  return base::Uint64ToString(next_persistent_id_++);
90}
91
92void GCMStoreImplTest::PumpLoop() { message_loop_.RunUntilIdle(); }
93
94void 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
103void GCMStoreImplTest::UpdateCallback(bool success) {
104  ASSERT_EQ(expected_success_, success);
105}
106
107// Verify creating a new database and loading it.
108TEST_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
123TEST_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
145TEST_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
166TEST_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
214TEST_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.
287TEST_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.
328TEST_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.
382TEST_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.
447TEST_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.
526TEST_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
552TEST_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