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