extension_app_model_builder_unittest.cc revision 1e9bf3e0803691d0a228da41fc608347b6db4340
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/extension_sorting.h"
17#include "chrome/browser/extensions/install_tracker.h"
18#include "chrome/browser/extensions/install_tracker_factory.h"
19#include "chrome/common/extensions/extension_constants.h"
20#include "chrome/common/pref_names.h"
21#include "chrome/test/base/testing_profile.h"
22#include "extensions/common/manifest.h"
23#include "testing/gtest/include/gtest/gtest.h"
24#include "ui/app_list/app_list_item_model.h"
25
26namespace {
27
28const char kHostedAppId[] = "dceacbkfkmllgmjmbhgkpjegnodmildf";
29const char kPackagedApp1Id[] = "emfkafnhnpcmabnnkckkchdilgeoekbo";
30const char kPackagedApp2Id[] = "jlklkagmeajbjiobondfhiekepofmljl";
31
32// Get a string of all apps in |model| joined with ','.
33std::string GetModelContent(app_list::AppListModel* model) {
34  std::string content;
35  for (size_t i = 0; i < model->item_list()->item_count(); ++i) {
36    if (i > 0)
37      content += ',';
38    content += model->item_list()->item_at(i)->title();
39  }
40  return content;
41}
42
43scoped_refptr<extensions::Extension> MakeApp(const std::string& name,
44                                             const std::string& version,
45                                             const std::string& url,
46                                             const std::string& id) {
47  std::string err;
48  DictionaryValue value;
49  value.SetString("name", name);
50  value.SetString("version", version);
51  value.SetString("app.launch.web_url", url);
52  scoped_refptr<extensions::Extension> app =
53      extensions::Extension::Create(
54          base::FilePath(),
55          extensions::Manifest::INTERNAL,
56          value,
57          extensions::Extension::WAS_INSTALLED_BY_DEFAULT,
58          id,
59          &err);
60  EXPECT_EQ(err, "");
61  return app;
62}
63
64const char kDefaultApps[] = "Packaged App 1,Packaged App 2,Hosted App";
65const size_t kDefaultAppCount = 3u;
66
67}  // namespace
68
69class ExtensionAppModelBuilderTest : public ExtensionServiceTestBase {
70 public:
71  ExtensionAppModelBuilderTest() {}
72  virtual ~ExtensionAppModelBuilderTest() {}
73
74  virtual void SetUp() OVERRIDE {
75    ExtensionServiceTestBase::SetUp();
76
77    // Load "app_list" extensions test profile.
78    // The test profile has 4 extensions:
79    // 1 dummy extension, 2 packaged extension apps and 1 hosted extension app.
80    base::FilePath source_install_dir = data_dir_
81        .AppendASCII("app_list")
82        .AppendASCII("Extensions");
83    base::FilePath pref_path = source_install_dir
84        .DirName()
85        .AppendASCII("Preferences");
86    InitializeInstalledExtensionService(pref_path, source_install_dir);
87    service_->Init();
88
89    // There should be 4 extensions in the test profile.
90    const ExtensionSet* extensions = service_->extensions();
91    ASSERT_EQ(static_cast<size_t>(4),  extensions->size());
92
93    model_.reset(new app_list::AppListModel);
94  }
95
96  virtual void TearDown() OVERRIDE {
97    model_.reset();
98  }
99
100 protected:
101  scoped_ptr<app_list::AppListModel> model_;
102
103 private:
104  DISALLOW_COPY_AND_ASSIGN(ExtensionAppModelBuilderTest);
105};
106
107TEST_F(ExtensionAppModelBuilderTest, Build) {
108  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
109
110  // The apps list would have 3 extension apps in the profile.
111  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
112}
113
114TEST_F(ExtensionAppModelBuilderTest, HideWebStore) {
115  // Install a "web store" app.
116  scoped_refptr<extensions::Extension> store =
117      MakeApp("webstore",
118              "0.0",
119              "http://google.com",
120              std::string(extension_misc::kWebStoreAppId));
121  service_->AddExtension(store.get());
122
123  // Install an "enterprise web store" app.
124  scoped_refptr<extensions::Extension> enterprise_store =
125      MakeApp("enterprise_webstore",
126              "0.0",
127              "http://google.com",
128              std::string(extension_misc::kEnterpriseWebStoreAppId));
129  service_->AddExtension(enterprise_store.get());
130
131  // Web stores should be present in the AppListModel.
132  app_list::AppListModel model1;
133  ExtensionAppModelBuilder builder1(profile_.get(), &model1, NULL);
134  std::string content = GetModelContent(&model1);
135  EXPECT_NE(std::string::npos, content.find("webstore"));
136  EXPECT_NE(std::string::npos, content.find("enterprise_webstore"));
137
138  // Activate the HideWebStoreIcon policy.
139  profile_->GetPrefs()->SetBoolean(prefs::kHideWebStoreIcon, true);
140
141  // Web stores should NOT be in the AppListModel.
142  app_list::AppListModel model2;
143  ExtensionAppModelBuilder builder2(profile_.get(), &model2, NULL);
144  content = GetModelContent(&model2);
145  EXPECT_EQ(std::string::npos, content.find("webstore"));
146  EXPECT_EQ(std::string::npos, content.find("enterprise_webstore"));
147}
148
149TEST_F(ExtensionAppModelBuilderTest, DisableAndEnable) {
150  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
151
152  service_->DisableExtension(kHostedAppId,
153                             extensions::Extension::DISABLE_NONE);
154  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
155
156  service_->EnableExtension(kHostedAppId);
157  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
158}
159
160TEST_F(ExtensionAppModelBuilderTest, Uninstall) {
161  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
162
163  service_->UninstallExtension(kPackagedApp2Id, false, NULL);
164  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
165            GetModelContent(model_.get()));
166
167  base::RunLoop().RunUntilIdle();
168}
169
170TEST_F(ExtensionAppModelBuilderTest, UninstallTerminatedApp) {
171  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
172
173  const extensions::Extension* app =
174      service_->GetInstalledExtension(kPackagedApp2Id);
175  ASSERT_TRUE(app != NULL);
176
177  // Simulate an app termination.
178  service_->TrackTerminatedExtensionForTest(app);
179
180  service_->UninstallExtension(kPackagedApp2Id, false, NULL);
181  EXPECT_EQ(std::string("Packaged App 1,Hosted App"),
182            GetModelContent(model_.get()));
183
184  base::RunLoop().RunUntilIdle();
185}
186
187TEST_F(ExtensionAppModelBuilderTest, Reinstall) {
188  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
189  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
190
191  // Install kPackagedApp1Id again should not create a new entry.
192  extensions::InstallTracker* tracker =
193      extensions::InstallTrackerFactory::GetForProfile(profile_.get());
194  tracker->OnBeginExtensionInstall(
195      kPackagedApp1Id, "", gfx::ImageSkia(), true, true);
196
197  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
198}
199
200TEST_F(ExtensionAppModelBuilderTest, OrdinalPrefsChange) {
201  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
202
203  ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting();
204
205  syncer::StringOrdinal package_app_page =
206      sorting->GetPageOrdinal(kPackagedApp1Id);
207  sorting->SetPageOrdinal(kHostedAppId, package_app_page.CreateBefore());
208  // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
209  // New behavior: Sorting order doesn't change.
210  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
211
212  syncer::StringOrdinal app1_ordinal =
213      sorting->GetAppLaunchOrdinal(kPackagedApp1Id);
214  syncer::StringOrdinal app2_ordinal =
215      sorting->GetAppLaunchOrdinal(kPackagedApp2Id);
216  sorting->SetPageOrdinal(kHostedAppId, package_app_page);
217  sorting->SetAppLaunchOrdinal(kHostedAppId,
218                               app1_ordinal.CreateBetween(app2_ordinal));
219  // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
220  // New behavior: Sorting order doesn't change.
221  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
222}
223
224TEST_F(ExtensionAppModelBuilderTest, OnExtensionMoved) {
225  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
226
227  ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting();
228  sorting->SetPageOrdinal(kHostedAppId,
229                          sorting->GetPageOrdinal(kPackagedApp1Id));
230
231  service_->OnExtensionMoved(kHostedAppId, kPackagedApp1Id, kPackagedApp2Id);
232  // Old behavior: This would be "Packaged App 1,Hosted App,Packaged App 2"
233  // New behavior: Sorting order doesn't change.
234  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
235
236  service_->OnExtensionMoved(kHostedAppId, kPackagedApp2Id, std::string());
237  // Old behavior: This would be restored to the default order.
238  // New behavior: Sorting order still doesn't change.
239  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
240
241  service_->OnExtensionMoved(kHostedAppId, std::string(), kPackagedApp1Id);
242  // Old behavior: This would be "Hosted App,Packaged App 1,Packaged App 2"
243  // New behavior: Sorting order doesn't change.
244  EXPECT_EQ(std::string(kDefaultApps), GetModelContent(model_.get()));
245}
246
247TEST_F(ExtensionAppModelBuilderTest, InvalidOrdinal) {
248  // Creates a no-ordinal case.
249  ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting();
250  sorting->ClearOrdinals(kPackagedApp1Id);
251
252  // Creates an corrupted ordinal case.
253  ExtensionScopedPrefs* scoped_prefs = service_->extension_prefs();
254  scoped_prefs->UpdateExtensionPref(
255      kHostedAppId,
256      "page_ordinal",
257      base::Value::CreateStringValue("a corrupted ordinal"));
258
259  // This should not assert or crash.
260  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
261}
262
263TEST_F(ExtensionAppModelBuilderTest, OrdinalConfilicts) {
264  // Creates conflict ordinals for app1 and app2.
265  syncer::StringOrdinal conflict_ordinal =
266      syncer::StringOrdinal::CreateInitialOrdinal();
267
268  ExtensionSorting* sorting = service_->extension_prefs()->extension_sorting();
269  sorting->SetPageOrdinal(kHostedAppId, conflict_ordinal);
270  sorting->SetAppLaunchOrdinal(kHostedAppId, conflict_ordinal);
271
272  sorting->SetPageOrdinal(kPackagedApp1Id, conflict_ordinal);
273  sorting->SetAppLaunchOrdinal(kPackagedApp1Id, conflict_ordinal);
274
275  sorting->SetPageOrdinal(kPackagedApp2Id, conflict_ordinal);
276  sorting->SetAppLaunchOrdinal(kPackagedApp2Id, conflict_ordinal);
277
278  // This should not assert or crash.
279  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
280
281  // By default, conflicted items are sorted by their app ids (= order added).
282  EXPECT_EQ(std::string("Hosted App,Packaged App 1,Packaged App 2"),
283            GetModelContent(model_.get()));
284}
285
286TEST_F(ExtensionAppModelBuilderTest, SwitchProfile) {
287  ExtensionAppModelBuilder builder(profile_.get(), model_.get(), NULL);
288  EXPECT_EQ(kDefaultAppCount, model_->item_list()->item_count());
289
290  // Switch to a profile with no apps, ensure all apps are removed.
291  TestingProfile::Builder profile_builder;
292  scoped_ptr<TestingProfile> profile2(profile_builder.Build());
293  builder.SwitchProfile(profile2.get());
294  EXPECT_EQ(0u, model_->item_list()->item_count());
295
296  // Switch back to the main profile, ensure apps are restored.
297  builder.SwitchProfile(profile_.get());
298  EXPECT_EQ(kDefaultAppCount, model_->item_list()->item_count());
299}
300