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/apps/drive/drive_app_provider.h"
6
7#include "base/logging.h"
8#include "base/macros.h"
9#include "base/memory/scoped_ptr.h"
10#include "base/path_service.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/timer/timer.h"
13#include "chrome/browser/apps/drive/drive_app_mapping.h"
14#include "chrome/browser/apps/drive/drive_service_bridge.h"
15#include "chrome/browser/drive/drive_app_registry.h"
16#include "chrome/browser/drive/fake_drive_service.h"
17#include "chrome/browser/extensions/crx_installer.h"
18#include "chrome/browser/extensions/extension_browsertest.h"
19#include "chrome/browser/extensions/install_tracker.h"
20#include "chrome/browser/ui/app_list/app_list_syncable_service.h"
21#include "chrome/browser/ui/app_list/app_list_syncable_service_factory.h"
22#include "chrome/common/chrome_paths.h"
23#include "chrome/common/extensions/manifest_handlers/app_launch_info.h"
24#include "chrome/common/web_application_info.h"
25#include "content/public/test/test_utils.h"
26#include "extensions/browser/extension_registry.h"
27#include "extensions/browser/extension_system.h"
28
29using extensions::AppLaunchInfo;
30using extensions::Extension;
31using extensions::ExtensionRegistry;
32
33namespace {
34
35const char kDriveAppId[] = "drive_app_id";
36const char kDriveAppName[] = "Fake Drive App";
37const char kLaunchUrl[] = "http://example.com/drive";
38
39// App id of hosted_app.crx.
40const char kChromeAppId[] = "kbmnembihfiondgfjekmnmcbddelicoi";
41
42// Stub drive service bridge.
43class TestDriveServiceBridge : public DriveServiceBridge {
44 public:
45  explicit TestDriveServiceBridge(drive::DriveAppRegistry* registry)
46      : registry_(registry) {}
47  virtual ~TestDriveServiceBridge() {}
48
49  virtual drive::DriveAppRegistry* GetAppRegistry() OVERRIDE {
50    return registry_;
51  }
52
53 private:
54  drive::DriveAppRegistry* registry_;
55
56  DISALLOW_COPY_AND_ASSIGN(TestDriveServiceBridge);
57};
58
59}  // namespace
60
61class DriveAppProviderTest : public ExtensionBrowserTest,
62                             public extensions::InstallObserver {
63 public:
64  DriveAppProviderTest() {}
65  virtual ~DriveAppProviderTest() {}
66
67  // ExtensionBrowserTest:
68  virtual void SetUpOnMainThread() OVERRIDE {
69    ExtensionBrowserTest::SetUpOnMainThread();
70
71    fake_drive_service_.reset(new drive::FakeDriveService);
72    fake_drive_service_->LoadAppListForDriveApi("drive/applist_empty.json");
73    apps_registry_.reset(
74        new drive::DriveAppRegistry(fake_drive_service_.get()));
75
76    provider_.reset(new DriveAppProvider(profile()));
77    provider_->SetDriveServiceBridgeForTest(
78        make_scoped_ptr(new TestDriveServiceBridge(apps_registry_.get()))
79            .PassAs<DriveServiceBridge>());
80
81    // The DriveAppProvider in AppListSyncalbeService interferes with the
82    // test. So resets it.
83    app_list::AppListSyncableServiceFactory::GetForProfile(profile())
84        ->ResetDriveAppProviderForTest();
85  }
86
87  virtual void TearDownOnMainThread() OVERRIDE {
88    provider_.reset();
89    apps_registry_.reset();
90    fake_drive_service_.reset();
91
92    ExtensionBrowserTest::TearDownOnMainThread();
93  }
94
95  const Extension* InstallChromeApp(int expected_change) {
96    base::FilePath test_data_path;
97    if (!PathService::Get(chrome::DIR_TEST_DATA, &test_data_path)) {
98      ADD_FAILURE();
99      return NULL;
100    }
101    test_data_path =
102        test_data_path.AppendASCII("extensions").AppendASCII("hosted_app.crx");
103    const Extension* extension =
104        InstallExtension(test_data_path, expected_change);
105    return extension;
106  }
107
108  void RefreshDriveAppRegistry() {
109    apps_registry_->Update();
110    content::RunAllPendingInMessageLoop();
111  }
112
113  void WaitForPendingDriveAppConverters() {
114    DCHECK(!runner_.get());
115
116    if (provider_->pending_converters_.empty())
117      return;
118
119    runner_ = new content::MessageLoopRunner;
120
121    pending_drive_app_converter_check_timer_.Start(
122        FROM_HERE,
123        base::TimeDelta::FromMilliseconds(50),
124        base::Bind(&DriveAppProviderTest::OnPendingDriveAppConverterCheckTimer,
125                   base::Unretained(this)));
126
127    runner_->Run();
128
129    pending_drive_app_converter_check_timer_.Stop();
130    runner_ = NULL;
131  }
132
133  void InstallUserUrlApp(const std::string& url) {
134    DCHECK(!runner_.get());
135    runner_ = new content::MessageLoopRunner;
136
137    WebApplicationInfo web_app;
138    web_app.title = base::ASCIIToUTF16("User installed Url app");
139    web_app.app_url = GURL(url);
140
141    scoped_refptr<extensions::CrxInstaller> crx_installer =
142        extensions::CrxInstaller::CreateSilent(
143            extensions::ExtensionSystem::Get(profile())->extension_service());
144    crx_installer->set_creation_flags(Extension::FROM_BOOKMARK);
145    extensions::InstallTracker::Get(profile())->AddObserver(this);
146    crx_installer->InstallWebApp(web_app);
147
148    runner_->Run();
149    runner_ = NULL;
150    extensions::InstallTracker::Get(profile())->RemoveObserver(this);
151
152    content::RunAllPendingInMessageLoop();
153  }
154
155  bool HasPendingConverters() const {
156    return !provider_->pending_converters_.empty();
157  }
158
159  drive::FakeDriveService* fake_drive_service() {
160    return fake_drive_service_.get();
161  }
162  DriveAppProvider* provider() { return provider_.get(); }
163  DriveAppMapping* mapping() { return provider_->mapping_.get(); }
164
165 private:
166  void OnPendingDriveAppConverterCheckTimer() {
167    if (!HasPendingConverters())
168      runner_->Quit();
169  }
170
171  // extensions::InstallObserver
172  virtual void OnFinishCrxInstall(const std::string& extension_id,
173                                  bool success) OVERRIDE {
174    runner_->Quit();
175  }
176
177  scoped_ptr<drive::FakeDriveService> fake_drive_service_;
178  scoped_ptr<drive::DriveAppRegistry> apps_registry_;
179  scoped_ptr<DriveAppProvider> provider_;
180
181  base::RepeatingTimer<DriveAppProviderTest>
182      pending_drive_app_converter_check_timer_;
183  scoped_refptr<content::MessageLoopRunner> runner_;
184
185  DISALLOW_COPY_AND_ASSIGN(DriveAppProviderTest);
186};
187
188// A Drive app maps to an existing Chrome app that has a matching id.
189// Uninstalling the chrome app would also disconnect the drive app.
190IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, ExistingChromeApp) {
191  // Prepare an existing chrome app.
192  const Extension* chrome_app = InstallChromeApp(1);
193  ASSERT_TRUE(chrome_app);
194
195  // Prepare a Drive app that matches the chrome app id.
196  fake_drive_service()->AddApp(
197      kDriveAppId, kDriveAppName, chrome_app->id(), kLaunchUrl);
198  RefreshDriveAppRegistry();
199  EXPECT_FALSE(HasPendingConverters());
200
201  // The Drive app should use the matching chrome app.
202  EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
203  EXPECT_FALSE(mapping()->IsChromeAppGenerated(chrome_app->id()));
204
205  // Unintalling chrome app should disconnect the Drive app on server.
206  EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
207  UninstallExtension(chrome_app->id());
208  EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
209}
210
211// A Drive app creates an URL app when no matching Chrome app presents.
212IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, CreateUrlApp) {
213  // Prepare a Drive app with no underlying chrome app.
214  fake_drive_service()->AddApp(kDriveAppId, kDriveAppName, "", kLaunchUrl);
215  RefreshDriveAppRegistry();
216  WaitForPendingDriveAppConverters();
217
218  // An Url app should be created.
219  const Extension* chrome_app =
220      ExtensionRegistry::Get(profile())->GetExtensionById(
221          mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
222  ASSERT_TRUE(chrome_app);
223  EXPECT_EQ(kDriveAppName, chrome_app->name());
224  EXPECT_TRUE(chrome_app->is_hosted_app());
225  EXPECT_TRUE(chrome_app->from_bookmark());
226  EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(chrome_app));
227
228  EXPECT_EQ(chrome_app->id(), mapping()->GetChromeApp(kDriveAppId));
229  EXPECT_TRUE(mapping()->IsChromeAppGenerated(chrome_app->id()));
230
231  // Unintalling the chrome app should disconnect the Drive app on server.
232  EXPECT_TRUE(fake_drive_service()->HasApp(kDriveAppId));
233  UninstallExtension(chrome_app->id());
234  EXPECT_FALSE(fake_drive_service()->HasApp(kDriveAppId));
235}
236
237// A matching Chrome app replaces the created URL app.
238IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, MatchingChromeAppInstalled) {
239  // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
240  fake_drive_service()->AddApp(
241      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
242  RefreshDriveAppRegistry();
243  WaitForPendingDriveAppConverters();
244
245  // An Url app should be created.
246  const Extension* url_app =
247      ExtensionRegistry::Get(profile())->GetExtensionById(
248          mapping()->GetChromeApp(kDriveAppId), ExtensionRegistry::EVERYTHING);
249  EXPECT_TRUE(url_app->is_hosted_app());
250  EXPECT_TRUE(url_app->from_bookmark());
251
252  const std::string url_app_id = url_app->id();
253  EXPECT_NE(kChromeAppId, url_app_id);
254  EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
255  EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
256
257  // Installs a chrome app with matching id.
258  InstallChromeApp(0);
259
260  // The Drive app should be mapped to chrome app.
261  EXPECT_EQ(kChromeAppId, mapping()->GetChromeApp(kDriveAppId));
262  EXPECT_FALSE(mapping()->IsChromeAppGenerated(kChromeAppId));
263
264  // Url app should be auto uninstalled.
265  EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
266      url_app_id, ExtensionRegistry::EVERYTHING));
267}
268
269// Tests that the corresponding URL app is uninstalled when a Drive app is
270// disconnected.
271IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
272                       DisconnectDriveAppUninstallUrlApp) {
273  // Prepare a Drive app that matches the not-yet-installed kChromeAppId.
274  fake_drive_service()->AddApp(
275      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
276  RefreshDriveAppRegistry();
277  WaitForPendingDriveAppConverters();
278
279  // Url app is created.
280  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
281  EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
282      url_app_id, ExtensionRegistry::EVERYTHING));
283
284  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
285  RefreshDriveAppRegistry();
286
287  // Url app is auto uninstalled.
288  EXPECT_FALSE(ExtensionRegistry::Get(profile())->GetExtensionById(
289      url_app_id, ExtensionRegistry::EVERYTHING));
290}
291
292// Tests that the matching Chrome app is preserved when a Drive app is
293// disconnected.
294IN_PROC_BROWSER_TEST_F(DriveAppProviderTest,
295                       DisconnectDriveAppPreserveChromeApp) {
296  // Prepare an existing chrome app.
297  const Extension* chrome_app = InstallChromeApp(1);
298  ASSERT_TRUE(chrome_app);
299
300  // Prepare a Drive app that matches the chrome app id.
301  fake_drive_service()->AddApp(
302      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
303  RefreshDriveAppRegistry();
304  EXPECT_FALSE(HasPendingConverters());
305
306  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
307  RefreshDriveAppRegistry();
308
309  // Chrome app is still present after the Drive app is disconnected.
310  EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
311      kChromeAppId, ExtensionRegistry::EVERYTHING));
312}
313
314// The "generated" flag of an app should stay across Drive app conversion.
315IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, KeepGeneratedFlagBetweenUpdates) {
316  // Prepare a Drive app with no underlying chrome app.
317  fake_drive_service()->AddApp(
318      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
319  RefreshDriveAppRegistry();
320  WaitForPendingDriveAppConverters();
321
322  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
323  EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
324
325  // Change name to trigger an update.
326  const char kChangedName[] = "Changed name";
327  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
328  fake_drive_service()->AddApp(
329      kDriveAppId, kChangedName, kChromeAppId, kLaunchUrl);
330  RefreshDriveAppRegistry();
331  WaitForPendingDriveAppConverters();
332
333  // It should still map to the same url app id and tagged as generated.
334  EXPECT_EQ(url_app_id, mapping()->GetChromeApp(kDriveAppId));
335  EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
336}
337
338// A new URL app replaces the existing one and keeps existing// position when a
339// Drive app changes its name or URL.
340IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, DriveAppChanged) {
341  // Prepare a Drive app with no underlying chrome app.
342  fake_drive_service()->AddApp(
343      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
344  RefreshDriveAppRegistry();
345  WaitForPendingDriveAppConverters();
346
347  // An Url app should be created.
348  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
349  const Extension* url_app =
350      ExtensionRegistry::Get(profile())
351          ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
352  ASSERT_TRUE(url_app);
353  EXPECT_EQ(kDriveAppName, url_app->name());
354  EXPECT_TRUE(url_app->is_hosted_app());
355  EXPECT_TRUE(url_app->from_bookmark());
356  EXPECT_EQ(GURL(kLaunchUrl), AppLaunchInfo::GetLaunchWebURL(url_app));
357  EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
358
359  // Register the Drive app with a different name and URL.
360  const char kAnotherName[] = "Another drive app name";
361  const char kAnotherLaunchUrl[] = "http://example.com/another_end_point";
362  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
363  fake_drive_service()->AddApp(
364      kDriveAppId, kAnotherName, kChromeAppId, kAnotherLaunchUrl);
365  RefreshDriveAppRegistry();
366  WaitForPendingDriveAppConverters();
367
368  // Old URL app should be auto uninstalled.
369  url_app = ExtensionRegistry::Get(profile())
370                ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
371  EXPECT_FALSE(url_app);
372
373  // New URL app should be used.
374  const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
375  EXPECT_NE(new_url_app_id, url_app_id);
376  EXPECT_TRUE(mapping()->IsChromeAppGenerated(new_url_app_id));
377
378  const Extension* new_url_app =
379      ExtensionRegistry::Get(profile())
380          ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
381  ASSERT_TRUE(new_url_app);
382  EXPECT_EQ(kAnotherName, new_url_app->name());
383  EXPECT_TRUE(new_url_app->is_hosted_app());
384  EXPECT_TRUE(new_url_app->from_bookmark());
385  EXPECT_EQ(GURL(kAnotherLaunchUrl),
386            AppLaunchInfo::GetLaunchWebURL(new_url_app));
387}
388
389// An existing URL app is not changed when underlying drive app data (name and
390// URL) is not changed.
391IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, NoChange) {
392  // Prepare one Drive app.
393  fake_drive_service()->AddApp(
394      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
395  RefreshDriveAppRegistry();
396  WaitForPendingDriveAppConverters();
397
398  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
399  const Extension* url_app =
400      ExtensionRegistry::Get(profile())
401          ->GetExtensionById(url_app_id, ExtensionRegistry::EVERYTHING);
402
403  // Refresh with no actual change.
404  RefreshDriveAppRegistry();
405  EXPECT_FALSE(HasPendingConverters());
406
407  // Url app should remain unchanged.
408  const std::string new_url_app_id = mapping()->GetChromeApp(kDriveAppId);
409  EXPECT_EQ(new_url_app_id, url_app_id);
410
411  const Extension* new_url_app =
412      ExtensionRegistry::Get(profile())
413          ->GetExtensionById(new_url_app_id, ExtensionRegistry::EVERYTHING);
414  EXPECT_EQ(url_app, new_url_app);
415}
416
417// User installed url app before Drive app conversion should not be tagged
418// as generated and not auto uninstalled.
419IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledBeforeDriveApp) {
420  InstallUserUrlApp(kLaunchUrl);
421
422  fake_drive_service()->AddApp(
423      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
424  RefreshDriveAppRegistry();
425  WaitForPendingDriveAppConverters();
426
427  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
428  EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
429
430  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
431  RefreshDriveAppRegistry();
432
433  // Url app is still present after the Drive app is disconnected.
434  EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
435      url_app_id, ExtensionRegistry::EVERYTHING));
436}
437
438// Similar to UserInstalledBeforeDriveApp but test the case where user
439// installation happens after Drive app conversion.
440IN_PROC_BROWSER_TEST_F(DriveAppProviderTest, UserInstalledAfterDriveApp) {
441  fake_drive_service()->AddApp(
442      kDriveAppId, kDriveAppName, kChromeAppId, kLaunchUrl);
443  RefreshDriveAppRegistry();
444  WaitForPendingDriveAppConverters();
445
446  // Drive app converted and tagged as generated.
447  const std::string url_app_id = mapping()->GetChromeApp(kDriveAppId);
448  EXPECT_TRUE(mapping()->IsChromeAppGenerated(url_app_id));
449
450  // User installation resets the generated flag.
451  InstallUserUrlApp(kLaunchUrl);
452  EXPECT_FALSE(mapping()->IsChromeAppGenerated(url_app_id));
453
454  fake_drive_service()->RemoveAppByProductId(kChromeAppId);
455  RefreshDriveAppRegistry();
456
457  // Url app is still present after the Drive app is disconnected.
458  EXPECT_TRUE(ExtensionRegistry::Get(profile())->GetExtensionById(
459      url_app_id, ExtensionRegistry::EVERYTHING));
460}
461