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