extension_gcm_app_handler_unittest.cc revision 5f1c94371a64b3196d4be9466099bb892df9b88e
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 "chrome/browser/extensions/extension_gcm_app_handler.h"
6
7#include <vector>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/command_line.h"
12#include "base/file_util.h"
13#include "base/files/file_path.h"
14#include "base/files/scoped_temp_dir.h"
15#include "base/location.h"
16#include "base/logging.h"
17#include "base/memory/ref_counted.h"
18#include "base/message_loop/message_loop.h"
19#include "base/path_service.h"
20#include "base/prefs/pref_service.h"
21#include "base/run_loop.h"
22#include "base/values.h"
23#include "chrome/browser/chrome_notification_types.h"
24#include "chrome/browser/extensions/extension_service.h"
25#include "chrome/browser/extensions/test_extension_service.h"
26#include "chrome/browser/extensions/test_extension_system.h"
27#include "chrome/browser/profiles/profile.h"
28#include "chrome/browser/services/gcm/fake_signin_manager.h"
29#include "chrome/browser/services/gcm/gcm_profile_service.h"
30#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
31#include "chrome/browser/signin/signin_manager_factory.h"
32#include "chrome/common/chrome_paths.h"
33#include "chrome/common/pref_names.h"
34#include "chrome/test/base/testing_profile.h"
35#include "components/gcm_driver/fake_gcm_app_handler.h"
36#include "components/gcm_driver/fake_gcm_client.h"
37#include "components/gcm_driver/fake_gcm_client_factory.h"
38#include "components/gcm_driver/gcm_client_factory.h"
39#include "components/gcm_driver/gcm_driver.h"
40#include "components/keyed_service/core/keyed_service.h"
41#include "content/public/browser/browser_context.h"
42#include "content/public/browser/browser_thread.h"
43#include "content/public/test/test_browser_thread_bundle.h"
44#include "content/public/test/test_utils.h"
45#include "extensions/browser/extension_system.h"
46#include "extensions/browser/uninstall_reason.h"
47#include "extensions/common/extension.h"
48#include "extensions/common/manifest.h"
49#include "extensions/common/manifest_constants.h"
50#include "extensions/common/permissions/api_permission.h"
51#include "extensions/common/permissions/permissions_data.h"
52#include "testing/gtest/include/gtest/gtest.h"
53
54#if !defined(OS_ANDROID)
55#include "chrome/browser/extensions/api/gcm/gcm_api.h"
56#endif
57
58#if defined(OS_CHROMEOS)
59#include "chrome/browser/chromeos/login/users/user_manager.h"
60#include "chrome/browser/chromeos/settings/cros_settings.h"
61#include "chrome/browser/chromeos/settings/device_settings_service.h"
62#endif
63
64namespace extensions {
65
66namespace {
67
68const char kTestExtensionName[] = "FooBar";
69const char kTestingUsername[] = "user1@example.com";
70
71}  // namespace
72
73// Helper class for asynchronous waiting.
74class Waiter {
75 public:
76  Waiter() {}
77  ~Waiter() {}
78
79  // Waits until the asynchronous operation finishes.
80  void WaitUntilCompleted() {
81    run_loop_.reset(new base::RunLoop);
82    run_loop_->Run();
83  }
84
85  // Signals that the asynchronous operation finishes.
86  void SignalCompleted() {
87    if (run_loop_ && run_loop_->running())
88      run_loop_->Quit();
89  }
90
91  // Runs until UI loop becomes idle.
92  void PumpUILoop() {
93    base::MessageLoop::current()->RunUntilIdle();
94  }
95
96  // Runs until IO loop becomes idle.
97  void PumpIOLoop() {
98    content::BrowserThread::PostTask(
99        content::BrowserThread::IO,
100        FROM_HERE,
101        base::Bind(&Waiter::OnIOLoopPump, base::Unretained(this)));
102
103    WaitUntilCompleted();
104  }
105
106 private:
107  void PumpIOLoopCompleted() {
108    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
109
110    SignalCompleted();
111  }
112
113  void OnIOLoopPump() {
114    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
115
116    content::BrowserThread::PostTask(
117        content::BrowserThread::IO,
118        FROM_HERE,
119        base::Bind(&Waiter::OnIOLoopPumpCompleted, base::Unretained(this)));
120  }
121
122  void OnIOLoopPumpCompleted() {
123    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
124
125    content::BrowserThread::PostTask(
126        content::BrowserThread::UI,
127        FROM_HERE,
128        base::Bind(&Waiter::PumpIOLoopCompleted, base::Unretained(this)));
129  }
130
131  scoped_ptr<base::RunLoop> run_loop_;
132
133  DISALLOW_COPY_AND_ASSIGN(Waiter);
134};
135
136class FakeExtensionGCMAppHandler : public ExtensionGCMAppHandler {
137 public:
138  FakeExtensionGCMAppHandler(Profile* profile, Waiter* waiter)
139      : ExtensionGCMAppHandler(profile),
140        waiter_(waiter),
141        unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR),
142        app_handler_count_drop_to_zero_(false) {
143  }
144
145  virtual ~FakeExtensionGCMAppHandler() {
146  }
147
148  virtual void OnMessage(
149      const std::string& app_id,
150      const gcm::GCMClient::IncomingMessage& message) OVERRIDE {
151  }
152
153  virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE {
154  }
155
156  virtual void OnSendError(
157      const std::string& app_id,
158      const gcm::GCMClient::SendErrorDetails& send_error_details) OVERRIDE {
159  }
160
161  virtual void OnUnregisterCompleted(const std::string& app_id,
162                                     gcm::GCMClient::Result result) OVERRIDE {
163    unregistration_result_ = result;
164    waiter_->SignalCompleted();
165  }
166
167  virtual void RemoveAppHandler(const std::string& app_id) OVERRIDE{
168    ExtensionGCMAppHandler::RemoveAppHandler(app_id);
169    if (!GetGCMDriver()->app_handlers().size())
170      app_handler_count_drop_to_zero_ = true;
171  }
172
173  gcm::GCMClient::Result unregistration_result() const {
174    return unregistration_result_;
175  }
176  bool app_handler_count_drop_to_zero() const {
177    return app_handler_count_drop_to_zero_;
178  }
179
180 private:
181  Waiter* waiter_;
182  gcm::GCMClient::Result unregistration_result_;
183  bool app_handler_count_drop_to_zero_;
184
185  DISALLOW_COPY_AND_ASSIGN(FakeExtensionGCMAppHandler);
186};
187
188class ExtensionGCMAppHandlerTest : public testing::Test {
189 public:
190  static KeyedService* BuildGCMProfileService(
191      content::BrowserContext* context) {
192    return new gcm::GCMProfileService(
193        Profile::FromBrowserContext(context),
194        scoped_ptr<gcm::GCMClientFactory>(new gcm::FakeGCMClientFactory(
195            gcm::FakeGCMClient::NO_DELAY_START,
196            content::BrowserThread::GetMessageLoopProxyForThread(
197                content::BrowserThread::UI),
198            content::BrowserThread::GetMessageLoopProxyForThread(
199                content::BrowserThread::IO))));
200  }
201
202  ExtensionGCMAppHandlerTest()
203      : extension_service_(NULL),
204        registration_result_(gcm::GCMClient::UNKNOWN_ERROR),
205        unregistration_result_(gcm::GCMClient::UNKNOWN_ERROR) {
206  }
207
208  virtual ~ExtensionGCMAppHandlerTest() {
209  }
210
211  // Overridden from test::Test:
212  virtual void SetUp() OVERRIDE {
213    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
214
215    // Make BrowserThread work in unittest.
216    thread_bundle_.reset(new content::TestBrowserThreadBundle(
217        content::TestBrowserThreadBundle::REAL_IO_THREAD));
218
219    // Allow extension update to unpack crx in process.
220    in_process_utility_thread_helper_.reset(
221        new content::InProcessUtilityThreadHelper);
222
223    // This is needed to create extension service under CrOS.
224#if defined(OS_CHROMEOS)
225    test_user_manager_.reset(new chromeos::ScopedTestUserManager());
226#endif
227
228    // Create a new profile.
229    TestingProfile::Builder builder;
230    builder.AddTestingFactory(SigninManagerFactory::GetInstance(),
231                              gcm::FakeSigninManager::Build);
232    profile_ = builder.Build();
233    signin_manager_ = static_cast<gcm::FakeSigninManager*>(
234        SigninManagerFactory::GetInstance()->GetForProfile(profile_.get()));
235
236    // Create extension service in order to uninstall the extension.
237    TestExtensionSystem* extension_system(
238        static_cast<TestExtensionSystem*>(ExtensionSystem::Get(profile())));
239    base::FilePath extensions_install_dir =
240        temp_dir_.path().Append(FILE_PATH_LITERAL("Extensions"));
241    extension_system->CreateExtensionService(
242        CommandLine::ForCurrentProcess(), extensions_install_dir, false);
243    extension_service_ = extension_system->Get(profile())->extension_service();
244    extension_service_->set_extensions_enabled(true);
245    extension_service_->set_show_extensions_prompts(false);
246    extension_service_->set_install_updates_when_idle_for_test(false);
247
248    // Enable GCM such that tests could be run on all channels.
249    profile()->GetPrefs()->SetBoolean(prefs::kGCMChannelEnabled, true);
250
251    // Create GCMProfileService that talks with fake GCMClient.
252    gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactoryAndUse(
253        profile(), &ExtensionGCMAppHandlerTest::BuildGCMProfileService);
254
255    // Create a fake version of ExtensionGCMAppHandler.
256    gcm_app_handler_.reset(new FakeExtensionGCMAppHandler(profile(), &waiter_));
257  }
258
259  virtual void TearDown() OVERRIDE {
260#if defined(OS_CHROMEOS)
261    test_user_manager_.reset();
262#endif
263
264    waiter_.PumpUILoop();
265  }
266
267  // Returns a barebones test extension.
268  scoped_refptr<Extension> CreateExtension() {
269    base::DictionaryValue manifest;
270    manifest.SetString(manifest_keys::kVersion, "1.0.0.0");
271    manifest.SetString(manifest_keys::kName, kTestExtensionName);
272    base::ListValue* permission_list = new base::ListValue;
273    permission_list->Append(new base::StringValue("gcm"));
274    manifest.Set(manifest_keys::kPermissions, permission_list);
275
276    std::string error;
277    scoped_refptr<Extension> extension = Extension::Create(
278        temp_dir_.path(),
279        Manifest::UNPACKED,
280        manifest,
281        Extension::NO_FLAGS,
282        "ldnnhddmnhbkjipkidpdiheffobcpfmf",
283        &error);
284    EXPECT_TRUE(extension.get()) << error;
285    EXPECT_TRUE(
286        extension->permissions_data()->HasAPIPermission(APIPermission::kGcm));
287
288    return extension;
289  }
290
291  void LoadExtension(const Extension* extension) {
292    extension_service_->AddExtension(extension);
293  }
294
295  static bool IsCrxInstallerDone(extensions::CrxInstaller** installer,
296                                 const content::NotificationSource& source,
297                                 const content::NotificationDetails& details) {
298    return content::Source<extensions::CrxInstaller>(source).ptr() ==
299           *installer;
300  }
301
302  void UpdateExtension(const Extension* extension,
303                       const std::string& update_crx) {
304    base::FilePath data_dir;
305    if (!PathService::Get(chrome::DIR_TEST_DATA, &data_dir)) {
306      ADD_FAILURE();
307      return;
308    }
309    data_dir = data_dir.AppendASCII("extensions");
310    data_dir = data_dir.AppendASCII(update_crx);
311
312    base::FilePath path = temp_dir_.path();
313    path = path.Append(data_dir.BaseName());
314    ASSERT_TRUE(base::CopyFile(data_dir, path));
315
316    extensions::CrxInstaller* installer = NULL;
317    content::WindowedNotificationObserver observer(
318        extensions::NOTIFICATION_CRX_INSTALLER_DONE,
319        base::Bind(&IsCrxInstallerDone, &installer));
320    extension_service_->UpdateExtension(
321        extension->id(), path, true, &installer);
322
323    if (installer)
324      observer.Wait();
325  }
326
327  void DisableExtension(const Extension* extension) {
328    extension_service_->DisableExtension(
329        extension->id(), Extension::DISABLE_USER_ACTION);
330  }
331
332  void EnableExtension(const Extension* extension) {
333    extension_service_->EnableExtension(extension->id());
334  }
335
336  void UninstallExtension(const Extension* extension) {
337    extension_service_->UninstallExtension(
338        extension->id(),
339        extensions::UNINSTALL_REASON_FOR_TESTING,
340        base::Bind(&base::DoNothing),
341        NULL);
342  }
343
344  void SignIn(const std::string& username) {
345    signin_manager_->SignIn(username);
346    waiter_.PumpIOLoop();
347  }
348
349  void SignOut() {
350    signin_manager_->SignOut(signin_metrics::SIGNOUT_TEST);
351    waiter_.PumpIOLoop();
352  }
353
354  void Register(const std::string& app_id,
355                const std::vector<std::string>& sender_ids) {
356    GetGCMDriver()->Register(
357        app_id,
358        sender_ids,
359        base::Bind(&ExtensionGCMAppHandlerTest::RegisterCompleted,
360                   base::Unretained(this)));
361  }
362
363  void RegisterCompleted(const std::string& registration_id,
364                         gcm::GCMClient::Result result) {
365    registration_result_ = result;
366    waiter_.SignalCompleted();
367  }
368
369  gcm::GCMDriver* GetGCMDriver() const {
370    return gcm::GCMProfileServiceFactory::GetForProfile(profile())->driver();
371  }
372
373  bool HasAppHandlers(const std::string& app_id) const {
374    return GetGCMDriver()->app_handlers().count(app_id);
375  }
376
377  Profile* profile() const { return profile_.get(); }
378  Waiter* waiter() { return &waiter_; }
379  FakeExtensionGCMAppHandler* gcm_app_handler() const {
380    return gcm_app_handler_.get();
381  }
382  gcm::GCMClient::Result registration_result() const {
383    return registration_result_;
384  }
385  gcm::GCMClient::Result unregistration_result() const {
386    return unregistration_result_;
387  }
388
389 private:
390  scoped_ptr<content::TestBrowserThreadBundle> thread_bundle_;
391  scoped_ptr<content::InProcessUtilityThreadHelper>
392      in_process_utility_thread_helper_;
393  scoped_ptr<TestingProfile> profile_;
394  ExtensionService* extension_service_;  // Not owned.
395  gcm::FakeSigninManager* signin_manager_;  // Not owned.
396  base::ScopedTempDir temp_dir_;
397
398  // This is needed to create extension service under CrOS.
399#if defined(OS_CHROMEOS)
400  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
401  chromeos::ScopedTestCrosSettings test_cros_settings_;
402  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
403#endif
404
405  Waiter waiter_;
406  scoped_ptr<FakeExtensionGCMAppHandler> gcm_app_handler_;
407  gcm::GCMClient::Result registration_result_;
408  gcm::GCMClient::Result unregistration_result_;
409
410  DISALLOW_COPY_AND_ASSIGN(ExtensionGCMAppHandlerTest);
411};
412
413TEST_F(ExtensionGCMAppHandlerTest, AddAndRemoveAppHandler) {
414  scoped_refptr<Extension> extension(CreateExtension());
415
416  // App handler is added when extension is loaded.
417  LoadExtension(extension);
418  waiter()->PumpUILoop();
419  EXPECT_TRUE(HasAppHandlers(extension->id()));
420
421  // App handler is removed when extension is unloaded.
422  DisableExtension(extension);
423  waiter()->PumpUILoop();
424  EXPECT_FALSE(HasAppHandlers(extension->id()));
425
426  // App handler is added when extension is reloaded.
427  EnableExtension(extension);
428  waiter()->PumpUILoop();
429  EXPECT_TRUE(HasAppHandlers(extension->id()));
430
431  // App handler is removed when extension is uninstalled.
432  UninstallExtension(extension);
433  waiter()->PumpUILoop();
434  EXPECT_FALSE(HasAppHandlers(extension->id()));
435}
436
437TEST_F(ExtensionGCMAppHandlerTest, UnregisterOnExtensionUninstall) {
438  scoped_refptr<Extension> extension(CreateExtension());
439  LoadExtension(extension);
440
441  // Sign-in is needed for registration.
442  SignIn(kTestingUsername);
443
444  // Kick off registration.
445  std::vector<std::string> sender_ids;
446  sender_ids.push_back("sender1");
447  Register(extension->id(), sender_ids);
448  waiter()->WaitUntilCompleted();
449  EXPECT_EQ(gcm::GCMClient::SUCCESS, registration_result());
450
451  // Add another app handler in order to prevent the GCM service from being
452  // stopped when the extension is uninstalled. This is needed because otherwise
453  // we are not able to receive the unregistration result.
454  GetGCMDriver()->AddAppHandler("Foo", gcm_app_handler());
455
456  // Unregistration should be triggered when the extension is uninstalled.
457  UninstallExtension(extension);
458  waiter()->WaitUntilCompleted();
459  EXPECT_EQ(gcm::GCMClient::SUCCESS,
460            gcm_app_handler()->unregistration_result());
461
462  // Clean up.
463  GetGCMDriver()->RemoveAppHandler("Foo");
464}
465
466TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionKept) {
467  scoped_refptr<Extension> extension(CreateExtension());
468
469  // App handler is added when the extension is loaded.
470  LoadExtension(extension);
471  waiter()->PumpUILoop();
472  EXPECT_TRUE(HasAppHandlers(extension->id()));
473
474  // App handler count should not drop to zero when the extension is updated.
475  UpdateExtension(extension, "gcm2.crx");
476  waiter()->PumpUILoop();
477  EXPECT_FALSE(gcm_app_handler()->app_handler_count_drop_to_zero());
478  EXPECT_TRUE(HasAppHandlers(extension->id()));
479}
480
481TEST_F(ExtensionGCMAppHandlerTest, UpdateExtensionWithGcmPermissionRemoved) {
482  scoped_refptr<Extension> extension(CreateExtension());
483
484  // App handler is added when the extension is loaded.
485  LoadExtension(extension);
486  waiter()->PumpUILoop();
487  EXPECT_TRUE(HasAppHandlers(extension->id()));
488
489  // App handler is removed when the extension is updated to the version that
490  // has GCM permission removed.
491  UpdateExtension(extension, "good2.crx");
492  waiter()->PumpUILoop();
493  EXPECT_TRUE(gcm_app_handler()->app_handler_count_drop_to_zero());
494  EXPECT_FALSE(HasAppHandlers(extension->id()));
495}
496
497}  // namespace extensions
498