Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / media_galleries / mac / mtp_device_delegate_impl_mac_unittest.mm
blobd5ef338a437a8f5638dddee34bccbec8c5be6994
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.
5 #import <Foundation/Foundation.h>
6 #import <ImageCaptureCore/ImageCaptureCore.h>
8 #include "base/files/file.h"
9 #include "base/files/file_util.h"
10 #include "base/files/scoped_temp_dir.h"
11 #include "base/mac/cocoa_protocols.h"
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_nsobject.h"
14 #include "base/mac/sdk_forward_declarations.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/run_loop.h"
17 #include "base/strings/sys_string_conversions.h"
18 #include "base/synchronization/waitable_event.h"
19 #include "base/test/sequenced_worker_pool_owner.h"
20 #include "base/threading/sequenced_worker_pool.h"
21 #include "chrome/browser/media_galleries/mac/mtp_device_delegate_impl_mac.h"
22 #include "components/storage_monitor/image_capture_device_manager.h"
23 #include "components/storage_monitor/test_storage_monitor.h"
24 #include "content/public/browser/browser_thread.h"
25 #include "content/public/test/test_browser_thread.h"
26 #include "testing/gtest/include/gtest/gtest.h"
28 namespace {
30 const char kDeviceId[] = "id";
31 const char kDevicePath[] = "/ic:id";
32 const char kTestFileContents[] = "test";
34 }  // namespace
36 @interface MockMTPICCameraDevice : ICCameraDevice {
37  @private
38   base::scoped_nsobject<NSMutableArray> allMediaFiles_;
41 - (void)addMediaFile:(ICCameraFile*)file;
43 @end
45 @implementation MockMTPICCameraDevice
47 - (NSString*)mountPoint {
48   return @"mountPoint";
51 - (NSString*)name {
52   return @"name";
55 - (NSString*)UUIDString {
56   return base::SysUTF8ToNSString(kDeviceId);
59 - (ICDeviceType)type {
60   return ICDeviceTypeCamera;
63 - (void)requestOpenSession {
66 - (void)requestCloseSession {
69 - (NSArray*)mediaFiles {
70   return allMediaFiles_;
73 - (void)addMediaFile:(ICCameraFile*)file {
74   if (!allMediaFiles_.get())
75     allMediaFiles_.reset([[NSMutableArray alloc] init]);
76   [allMediaFiles_ addObject:file];
79 - (void)requestDownloadFile:(ICCameraFile*)file
80                     options:(NSDictionary*)options
81            downloadDelegate:(id<ICCameraDeviceDownloadDelegate>)downloadDelegate
82         didDownloadSelector:(SEL)selector
83                 contextInfo:(void*)contextInfo {
84   base::FilePath saveDir(base::SysNSStringToUTF8(
85       [[options objectForKey:ICDownloadsDirectoryURL] path]));
86   std::string saveAsFilename =
87       base::SysNSStringToUTF8([options objectForKey:ICSaveAsFilename]);
88   // It appears that the ImageCapture library adds an extension to the requested
89   // filename. Do that here to require a rename.
90   saveAsFilename += ".jpg";
91   base::FilePath toBeSaved = saveDir.Append(saveAsFilename);
92   ASSERT_EQ(static_cast<int>(strlen(kTestFileContents)),
93             base::WriteFile(toBeSaved, kTestFileContents,
94                             strlen(kTestFileContents)));
96   NSMutableDictionary* returnOptions =
97       [NSMutableDictionary dictionaryWithDictionary:options];
98   [returnOptions setObject:base::SysUTF8ToNSString(saveAsFilename)
99                     forKey:ICSavedFilename];
101   [static_cast<NSObject<ICCameraDeviceDownloadDelegate>*>(downloadDelegate)
102    didDownloadFile:file
103              error:nil
104            options:returnOptions
105        contextInfo:contextInfo];
108 @end
110 @interface MockMTPICCameraFile : ICCameraFile {
111  @private
112   base::scoped_nsobject<NSString> name_;
113   base::scoped_nsobject<NSDate> date_;
116 - (id)init:(NSString*)name;
118 @end
120 @implementation MockMTPICCameraFile
122 - (id)init:(NSString*)name {
123   if ((self = [super init])) {
124     name_.reset([name retain]);
125     date_.reset([[NSDate dateWithNaturalLanguageString:@"12/12/12"] retain]);
126   }
127   return self;
130 - (NSString*)name {
131   return name_.get();
134 - (NSString*)UTI {
135   return base::mac::CFToNSCast(kUTTypeImage);
138 - (NSDate*)modificationDate {
139   return date_.get();
142 - (NSDate*)creationDate {
143   return date_.get();
146 - (off_t)fileSize {
147   return 1000;
150 @end
152 class MTPDeviceDelegateImplMacTest : public testing::Test {
153  public:
154   MTPDeviceDelegateImplMacTest() : camera_(NULL), delegate_(NULL) {}
156   void SetUp() override {
157     ui_thread_.reset(new content::TestBrowserThread(
158         content::BrowserThread::UI, &message_loop_));
159     file_thread_.reset(new content::TestBrowserThread(
160         content::BrowserThread::FILE, &message_loop_));
161     io_thread_.reset(new content::TestBrowserThread(
162         content::BrowserThread::IO));
163     ASSERT_TRUE(io_thread_->Start());
165     storage_monitor::TestStorageMonitor* monitor =
166         storage_monitor::TestStorageMonitor::CreateAndInstall();
167     manager_.SetNotifications(monitor->receiver());
169     camera_ = [MockMTPICCameraDevice alloc];
170     id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
171     [delegate deviceBrowser:nil didAddDevice:camera_ moreComing:NO];
173     delegate_ = new MTPDeviceDelegateImplMac(kDeviceId, kDevicePath);
174   }
176   void TearDown() override {
177     id<ICDeviceBrowserDelegate> delegate = manager_.device_browser();
178     [delegate deviceBrowser:nil didRemoveDevice:camera_ moreGoing:NO];
180     delegate_->CancelPendingTasksAndDeleteDelegate();
182     storage_monitor::TestStorageMonitor::Destroy();
184     io_thread_->Stop();
185   }
187   void OnError(base::WaitableEvent* event, base::File::Error error) {
188     error_ = error;
189     event->Signal();
190   }
192   void OverlappedOnError(base::WaitableEvent* event,
193                          base::File::Error error) {
194     overlapped_error_ = error;
195     event->Signal();
196   }
198   void OnFileInfo(base::WaitableEvent* event,
199                   const base::File::Info& info) {
200     error_ = base::File::FILE_OK;
201     info_ = info;
202     event->Signal();
203   }
205   void OnReadDir(base::WaitableEvent* event,
206                  const storage::AsyncFileUtil::EntryList& files,
207                  bool has_more) {
208     error_ = base::File::FILE_OK;
209     ASSERT_FALSE(has_more);
210     file_list_ = files;
211     event->Signal();
212   }
214   void OverlappedOnReadDir(base::WaitableEvent* event,
215                            const storage::AsyncFileUtil::EntryList& files,
216                            bool has_more) {
217     overlapped_error_ = base::File::FILE_OK;
218     ASSERT_FALSE(has_more);
219     overlapped_file_list_ = files;
220     event->Signal();
221   }
223   void OnDownload(base::WaitableEvent* event,
224                   const base::File::Info& file_info,
225                   const base::FilePath& local_path) {
226     error_ = base::File::FILE_OK;
227     event->Signal();
228   }
230   base::File::Error GetFileInfo(const base::FilePath& path,
231                                 base::File::Info* info) {
232     base::WaitableEvent wait(true, false);
233     delegate_->GetFileInfo(
234       path,
235       base::Bind(&MTPDeviceDelegateImplMacTest::OnFileInfo,
236                  base::Unretained(this),
237                  &wait),
238       base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
239                  base::Unretained(this),
240                  &wait));
241     base::RunLoop loop;
242     loop.RunUntilIdle();
243     EXPECT_TRUE(wait.IsSignaled());
244     *info = info_;
245     return error_;
246   }
248   base::File::Error ReadDir(const base::FilePath& path) {
249     base::WaitableEvent wait(true, false);
250     delegate_->ReadDirectory(
251         path,
252         base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
253                    base::Unretained(this),
254                    &wait),
255         base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
256                    base::Unretained(this),
257                    &wait));
258     base::RunLoop loop;
259     loop.RunUntilIdle();
260     wait.Wait();
261     return error_;
262   }
264   base::File::Error DownloadFile(
265       const base::FilePath& path,
266       const base::FilePath& local_path) {
267     base::WaitableEvent wait(true, false);
268     delegate_->CreateSnapshotFile(
269         path, local_path,
270         base::Bind(&MTPDeviceDelegateImplMacTest::OnDownload,
271                    base::Unretained(this),
272                    &wait),
273         base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
274                    base::Unretained(this),
275                    &wait));
276     base::RunLoop loop;
277     loop.RunUntilIdle();
278     wait.Wait();
279     return error_;
280   }
282  protected:
283   base::MessageLoopForUI message_loop_;
284   // Note: threads must be made in this order: UI > FILE > IO
285   scoped_ptr<content::TestBrowserThread> ui_thread_;
286   scoped_ptr<content::TestBrowserThread> file_thread_;
287   scoped_ptr<content::TestBrowserThread> io_thread_;
288   base::ScopedTempDir temp_dir_;
289   storage_monitor::ImageCaptureDeviceManager manager_;
290   MockMTPICCameraDevice* camera_;
292   // This object needs special deletion inside the above |task_runner_|.
293   MTPDeviceDelegateImplMac* delegate_;
295   base::File::Error error_;
296   base::File::Info info_;
297   storage::AsyncFileUtil::EntryList file_list_;
299   base::File::Error overlapped_error_;
300   storage::AsyncFileUtil::EntryList overlapped_file_list_;
302  private:
303   DISALLOW_COPY_AND_ASSIGN(MTPDeviceDelegateImplMacTest);
306 TEST_F(MTPDeviceDelegateImplMacTest, TestGetRootFileInfo) {
307   base::File::Info info;
308   // Making a fresh delegate should have a single file entry for the synthetic
309   // root directory, with the name equal to the device id string.
310   EXPECT_EQ(base::File::FILE_OK,
311             GetFileInfo(base::FilePath(kDevicePath), &info));
312   EXPECT_TRUE(info.is_directory);
313   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
314             GetFileInfo(base::FilePath("/nonexistent"), &info));
316   // Signal the delegate that no files are coming.
317   delegate_->NoMoreItems();
319   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
320   EXPECT_EQ(0U, file_list_.size());
323 TEST_F(MTPDeviceDelegateImplMacTest, TestOverlappedReadDir) {
324   base::Time time1 = base::Time::Now();
325   base::File::Info info1;
326   info1.size = 1;
327   info1.is_directory = false;
328   info1.is_symbolic_link = false;
329   info1.last_modified = time1;
330   info1.last_accessed = time1;
331   info1.creation_time = time1;
332   delegate_->ItemAdded("name1", info1);
334   base::WaitableEvent wait(true, false);
336   delegate_->ReadDirectory(
337       base::FilePath(kDevicePath),
338       base::Bind(&MTPDeviceDelegateImplMacTest::OnReadDir,
339                  base::Unretained(this),
340                  &wait),
341       base::Bind(&MTPDeviceDelegateImplMacTest::OnError,
342                  base::Unretained(this),
343                  &wait));
345   delegate_->ReadDirectory(
346       base::FilePath(kDevicePath),
347       base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnReadDir,
348                  base::Unretained(this),
349                  &wait),
350       base::Bind(&MTPDeviceDelegateImplMacTest::OverlappedOnError,
351                  base::Unretained(this),
352                  &wait));
355   // Signal the delegate that no files are coming.
356   delegate_->NoMoreItems();
358   base::RunLoop loop;
359   loop.RunUntilIdle();
360   wait.Wait();
362   EXPECT_EQ(base::File::FILE_OK, error_);
363   EXPECT_EQ(1U, file_list_.size());
364   EXPECT_EQ(base::File::FILE_OK, overlapped_error_);
365   EXPECT_EQ(1U, overlapped_file_list_.size());
368 TEST_F(MTPDeviceDelegateImplMacTest, TestGetFileInfo) {
369   base::Time time1 = base::Time::Now();
370   base::File::Info info1;
371   info1.size = 1;
372   info1.is_directory = false;
373   info1.is_symbolic_link = false;
374   info1.last_modified = time1;
375   info1.last_accessed = time1;
376   info1.creation_time = time1;
377   delegate_->ItemAdded("name1", info1);
379   base::File::Info info;
380   EXPECT_EQ(base::File::FILE_OK,
381             GetFileInfo(base::FilePath("/ic:id/name1"), &info));
382   EXPECT_EQ(info1.size, info.size);
383   EXPECT_EQ(info1.is_directory, info.is_directory);
384   EXPECT_EQ(info1.last_modified, info.last_modified);
385   EXPECT_EQ(info1.last_accessed, info.last_accessed);
386   EXPECT_EQ(info1.creation_time, info.creation_time);
388   info1.size = 2;
389   delegate_->ItemAdded("name2", info1);
390   delegate_->NoMoreItems();
392   EXPECT_EQ(base::File::FILE_OK,
393             GetFileInfo(base::FilePath("/ic:id/name2"), &info));
394   EXPECT_EQ(info1.size, info.size);
396   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
398   ASSERT_EQ(2U, file_list_.size());
399   EXPECT_EQ(time1, file_list_[0].last_modified_time);
400   EXPECT_FALSE(file_list_[0].is_directory);
401   EXPECT_EQ("name1", file_list_[0].name);
403   EXPECT_EQ(time1, file_list_[1].last_modified_time);
404   EXPECT_FALSE(file_list_[1].is_directory);
405   EXPECT_EQ("name2", file_list_[1].name);
408 TEST_F(MTPDeviceDelegateImplMacTest, TestDirectoriesAndSorting) {
409   base::Time time1 = base::Time::Now();
410   base::File::Info info1;
411   info1.size = 1;
412   info1.is_directory = false;
413   info1.is_symbolic_link = false;
414   info1.last_modified = time1;
415   info1.last_accessed = time1;
416   info1.creation_time = time1;
417   delegate_->ItemAdded("name2", info1);
419   info1.is_directory = true;
420   delegate_->ItemAdded("dir2", info1);
421   delegate_->ItemAdded("dir1", info1);
423   info1.is_directory = false;
424   delegate_->ItemAdded("name1", info1);
425   delegate_->NoMoreItems();
427   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
429   ASSERT_EQ(4U, file_list_.size());
430   EXPECT_EQ("dir1", file_list_[0].name);
431   EXPECT_EQ("dir2", file_list_[1].name);
432   EXPECT_EQ(time1, file_list_[2].last_modified_time);
433   EXPECT_FALSE(file_list_[2].is_directory);
434   EXPECT_EQ("name1", file_list_[2].name);
436   EXPECT_EQ(time1, file_list_[3].last_modified_time);
437   EXPECT_FALSE(file_list_[3].is_directory);
438   EXPECT_EQ("name2", file_list_[3].name);
441 TEST_F(MTPDeviceDelegateImplMacTest, SubDirectories) {
442   base::Time time1 = base::Time::Now();
443   base::File::Info info1;
444   info1.size = 0;
445   info1.is_directory = true;
446   info1.is_symbolic_link = false;
447   info1.last_modified = time1;
448   info1.last_accessed = time1;
449   info1.creation_time = time1;
450   delegate_->ItemAdded("dir1", info1);
452   info1.size = 1;
453   info1.is_directory = false;
454   info1.is_symbolic_link = false;
455   info1.last_modified = time1;
456   info1.last_accessed = time1;
457   info1.creation_time = time1;
458   delegate_->ItemAdded("dir1/name1", info1);
460   info1.is_directory = true;
461   info1.size = 0;
462   delegate_->ItemAdded("dir2", info1);
464   info1.is_directory = false;
465   info1.size = 1;
466   delegate_->ItemAdded("dir2/name2", info1);
468   info1.is_directory = true;
469   info1.size = 0;
470   delegate_->ItemAdded("dir2/subdir", info1);
472   info1.is_directory = false;
473   info1.size = 1;
474   delegate_->ItemAdded("dir2/subdir/name3", info1);
475   delegate_->ItemAdded("name4", info1);
477   delegate_->NoMoreItems();
479   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
480   ASSERT_EQ(3U, file_list_.size());
481   EXPECT_TRUE(file_list_[0].is_directory);
482   EXPECT_EQ("dir1", file_list_[0].name);
483   EXPECT_TRUE(file_list_[1].is_directory);
484   EXPECT_EQ("dir2", file_list_[1].name);
485   EXPECT_FALSE(file_list_[2].is_directory);
486   EXPECT_EQ("name4", file_list_[2].name);
488   EXPECT_EQ(base::File::FILE_OK,
489             ReadDir(base::FilePath(kDevicePath).Append("dir1")));
490   ASSERT_EQ(1U, file_list_.size());
491   EXPECT_FALSE(file_list_[0].is_directory);
492   EXPECT_EQ("name1", file_list_[0].name);
494   EXPECT_EQ(base::File::FILE_OK,
495             ReadDir(base::FilePath(kDevicePath).Append("dir2")));
496   ASSERT_EQ(2U, file_list_.size());
497   EXPECT_FALSE(file_list_[0].is_directory);
498   EXPECT_EQ("name2", file_list_[0].name);
499   EXPECT_TRUE(file_list_[1].is_directory);
500   EXPECT_EQ("subdir", file_list_[1].name);
502   EXPECT_EQ(base::File::FILE_OK,
503             ReadDir(base::FilePath(kDevicePath)
504                     .Append("dir2").Append("subdir")));
505   ASSERT_EQ(1U, file_list_.size());
506   EXPECT_FALSE(file_list_[0].is_directory);
507   EXPECT_EQ("name3", file_list_[0].name);
509   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
510             ReadDir(base::FilePath(kDevicePath)
511                     .Append("dir2").Append("subdir").Append("subdir")));
512   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
513             ReadDir(base::FilePath(kDevicePath)
514                     .Append("dir3").Append("subdir")));
515   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
516             ReadDir(base::FilePath(kDevicePath).Append("dir3")));
519 TEST_F(MTPDeviceDelegateImplMacTest, TestDownload) {
520   ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
521   base::Time t1 = base::Time::Now();
522   base::File::Info info;
523   info.size = 4;
524   info.is_directory = false;
525   info.is_symbolic_link = false;
526   info.last_modified = t1;
527   info.last_accessed = t1;
528   info.creation_time = t1;
529   std::string kTestFileName("filename");
530   base::scoped_nsobject<MockMTPICCameraFile> picture1(
531       [[MockMTPICCameraFile alloc]
532           init:base::SysUTF8ToNSString(kTestFileName)]);
533   [camera_ addMediaFile:picture1];
534   delegate_->ItemAdded(kTestFileName, info);
535   delegate_->NoMoreItems();
537   EXPECT_EQ(base::File::FILE_OK, ReadDir(base::FilePath(kDevicePath)));
538   ASSERT_EQ(1U, file_list_.size());
539   ASSERT_EQ("filename", file_list_[0].name);
541   EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND,
542             DownloadFile(base::FilePath("/ic:id/nonexist"),
543                          temp_dir_.path().Append("target")));
545   EXPECT_EQ(base::File::FILE_OK,
546             DownloadFile(base::FilePath("/ic:id/filename"),
547                          temp_dir_.path().Append("target")));
548   std::string contents;
549   EXPECT_TRUE(base::ReadFileToString(temp_dir_.path().Append("target"),
550                                      &contents));
551   EXPECT_EQ(kTestFileContents, contents);