Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 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 | // File contains browser tests for the fileBrowserHandler api. |
| 6 | |
| 7 | #include "chrome/browser/chromeos/extensions/file_manager/file_browser_handler_api.h" |
| 8 | |
| 9 | #include <vector> |
| 10 | |
| 11 | #include "base/bind.h" |
| 12 | #include "base/files/scoped_temp_dir.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 13 | #include "base/values.h" |
| 14 | #include "chrome/browser/extensions/extension_apitest.h" |
| 15 | #include "chrome/browser/extensions/extension_function_test_utils.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 16 | #include "chrome/browser/profiles/profile.h" |
| 17 | #include "chrome/browser/ui/browser.h" |
| 18 | #include "chrome/common/extensions/extension.h" |
| 19 | #include "chrome/test/base/in_process_browser_test.h" |
| 20 | #include "chrome/test/base/ui_test_utils.h" |
| 21 | #include "content/public/browser/browser_context.h" |
Torne (Richard Coles) | 90dce4d | 2013-05-29 14:40:03 +0100 | [diff] [blame] | 22 | #include "webkit/browser/fileapi/external_mount_points.h" |
| 23 | #include "webkit/common/fileapi/file_system_types.h" |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 24 | |
| 25 | namespace utils = extension_function_test_utils; |
| 26 | |
| 27 | using content::BrowserContext; |
| 28 | using extensions::Extension; |
| 29 | |
| 30 | namespace { |
| 31 | |
| 32 | // Data that defines FileSelector behaviour in each test case. |
| 33 | struct TestCase { |
| 34 | TestCase(const base::FilePath& suggested_name, |
| 35 | const std::vector<std::string>& allowed_extensions, |
| 36 | bool success, |
| 37 | const base::FilePath& selected_path) |
| 38 | : suggested_name(suggested_name), |
| 39 | allowed_extensions(allowed_extensions), |
| 40 | success(success), |
| 41 | selected_path(selected_path) { |
| 42 | } |
| 43 | ~TestCase() {} |
| 44 | |
| 45 | // Path that we expect to be suggested to the file selector. |
| 46 | base::FilePath suggested_name; |
| 47 | |
| 48 | // Extensions that we expect to be allowed to the file selector. |
| 49 | std::vector<std::string> allowed_extensions; |
| 50 | |
| 51 | // Whether file selector should fail. |
| 52 | bool success; |
| 53 | // The path file selector should return back to the function. |
| 54 | base::FilePath selected_path; |
| 55 | }; |
| 56 | |
| 57 | // Checks that file under path |selected_path| contains |expected_contents|. |
| 58 | // Must be called on the file thread. |
| 59 | void ExpectFileContentEquals(const base::FilePath& selected_path, |
| 60 | const std::string& expected_contents) { |
| 61 | std::string test_file_contents; |
| 62 | ASSERT_TRUE(file_util::ReadFileToString(selected_path, &test_file_contents)); |
| 63 | EXPECT_EQ(expected_contents, test_file_contents); |
| 64 | } |
| 65 | |
| 66 | // Mocks FileSelector used by FileBrowserHandlerInternalSelectFileFunction. |
| 67 | // When |SelectFile| is called, it will check that file name suggestion is as |
| 68 | // expected, and respond to the extension function with specified selection |
| 69 | // results. |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 70 | class MockFileSelector : public file_manager::FileSelector { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 71 | public: |
| 72 | MockFileSelector(const base::FilePath& suggested_name, |
| 73 | const std::vector<std::string>& allowed_extensions, |
| 74 | bool success, |
| 75 | const base::FilePath& selected_path) |
| 76 | : suggested_name_(suggested_name), |
| 77 | allowed_extensions_(allowed_extensions), |
| 78 | success_(success), |
| 79 | selected_path_(selected_path) { |
| 80 | } |
| 81 | virtual ~MockFileSelector() {} |
| 82 | |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 83 | // file_manager::FileSelector implementation. |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 84 | // |browser| is not used. |
| 85 | virtual void SelectFile( |
| 86 | const base::FilePath& suggested_name, |
| 87 | const std::vector<std::string>& allowed_extensions, |
| 88 | Browser* browser, |
| 89 | FileBrowserHandlerInternalSelectFileFunction* function) OVERRIDE { |
| 90 | // Confirm that the function suggested us the right name. |
| 91 | EXPECT_EQ(suggested_name_, suggested_name); |
| 92 | // Confirm that the function allowed us the right extensions. |
| 93 | EXPECT_EQ(allowed_extensions_.size(), allowed_extensions.size()); |
| 94 | if (allowed_extensions_.size() == allowed_extensions.size()) { |
| 95 | for (size_t i = 0; i < allowed_extensions_.size(); ++i) { |
| 96 | EXPECT_EQ(allowed_extensions_[i], allowed_extensions[i]); |
| 97 | } |
| 98 | } |
| 99 | |
| 100 | // Send response to the extension function. |
| 101 | // The callback will take a reference to the function and keep it alive. |
| 102 | base::MessageLoopProxy::current()->PostTask(FROM_HERE, |
| 103 | base::Bind(&FileBrowserHandlerInternalSelectFileFunction:: |
| 104 | OnFilePathSelected, |
| 105 | function, success_, selected_path_)); |
| 106 | delete this; |
| 107 | } |
| 108 | |
| 109 | private: |
| 110 | // File name that is expected to be suggested by the function. |
| 111 | base::FilePath suggested_name_; |
| 112 | |
| 113 | // Extensions that is expected to be allowed by the function. |
| 114 | std::vector<std::string> allowed_extensions_; |
| 115 | |
| 116 | // Whether the selection should succeed. |
| 117 | bool success_; |
| 118 | // File path that should be returned to the function. |
| 119 | base::FilePath selected_path_; |
| 120 | |
| 121 | DISALLOW_COPY_AND_ASSIGN(MockFileSelector); |
| 122 | }; |
| 123 | |
| 124 | // Mocks file selector factory for the test. |
| 125 | // When |CreateFileSelector| is invoked it will create mock file selector for |
| 126 | // the extension function with test parameters from the object ctor. |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 127 | class MockFileSelectorFactory : public file_manager::FileSelectorFactory { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 128 | public: |
| 129 | explicit MockFileSelectorFactory(const TestCase& test_case) |
| 130 | : suggested_name_(test_case.suggested_name), |
| 131 | allowed_extensions_(test_case.allowed_extensions), |
| 132 | success_(test_case.success), |
| 133 | selected_path_(test_case.selected_path) { |
| 134 | } |
| 135 | virtual ~MockFileSelectorFactory() {} |
| 136 | |
Torne (Richard Coles) | a36e592 | 2013-08-05 13:57:33 +0100 | [diff] [blame] | 137 | // file_manager::FileSelectorFactory implementation. |
| 138 | virtual file_manager::FileSelector* CreateFileSelector() const OVERRIDE { |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 139 | return new MockFileSelector(suggested_name_, |
| 140 | allowed_extensions_, |
| 141 | success_, |
| 142 | selected_path_); |
| 143 | } |
| 144 | |
| 145 | private: |
| 146 | // File name that is expected to be suggested by the function. |
| 147 | base::FilePath suggested_name_; |
| 148 | // Extensions that is expected to be allowed by the function. |
| 149 | std::vector<std::string> allowed_extensions_; |
| 150 | // Whether the selection should succeed. |
| 151 | bool success_; |
| 152 | // File path that should be returned to the function. |
| 153 | base::FilePath selected_path_; |
| 154 | |
| 155 | DISALLOW_COPY_AND_ASSIGN(MockFileSelectorFactory); |
| 156 | }; |
| 157 | |
| 158 | // Extension api test for the fileBrowserHandler extension API. |
| 159 | class FileBrowserHandlerExtensionTest : public ExtensionApiTest { |
| 160 | protected: |
| 161 | virtual void SetUp() OVERRIDE { |
| 162 | // Create mount point directory that will be used in the test. |
| 163 | // Mount point will be called "tmp", and it will be located in a tmp |
| 164 | // directory with an unique name. |
Ben Murdoch | eb525c5 | 2013-07-10 11:40:50 +0100 | [diff] [blame] | 165 | ASSERT_TRUE(scoped_tmp_dir_.CreateUniqueTempDir()); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 166 | tmp_mount_point_ = scoped_tmp_dir_.path().Append("tmp"); |
| 167 | file_util::CreateDirectory(tmp_mount_point_); |
| 168 | |
| 169 | ExtensionApiTest::SetUp(); |
| 170 | } |
| 171 | |
| 172 | // Creates new, test mount point. |
| 173 | void AddTmpMountPoint(const std::string& extension_id) { |
| 174 | BrowserContext::GetMountPoints(browser()->profile())->RegisterFileSystem( |
| 175 | "tmp", |
| 176 | fileapi::kFileSystemTypeNativeLocal, |
| 177 | tmp_mount_point_); |
| 178 | } |
| 179 | |
| 180 | base::FilePath GetFullPathOnTmpMountPoint( |
| 181 | const base::FilePath& relative_path) { |
| 182 | return tmp_mount_point_.Append(relative_path); |
| 183 | } |
| 184 | |
| 185 | // Creates a new FileBrowserHandlerInternalSelectFileFunction to be used in |
| 186 | // the test. This function will be called from ExtensionFunctinoDispatcher |
| 187 | // whenever an extension function for fileBrowserHandlerInternal.selectFile |
| 188 | // will be needed. |
| 189 | static ExtensionFunction* TestSelectFileFunctionFactory() { |
| 190 | EXPECT_TRUE(test_cases_); |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 191 | EXPECT_TRUE(!test_cases_ || current_test_case_ < test_cases_->size()); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 192 | |
| 193 | // If this happens, test failed. But, we still don't want to crash, so |
| 194 | // return valid extension function. |
Ben Murdoch | bb1529c | 2013-08-08 10:24:53 +0100 | [diff] [blame^] | 195 | if (!test_cases_ || current_test_case_ >= test_cases_->size()) |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 196 | return new FileBrowserHandlerInternalSelectFileFunction(); |
| 197 | |
| 198 | // Create file creator factory for the current test case. |
| 199 | MockFileSelectorFactory* mock_factory = |
| 200 | new MockFileSelectorFactory(test_cases_->at(current_test_case_)); |
| 201 | current_test_case_++; |
| 202 | |
| 203 | return new FileBrowserHandlerInternalSelectFileFunction( |
| 204 | mock_factory, false); |
| 205 | } |
| 206 | |
| 207 | // Sets up test parameters for extension function invocations that will be |
| 208 | // made during the test. |
| 209 | void SetTestCases(const std::vector<TestCase>* test_cases) { |
| 210 | test_cases_ = test_cases; |
| 211 | current_test_case_ = 0; |
| 212 | } |
| 213 | |
| 214 | private: |
| 215 | // List of test parameters for each extension function invocation that will be |
| 216 | // made during a test. |
| 217 | // Should be owned by the test code. |
| 218 | static const std::vector<TestCase>* test_cases_; |
| 219 | static size_t current_test_case_; |
| 220 | |
| 221 | base::ScopedTempDir scoped_tmp_dir_; |
| 222 | // Our test mount point path. |
| 223 | base::FilePath tmp_mount_point_; |
| 224 | }; |
| 225 | |
| 226 | const std::vector<TestCase>* FileBrowserHandlerExtensionTest::test_cases_ = |
| 227 | NULL; |
| 228 | size_t FileBrowserHandlerExtensionTest::current_test_case_ = 0; |
| 229 | |
| 230 | // End to end test that verifies that fileBrowserHandler.selectFile works as |
| 231 | // expected. It will run test extension under |
| 232 | // chrome/test/data/extensions/api_test/file_browser/filehandler_create. |
| 233 | // The extension will invoke fileBrowserHandler.selectFile function twice. |
| 234 | // Once with suggested name "some_file_name.txt", and once with suggested name |
| 235 | // "fail". The file selection should succeed the first time, but fail the second |
| 236 | // time. When the file is selected the test extension will verify that it can |
| 237 | // create, read and write the file under the selected file path. |
| 238 | IN_PROC_BROWSER_TEST_F(FileBrowserHandlerExtensionTest, EndToEnd) { |
| 239 | // Path that will be "selected" by file selector. |
| 240 | const base::FilePath selected_path = |
| 241 | GetFullPathOnTmpMountPoint(base::FilePath("test_file.txt")); |
| 242 | |
| 243 | std::vector<std::string> allowed_extensions; |
| 244 | allowed_extensions.push_back("txt"); |
| 245 | allowed_extensions.push_back("html"); |
| 246 | |
| 247 | std::vector<TestCase> test_cases; |
| 248 | test_cases.push_back( |
| 249 | TestCase(base::FilePath("some_file_name.txt"), |
| 250 | allowed_extensions, |
| 251 | true, |
| 252 | selected_path)); |
| 253 | test_cases.push_back( |
| 254 | TestCase(base::FilePath("fail"), |
| 255 | std::vector<std::string>(), |
| 256 | false, |
| 257 | base::FilePath())); |
| 258 | |
| 259 | SetTestCases(&test_cases); |
| 260 | |
| 261 | // Override extension function that will be used during the test. |
| 262 | ASSERT_TRUE(ExtensionFunctionDispatcher::OverrideFunction( |
| 263 | "fileBrowserHandlerInternal.selectFile", |
| 264 | FileBrowserHandlerExtensionTest::TestSelectFileFunctionFactory)); |
| 265 | |
| 266 | // Selected path should still not exist. |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 267 | ASSERT_FALSE(base::PathExists(selected_path)); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 268 | |
| 269 | const Extension* extension = LoadExtension( |
| 270 | test_data_dir_.AppendASCII("file_browser/filehandler_create")); |
| 271 | ASSERT_TRUE(extension) << message_; |
| 272 | |
| 273 | AddTmpMountPoint(extension->id()); |
| 274 | |
| 275 | ResultCatcher catcher; |
| 276 | |
| 277 | GURL url = extension->GetResourceURL("test.html"); |
| 278 | ui_test_utils::NavigateToURL(browser(), url); |
| 279 | |
| 280 | ASSERT_TRUE(catcher.GetNextResult()) << message_; |
| 281 | |
| 282 | // Selected path should have been created by the test extension after the |
| 283 | // extension function call. |
Ben Murdoch | 7dbb3d5 | 2013-07-17 14:55:54 +0100 | [diff] [blame] | 284 | ASSERT_TRUE(base::PathExists(selected_path)); |
Torne (Richard Coles) | c2e0dbd | 2013-05-09 18:35:53 +0100 | [diff] [blame] | 285 | |
| 286 | // Let's check that the file has the expected content. |
| 287 | const std::string expected_contents = "hello from test extension."; |
| 288 | content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE, |
| 289 | base::Bind(&ExpectFileContentEquals, selected_path, expected_contents)); |
| 290 | |
| 291 | // Make sure test doesn't finish until we check on file thread that the |
| 292 | // selected file's content is as expected. |
| 293 | content::RunAllPendingInMessageLoop(content::BrowserThread::FILE); |
| 294 | |
| 295 | SetTestCases(NULL); |
| 296 | } |
| 297 | |
| 298 | // Tests that verifies the fileBrowserHandlerInternal.selectFile function fails |
| 299 | // when invoked without user gesture. |
| 300 | IN_PROC_BROWSER_TEST_F(FileBrowserHandlerExtensionTest, NoUserGesture) { |
| 301 | scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> |
| 302 | select_file_function( |
| 303 | new FileBrowserHandlerInternalSelectFileFunction()); |
| 304 | |
| 305 | std::string error = |
| 306 | utils::RunFunctionAndReturnError( |
| 307 | select_file_function.get(), |
| 308 | "[{\"suggestedName\": \"foo\"}]", |
| 309 | browser()); |
| 310 | |
| 311 | const std::string expected_error = |
| 312 | "This method can only be called in response to user gesture, such as a " |
| 313 | "mouse click or key press."; |
| 314 | EXPECT_EQ(expected_error, error); |
| 315 | } |
| 316 | |
| 317 | // Tests that checks that the fileHandlerInternal.selectFile function returns |
| 318 | // dictionary with |success == false| and no file entry when user cancels file |
| 319 | // selection. |
| 320 | IN_PROC_BROWSER_TEST_F(FileBrowserHandlerExtensionTest, SelectionFailed) { |
| 321 | TestCase test_case(base::FilePath("some_file_name.txt"), |
| 322 | std::vector<std::string>(), |
| 323 | false, |
| 324 | base::FilePath()); |
| 325 | |
| 326 | scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> |
| 327 | select_file_function( |
| 328 | new FileBrowserHandlerInternalSelectFileFunction( |
| 329 | new MockFileSelectorFactory(test_case), |
| 330 | false)); |
| 331 | |
| 332 | select_file_function->set_has_callback(true); |
| 333 | select_file_function->set_user_gesture(true); |
| 334 | |
| 335 | scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( |
| 336 | utils::RunFunctionAndReturnSingleResult( |
| 337 | select_file_function.get(), |
| 338 | "[{\"suggestedName\": \"some_file_name.txt\"}]", |
| 339 | browser()))); |
| 340 | |
| 341 | EXPECT_FALSE(utils::GetBoolean(result.get(), "success")); |
| 342 | DictionaryValue* entry_info; |
| 343 | EXPECT_FALSE(result->GetDictionary("entry", &entry_info)); |
| 344 | } |
| 345 | |
| 346 | // Tests that user cannot be suggested a full file path when selecting a file, |
| 347 | // only a file name (i.e. that extension function caller has no influence on |
| 348 | // which directory contents will be initially displayed in selection dialog). |
| 349 | IN_PROC_BROWSER_TEST_F(FileBrowserHandlerExtensionTest, SuggestedFullPath) { |
| 350 | TestCase test_case(base::FilePath("some_file_name.txt"), |
| 351 | std::vector<std::string>(), |
| 352 | false, |
| 353 | base::FilePath()); |
| 354 | |
| 355 | scoped_refptr<FileBrowserHandlerInternalSelectFileFunction> |
| 356 | select_file_function( |
| 357 | new FileBrowserHandlerInternalSelectFileFunction( |
| 358 | new MockFileSelectorFactory(test_case), |
| 359 | false)); |
| 360 | |
| 361 | select_file_function->set_has_callback(true); |
| 362 | select_file_function->set_user_gesture(true); |
| 363 | |
| 364 | scoped_ptr<base::DictionaryValue> result(utils::ToDictionary( |
| 365 | utils::RunFunctionAndReturnSingleResult( |
| 366 | select_file_function.get(), |
| 367 | "[{\"suggestedName\": \"/path_to_file/some_file_name.txt\"}]", |
| 368 | browser()))); |
| 369 | |
| 370 | EXPECT_FALSE(utils::GetBoolean(result.get(), "success")); |
| 371 | DictionaryValue* entry_info; |
| 372 | EXPECT_FALSE(result->GetDictionary("entry", &entry_info)); |
| 373 | } |
| 374 | |
| 375 | } // namespace |