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// MediaFileSystemRegistry unit tests.
6
7#include <algorithm>
8#include <set>
9
10#include "base/bind_helpers.h"
11#include "base/command_line.h"
12#include "base/file_util.h"
13#include "base/files/scoped_temp_dir.h"
14#include "base/memory/ref_counted.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/memory/scoped_vector.h"
17#include "base/message_loop/message_loop.h"
18#include "base/path_service.h"
19#include "base/run_loop.h"
20#include "base/stl_util.h"
21#include "base/strings/stringprintf.h"
22#include "base/strings/utf_string_conversions.h"
23#include "base/threading/sequenced_worker_pool.h"
24#include "base/values.h"
25#include "chrome/browser/extensions/extension_service.h"
26#include "chrome/browser/extensions/extension_system.h"
27#include "chrome/browser/extensions/test_extension_system.h"
28#include "chrome/browser/media_galleries/media_file_system_context.h"
29#include "chrome/browser/media_galleries/media_file_system_registry.h"
30#include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
31#include "chrome/browser/media_galleries/media_galleries_test_util.h"
32#include "chrome/browser/storage_monitor/removable_device_constants.h"
33#include "chrome/browser/storage_monitor/storage_info.h"
34#include "chrome/browser/storage_monitor/storage_monitor.h"
35#include "chrome/browser/storage_monitor/test_storage_monitor.h"
36#include "chrome/common/chrome_paths.h"
37#include "chrome/test/base/chrome_render_view_host_test_harness.h"
38#include "chrome/test/base/testing_browser_process.h"
39#include "chrome/test/base/testing_profile.h"
40#include "content/public/browser/render_process_host.h"
41#include "content/public/browser/render_process_host_factory.h"
42#include "content/public/browser/render_view_host.h"
43#include "content/public/browser/web_contents.h"
44#include "content/public/test/mock_render_process_host.h"
45#include "content/public/test/test_browser_thread.h"
46#include "content/public/test/web_contents_tester.h"
47#include "extensions/common/extension.h"
48#include "sync/api/string_ordinal.h"
49#include "testing/gtest/include/gtest/gtest.h"
50
51#if defined(OS_CHROMEOS)
52#include "chrome/browser/chromeos/login/user_manager.h"
53#include "chrome/browser/chromeos/settings/cros_settings.h"
54#include "chrome/browser/chromeos/settings/device_settings_service.h"
55#endif
56
57using content::BrowserThread;
58
59// Not anonymous so it can be friends with MediaFileSystemRegistry.
60class TestMediaFileSystemContext : public MediaFileSystemContext {
61 public:
62  struct FSInfo {
63    FSInfo() {}
64    FSInfo(const std::string& device_id, const base::FilePath& path,
65           const std::string& fsid);
66
67    bool operator<(const FSInfo& other) const;
68
69    std::string device_id;
70    base::FilePath path;
71    std::string fsid;
72  };
73
74  explicit TestMediaFileSystemContext(MediaFileSystemRegistry* registry);
75  virtual ~TestMediaFileSystemContext() {}
76
77  // MediaFileSystemContext implementation.
78  virtual std::string RegisterFileSystem(
79      const std::string& device_id, const base::FilePath& path) OVERRIDE;
80
81  virtual void RevokeFileSystem(const std::string& fsid) OVERRIDE;
82
83  base::FilePath GetPathForId(const std::string& fsid) const;
84
85  MediaFileSystemRegistry* registry() { return registry_; }
86
87 private:
88  std::string AddFSEntry(const std::string& device_id,
89                         const base::FilePath& path);
90
91  MediaFileSystemRegistry* registry_;
92
93  // A counter used to construct mock FSIDs.
94  int fsid_;
95
96  // The currently allocated mock file systems.
97  std::map<std::string /*fsid*/, FSInfo> file_systems_by_id_;
98};
99
100TestMediaFileSystemContext::FSInfo::FSInfo(const std::string& device_id,
101                                           const base::FilePath& path,
102                                           const std::string& fsid)
103    : device_id(device_id),
104      path(path),
105      fsid(fsid) {
106}
107
108bool TestMediaFileSystemContext::FSInfo::operator<(const FSInfo& other) const {
109  if (device_id != other.device_id)
110    return device_id < other.device_id;
111  if (path.value() != other.path.value())
112    return path.value() < other.path.value();
113  return fsid < other.fsid;
114}
115
116TestMediaFileSystemContext::TestMediaFileSystemContext(
117    MediaFileSystemRegistry* registry)
118    : registry_(registry),
119      fsid_(0) {
120  registry_->file_system_context_.reset(this);
121}
122
123std::string TestMediaFileSystemContext::RegisterFileSystem(
124    const std::string& device_id, const base::FilePath& path) {
125  std::string fsid = AddFSEntry(device_id, path);
126  return fsid;
127}
128
129void TestMediaFileSystemContext::RevokeFileSystem(const std::string& fsid) {
130  if (!ContainsKey(file_systems_by_id_, fsid))
131    return;
132  EXPECT_EQ(1U, file_systems_by_id_.erase(fsid));
133}
134
135base::FilePath TestMediaFileSystemContext::GetPathForId(
136    const std::string& fsid) const {
137  std::map<std::string /*fsid*/, FSInfo>::const_iterator it =
138      file_systems_by_id_.find(fsid);
139  if (it == file_systems_by_id_.end())
140    return base::FilePath();
141  return it->second.path;
142}
143
144std::string TestMediaFileSystemContext::AddFSEntry(const std::string& device_id,
145                                                   const base::FilePath& path) {
146  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
147  DCHECK(path.IsAbsolute());
148  DCHECK(!path.ReferencesParent());
149
150  std::string fsid = base::StringPrintf("FSID:%d", ++fsid_);
151  FSInfo info(device_id, path, fsid);
152  file_systems_by_id_[fsid] = info;
153  return fsid;
154}
155
156namespace {
157
158typedef std::map<MediaGalleryPrefId, MediaFileSystemInfo> FSInfoMap;
159
160void GetGalleryInfoCallback(
161    FSInfoMap* results,
162    const std::vector<MediaFileSystemInfo>& file_systems) {
163  for (size_t i = 0; i < file_systems.size(); ++i) {
164    ASSERT_FALSE(ContainsKey(*results, file_systems[i].pref_id));
165    (*results)[file_systems[i].pref_id] = file_systems[i];
166  }
167}
168
169void CheckGalleryInfo(const MediaFileSystemInfo& info,
170                      TestMediaFileSystemContext* fs_context,
171                      const base::FilePath& path,
172                      bool removable,
173                      bool media_device) {
174  EXPECT_EQ(path, info.path);
175  EXPECT_EQ(removable, info.removable);
176  EXPECT_EQ(media_device, info.media_device);
177  EXPECT_NE(0UL, info.pref_id);
178
179  if (removable)
180    EXPECT_NE(0UL, info.transient_device_id.size());
181  else
182    EXPECT_EQ(0UL, info.transient_device_id.size());
183
184  base::FilePath fsid_path = fs_context->GetPathForId(info.fsid);
185  EXPECT_EQ(path, fsid_path);
186}
187
188class MockProfileSharedRenderProcessHostFactory
189    : public content::RenderProcessHostFactory {
190 public:
191  MockProfileSharedRenderProcessHostFactory() {}
192  virtual ~MockProfileSharedRenderProcessHostFactory();
193
194  // RPH created with this factory are owned by it.  If the RPH is destroyed
195  // for testing purposes, it must be removed from the factory first.
196  content::MockRenderProcessHost* ReleaseRPH(
197      content::BrowserContext* browser_context);
198
199  virtual content::RenderProcessHost* CreateRenderProcessHost(
200      content::BrowserContext* browser_context,
201      content::SiteInstance* site_instance) const OVERRIDE;
202
203 private:
204  typedef std::map<content::BrowserContext*, content::MockRenderProcessHost*>
205      ProfileRPHMap;
206  mutable ProfileRPHMap rph_map_;
207
208  DISALLOW_COPY_AND_ASSIGN(MockProfileSharedRenderProcessHostFactory);
209};
210
211class ProfileState {
212 public:
213  explicit ProfileState(
214      MockProfileSharedRenderProcessHostFactory* rph_factory);
215  ~ProfileState();
216
217  MediaGalleriesPreferences* GetMediaGalleriesPrefs();
218
219  void CheckGalleries(
220      const std::string& test,
221      const std::vector<MediaFileSystemInfo>& regular_extension_galleries,
222      const std::vector<MediaFileSystemInfo>& all_extension_galleries);
223
224  FSInfoMap GetGalleriesInfo(extensions::Extension* extension);
225
226  extensions::Extension* all_permission_extension();
227  extensions::Extension* regular_permission_extension();
228  Profile* profile();
229
230  void AddNameForReadCompare(const base::string16& name);
231  void AddNameForAllCompare(const base::string16& name);
232
233 private:
234  void CompareResults(const std::string& test,
235                      const std::vector<base::string16>& names,
236                      const std::vector<MediaFileSystemInfo>& expected,
237                      const std::vector<MediaFileSystemInfo>& actual);
238  bool ContainsEntry(const MediaFileSystemInfo& info,
239                     const std::vector<MediaFileSystemInfo>& container);
240
241  int GetAndClearComparisonCount();
242
243  int num_comparisons_;
244
245  scoped_ptr<TestingProfile> profile_;
246
247  scoped_refptr<extensions::Extension> all_permission_extension_;
248  scoped_refptr<extensions::Extension> regular_permission_extension_;
249  scoped_refptr<extensions::Extension> no_permissions_extension_;
250
251  scoped_ptr<content::WebContents> single_web_contents_;
252  scoped_ptr<content::WebContents> shared_web_contents1_;
253  scoped_ptr<content::WebContents> shared_web_contents2_;
254
255  // The RenderProcessHosts are freed when their respective WebContents /
256  // RenderViewHosts go away.
257  content::MockRenderProcessHost* single_rph_;
258  content::MockRenderProcessHost* shared_rph_;
259
260  std::vector<base::string16> compare_names_read_;
261  std::vector<base::string16> compare_names_all_;
262
263  DISALLOW_COPY_AND_ASSIGN(ProfileState);
264};
265
266}  // namespace
267
268class MediaFileSystemRegistryTest : public ChromeRenderViewHostTestHarness {
269 public:
270  void CreateProfileState(size_t profile_count);
271
272  ProfileState* GetProfileState(size_t i);
273
274  MediaGalleriesPreferences* GetPreferences(Profile* profile);
275
276  base::FilePath empty_dir() {
277    return empty_dir_;
278  }
279
280  base::FilePath dcim_dir() {
281    return dcim_dir_;
282  }
283
284  TestMediaFileSystemContext* test_file_system_context() {
285    return test_file_system_context_;
286  }
287
288  // Create a user added gallery based on the information passed and add it to
289  // |profiles|. Returns the device id.
290  std::string AddUserGallery(StorageInfo::Type type,
291                             const std::string& unique_id,
292                             const base::FilePath& path);
293
294  // Returns the device id.
295  std::string AttachDevice(StorageInfo::Type type,
296                           const std::string& unique_id,
297                           const base::FilePath& location);
298
299  void DetachDevice(const std::string& device_id);
300
301  void SetGalleryPermission(ProfileState* profile_state,
302                            extensions::Extension* extension,
303                            const std::string& device_id,
304                            bool has_access);
305
306  void AssertAllAutoAddedGalleries();
307
308  void InitForGalleriesInfoTest(FSInfoMap* galleries_info);
309
310  void CheckNewGalleryInfo(ProfileState* profile_state,
311                           const FSInfoMap& galleries_info,
312                           const base::FilePath& location,
313                           bool removable,
314                           bool media_device);
315
316  std::vector<MediaFileSystemInfo> GetAutoAddedGalleries(
317      ProfileState* profile_state);
318
319  void ProcessAttach(const std::string& id,
320                     const base::string16& name,
321                     const base::FilePath::StringType& location) {
322    StorageInfo info(id, base::string16(), location, name, base::string16(),
323                     base::string16(), 0);
324    StorageMonitor::GetInstance()->receiver()->ProcessAttach(info);
325  }
326
327  void ProcessDetach(const std::string& id) {
328    StorageMonitor::GetInstance()->receiver()->ProcessDetach(id);
329  }
330
331  MediaFileSystemRegistry* registry() {
332    return test_file_system_context_->registry();
333  }
334
335  size_t GetExtensionGalleriesHostCount(
336      const MediaFileSystemRegistry* registry) const;
337
338  int num_auto_galleries() {
339    return media_directories_.num_galleries();
340  }
341
342 protected:
343  virtual void SetUp() OVERRIDE;
344  virtual void TearDown() OVERRIDE;
345
346 private:
347  // This makes sure that at least one default gallery exists on the file
348  // system.
349  EnsureMediaDirectoriesExists media_directories_;
350
351  // Some test gallery directories.
352  base::ScopedTempDir galleries_dir_;
353  // An empty directory in |galleries_dir_|
354  base::FilePath empty_dir_;
355  // A directory in |galleries_dir_| with a DCIM directory in it.
356  base::FilePath dcim_dir_;
357
358  // MediaFileSystemRegistry owns this.
359  TestMediaFileSystemContext* test_file_system_context_;
360
361  // Needed for extension service & friends to work.
362
363#if defined OS_CHROMEOS
364  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
365  chromeos::ScopedTestCrosSettings test_cros_settings_;
366  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
367#endif
368
369  MockProfileSharedRenderProcessHostFactory rph_factory_;
370
371  ScopedVector<ProfileState> profile_states_;
372};
373
374namespace {
375
376bool MediaFileSystemInfoComparator(const MediaFileSystemInfo& a,
377                                   const MediaFileSystemInfo& b) {
378  CHECK_NE(a.name, b.name);  // Name must be unique.
379  return a.name < b.name;
380}
381
382///////////////////////////////////////////////
383// MockProfileSharedRenderProcessHostFactory //
384///////////////////////////////////////////////
385
386MockProfileSharedRenderProcessHostFactory::
387    ~MockProfileSharedRenderProcessHostFactory() {
388  STLDeleteValues(&rph_map_);
389}
390
391content::MockRenderProcessHost*
392MockProfileSharedRenderProcessHostFactory::ReleaseRPH(
393    content::BrowserContext* browser_context) {
394  ProfileRPHMap::iterator existing = rph_map_.find(browser_context);
395  if (existing == rph_map_.end())
396    return NULL;
397  content::MockRenderProcessHost* result = existing->second;
398  rph_map_.erase(existing);
399  return result;
400}
401
402content::RenderProcessHost*
403MockProfileSharedRenderProcessHostFactory::CreateRenderProcessHost(
404    content::BrowserContext* browser_context,
405    content::SiteInstance* site_instance) const {
406  ProfileRPHMap::const_iterator existing = rph_map_.find(browser_context);
407  if (existing != rph_map_.end())
408    return existing->second;
409  rph_map_[browser_context] =
410      new content::MockRenderProcessHost(browser_context);
411  return rph_map_[browser_context];
412}
413
414//////////////////
415// ProfileState //
416//////////////////
417
418ProfileState::ProfileState(
419    MockProfileSharedRenderProcessHostFactory* rph_factory)
420    : num_comparisons_(0),
421      profile_(new TestingProfile()) {
422  extensions::TestExtensionSystem* extension_system(
423      static_cast<extensions::TestExtensionSystem*>(
424          extensions::ExtensionSystem::Get(profile_.get())));
425  extension_system->CreateExtensionService(
426      CommandLine::ForCurrentProcess(), base::FilePath(), false);
427
428  std::vector<std::string> all_permissions;
429  all_permissions.push_back("allAutoDetected");
430  all_permissions.push_back("read");
431  std::vector<std::string> read_permissions;
432  read_permissions.push_back("read");
433
434  all_permission_extension_ =
435      AddMediaGalleriesApp("all", all_permissions, profile_.get());
436  regular_permission_extension_ =
437      AddMediaGalleriesApp("regular", read_permissions, profile_.get());
438  no_permissions_extension_ =
439      AddMediaGalleriesApp("no", read_permissions, profile_.get());
440
441  single_web_contents_.reset(
442      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
443  single_rph_ = rph_factory->ReleaseRPH(profile_.get());
444
445  shared_web_contents1_.reset(
446      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
447  shared_web_contents2_.reset(
448      content::WebContentsTester::CreateTestWebContents(profile_.get(), NULL));
449  shared_rph_ = rph_factory->ReleaseRPH(profile_.get());
450}
451
452ProfileState::~ProfileState() {
453  // TestExtensionSystem uses DeleteSoon, so we need to delete the profiles
454  // and then run the message queue to clean up.  But first we have to
455  // delete everything that references the profile.
456  single_web_contents_.reset();
457  shared_web_contents1_.reset();
458  shared_web_contents2_.reset();
459  profile_.reset();
460
461  base::MessageLoop::current()->RunUntilIdle();
462}
463
464MediaGalleriesPreferences* ProfileState::GetMediaGalleriesPrefs() {
465  MediaGalleriesPreferences* prefs =
466      MediaGalleriesPreferencesFactory::GetForProfile(profile_.get());
467  base::RunLoop loop;
468  prefs->EnsureInitialized(loop.QuitClosure());
469  loop.Run();
470  return prefs;
471}
472
473void ProfileState::CheckGalleries(
474    const std::string& test,
475    const std::vector<MediaFileSystemInfo>& regular_extension_galleries,
476    const std::vector<MediaFileSystemInfo>& all_extension_galleries) {
477  content::RenderViewHost* rvh = single_web_contents_->GetRenderViewHost();
478  MediaFileSystemRegistry* registry =
479      g_browser_process->media_file_system_registry();
480
481  // No Media Galleries permissions.
482  std::vector<MediaFileSystemInfo> empty_expectation;
483  std::vector<base::string16> empty_names;
484  registry->GetMediaFileSystemsForExtension(
485      rvh, no_permissions_extension_.get(),
486      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
487                 base::StringPrintf("%s (no permission)", test.c_str()),
488                 base::ConstRef(empty_names),
489                 base::ConstRef(empty_expectation)));
490  base::MessageLoop::current()->RunUntilIdle();
491  EXPECT_EQ(1, GetAndClearComparisonCount());
492
493  // Read permission only.
494  registry->GetMediaFileSystemsForExtension(
495      rvh, regular_permission_extension_.get(),
496      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
497                 base::StringPrintf("%s (regular permission)", test.c_str()),
498                 base::ConstRef(compare_names_read_),
499                 base::ConstRef(regular_extension_galleries)));
500  base::MessageLoop::current()->RunUntilIdle();
501  EXPECT_EQ(1, GetAndClearComparisonCount());
502
503  // All galleries permission.
504  registry->GetMediaFileSystemsForExtension(
505      rvh, all_permission_extension_.get(),
506      base::Bind(&ProfileState::CompareResults, base::Unretained(this),
507                 base::StringPrintf("%s (all permission)", test.c_str()),
508                 base::ConstRef(compare_names_all_),
509                 base::ConstRef(all_extension_galleries)));
510  base::MessageLoop::current()->RunUntilIdle();
511  EXPECT_EQ(1, GetAndClearComparisonCount());
512}
513
514FSInfoMap ProfileState::GetGalleriesInfo(extensions::Extension* extension) {
515  content::RenderViewHost* rvh = single_web_contents_->GetRenderViewHost();
516  FSInfoMap results;
517  MediaFileSystemRegistry* registry =
518      g_browser_process->media_file_system_registry();
519  registry->GetMediaFileSystemsForExtension(
520      rvh, extension,
521      base::Bind(&GetGalleryInfoCallback, base::Unretained(&results)));
522  base::MessageLoop::current()->RunUntilIdle();
523  return results;
524}
525
526extensions::Extension* ProfileState::all_permission_extension() {
527  return all_permission_extension_.get();
528}
529
530extensions::Extension* ProfileState::regular_permission_extension() {
531  return regular_permission_extension_.get();
532}
533
534Profile* ProfileState::profile() {
535  return profile_.get();
536}
537
538void ProfileState::AddNameForReadCompare(const base::string16& name) {
539  compare_names_read_.push_back(name);
540}
541
542void ProfileState::AddNameForAllCompare(const base::string16& name) {
543  compare_names_all_.push_back(name);
544}
545
546bool ProfileState::ContainsEntry(
547    const MediaFileSystemInfo& info,
548    const std::vector<MediaFileSystemInfo>& container) {
549  for (size_t i = 0; i < container.size(); ++i) {
550    if (info.path.value() == container[i].path.value()) {
551      EXPECT_FALSE(container[i].fsid.empty());
552      if (!info.fsid.empty())
553        EXPECT_EQ(info.fsid, container[i].fsid);
554      return true;
555    }
556  }
557  return false;
558}
559
560void ProfileState::CompareResults(
561    const std::string& test,
562    const std::vector<base::string16>& names,
563    const std::vector<MediaFileSystemInfo>& expected,
564    const std::vector<MediaFileSystemInfo>& actual) {
565  num_comparisons_++;
566  EXPECT_EQ(expected.size(), actual.size()) << test;
567
568  // Order isn't important, so sort the results.
569  std::vector<MediaFileSystemInfo> sorted(actual);
570  std::sort(sorted.begin(), sorted.end(), MediaFileSystemInfoComparator);
571  std::vector<MediaFileSystemInfo> expect(expected);
572  std::sort(expect.begin(), expect.end(), MediaFileSystemInfoComparator);
573  std::vector<base::string16> expect_names(names);
574  std::sort(expect_names.begin(), expect_names.end());
575
576  for (size_t i = 0; i < expect.size() && i < sorted.size(); ++i) {
577    if (expect_names.size() > i)
578      EXPECT_EQ(expect_names[i], sorted[i].name) << test;
579    EXPECT_TRUE(ContainsEntry(expect[i], sorted)) << test;
580  }
581}
582
583int ProfileState::GetAndClearComparisonCount() {
584  int result = num_comparisons_;
585  num_comparisons_ = 0;
586  return result;
587}
588
589}  // namespace
590
591/////////////////////////////////
592// MediaFileSystemRegistryTest //
593/////////////////////////////////
594
595void MediaFileSystemRegistryTest::CreateProfileState(size_t profile_count) {
596  for (size_t i = 0; i < profile_count; ++i) {
597    ProfileState* state = new ProfileState(&rph_factory_);
598    profile_states_.push_back(state);
599  }
600}
601
602ProfileState* MediaFileSystemRegistryTest::GetProfileState(size_t i) {
603  return profile_states_[i];
604}
605
606MediaGalleriesPreferences* MediaFileSystemRegistryTest::GetPreferences(
607    Profile* profile) {
608  MediaGalleriesPreferences* prefs = registry()->GetPreferences(profile);
609  base::RunLoop loop;
610  prefs->EnsureInitialized(loop.QuitClosure());
611  loop.Run();
612  return prefs;
613}
614
615std::string MediaFileSystemRegistryTest::AddUserGallery(
616    StorageInfo::Type type,
617    const std::string& unique_id,
618    const base::FilePath& path) {
619  std::string device_id = StorageInfo::MakeDeviceId(type, unique_id);
620  DCHECK(!StorageInfo::IsMediaDevice(device_id));
621
622  for (size_t i = 0; i < profile_states_.size(); ++i) {
623    profile_states_[i]->GetMediaGalleriesPrefs()->AddGallery(
624        device_id, base::FilePath(), true /*user_added*/,
625        base::string16(), base::string16(), base::string16(), 0,
626        base::Time::Now());
627  }
628  return device_id;
629}
630
631std::string MediaFileSystemRegistryTest::AttachDevice(
632    StorageInfo::Type type,
633    const std::string& unique_id,
634    const base::FilePath& location) {
635  std::string device_id = StorageInfo::MakeDeviceId(type, unique_id);
636  DCHECK(StorageInfo::IsRemovableDevice(device_id));
637  base::string16 label = location.BaseName().LossyDisplayName();
638  ProcessAttach(device_id, label, location.value());
639  base::MessageLoop::current()->RunUntilIdle();
640  return device_id;
641}
642
643void MediaFileSystemRegistryTest::DetachDevice(const std::string& device_id) {
644  DCHECK(StorageInfo::IsRemovableDevice(device_id));
645  ProcessDetach(device_id);
646  base::MessageLoop::current()->RunUntilIdle();
647}
648
649void MediaFileSystemRegistryTest::SetGalleryPermission(
650    ProfileState* profile_state, extensions::Extension* extension,
651    const std::string& device_id, bool has_access) {
652  MediaGalleriesPreferences* preferences =
653      profile_state->GetMediaGalleriesPrefs();
654  MediaGalleryPrefIdSet pref_id =
655      preferences->LookUpGalleriesByDeviceId(device_id);
656  ASSERT_EQ(1U, pref_id.size());
657  preferences->SetGalleryPermissionForExtension(*extension, *pref_id.begin(),
658                                                has_access);
659}
660
661void MediaFileSystemRegistryTest::AssertAllAutoAddedGalleries() {
662  for (size_t i = 0; i < profile_states_.size(); ++i) {
663    MediaGalleriesPreferences* prefs =
664        profile_states_[0]->GetMediaGalleriesPrefs();
665
666    // Make sure that we have at least one gallery and that they are all
667    // auto added galleries.
668    const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
669#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
670    ASSERT_GT(galleries.size(), 0U);
671#endif
672    for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
673         it != galleries.end();
674         ++it) {
675      ASSERT_EQ(MediaGalleryPrefInfo::kAutoDetected, it->second.type);
676    }
677  }
678}
679
680void MediaFileSystemRegistryTest::InitForGalleriesInfoTest(
681    FSInfoMap* galleries_info) {
682  CreateProfileState(1);
683  AssertAllAutoAddedGalleries();
684
685  // Get all existing gallery names.
686  ProfileState* profile_state = GetProfileState(0U);
687  *galleries_info = profile_state->GetGalleriesInfo(
688      profile_state->all_permission_extension());
689#if !defined(OS_CHROMEOS) && !defined(OS_ANDROID)
690  ASSERT_EQ(3U, galleries_info->size());
691#else
692  ASSERT_EQ(0U, galleries_info->size());
693#endif
694}
695
696void MediaFileSystemRegistryTest::CheckNewGalleryInfo(
697    ProfileState* profile_state,
698    const FSInfoMap& galleries_info,
699    const base::FilePath& location,
700    bool removable,
701    bool media_device) {
702  // Get new galleries.
703  FSInfoMap new_galleries_info = profile_state->GetGalleriesInfo(
704      profile_state->all_permission_extension());
705  ASSERT_EQ(galleries_info.size() + 1U, new_galleries_info.size());
706
707  bool found_new = false;
708  for (FSInfoMap::const_iterator it = new_galleries_info.begin();
709       it != new_galleries_info.end();
710       ++it) {
711    if (ContainsKey(galleries_info, it->first))
712      continue;
713
714    ASSERT_FALSE(found_new);
715    CheckGalleryInfo(it->second, test_file_system_context_, location,
716                     removable, media_device);
717    found_new = true;
718  }
719  ASSERT_TRUE(found_new);
720}
721
722std::vector<MediaFileSystemInfo>
723MediaFileSystemRegistryTest::GetAutoAddedGalleries(
724    ProfileState* profile_state) {
725  const MediaGalleriesPrefInfoMap& galleries =
726      profile_state->GetMediaGalleriesPrefs()->known_galleries();
727  std::vector<MediaFileSystemInfo> result;
728  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
729       it != galleries.end();
730       ++it) {
731    if (it->second.type == MediaGalleryPrefInfo::kAutoDetected) {
732      base::FilePath path = it->second.AbsolutePath();
733      MediaFileSystemInfo info(path.BaseName().LossyDisplayName(), path,
734                               std::string(), 0, std::string(), false, false);
735      result.push_back(info);
736    }
737  }
738  std::sort(result.begin(), result.end(), MediaFileSystemInfoComparator);
739  return result;
740}
741
742size_t MediaFileSystemRegistryTest::GetExtensionGalleriesHostCount(
743    const MediaFileSystemRegistry* registry) const {
744  size_t extension_galleries_host_count = 0;
745  for (MediaFileSystemRegistry::ExtensionGalleriesHostMap::const_iterator it =
746           registry->extension_hosts_map_.begin();
747       it != registry->extension_hosts_map_.end();
748       ++it) {
749    extension_galleries_host_count += it->second.size();
750  }
751  return extension_galleries_host_count;
752}
753
754
755void MediaFileSystemRegistryTest::SetUp() {
756  ChromeRenderViewHostTestHarness::SetUp();
757  ASSERT_TRUE(TestStorageMonitor::CreateAndInstall());
758
759  DeleteContents();
760  SetRenderProcessHostFactory(&rph_factory_);
761
762  test_file_system_context_ = new TestMediaFileSystemContext(
763      g_browser_process->media_file_system_registry());
764
765#if defined(OS_CHROMEOS)
766  test_user_manager_.reset(new chromeos::ScopedTestUserManager());
767#endif
768
769  ASSERT_TRUE(galleries_dir_.CreateUniqueTempDir());
770  empty_dir_ = galleries_dir_.path().AppendASCII("empty");
771  ASSERT_TRUE(base::CreateDirectory(empty_dir_));
772  dcim_dir_ = galleries_dir_.path().AppendASCII("with_dcim");
773  ASSERT_TRUE(base::CreateDirectory(dcim_dir_));
774  ASSERT_TRUE(base::CreateDirectory(dcim_dir_.Append(kDCIMDirectoryName)));
775}
776
777void MediaFileSystemRegistryTest::TearDown() {
778  profile_states_.clear();
779  MediaFileSystemRegistry* registry =
780      g_browser_process->media_file_system_registry();
781  EXPECT_EQ(0U, GetExtensionGalleriesHostCount(registry));
782  TestStorageMonitor::RemoveSingleton();
783#if defined(OS_CHROMEOS)
784  test_user_manager_.reset();
785#endif
786
787  ChromeRenderViewHostTestHarness::TearDown();
788}
789
790///////////
791// Tests //
792///////////
793
794TEST_F(MediaFileSystemRegistryTest, Basic) {
795  CreateProfileState(1);
796  AssertAllAutoAddedGalleries();
797
798  ProfileState* profile_state = GetProfileState(0);
799  std::vector<MediaFileSystemInfo> auto_galleries =
800      GetAutoAddedGalleries(profile_state);
801  std::vector<MediaFileSystemInfo> empty_expectation;
802  profile_state->CheckGalleries("basic", empty_expectation, auto_galleries);
803}
804
805TEST_F(MediaFileSystemRegistryTest, UserAddedGallery) {
806  CreateProfileState(1);
807  AssertAllAutoAddedGalleries();
808  ProfileState* profile_state = GetProfileState(0);
809  std::vector<MediaFileSystemInfo> auto_galleries =
810      GetAutoAddedGalleries(profile_state);
811  std::vector<MediaFileSystemInfo> added_galleries;
812  profile_state->CheckGalleries("user added init", added_galleries,
813                                auto_galleries);
814
815  // Add a user gallery to the regular permission extension.
816  std::string device_id = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
817                                         empty_dir().AsUTF8Unsafe(),
818                                         empty_dir());
819  SetGalleryPermission(profile_state,
820                       profile_state->regular_permission_extension(),
821                       device_id,
822                       true /*has access*/);
823  MediaFileSystemInfo added_info(empty_dir().LossyDisplayName(), empty_dir(),
824                                 std::string(), 0, std::string(), false, false);
825  added_galleries.push_back(added_info);
826  profile_state->CheckGalleries("user added regular", added_galleries,
827                                auto_galleries);
828
829  // Add it to the all galleries extension.
830  SetGalleryPermission(profile_state,
831                       profile_state->all_permission_extension(),
832                       device_id,
833                       true /*has access*/);
834  auto_galleries.push_back(added_info);
835  profile_state->CheckGalleries("user added all", added_galleries,
836                                auto_galleries);
837}
838
839// Regression test to make sure erasing galleries does not result a crash.
840TEST_F(MediaFileSystemRegistryTest, EraseGalleries) {
841  CreateProfileState(1);
842  AssertAllAutoAddedGalleries();
843
844  ProfileState* profile_state = GetProfileState(0);
845  std::vector<MediaFileSystemInfo> auto_galleries =
846      GetAutoAddedGalleries(profile_state);
847  std::vector<MediaFileSystemInfo> empty_expectation;
848  profile_state->CheckGalleries("erase", empty_expectation, auto_galleries);
849
850  MediaGalleriesPreferences* prefs = profile_state->GetMediaGalleriesPrefs();
851  MediaGalleriesPrefInfoMap galleries = prefs->known_galleries();
852  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
853       it != galleries.end(); ++it) {
854    prefs->ForgetGalleryById(it->first);
855  }
856}
857
858// Regression test to make sure calling GetPreferences() does not re-insert
859// galleries on auto-detected removable devices that were blacklisted.
860TEST_F(MediaFileSystemRegistryTest,
861       GetPreferencesDoesNotReinsertBlacklistedGalleries) {
862  CreateProfileState(1);
863  AssertAllAutoAddedGalleries();
864
865  ProfileState* profile_state = GetProfileState(0);
866  const size_t gallery_count = GetAutoAddedGalleries(profile_state).size();
867
868  // Attach a device.
869  const std::string device_id = AttachDevice(
870      StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
871      "removable_dcim_fake_id",
872      dcim_dir());
873  EXPECT_EQ(gallery_count + 1, GetAutoAddedGalleries(profile_state).size());
874
875  // Forget the device.
876  bool forget_gallery = false;
877  MediaGalleriesPreferences* prefs = GetPreferences(profile_state->profile());
878  const MediaGalleriesPrefInfoMap& galleries = prefs->known_galleries();
879  for (MediaGalleriesPrefInfoMap::const_iterator it = galleries.begin();
880       it != galleries.end(); ++it) {
881    if (it->second.device_id == device_id) {
882      prefs->ForgetGalleryById(it->first);
883      forget_gallery = true;
884      break;
885    }
886  }
887  base::MessageLoop::current()->RunUntilIdle();
888  EXPECT_TRUE(forget_gallery);
889  EXPECT_EQ(gallery_count, GetAutoAddedGalleries(profile_state).size());
890
891  // Call GetPreferences() and the gallery count should not change.
892  prefs = GetPreferences(profile_state->profile());
893  EXPECT_EQ(gallery_count, GetAutoAddedGalleries(profile_state).size());
894}
895
896TEST_F(MediaFileSystemRegistryTest, GalleryNameDefault) {
897  FSInfoMap galleries_info;
898  InitForGalleriesInfoTest(&galleries_info);
899
900  for (FSInfoMap::const_iterator it = galleries_info.begin();
901       it != galleries_info.end();
902       ++it) {
903    CheckGalleryInfo(it->second, test_file_system_context(),
904                     it->second.path, false, false);
905  }
906}
907
908// TODO(gbillock): Move the remaining test into the linux directory.
909#if !defined(OS_MACOSX) && !defined(OS_WIN)
910TEST_F(MediaFileSystemRegistryTest, GalleryMTP) {
911  FSInfoMap galleries_info;
912  InitForGalleriesInfoTest(&galleries_info);
913
914  base::FilePath location(FILE_PATH_LITERAL("/mtp_bogus"));
915  AttachDevice(StorageInfo::MTP_OR_PTP, "mtp_fake_id", location);
916  CheckNewGalleryInfo(GetProfileState(0U), galleries_info, location,
917                      true /*removable*/, true /* media device */);
918}
919#endif
920
921TEST_F(MediaFileSystemRegistryTest, GalleryDCIM) {
922  FSInfoMap galleries_info;
923  InitForGalleriesInfoTest(&galleries_info);
924
925  AttachDevice(StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
926               "removable_dcim_fake_id",
927               dcim_dir());
928  CheckNewGalleryInfo(GetProfileState(0U), galleries_info, dcim_dir(),
929                      true /*removable*/, true /* media device */);
930}
931
932TEST_F(MediaFileSystemRegistryTest, GalleryNoDCIM) {
933  FSInfoMap galleries_info;
934  InitForGalleriesInfoTest(&galleries_info);
935
936  std::string device_id =
937      AttachDevice(StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
938                   empty_dir().AsUTF8Unsafe(),
939                   empty_dir());
940  std::string device_id2 =
941      AddUserGallery(StorageInfo::REMOVABLE_MASS_STORAGE_NO_DCIM,
942                     empty_dir().AsUTF8Unsafe(),
943                     empty_dir());
944  ASSERT_EQ(device_id, device_id2);
945  // Add permission for new non-default gallery.
946  ProfileState* profile_state = GetProfileState(0U);
947  SetGalleryPermission(profile_state,
948                       profile_state->all_permission_extension(),
949                       device_id,
950                       true /*has access*/);
951  CheckNewGalleryInfo(profile_state, galleries_info, empty_dir(),
952                      true /*removable*/, false /* media device */);
953}
954
955TEST_F(MediaFileSystemRegistryTest, GalleryUserAddedPath) {
956  FSInfoMap galleries_info;
957  InitForGalleriesInfoTest(&galleries_info);
958
959  std::string device_id = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
960                                         empty_dir().AsUTF8Unsafe(),
961                                         empty_dir());
962  // Add permission for new non-default gallery.
963  ProfileState* profile_state = GetProfileState(0U);
964  SetGalleryPermission(profile_state,
965                       profile_state->all_permission_extension(),
966                       device_id,
967                       true /*has access*/);
968  CheckNewGalleryInfo(profile_state, galleries_info, empty_dir(),
969                      false /*removable*/, false /* media device */);
970}
971
972TEST_F(MediaFileSystemRegistryTest, DetachedDeviceGalleryPath) {
973  const std::string device_id = AttachDevice(
974      StorageInfo::REMOVABLE_MASS_STORAGE_WITH_DCIM,
975      "removable_dcim_fake_id",
976      dcim_dir());
977
978  MediaGalleryPrefInfo pref_info;
979  pref_info.device_id = device_id;
980  EXPECT_EQ(dcim_dir().value(), pref_info.AbsolutePath().value());
981
982  MediaGalleryPrefInfo pref_info_with_relpath;
983  pref_info_with_relpath.path =
984      base::FilePath(FILE_PATH_LITERAL("test_relpath"));
985  pref_info_with_relpath.device_id = device_id;
986  EXPECT_EQ(dcim_dir().Append(pref_info_with_relpath.path).value(),
987            pref_info_with_relpath.AbsolutePath().value());
988
989  DetachDevice(device_id);
990  EXPECT_TRUE(pref_info.AbsolutePath().empty());
991  EXPECT_TRUE(pref_info_with_relpath.AbsolutePath().empty());
992}
993
994TEST_F(MediaFileSystemRegistryTest, TestNameConstruction) {
995  CreateProfileState(1);
996  AssertAllAutoAddedGalleries();
997
998  ProfileState* profile_state = GetProfileState(0);
999
1000  std::string user_gallery = AddUserGallery(StorageInfo::FIXED_MASS_STORAGE,
1001                                            empty_dir().AsUTF8Unsafe(),
1002                                            empty_dir());
1003  SetGalleryPermission(profile_state,
1004                       profile_state->regular_permission_extension(),
1005                       user_gallery,
1006                       true /*has access*/);
1007  SetGalleryPermission(profile_state,
1008                       profile_state->all_permission_extension(),
1009                       user_gallery,
1010                       true /*has access*/);
1011
1012  std::vector<MediaFileSystemInfo> auto_galleries =
1013      GetAutoAddedGalleries(profile_state);
1014  MediaFileSystemInfo added_info(empty_dir().BaseName().LossyDisplayName(),
1015                                 empty_dir(), std::string(), 0, std::string(),
1016                                 false, false);
1017  auto_galleries.push_back(added_info);
1018  std::vector<MediaFileSystemInfo> one_expectation;
1019  one_expectation.push_back(added_info);
1020
1021  profile_state->AddNameForReadCompare(
1022#if defined(OS_CHROMEOS)
1023      empty_dir().BaseName().LossyDisplayName());
1024#else
1025      empty_dir().LossyDisplayName());
1026#endif
1027  profile_state->AddNameForAllCompare(
1028#if defined(OS_CHROMEOS)
1029      empty_dir().BaseName().LossyDisplayName());
1030#else
1031      empty_dir().LossyDisplayName());
1032#endif
1033
1034  // This part of the test is conditional on default directories existing
1035  // on the test platform. In ChromeOS, these directories do not exist.
1036  base::FilePath path;
1037  if (num_auto_galleries() > 0) {
1038    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_MUSIC, &path));
1039#if defined(OS_CHROMEOS)
1040    profile_state->AddNameForAllCompare(path.BaseName().LossyDisplayName());
1041#else
1042    profile_state->AddNameForAllCompare(path.LossyDisplayName());
1043#endif
1044    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_PICTURES, &path));
1045#if defined(OS_CHROMEOS)
1046    profile_state->AddNameForAllCompare(path.BaseName().LossyDisplayName());
1047#else
1048    profile_state->AddNameForAllCompare(path.LossyDisplayName());
1049#endif
1050    ASSERT_TRUE(PathService::Get(chrome::DIR_USER_VIDEOS, &path));
1051#if defined(OS_CHROMEOS)
1052    profile_state->AddNameForAllCompare(path.BaseName().LossyDisplayName());
1053#else
1054    profile_state->AddNameForAllCompare(path.LossyDisplayName());
1055#endif
1056
1057    profile_state->CheckGalleries("names-dir", one_expectation, auto_galleries);
1058  } else {
1059    profile_state->CheckGalleries("names", one_expectation, one_expectation);
1060  }
1061}
1062