extension_app_model_builder_unittest.cc revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
1// Copyright (c) 2012 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/ui/app_list/extension_app_model_builder.h" 6 7#include <string> 8 9#include "base/files/file_path.h" 10#include "base/memory/scoped_ptr.h" 11#include "base/prefs/pref_service.h" 12#include "base/run_loop.h" 13#include "base/values.h" 14#include "chrome/browser/extensions/extension_function_test_utils.h" 15#include "chrome/browser/extensions/extension_service_unittest.h" 16#include "chrome/browser/extensions/install_tracker.h" 17#include "chrome/browser/extensions/install_tracker_factory.h" 18#include "chrome/browser/ui/app_list/app_list_controller_delegate_impl.h" 19#include "chrome/common/chrome_constants.h" 20#include "chrome/common/extensions/extension_constants.h" 21#include "chrome/common/pref_names.h" 22#include "chrome/test/base/testing_profile.h" 23#include "extensions/browser/app_sorting.h" 24#include "extensions/common/extension_set.h" 25#include "extensions/common/manifest.h" 26#include "testing/gtest/include/gtest/gtest.h" 27#include "ui/app_list/app_list_item.h" 28 29namespace { 30 31const char kHostedAppId[] = "dceacbkfkmllgmjmbhgkpjegnodmildf"; 32const char kPackagedApp1Id[] = "emfkafnhnpcmabnnkckkchdilgeoekbo"; 33const char kPackagedApp2Id[] = "jlklkagmeajbjiobondfhiekepofmljl"; 34 35// Get a string of all apps in |model| joined with ','. 36std::string GetModelContent(app_list::AppListModel* model) { 37 std::string content; 38 for (size_t i = 0; i < model->item_list()->item_count(); ++i) { 39 if (i > 0) 40 content += ','; 41 content += model->item_list()->item_at(i)->title(); 42 } 43 return content; 44} 45 46scoped_refptr<extensions::Extension> MakeApp(const std::string& name, 47 const std::string& version, 48 const std::string& url, 49 const std::string& id) { 50 std::string err; 51 base::DictionaryValue value; 52 value.SetString("name", name); 53 value.SetString("version", version); 54 value.SetString("app.launch.web_url", url); 55 scoped_refptr<extensions::Extension> app = 56 extensions::Extension::Create( 57 base::FilePath(), 58 extensions::Manifest::INTERNAL, 59 value, 60 extensions::Extension::WAS_INSTALLED_BY_DEFAULT, 61 id, 62 &err); 63 EXPECT_EQ(err, ""); 64 return app; 65} 66 67class TestAppListControllerDelegate : public AppListControllerDelegate { 68 public: 69 virtual ~TestAppListControllerDelegate() {} 70 virtual void DismissView() OVERRIDE {} 71 virtual gfx::NativeWindow GetAppListWindow() OVERRIDE { return NULL; } 72 virtual gfx::ImageSkia GetWindowIcon() OVERRIDE { return gfx::ImageSkia(); } 73 virtual bool IsAppPinned(const std::string& extension_id) OVERRIDE { 74 return false; 75 } 76 virtual void PinApp(const std::string& extension_id) OVERRIDE {} 77 virtual void UnpinApp(const std::string& extension_id) OVERRIDE {} 78 virtual Pinnable GetPinnable() OVERRIDE { return NO_PIN; } 79 virtual bool CanDoCreateShortcutsFlow() OVERRIDE { return false; } 80 virtual void DoCreateShortcutsFlow(Profile* profile, 81 const std::string& extension_id) OVERRIDE { 82 } 83 virtual bool CanDoShowAppInfoFlow() OVERRIDE { return false; } 84 virtual void DoShowAppInfoFlow(Profile* profile, 85 const std::string& extension_id) OVERRIDE { 86 }; 87 virtual void CreateNewWindow(Profile* profile, bool incognito) OVERRIDE {} 88 virtual void ActivateApp(Profile* profile, 89 const extensions::Extension* extension, 90 AppListSource source, 91 int event_flags) OVERRIDE {} 92 virtual void LaunchApp(Profile* profile, 93 const extensions::Extension* extension, 94 AppListSource source, 95 int event_flags) OVERRIDE {} 96 virtual void ShowForProfileByPath( 97 const base::FilePath& profile_path) OVERRIDE {} 98 virtual bool ShouldShowUserIcon() OVERRIDE { return false; } 99}; 100 101const char kDefaultApps[] = "Packaged App 1,Packaged App 2,Hosted App"; 102const size_t kDefaultAppCount = 3u; 103 104} // namespace 105 106class ExtensionAppModelBuilderTest : public ExtensionServiceTestBase { 107 public: 108 ExtensionAppModelBuilderTest() {} 109 virtual ~ExtensionAppModelBuilderTest() {} 110 111 virtual void SetUp() OVERRIDE { 112 ExtensionServiceTestBase::SetUp(); 113 114 // Load "app_list" extensions test profile. 115 // The test profile has 4 extensions: 116 // 1 dummy extension, 2 packaged extension apps and 1 hosted extension app. 117 base::FilePath source_install_dir = data_dir_ 118 .AppendASCII("app_list") 119 .AppendASCII("Extensions"); 120 base::FilePath pref_path = source_install_dir 121 .DirName() 122 .Append(chrome::kPreferencesFilename); 123 InitializeInstalledExtensionService(pref_path, source_install_dir); 124 service_->Init(); 125 126 // There should be 4 extensions in the test profile. 127 const extensions::ExtensionSet* extensions = service_->extensions(); 128 ASSERT_EQ(static_cast<size_t>(4), extensions->size()); 129 130 CreateBuilder(); 131 } 132 133 virtual void TearDown() OVERRIDE { 134 ResetBuilder(); 135 } 136 137 protected: 138 // Creates a new builder, destroying any existing one. 139 void CreateBuilder() { 140 ResetBuilder(); // Destroy any existing builder in the correct order. 141 142 model_.reset(new app_list::AppListModel); 143 controller_.reset(new TestAppListControllerDelegate); 144 builder_.reset(new ExtensionAppModelBuilder(controller_.get())); 145 builder_->InitializeWithProfile(profile_.get(), model_.get()); 146 } 147 148 void ResetBuilder() { 149 builder_.reset(); 150 controller_.reset(); 151 model_.reset(); 152 } 153 154 scoped_ptr<app_list::AppListModel> model_; 155 scoped_ptr<TestAppListControllerDelegate> controller_; 156 scoped_ptr<ExtensionAppModelBuilder> builder_; 157 158 base::ScopedTempDir second_profile_temp_dir_; 159 160 private: 161 DISALLOW_COPY_AND_ASSIGN(ExtensionAppModelBuilderTest); 162}; 163 164TEST_F(ExtensionAppModelBuilderTest, Build) { 165 // The apps list would have 3 extension apps in the profile. 166 EXPECT_EQ(kDefaultAppCount, model_->item_list()->item_count()); 167 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 168} 169 170TEST_F(ExtensionAppModelBuilderTest, HideWebStore) { 171 // Install a "web store" app. 172 scoped_refptr<extensions::Extension> store = 173 MakeApp("webstore", 174 "0.0", 175 "http://google.com", 176 std::string(extension_misc::kWebStoreAppId)); 177 service_->AddExtension(store.get()); 178 179 // Install an "enterprise web store" app. 180 scoped_refptr<extensions::Extension> enterprise_store = 181 MakeApp("enterprise_webstore", 182 "0.0", 183 "http://google.com", 184 std::string(extension_misc::kEnterpriseWebStoreAppId)); 185 service_->AddExtension(enterprise_store.get()); 186 187 // Web stores should be present in the AppListModel. 188 app_list::AppListModel model1; 189 ExtensionAppModelBuilder builder1(controller_.get()); 190 builder1.InitializeWithProfile(profile_.get(), &model1); 191 std::string content = GetModelContent(&model1); 192 EXPECT_NE(std::string::npos, content.find("webstore")); 193 EXPECT_NE(std::string::npos, content.find("enterprise_webstore")); 194 195 // Activate the HideWebStoreIcon policy. 196 profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true); 197 198 // Web stores should NOT be in the AppListModel. 199 app_list::AppListModel model2; 200 ExtensionAppModelBuilder builder2(controller_.get()); 201 builder2.InitializeWithProfile(profile_.get(), &model2); 202 content = GetModelContent(&model2); 203 EXPECT_EQ(std::string::npos, content.find("webstore")); 204 EXPECT_EQ(std::string::npos, content.find("enterprise_webstore")); 205} 206 207TEST_F(ExtensionAppModelBuilderTest, DisableAndEnable) { 208 service_->DisableExtension(kHostedAppId, 209 extensions::Extension::DISABLE_NONE); 210 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 211 212 service_->EnableExtension(kHostedAppId); 213 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 214} 215 216TEST_F(ExtensionAppModelBuilderTest, Uninstall) { 217 service_->UninstallExtension(kPackagedApp2Id, false, NULL); 218 EXPECT_EQ(std::string("Packaged App 1,Hosted App"), 219 GetModelContent(model_.get())); 220 221 base::RunLoop().RunUntilIdle(); 222} 223 224TEST_F(ExtensionAppModelBuilderTest, UninstallTerminatedApp) { 225 const extensions::Extension* app = 226 service_->GetInstalledExtension(kPackagedApp2Id); 227 ASSERT_TRUE(app != NULL); 228 229 // Simulate an app termination. 230 service_->TrackTerminatedExtensionForTest(app); 231 232 service_->UninstallExtension(kPackagedApp2Id, false, NULL); 233 EXPECT_EQ(std::string("Packaged App 1,Hosted App"), 234 GetModelContent(model_.get())); 235 236 base::RunLoop().RunUntilIdle(); 237} 238 239TEST_F(ExtensionAppModelBuilderTest, Reinstall) { 240 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 241 242 // Install kPackagedApp1Id again should not create a new entry. 243 extensions::InstallTracker* tracker = 244 extensions::InstallTrackerFactory::GetForProfile(profile_.get()); 245 extensions::InstallObserver::ExtensionInstallParams params( 246 kPackagedApp1Id, "", gfx::ImageSkia(), true, true); 247 tracker->OnBeginExtensionInstall(params); 248 249 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 250} 251 252TEST_F(ExtensionAppModelBuilderTest, OrdinalPrefsChange) { 253 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting(); 254 255 syncer::StringOrdinal package_app_page = 256 sorting->GetPageOrdinal(kPackagedApp1Id); 257 sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore()); 258 // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2" 259 // New behavior: Sorting order doesn't change. 260 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 261 262 syncer::StringOrdinal app1_ordinal = 263 sorting->GetAppLaunchOrdinal(kPackagedApp1Id); 264 syncer::StringOrdinal app2_ordinal = 265 sorting->GetAppLaunchOrdinal(kPackagedApp2Id); 266 sorting->SetPageOrdinal(kHostedAppId, package_app_page); 267 sorting->SetAppLaunchOrdinal(kHostedAppId, 268 app1_ordinal.CreateBetween(app2_ordinal)); 269 // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2" 270 // New behavior: Sorting order doesn't change. 271 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 272} 273 274TEST_F(ExtensionAppModelBuilderTest, OnExtensionMoved) { 275 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting(); 276 sorting->SetPageOrdinal(kHostedAppId, 277 sorting->GetPageOrdinal(kPackagedApp1Id)); 278 279 service_->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id); 280 // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2" 281 // New behavior: Sorting order doesn't change. 282 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 283 284 service_->OnExtensionMoved(kHostedAppId, kPackagedApp2Id, std::string()); 285 // Old behavior: This would be restored to the default order. 286 // New behavior: Sorting order still doesn't change. 287 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 288 289 service_->OnExtensionMoved(kHostedAppId, std::string(), kPackagedApp1Id); 290 // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2" 291 // New behavior: Sorting order doesn't change. 292 EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get())); 293} 294 295TEST_F(ExtensionAppModelBuilderTest, InvalidOrdinal) { 296 // Creates a no-ordinal case. 297 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting(); 298 sorting->ClearOrdinals(kPackagedApp1Id); 299 300 // Creates a corrupted ordinal case. 301 extensions::ExtensionScopedPrefs* scoped_prefs = service_->extension_prefs(); 302 scoped_prefs->UpdateExtensionPref( 303 kHostedAppId, 304 "page_ordinal", 305 base::Value::CreateStringValue("a corrupted ordinal")); 306 307 // This should not assert or crash. 308 CreateBuilder(); 309} 310 311TEST_F(ExtensionAppModelBuilderTest, OrdinalConfilicts) { 312 // Creates conflict ordinals for app1 and app2. 313 syncer::StringOrdinal conflict_ordinal = 314 syncer::StringOrdinal::CreateInitialOrdinal(); 315 316 extensions::AppSorting* sorting = service_->extension_prefs()->app_sorting(); 317 sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal); 318 sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal); 319 320 sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal); 321 sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal); 322 323 sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal); 324 sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal); 325 326 // This should not assert or crash. 327 CreateBuilder(); 328 329 // By default, conflicted items are sorted by their app ids (= order added). 330 EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"), 331 GetModelContent(model_.get())); 332} 333 334// This test adds a bookmark app to the app list. 335TEST_F(ExtensionAppModelBuilderTest, BookmarkApp) { 336 const std::string kAppName = "Bookmark App"; 337 const std::string kAppVersion = "2014.1.24.19748"; 338 const std::string kAppUrl = "http://google.com"; 339 const std::string kAppId = "podhdnefolignjhecmjkbimfgioanahm"; 340 std::string err; 341 base::DictionaryValue value; 342 value.SetString("name", kAppName); 343 value.SetString("version", kAppVersion); 344 value.SetString("app.launch.web_url", kAppUrl); 345 scoped_refptr<extensions::Extension> bookmark_app = 346 extensions::Extension::Create( 347 base::FilePath(), 348 extensions::Manifest::INTERNAL, 349 value, 350 extensions::Extension::WAS_INSTALLED_BY_DEFAULT | 351 extensions::Extension::FROM_BOOKMARK, 352 kAppId, 353 &err); 354 EXPECT_TRUE(err.empty()); 355 356 service_->AddExtension(bookmark_app.get()); 357 EXPECT_EQ(kDefaultAppCount + 1, model_->item_list()->item_count()); 358 EXPECT_NE(std::string::npos, GetModelContent(model_.get()).find(kAppName)); 359} 360