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