assertEqual($before->fid, $after->fid, t('File id is the same: %file1 == %file2.', array('%file1' => $before->fid, '%file2' => $after->fid)), 'File unchanged'); $this->assertEqual($before->uid, $after->uid, t('File owner is the same: %file1 == %file2.', array('%file1' => $before->uid, '%file2' => $after->uid)), 'File unchanged'); $this->assertEqual($before->filename, $after->filename, t('File name is the same: %file1 == %file2.', array('%file1' => $before->filename, '%file2' => $after->filename)), 'File unchanged'); $this->assertEqual($before->uri, $after->uri, t('File path is the same: %file1 == %file2.', array('%file1' => $before->uri, '%file2' => $after->uri)), 'File unchanged'); $this->assertEqual($before->filemime, $after->filemime, t('File MIME type is the same: %file1 == %file2.', array('%file1' => $before->filemime, '%file2' => $after->filemime)), 'File unchanged'); $this->assertEqual($before->filesize, $after->filesize, t('File size is the same: %file1 == %file2.', array('%file1' => $before->filesize, '%file2' => $after->filesize)), 'File unchanged'); $this->assertEqual($before->status, $after->status, t('File status is the same: %file1 == %file2.', array('%file1' => $before->status, '%file2' => $after->status)), 'File unchanged'); } /** * Check that two files are not the same by comparing the fid and filepath. * * @param $file1 * File object to compare. * @param $file2 * File object to compare. */ function assertDifferentFile($file1, $file2) { $this->assertNotEqual($file1->fid, $file2->fid, t('Files have different ids: %file1 != %file2.', array('%file1' => $file1->fid, '%file2' => $file2->fid)), 'Different file'); $this->assertNotEqual($file1->uri, $file2->uri, t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Different file'); } /** * Check that two files are the same by comparing the fid and filepath. * * @param $file1 * File object to compare. * @param $file2 * File object to compare. */ function assertSameFile($file1, $file2) { $this->assertEqual($file1->fid, $file2->fid, t('Files have the same ids: %file1 == %file2.', array('%file1' => $file1->fid, '%file2-fid' => $file2->fid)), 'Same file'); $this->assertEqual($file1->uri, $file2->uri, t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Same file'); } /** * Helper function to test the permissions of a file. * * @param $filepath * String file path. * @param $expected_mode * Octal integer like 0664 or 0777. * @param $message * Optional message. */ function assertFilePermissions($filepath, $expected_mode, $message = NULL) { // Clear out PHP's file stat cache to be sure we see the current value. clearstatcache(); // Mask out all but the last three octets. $actual_mode = fileperms($filepath) & 0777; // PHP on Windows has limited support for file permissions. Usually each of // "user", "group" and "other" use one octal digit (3 bits) to represent the // read/write/execute bits. On Windows, chmod() ignores the "group" and // "other" bits, and fileperms() returns the "user" bits in all three // positions. $expected_mode is updated to reflect this. if (substr(PHP_OS, 0, 3) == 'WIN') { // Reset the "group" and "other" bits. $expected_mode = $expected_mode & 0700; // Shift the "user" bits to the "group" and "other" positions also. $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6; } if (!isset($message)) { $message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode))); } $this->assertEqual($actual_mode, $expected_mode, $message); } /** * Helper function to test the permissions of a directory. * * @param $directory * String directory path. * @param $expected_mode * Octal integer like 0664 or 0777. * @param $message * Optional message. */ function assertDirectoryPermissions($directory, $expected_mode, $message = NULL) { // Clear out PHP's file stat cache to be sure we see the current value. clearstatcache(); // Mask out all but the last three octets. $actual_mode = fileperms($directory) & 0777; // PHP on Windows has limited support for file permissions. Usually each of // "user", "group" and "other" use one octal digit (3 bits) to represent the // read/write/execute bits. On Windows, chmod() ignores the "group" and // "other" bits, and fileperms() returns the "user" bits in all three // positions. $expected_mode is updated to reflect this. if (substr(PHP_OS, 0, 3) == 'WIN') { // Reset the "group" and "other" bits. $expected_mode = $expected_mode & 0700; // Shift the "user" bits to the "group" and "other" positions also. $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6; } if (!isset($message)) { $message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode))); } $this->assertEqual($actual_mode, $expected_mode, $message); } /** * Create a directory and assert it exists. * * @param $path * Optional string with a directory path. If none is provided, a random * name in the site's files directory will be used. * @return * The path to the directory. */ function createDirectory($path = NULL) { // A directory to operate on. if (!isset($path)) { $path = file_default_scheme() . '://' . $this->randomName(); } $this->assertTrue(drupal_mkdir($path) && is_dir($path), t('Directory was created successfully.')); return $path; } /** * Create a file and save it to the files table and assert that it occurs * correctly. * * @param $filepath * Optional string specifying the file path. If none is provided then a * randomly named file will be created in the site's files directory. * @param $contents * Optional contents to save into the file. If a NULL value is provided an * arbitrary string will be used. * @param $scheme * Optional string indicating the stream scheme to use. Drupal core includes * public, private, and temporary. The public wrapper is the default. * @return * File object. */ function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) { if (!isset($filepath)) { // Prefix with non-latin characters to ensure that all file-related // tests work with international filenames. $filepath = 'Файл для тестирования ' . $this->randomName(); } if (!isset($scheme)) { $scheme = file_default_scheme(); } $filepath = $scheme . '://' . $filepath; if (!isset($contents)) { $contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data."; } file_put_contents($filepath, $contents); $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file'); $file = new stdClass(); $file->uri = $filepath; $file->filename = drupal_basename($file->uri); $file->filemime = 'text/plain'; $file->uid = 1; $file->timestamp = REQUEST_TIME; $file->filesize = filesize($file->uri); $file->status = 0; // Write the record directly rather than calling file_save() so we don't // invoke the hooks. $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file'); return $file; } } /** * Base class for file tests that use the file_test module to test uploads and * hooks. */ class FileHookTestCase extends FileTestCase { function setUp() { // Install file_test module parent::setUp('file_test'); // Clear out any hook calls. file_test_reset(); } /** * Assert that all of the specified hook_file_* hooks were called once, other * values result in failure. * * @param $expected * Array with string containing with the hook name, e.g. 'load', 'save', * 'insert', etc. */ function assertFileHooksCalled($expected) { // Determine which hooks were called. $actual = array_keys(array_filter(file_test_get_all_calls())); // Determine if there were any expected that were not called. $uncalled = array_diff($expected, $actual); if (count($uncalled)) { $this->assertTrue(FALSE, t('Expected hooks %expected to be called but %uncalled was not called.', array('%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled)))); } else { $this->assertTrue(TRUE, t('All the expected hooks were called: %expected', array('%expected' => empty($expected) ? t('(none)') : implode(', ', $expected)))); } // Determine if there were any unexpected calls. $unexpected = array_diff($actual, $expected); if (count($unexpected)) { $this->assertTrue(FALSE, t('Unexpected hooks were called: %unexpected.', array('%unexpected' => empty($unexpected) ? t('(none)') : implode(', ', $unexpected)))); } else { $this->assertTrue(TRUE, t('No unexpected hooks were called.')); } } /** * Assert that a hook_file_* hook was called a certain number of times. * * @param $hook * String with the hook name, e.g. 'load', 'save', 'insert', etc. * @param $expected_count * Optional integer count. * @param $message * Optional translated string message. */ function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) { $actual_count = count(file_test_get_calls($hook)); if (!isset($message)) { if ($actual_count == $expected_count) { $message = t('hook_file_@name was called correctly.', array('@name' => $hook)); } elseif ($expected_count == 0) { $message = format_plural($actual_count, 'hook_file_@name was not expected to be called but was actually called once.', 'hook_file_@name was not expected to be called but was actually called @count times.', array('@name' => $hook, '@count' => $actual_count)); } else { $message = t('hook_file_@name was expected to be called %expected times but was called %actual times.', array('@name' => $hook, '%expected' => $expected_count, '%actual' => $actual_count)); } } $this->assertEqual($actual_count, $expected_count, $message); } } /** * This will run tests against the file_space_used() function. */ class FileSpaceUsedTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File space used tests', 'description' => 'Tests the file_space_used() function.', 'group' => 'File API', ); } function setUp() { parent::setUp(); // Create records for a couple of users with different sizes. $file = array('uid' => 2, 'uri' => 'public://example1.txt', 'filesize' => 50, 'status' => FILE_STATUS_PERMANENT); drupal_write_record('file_managed', $file); $file = array('uid' => 2, 'uri' => 'public://example2.txt', 'filesize' => 20, 'status' => FILE_STATUS_PERMANENT); drupal_write_record('file_managed', $file); $file = array('uid' => 3, 'uri' => 'public://example3.txt', 'filesize' => 100, 'status' => FILE_STATUS_PERMANENT); drupal_write_record('file_managed', $file); $file = array('uid' => 3, 'uri' => 'public://example4.txt', 'filesize' => 200, 'status' => FILE_STATUS_PERMANENT); drupal_write_record('file_managed', $file); // Now create some non-permanent files. $file = array('uid' => 2, 'uri' => 'public://example5.txt', 'filesize' => 1, 'status' => 0); drupal_write_record('file_managed', $file); $file = array('uid' => 3, 'uri' => 'public://example6.txt', 'filesize' => 3, 'status' => 0); drupal_write_record('file_managed', $file); } /** * Test different users with the default status. */ function testFileSpaceUsed() { // Test different users with default status. $this->assertEqual(file_space_used(2), 70); $this->assertEqual(file_space_used(3), 300); $this->assertEqual(file_space_used(), 370); // Test the status fields $this->assertEqual(file_space_used(NULL, 0), 4); $this->assertEqual(file_space_used(NULL, FILE_STATUS_PERMANENT), 370); // Test both the user and status. $this->assertEqual(file_space_used(1, 0), 0); $this->assertEqual(file_space_used(1, FILE_STATUS_PERMANENT), 0); $this->assertEqual(file_space_used(2, 0), 1); $this->assertEqual(file_space_used(2, FILE_STATUS_PERMANENT), 70); $this->assertEqual(file_space_used(3, 0), 3); $this->assertEqual(file_space_used(3, FILE_STATUS_PERMANENT), 300); } } /** * This will run tests against the file validation functions (file_validate_*). */ class FileValidatorTest extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'File validator tests', 'description' => 'Tests the functions used to validate uploaded files.', 'group' => 'File API', ); } function setUp() { parent::setUp(); $this->image = new stdClass(); $this->image->uri = 'misc/druplicon.png'; $this->image->filename = drupal_basename($this->image->uri); $this->non_image = new stdClass(); $this->non_image->uri = 'misc/jquery.js'; $this->non_image->filename = drupal_basename($this->non_image->uri); } /** * Test the file_validate_extensions() function. */ function testFileValidateExtensions() { $file = new stdClass(); $file->filename = 'asdf.txt'; $errors = file_validate_extensions($file, 'asdf txt pork'); $this->assertEqual(count($errors), 0, t('Valid extension accepted.'), 'File'); $file->filename = 'asdf.txt'; $errors = file_validate_extensions($file, 'exe png'); $this->assertEqual(count($errors), 1, t('Invalid extension blocked.'), 'File'); } /** * This ensures a specific file is actually an image. */ function testFileValidateIsImage() { $this->assertTrue(file_exists($this->image->uri), t('The image being tested exists.'), 'File'); $errors = file_validate_is_image($this->image); $this->assertEqual(count($errors), 0, t('No error reported for our image file.'), 'File'); $this->assertTrue(file_exists($this->non_image->uri), t('The non-image being tested exists.'), 'File'); $errors = file_validate_is_image($this->non_image); $this->assertEqual(count($errors), 1, t('An error reported for our non-image file.'), 'File'); } /** * This ensures the resolution of a specific file is within bounds. * The image will be resized if it's too large. */ function testFileValidateImageResolution() { // Non-images. $errors = file_validate_image_resolution($this->non_image); $this->assertEqual(count($errors), 0, t("Shouldn't get any errors for a non-image file."), 'File'); $errors = file_validate_image_resolution($this->non_image, '50x50', '100x100'); $this->assertEqual(count($errors), 0, t("Don't check the resolution on non files."), 'File'); // Minimum size. $errors = file_validate_image_resolution($this->image); $this->assertEqual(count($errors), 0, t('No errors for an image when there is no minimum or maximum resolution.'), 'File'); $errors = file_validate_image_resolution($this->image, 0, '200x1'); $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't wide enough."), 'File'); $errors = file_validate_image_resolution($this->image, 0, '1x200'); $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't tall enough."), 'File'); $errors = file_validate_image_resolution($this->image, 0, '200x200'); $this->assertEqual(count($errors), 1, t('Small images report an error.'), 'File'); // Maximum size. if (image_get_toolkit()) { // Copy the image so that the original doesn't get resized. copy('misc/druplicon.png', 'temporary://druplicon.png'); $this->image->uri = 'temporary://druplicon.png'; $errors = file_validate_image_resolution($this->image, '10x5'); $this->assertEqual(count($errors), 0, t('No errors should be reported when an oversized image can be scaled down.'), 'File'); $info = image_get_info($this->image->uri); $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File'); $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File'); drupal_unlink('temporary://druplicon.png'); } else { // TODO: should check that the error is returned if no toolkit is available. $errors = file_validate_image_resolution($this->image, '5x10'); $this->assertEqual(count($errors), 1, t("Oversize images that can't be scaled get an error."), 'File'); } } /** * This will ensure the filename length is valid. */ function testFileValidateNameLength() { // Create a new file object. $file = new stdClass(); // Add a filename with an allowed length and test it. $file->filename = str_repeat('x', 240); $this->assertEqual(strlen($file->filename), 240); $errors = file_validate_name_length($file); $this->assertEqual(count($errors), 0, t('No errors reported for 240 length filename.'), 'File'); // Add a filename with a length too long and test it. $file->filename = str_repeat('x', 241); $errors = file_validate_name_length($file); $this->assertEqual(count($errors), 1, t('An error reported for 241 length filename.'), 'File'); // Add a filename with an empty string and test it. $file->filename = ''; $errors = file_validate_name_length($file); $this->assertEqual(count($errors), 1, t('An error reported for 0 length filename.'), 'File'); } /** * Test file_validate_size(). */ function testFileValidateSize() { global $user; $original_user = $user; drupal_save_session(FALSE); // Run these test as uid = 1. $user = user_load(1); $file = new stdClass(); $file->filesize = 999999; $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File'); // Run these tests as a regular user. $user = $this->drupalCreateUser(); // Create a file with a size of 1000 bytes, and quotas of only 1 byte. $file = new stdClass(); $file->filesize = 1000; $errors = file_validate_size($file, 0, 0); $this->assertEqual(count($errors), 0, t('No limits means no errors.'), 'File'); $errors = file_validate_size($file, 1, 0); $this->assertEqual(count($errors), 1, t('Error for the file being over the limit.'), 'File'); $errors = file_validate_size($file, 0, 1); $this->assertEqual(count($errors), 1, t('Error for the user being over their limit.'), 'File'); $errors = file_validate_size($file, 1, 1); $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File'); $user = $original_user; drupal_save_session(TRUE); } } /** * Tests the file_unmanaged_save_data() function. */ class FileUnmanagedSaveDataTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'Unmanaged file save data', 'description' => 'Tests the unmanaged file save data function.', 'group' => 'File API', ); } /** * Test the file_unmanaged_save_data() function. */ function testFileSaveData() { $contents = $this->randomName(8); // No filename. $filepath = file_unmanaged_save_data($contents); $this->assertTrue($filepath, t('Unnamed file saved correctly.')); $this->assertEqual(file_uri_scheme($filepath), file_default_scheme(), t("File was placed in Drupal's files directory.")); $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.')); // Provide a filename. $filepath = file_unmanaged_save_data($contents, 'public://asdf.txt', FILE_EXISTS_REPLACE); $this->assertTrue($filepath, t('Unnamed file saved correctly.')); $this->assertEqual('asdf.txt', drupal_basename($filepath), t('File was named correctly.')); $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.')); $this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664)); } } /** * Tests the file_unmanaged_save_data() function on remote filesystems. */ class RemoteFileUnmanagedSaveDataTest extends FileUnmanagedSaveDataTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Test the file_save_upload() function. */ class FileSaveUploadTest extends FileHookTestCase { /** * An image file path for uploading. */ protected $image; /** * A PHP file path for upload security testing. */ protected $phpfile; /** * The largest file id when the test starts. */ protected $maxFidBefore; public static function getInfo() { return array( 'name' => 'File uploading', 'description' => 'Tests the file uploading functions.', 'group' => 'File API', ); } function setUp() { parent::setUp(); $account = $this->drupalCreateUser(array('access content')); $this->drupalLogin($account); $image_files = $this->drupalGetTestFiles('image'); $this->image = current($image_files); list(, $this->image_extension) = explode('.', $this->image->filename); $this->assertTrue(is_file($this->image->uri), t("The image file we're going to upload exists.")); $this->phpfile = current($this->drupalGetTestFiles('php')); $this->assertTrue(is_file($this->phpfile->uri), t("The PHP file we're going to upload exists.")); $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); // Upload with replace to guarantee there's something there. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri), ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called then clean out the hook // counters. $this->assertFileHooksCalled(array('validate', 'insert')); file_test_reset(); } /** * Test the file_save_upload() function. */ function testNormal() { $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); $this->assertTrue($max_fid_after > $this->maxFidBefore, t('A new file was created.')); $file1 = file_load($max_fid_after); $this->assertTrue($file1, t('Loaded the file.')); // MIME type of the uploaded image may be either image/jpeg or image/png. $this->assertEqual(substr($file1->filemime, 0, 5), 'image', 'A MIME type was set.'); // Reset the hook counters to get rid of the 'load' we just called. file_test_reset(); // Upload a second file. $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); $image2 = current($this->drupalGetTestFiles('image')); $edit = array('files[file_test_upload]' => drupal_realpath($image2->uri)); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!')); $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); $file2 = file_load($max_fid_after); $this->assertTrue($file2); // MIME type of the uploaded image may be either image/jpeg or image/png. $this->assertEqual(substr($file2->filemime, 0, 5), 'image', 'A MIME type was set.'); // Load both files using file_load_multiple(). $files = file_load_multiple(array($file1->fid, $file2->fid)); $this->assertTrue(isset($files[$file1->fid]), t('File was loaded successfully')); $this->assertTrue(isset($files[$file2->fid]), t('File was loaded successfully')); // Upload a third file to a subdirectory. $image3 = current($this->drupalGetTestFiles('image')); $image3_realpath = drupal_realpath($image3->uri); $dir = $this->randomName(); $edit = array( 'files[file_test_upload]' => $image3_realpath, 'file_subdir' => $dir, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!')); $this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath)))); // Check that file_load_multiple() with no arguments returns FALSE. $this->assertFalse(file_load_multiple(), t('No files were loaded.')); } /** * Test extension handling. */ function testHandleExtension() { // The file being tested is a .gif which is in the default safe list // of extensions to allow when the extension validator isn't used. This is // implicitly tested at the testNormal() test. Here we tell // file_save_upload() to only allow ".foo". $extensions = 'foo'; $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $message = t('Only files with the following extensions are allowed:') . ' ' . $extensions . ''; $this->assertRaw($message, t('Can\'t upload a disallowed extension')); $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate')); // Reset the hook counters. file_test_reset(); $extensions = 'foo ' . $this->image_extension; // Now tell file_save_upload() to allow the extension of our test image. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload an allowed extension.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'load', 'update')); // Reset the hook counters. file_test_reset(); // Now tell file_save_upload() to allow any extension. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri), 'allow_all_extensions' => TRUE, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload any extension.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'load', 'update')); } /** * Test dangerous file handling. */ function testHandleDangerousFile() { // Allow the .php extension and make sure it gets renamed to .txt for // safety. Also check to make sure its MIME type was changed. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->phpfile->uri), 'is_image_file' => FALSE, 'extensions' => 'php', ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $message = t('For security reasons, your upload has been renamed to') . ' ' . $this->phpfile->filename . '.txt' . ''; $this->assertRaw($message, t('Dangerous file was renamed.')); $this->assertRaw(t('File MIME type is text/plain.'), t('Dangerous file\'s MIME type was changed.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); // Ensure dangerous files are not renamed when insecure uploads is TRUE. // Turn on insecure uploads. variable_set('allow_insecure_uploads', 1); // Reset the hook counters. file_test_reset(); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.')); $this->assertRaw(t('File name is !filename', array('!filename' => $this->phpfile->filename)), t('Dangerous file was not renamed when insecure uploads is TRUE.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); // Turn off insecure uploads. variable_set('allow_insecure_uploads', 0); } /** * Test file munge handling. */ function testHandleFileMunge() { // Ensure insecure uploads are disabled for this test. variable_set('allow_insecure_uploads', 0); $this->image = file_move($this->image, $this->image->uri . '.foo.' . $this->image_extension); // Reset the hook counters to get rid of the 'move' we just called. file_test_reset(); $extensions = $this->image_extension; $edit = array( 'files[file_test_upload]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); $munged_filename = $this->image->filename; $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.')); $munged_filename .= '_.' . $this->image_extension; $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('For security reasons, your upload has been renamed'), t('Found security message.')); $this->assertRaw(t('File name is !filename', array('!filename' => $munged_filename)), t('File was successfully munged.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); // Ensure we don't munge files if we're allowing any extension. // Reset the hook counters. file_test_reset(); $edit = array( 'files[file_test_upload]' => drupal_realpath($this->image->uri), 'allow_all_extensions' => TRUE, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.')); $this->assertRaw(t('File name is !filename', array('!filename' => $this->image->filename)), t('File was not munged when allowing any extension.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); } /** * Test renaming when uploading over a file that already exists. */ function testExistingRename() { $edit = array( 'file_test_replace' => FILE_EXISTS_RENAME, 'files[file_test_upload]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'insert')); } /** * Test replacement when uploading over a file that already exists. */ function testExistingReplace() { $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, 'files[file_test_upload]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!'), t('Found the success message.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('validate', 'load', 'update')); } /** * Test for failure when uploading over a file that already exists. */ function testExistingError() { $edit = array( 'file_test_replace' => FILE_EXISTS_ERROR, 'files[file_test_upload]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.')); // Check that the no hooks were called while failing. $this->assertFileHooksCalled(array()); } /** * Test for no failures when not uploading a file. */ function testNoUpload() { $this->drupalPost('file-test/upload', array(), t('Submit')); $this->assertNoRaw(t('Epic upload FAIL!'), t('Failure message not found.')); } } /** * Test the file_save_upload() function on remote filesystems. */ class RemoteFileSaveUploadTest extends FileSaveUploadTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Directory related tests. */ class FileDirectoryTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File paths and directories', 'description' => 'Tests operations dealing with directories.', 'group' => 'File API', ); } /** * Test directory handling functions. */ function testFileCheckDirectoryHandling() { // A directory to operate on. $directory = file_default_scheme() . '://' . $this->randomName() . '/' . $this->randomName(); $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.')); // Non-existent directory. $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for non-existing directory.'), 'File'); // Make a directory. $this->assertTrue(file_prepare_directory($directory, FILE_CREATE_DIRECTORY), t('No error reported when creating a new directory.'), 'File'); // Make sure directory actually exists. $this->assertTrue(is_dir($directory), t('Directory actually exists.'), 'File'); if (substr(PHP_OS, 0, 3) != 'WIN') { // PHP on Windows doesn't support any kind of useful read-only mode for // directories. When executing a chmod() on a directory, PHP only sets the // read-only flag, which doesn't prevent files to actually be written // in the directory on any recent version of Windows. // Make directory read only. @drupal_chmod($directory, 0444); $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for a non-writeable directory.'), 'File'); // Test directory permission modification. $this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File'); } // Test that the directory has the correct permissions. $this->assertDirectoryPermissions($directory, variable_get('file_chmod_directory', 0775)); // Remove .htaccess file to then test that it gets re-created. @drupal_unlink(file_default_scheme() . '://.htaccess'); $this->assertFalse(is_file(file_default_scheme() . '://.htaccess'), t('Successfully removed the .htaccess file in the files directory.'), 'File'); file_ensure_htaccess(); $this->assertTrue(is_file(file_default_scheme() . '://.htaccess'), t('Successfully re-created the .htaccess file in the files directory.'), 'File'); // Verify contents of .htaccess file. $file = file_get_contents(file_default_scheme() . '://.htaccess'); $this->assertEqual($file, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks", t('The .htaccess file contains the proper content.'), 'File'); } /** * This will take a directory and path, and find a valid filepath that is not * taken by another file. */ function testFileCreateNewFilepath() { // First we test against an imaginary file that does not exist in a // directory. $basename = 'xyz.txt'; $directory = 'misc'; $original = $directory . '/' . $basename; $path = file_create_filename($basename, $directory); $this->assertEqual($path, $original, t('New filepath %new equals %original.', array('%new' => $path, '%original' => $original)), 'File'); // Then we test against a file that already exists within that directory. $basename = 'druplicon.png'; $original = $directory . '/' . $basename; $expected = $directory . '/druplicon_0.png'; $path = file_create_filename($basename, $directory); $this->assertEqual($path, $expected, t('Creating a new filepath from %original equals %new.', array('%new' => $path, '%original' => $original)), 'File'); // @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix. } /** * This will test the filepath for a destination based on passed flags and * whether or not the file exists. * * If a file exists, file_destination($destination, $replace) will either * return: * - the existing filepath, if $replace is FILE_EXISTS_REPLACE * - a new filepath if FILE_EXISTS_RENAME * - an error (returning FALSE) if FILE_EXISTS_ERROR. * If the file doesn't currently exist, then it will simply return the * filepath. */ function testFileDestination() { // First test for non-existent file. $destination = 'misc/xyz.txt'; $path = file_destination($destination, FILE_EXISTS_REPLACE); $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_REPLACE.'), 'File'); $path = file_destination($destination, FILE_EXISTS_RENAME); $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_RENAME.'), 'File'); $path = file_destination($destination, FILE_EXISTS_ERROR); $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_ERROR.'), 'File'); $destination = 'misc/druplicon.png'; $path = file_destination($destination, FILE_EXISTS_REPLACE); $this->assertEqual($path, $destination, t('Existing filepath destination remains the same with FILE_EXISTS_REPLACE.'), 'File'); $path = file_destination($destination, FILE_EXISTS_RENAME); $this->assertNotEqual($path, $destination, t('A new filepath destination is created when filepath destination already exists with FILE_EXISTS_RENAME.'), 'File'); $path = file_destination($destination, FILE_EXISTS_ERROR); $this->assertEqual($path, FALSE, t('An error is returned when filepath destination already exists with FILE_EXISTS_ERROR.'), 'File'); } /** * Ensure that the file_directory_temp() function always returns a value. */ function testFileDirectoryTemp() { // Start with an empty variable to ensure we have a clean slate. variable_set('file_temporary_path', ''); $tmp_directory = file_directory_temp(); $this->assertEqual(empty($tmp_directory), FALSE, t('file_directory_temp() returned a non-empty value.')); $setting = variable_get('file_temporary_path', ''); $this->assertEqual($setting, $tmp_directory, t("The 'file_temporary_path' variable has the same value that file_directory_temp() returned.")); } } /** * Directory related tests. */ class RemoteFileDirectoryTest extends FileDirectoryTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Tests the file_scan_directory() function. */ class FileScanDirectoryTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File scan directory', 'description' => 'Tests the file_scan_directory() function.', 'group' => 'File API', ); } function setUp() { parent::setUp(); $this->path = drupal_get_path('module', 'simpletest') . '/files'; } /** * Check the format of the returned values. */ function testReturn() { // Grab a listing of all the JavaSscript files and check that they're // passed to the callback. $all_files = file_scan_directory($this->path, '/^javascript-/'); ksort($all_files); $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.')); // Check the first file. $file = reset($all_files); $this->assertEqual(key($all_files), $file->uri, t('Correct array key was used for the first returned file.')); $this->assertEqual($file->uri, $this->path . '/javascript-1.txt', t('First file name was set correctly.')); $this->assertEqual($file->filename, 'javascript-1.txt', t('First basename was set correctly')); $this->assertEqual($file->name, 'javascript-1', t('First name was set correctly.')); // Check the second file. $file = next($all_files); $this->assertEqual(key($all_files), $file->uri, t('Correct array key was used for the second returned file.')); $this->assertEqual($file->uri, $this->path . '/javascript-2.script', t('Second file name was set correctly.')); $this->assertEqual($file->filename, 'javascript-2.script', t('Second basename was set correctly')); $this->assertEqual($file->name, 'javascript-2', t('Second name was set correctly.')); } /** * Check that the callback function is called correctly. */ function testOptionCallback() { // When nothing is matched nothing should be passed to the callback. $all_files = file_scan_directory($this->path, '/^NONEXISTINGFILENAME/', array('callback' => 'file_test_file_scan_callback')); $this->assertEqual(0, count($all_files), t('No files were found.')); $results = file_test_file_scan_callback(); file_test_file_scan_callback_reset(); $this->assertEqual(0, count($results), t('No files were passed to the callback.')); // Grab a listing of all the JavaSscript files and check that they're // passed to the callback. $all_files = file_scan_directory($this->path, '/^javascript-/', array('callback' => 'file_test_file_scan_callback')); $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.')); $results = file_test_file_scan_callback(); file_test_file_scan_callback_reset(); $this->assertEqual(2, count($results), t('Files were passed to the callback.')); } /** * Check that the no-mask parameter is honored. */ function testOptionNoMask() { // Grab a listing of all the JavaSscript files. $all_files = file_scan_directory($this->path, '/^javascript-/'); $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.')); // Now use the nomast parameter to filter out the .script file. $filtered_files = file_scan_directory($this->path, '/^javascript-/', array('nomask' => '/.script$/')); $this->assertEqual(1, count($filtered_files), t('Filtered correctly.')); } /** * Check that key parameter sets the return value's key. */ function testOptionKey() { // "filename", for the path starting with $dir. $expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script'); $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filepath'))); sort($actual); $this->assertEqual($expected, $actual, t('Returned the correct values for the filename key.')); // "basename", for the basename of the file. $expected = array('javascript-1.txt', 'javascript-2.script'); $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filename'))); sort($actual); $this->assertEqual($expected, $actual, t('Returned the correct values for the basename key.')); // "name" for the name of the file without an extension. $expected = array('javascript-1', 'javascript-2'); $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'name'))); sort($actual); $this->assertEqual($expected, $actual, t('Returned the correct values for the name key.')); // Invalid option that should default back to "filename". $expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script'); $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'INVALID'))); sort($actual); $this->assertEqual($expected, $actual, t('An invalid key defaulted back to the default.')); } /** * Check that the recurse option decends into subdirectories. */ function testOptionRecurse() { $files = file_scan_directory(drupal_get_path('module', 'simpletest'), '/^javascript-/', array('recurse' => FALSE)); $this->assertTrue(empty($files), t("Without recursion couldn't find javascript files.")); $files = file_scan_directory(drupal_get_path('module', 'simpletest'), '/^javascript-/', array('recurse' => TRUE)); $this->assertEqual(2, count($files), t('With recursion we found the expected javascript files.')); } /** * Check that the min_depth options lets us ignore files in the starting * directory. */ function testOptionMinDepth() { $files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 0)); $this->assertEqual(2, count($files), t('No minimum-depth gets files in current directory.')); $files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 1)); $this->assertTrue(empty($files), t("Minimum-depth of 1 successfully excludes files from current directory.")); } } /** * Tests the file_scan_directory() function on remote filesystems. */ class RemoteFileScanDirectoryTest extends FileScanDirectoryTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Deletion related tests. */ class FileUnmanagedDeleteTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'Unmanaged file delete', 'description' => 'Tests the unmanaged file delete function.', 'group' => 'File API', ); } /** * Delete a normal file. */ function testNormal() { // Create a file for testing $file = $this->createFile(); // Delete a regular file $this->assertTrue(file_unmanaged_delete($file->uri), t('Deleted worked.')); $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.')); } /** * Try deleting a missing file. */ function testMissing() { // Try to delete a non-existing file $this->assertTrue(file_unmanaged_delete(file_default_scheme() . '/' . $this->randomName()), t('Returns true when deleting a non-existent file.')); } /** * Try deleting a directory. */ function testDirectory() { // A directory to operate on. $directory = $this->createDirectory(); // Try to delete a directory $this->assertFalse(file_unmanaged_delete($directory), t('Could not delete the delete directory.')); $this->assertTrue(file_exists($directory), t('Directory has not been deleted.')); } } /** * Deletion related tests on remote filesystems. */ class RemoteFileUnmanagedDeleteTest extends FileUnmanagedDeleteTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Deletion related tests. */ class FileUnmanagedDeleteRecursiveTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'Unmanaged recursive file delete', 'description' => 'Tests the unmanaged file delete recursive function.', 'group' => 'File API', ); } /** * Delete a normal file. */ function testSingleFile() { // Create a file for testing $filepath = file_default_scheme() . '://' . $this->randomName(); file_put_contents($filepath, ''); // Delete the file. $this->assertTrue(file_unmanaged_delete_recursive($filepath), t('Function reported success.')); $this->assertFalse(file_exists($filepath), t('Test file has been deleted.')); } /** * Try deleting an empty directory. */ function testEmptyDirectory() { // A directory to operate on. $directory = $this->createDirectory(); // Delete the directory. $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.')); $this->assertFalse(file_exists($directory), t('Directory has been deleted.')); } /** * Try deleting a directory with some files. */ function testDirectory() { // A directory to operate on. $directory = $this->createDirectory(); $filepathA = $directory . '/A'; $filepathB = $directory . '/B'; file_put_contents($filepathA, ''); file_put_contents($filepathB, ''); // Delete the directory. $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.')); $this->assertFalse(file_exists($filepathA), t('Test file A has been deleted.')); $this->assertFalse(file_exists($filepathB), t('Test file B has been deleted.')); $this->assertFalse(file_exists($directory), t('Directory has been deleted.')); } /** * Try deleting subdirectories with some files. */ function testSubDirectory() { // A directory to operate on. $directory = $this->createDirectory(); $subdirectory = $this->createDirectory($directory . '/sub'); $filepathA = $directory . '/A'; $filepathB = $subdirectory . '/B'; file_put_contents($filepathA, ''); file_put_contents($filepathB, ''); // Delete the directory. $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.')); $this->assertFalse(file_exists($filepathA), t('Test file A has been deleted.')); $this->assertFalse(file_exists($filepathB), t('Test file B has been deleted.')); $this->assertFalse(file_exists($subdirectory), t('Subdirectory has been deleted.')); $this->assertFalse(file_exists($directory), t('Directory has been deleted.')); } } /** * Deletion related tests on remote filesystems. */ class RemoteFileUnmanagedDeleteRecursiveTest extends FileUnmanagedDeleteRecursiveTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Unmanaged move related tests. */ class FileUnmanagedMoveTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'Unmanaged file moving', 'description' => 'Tests the unmanaged file move function.', 'group' => 'File API', ); } /** * Move a normal file. */ function testNormal() { // Create a file for testing $file = $this->createFile(); // Moving to a new name. $desired_filepath = 'public://' . $this->randomName(); $new_filepath = file_unmanaged_move($file->uri, $desired_filepath, FILE_EXISTS_ERROR); $this->assertTrue($new_filepath, t('Move was successful.')); $this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.')); $this->assertTrue(file_exists($new_filepath), t('File exists at the new location.')); $this->assertFalse(file_exists($file->uri), t('No file remains at the old location.')); $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664)); // Moving with rename. $desired_filepath = 'public://' . $this->randomName(); $this->assertTrue(file_exists($new_filepath), t('File exists before moving.')); $this->assertTrue(file_put_contents($desired_filepath, ' '), t('Created a file so a rename will have to happen.')); $newer_filepath = file_unmanaged_move($new_filepath, $desired_filepath, FILE_EXISTS_RENAME); $this->assertTrue($newer_filepath, t('Move was successful.')); $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.')); $this->assertTrue(file_exists($newer_filepath), t('File exists at the new location.')); $this->assertFalse(file_exists($new_filepath), t('No file remains at the old location.')); $this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664)); // TODO: test moving to a directory (rather than full directory/file path) // TODO: test creating and moving normal files (rather than streams) } /** * Try to move a missing file. */ function testMissing() { // Move non-existent file. $new_filepath = file_unmanaged_move($this->randomName(), $this->randomName()); $this->assertFalse($new_filepath, t('Moving a missing file fails.')); } /** * Try to move a file onto itself. */ function testOverwriteSelf() { // Create a file for testing. $file = $this->createFile(); // Move the file onto itself without renaming shouldn't make changes. $new_filepath = file_unmanaged_move($file->uri, $file->uri, FILE_EXISTS_REPLACE); $this->assertFalse($new_filepath, t('Moving onto itself without renaming fails.')); $this->assertTrue(file_exists($file->uri), t('File exists after moving onto itself.')); // Move the file onto itself with renaming will result in a new filename. $new_filepath = file_unmanaged_move($file->uri, $file->uri, FILE_EXISTS_RENAME); $this->assertTrue($new_filepath, t('Moving onto itself with renaming works.')); $this->assertFalse(file_exists($file->uri), t('Original file has been removed.')); $this->assertTrue(file_exists($new_filepath), t('File exists after moving onto itself.')); } } /** * Unmanaged move related tests on remote filesystems. */ class RemoteFileUnmanagedMoveTest extends FileUnmanagedMoveTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Unmanaged copy related tests. */ class FileUnmanagedCopyTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'Unmanaged file copying', 'description' => 'Tests the unmanaged file copy function.', 'group' => 'File API', ); } /** * Copy a normal file. */ function testNormal() { // Create a file for testing $file = $this->createFile(); // Copying to a new name. $desired_filepath = 'public://' . $this->randomName(); $new_filepath = file_unmanaged_copy($file->uri, $desired_filepath, FILE_EXISTS_ERROR); $this->assertTrue($new_filepath, t('Copy was successful.')); $this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.')); $this->assertTrue(file_exists($file->uri), t('Original file remains.')); $this->assertTrue(file_exists($new_filepath), t('New file exists.')); $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664)); // Copying with rename. $desired_filepath = 'public://' . $this->randomName(); $this->assertTrue(file_put_contents($desired_filepath, ' '), t('Created a file so a rename will have to happen.')); $newer_filepath = file_unmanaged_copy($file->uri, $desired_filepath, FILE_EXISTS_RENAME); $this->assertTrue($newer_filepath, t('Copy was successful.')); $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.')); $this->assertTrue(file_exists($file->uri), t('Original file remains.')); $this->assertTrue(file_exists($newer_filepath), t('New file exists.')); $this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664)); // TODO: test copying to a directory (rather than full directory/file path) // TODO: test copying normal files using normal paths (rather than only streams) } /** * Copy a non-existent file. */ function testNonExistent() { // Copy non-existent file $desired_filepath = $this->randomName(); $this->assertFalse(file_exists($desired_filepath), t("Randomly named file doesn't exists.")); $new_filepath = file_unmanaged_copy($desired_filepath, $this->randomName()); $this->assertFalse($new_filepath, t('Copying a missing file fails.')); } /** * Copy a file onto itself. */ function testOverwriteSelf() { // Create a file for testing $file = $this->createFile(); // Copy the file onto itself with renaming works. $new_filepath = file_unmanaged_copy($file->uri, $file->uri, FILE_EXISTS_RENAME); $this->assertTrue($new_filepath, t('Copying onto itself with renaming works.')); $this->assertNotEqual($new_filepath, $file->uri, t('Copied file has a new name.')); $this->assertTrue(file_exists($file->uri), t('Original file exists after copying onto itself.')); $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664)); // Copy the file onto itself without renaming fails. $new_filepath = file_unmanaged_copy($file->uri, $file->uri, FILE_EXISTS_ERROR); $this->assertFalse($new_filepath, t('Copying onto itself without renaming fails.')); $this->assertTrue(file_exists($file->uri), t('File exists after copying onto itself.')); // Copy the file into same directory without renaming fails. $new_filepath = file_unmanaged_copy($file->uri, drupal_dirname($file->uri), FILE_EXISTS_ERROR); $this->assertFalse($new_filepath, t('Copying onto itself fails.')); $this->assertTrue(file_exists($file->uri), t('File exists after copying onto itself.')); // Copy the file into same directory with renaming works. $new_filepath = file_unmanaged_copy($file->uri, drupal_dirname($file->uri), FILE_EXISTS_RENAME); $this->assertTrue($new_filepath, t('Copying into same directory works.')); $this->assertNotEqual($new_filepath, $file->uri, t('Copied file has a new name.')); $this->assertTrue(file_exists($file->uri), t('Original file exists after copying onto itself.')); $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664)); } } /** * Unmanaged copy related tests on remote filesystems. */ class RemoteFileUnmanagedCopyTest extends FileUnmanagedCopyTest { public static function getInfo() { $info = parent::getInfo(); $info['group'] = 'File API (remote)'; return $info; } function setUp() { parent::setUp('file_test'); variable_set('file_default_scheme', 'dummy-remote'); } } /** * Deletion related tests. */ class FileDeleteTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File delete', 'description' => 'Tests the file delete function.', 'group' => 'File API', ); } /** * Tries deleting a normal file (as opposed to a directory, symlink, etc). */ function testUnused() { $file = $this->createFile(); // Check that deletion removes the file and database record. $this->assertTrue(is_file($file->uri), t('File exists.')); $this->assertIdentical(file_delete($file), TRUE, t('Delete worked.')); $this->assertFileHooksCalled(array('delete')); $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.')); $this->assertFalse(file_load($file->fid), t('File was removed from the database.')); } /** * Tries deleting a file that is in use. */ function testInUse() { $file = $this->createFile(); file_usage_add($file, 'testing', 'test', 1); file_usage_add($file, 'testing', 'test', 1); file_usage_delete($file, 'testing', 'test', 1); file_delete($file); $usage = file_usage_list($file); $this->assertEqual($usage['testing']['test'], array(1 => 1), t('Test file is still in use.')); $this->assertTrue(file_exists($file->uri), t('File still exists on the disk.')); $this->assertTrue(file_load($file->fid), t('File still exists in the database.')); // Clear out the call to hook_file_load(). file_test_reset(); file_usage_delete($file, 'testing', 'test', 1); file_delete($file); $usage = file_usage_list($file); $this->assertFileHooksCalled(array('delete')); $this->assertTrue(empty($usage), t('File usage data was removed.')); $this->assertFalse(file_exists($file->uri), t('File has been deleted after its last usage was removed.')); $this->assertFalse(file_load($file->fid), t('File was removed from the database.')); } } /** * Move related tests */ class FileMoveTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File moving', 'description' => 'Tests the file move function.', 'group' => 'File API', ); } /** * Move a normal file. */ function testNormal() { $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $desired_filepath = 'public://' . $this->randomName(); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_move(clone $source, $desired_filepath, FILE_EXISTS_ERROR); // Check the return status and that the contents changed. $this->assertTrue($result, t('File moved successfully.')); $this->assertFalse(file_exists($source->uri)); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file correctly written.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('move', 'load', 'update')); // Make sure we got the same file back. $this->assertEqual($source->fid, $result->fid, t("Source file id's' %fid is unchanged after move.", array('%fid' => $source->fid))); // Reload the file from the database and check that the changes were // actually saved. $loaded_file = file_load($result->fid, TRUE); $this->assertTrue($loaded_file, t('File can be loaded from the database.')); $this->assertFileUnchanged($result, $loaded_file); } /** * Test renaming when moving onto a file that already exists. */ function testExistingRename() { // Setup a file to overwrite. $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $target = $this->createFile(); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_move(clone $source, $target->uri, FILE_EXISTS_RENAME); // Check the return status and that the contents changed. $this->assertTrue($result, t('File moved successfully.')); $this->assertFalse(file_exists($source->uri)); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file correctly written.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('move', 'load', 'update')); // Compare the returned value to what made it into the database. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); // The target file should not have been altered. $this->assertFileUnchanged($target, file_load($target->fid, TRUE)); // Make sure we end up with two distinct files afterwards. $this->assertDifferentFile($target, $result); // Compare the source and results. $loaded_source = file_load($source->fid, TRUE); $this->assertEqual($loaded_source->fid, $result->fid, t("Returned file's id matches the source.")); $this->assertNotEqual($loaded_source->uri, $source->uri, t("Returned file path has changed from the original.")); } /** * Test replacement when moving onto a file that already exists. */ function testExistingReplace() { // Setup a file to overwrite. $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $target = $this->createFile(); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_move(clone $source, $target->uri, FILE_EXISTS_REPLACE); // Look at the results. $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were overwritten.')); $this->assertFalse(file_exists($source->uri)); $this->assertTrue($result, t('File moved successfully.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('move', 'update', 'delete', 'load')); // Reload the file from the database and check that the changes were // actually saved. $loaded_result = file_load($result->fid, TRUE); $this->assertFileUnchanged($result, $loaded_result); // Check that target was re-used. $this->assertSameFile($target, $loaded_result); // Source and result should be totally different. $this->assertDifferentFile($source, $loaded_result); } /** * Test replacement when moving onto itself. */ function testExistingReplaceSelf() { // Setup a file to overwrite. $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); // Copy the file over itself. Clone the object so we don't have to worry // about the function changing our reference copy. $result = file_move(clone $source, $source->uri, FILE_EXISTS_REPLACE); $this->assertFalse($result, t('File move failed.')); $this->assertEqual($contents, file_get_contents($source->uri), t('Contents of file were not altered.')); // Check that no hooks were called while failing. $this->assertFileHooksCalled(array()); // Load the file from the database and make sure it is identical to what // was returned. $this->assertFileUnchanged($source, file_load($source->fid, TRUE)); } /** * Test that moving onto an existing file fails when FILE_EXISTS_ERROR is * specified. */ function testExistingError() { $contents = $this->randomName(10); $source = $this->createFile(); $target = $this->createFile(NULL, $contents); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_move(clone $source, $target->uri, FILE_EXISTS_ERROR); // Check the return status and that the contents did not change. $this->assertFalse($result, t('File move failed.')); $this->assertTrue(file_exists($source->uri)); $this->assertEqual($contents, file_get_contents($target->uri), t('Contents of file were not altered.')); // Check that no hooks were called while failing. $this->assertFileHooksCalled(array()); // Load the file from the database and make sure it is identical to what // was returned. $this->assertFileUnchanged($source, file_load($source->fid, TRUE)); $this->assertFileUnchanged($target, file_load($target->fid, TRUE)); } } /** * Copy related tests. */ class FileCopyTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File copying', 'description' => 'Tests the file copy function.', 'group' => 'File API', ); } /** * Test file copying in the normal, base case. */ function testNormal() { $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $desired_uri = 'public://' . $this->randomName(); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_copy(clone $source, $desired_uri, FILE_EXISTS_ERROR); // Check the return status and that the contents changed. $this->assertTrue($result, t('File copied successfully.')); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were copied correctly.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('copy', 'insert')); $this->assertDifferentFile($source, $result); $this->assertEqual($result->uri, $desired_uri, t('The copied file object has the desired filepath.')); $this->assertTrue(file_exists($source->uri), t('The original file still exists.')); $this->assertTrue(file_exists($result->uri), t('The copied file exists.')); // Reload the file from the database and check that the changes were // actually saved. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); } /** * Test renaming when copying over a file that already exists. */ function testExistingRename() { // Setup a file to overwrite. $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $target = $this->createFile(); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_copy(clone $source, $target->uri, FILE_EXISTS_RENAME); // Check the return status and that the contents changed. $this->assertTrue($result, t('File copied successfully.')); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were copied correctly.')); $this->assertNotEqual($result->uri, $source->uri, t('Returned file path has changed from the original.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('copy', 'insert')); // Load all the affected files to check the changes that actually made it // to the database. $loaded_source = file_load($source->fid, TRUE); $loaded_target = file_load($target->fid, TRUE); $loaded_result = file_load($result->fid, TRUE); // Verify that the source file wasn't changed. $this->assertFileUnchanged($source, $loaded_source); // Verify that what was returned is what's in the database. $this->assertFileUnchanged($result, $loaded_result); // Make sure we end up with three distinct files afterwards. $this->assertDifferentFile($loaded_source, $loaded_target); $this->assertDifferentFile($loaded_target, $loaded_result); $this->assertDifferentFile($loaded_source, $loaded_result); } /** * Test replacement when copying over a file that already exists. */ function testExistingReplace() { // Setup a file to overwrite. $contents = $this->randomName(10); $source = $this->createFile(NULL, $contents); $target = $this->createFile(); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_copy(clone $source, $target->uri, FILE_EXISTS_REPLACE); // Check the return status and that the contents changed. $this->assertTrue($result, t('File copied successfully.')); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were overwritten.')); $this->assertDifferentFile($source, $result); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('load', 'copy', 'update')); // Load all the affected files to check the changes that actually made it // to the database. $loaded_source = file_load($source->fid, TRUE); $loaded_target = file_load($target->fid, TRUE); $loaded_result = file_load($result->fid, TRUE); // Verify that the source file wasn't changed. $this->assertFileUnchanged($source, $loaded_source); // Verify that what was returned is what's in the database. $this->assertFileUnchanged($result, $loaded_result); // Target file was reused for the result. $this->assertFileUnchanged($loaded_target, $loaded_result); } /** * Test that copying over an existing file fails when FILE_EXISTS_ERROR is * specified. */ function testExistingError() { $contents = $this->randomName(10); $source = $this->createFile(); $target = $this->createFile(NULL, $contents); $this->assertDifferentFile($source, $target); // Clone the object so we don't have to worry about the function changing // our reference copy. $result = file_copy(clone $source, $target->uri, FILE_EXISTS_ERROR); // Check the return status and that the contents were not changed. $this->assertFalse($result, t('File copy failed.')); $this->assertEqual($contents, file_get_contents($target->uri), t('Contents of file were not altered.')); // Check that the correct hooks were called. $this->assertFileHooksCalled(array()); $this->assertFileUnchanged($source, file_load($source->fid, TRUE)); $this->assertFileUnchanged($target, file_load($target->fid, TRUE)); } } /** * Tests the file_load() function. */ class FileLoadTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File loading', 'description' => 'Tests the file_load() function.', 'group' => 'File API', ); } /** * Try to load a non-existent file by fid. */ function testLoadMissingFid() { $this->assertFalse(file_load(-1), t("Try to load an invalid fid fails.")); $this->assertFileHooksCalled(array()); } /** * Try to load a non-existent file by URI. */ function testLoadMissingFilepath() { $files = file_load_multiple(array(), array('uri' => 'foobar://misc/druplicon.png')); $this->assertFalse(reset($files), t("Try to load a file that doesn't exist in the database fails.")); $this->assertFileHooksCalled(array()); } /** * Try to load a non-existent file by status. */ function testLoadInvalidStatus() { $files = file_load_multiple(array(), array('status' => -99)); $this->assertFalse(reset($files), t("Trying to load a file with an invalid status fails.")); $this->assertFileHooksCalled(array()); } /** * Load a single file and ensure that the correct values are returned. */ function testSingleValues() { // Create a new file object from scratch so we know the values. $file = $this->createFile('druplicon.txt', NULL, 'public'); $by_fid_file = file_load($file->fid); $this->assertFileHookCalled('load'); $this->assertTrue(is_object($by_fid_file), t('file_load() returned an object.')); $this->assertEqual($by_fid_file->fid, $file->fid, t("Loading by fid got the same fid."), 'File'); $this->assertEqual($by_fid_file->uri, $file->uri, t("Loading by fid got the correct filepath."), 'File'); $this->assertEqual($by_fid_file->filename, $file->filename, t("Loading by fid got the correct filename."), 'File'); $this->assertEqual($by_fid_file->filemime, $file->filemime, t("Loading by fid got the correct MIME type."), 'File'); $this->assertEqual($by_fid_file->status, $file->status, t("Loading by fid got the correct status."), 'File'); $this->assertTrue($by_fid_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.')); } /** * This will test loading file data from the database. */ function testMultiple() { // Create a new file object. $file = $this->createFile('druplicon.txt', NULL, 'public'); // Load by path. file_test_reset(); $by_path_files = file_load_multiple(array(), array('uri' => $file->uri)); $this->assertFileHookCalled('load'); $this->assertEqual(1, count($by_path_files), t('file_load_multiple() returned an array of the correct size.')); $by_path_file = reset($by_path_files); $this->assertTrue($by_path_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.')); $this->assertEqual($by_path_file->fid, $file->fid, t("Loading by filepath got the correct fid."), 'File'); // Load by fid. file_test_reset(); $by_fid_files = file_load_multiple(array($file->fid), array()); $this->assertFileHookCalled('load'); $this->assertEqual(1, count($by_fid_files), t('file_load_multiple() returned an array of the correct size.')); $by_fid_file = reset($by_fid_files); $this->assertTrue($by_fid_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.')); $this->assertEqual($by_fid_file->uri, $file->uri, t("Loading by fid got the correct filepath."), 'File'); } } /** * Tests the file_save() function. */ class FileSaveTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File saving', 'description' => 'Tests the file_save() function.', 'group' => 'File API', ); } function testFileSave() { // Create a new file object. $file = array( 'uid' => 1, 'filename' => 'druplicon.txt', 'uri' => 'public://druplicon.txt', 'filemime' => 'text/plain', 'timestamp' => 1, 'status' => FILE_STATUS_PERMANENT, ); $file = (object) $file; file_put_contents($file->uri, 'hello world'); // Save it, inserting a new record. $saved_file = file_save($file); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('insert')); $this->assertNotNull($saved_file, t("Saving the file should give us back a file object."), 'File'); $this->assertTrue($saved_file->fid > 0, t("A new file ID is set when saving a new file to the database."), 'File'); $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ); $this->assertNotNull($loaded_file, t("Record exists in the database.")); $this->assertEqual($loaded_file->status, $file->status, t("Status was saved correctly.")); $this->assertEqual($saved_file->filesize, filesize($file->uri), t("File size was set correctly."), 'File'); $this->assertTrue($saved_file->timestamp > 1, t("File size was set correctly."), 'File'); // Resave the file, updating the existing record. file_test_reset(); $saved_file->status = 7; $resaved_file = file_save($saved_file); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('load', 'update')); $this->assertEqual($resaved_file->fid, $saved_file->fid, t("The file ID of an existing file is not changed when updating the database."), 'File'); $this->assertTrue($resaved_file->timestamp >= $saved_file->timestamp, t("Timestamp didn't go backwards."), 'File'); $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ); $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File'); $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly.")); // Try to insert a second file with the same name apart from case insensitivity // to ensure the 'uri' index allows for filenames with different cases. $file = (object) array( 'uid' => 1, 'filename' => 'DRUPLICON.txt', 'uri' => 'public://DRUPLICON.txt', 'filemime' => 'text/plain', 'timestamp' => 1, 'status' => FILE_STATUS_PERMANENT, ); file_put_contents($file->uri, 'hello world'); file_save($file); } } /** * Tests file usage functions. */ class FileUsageTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File usage', 'description' => 'Tests the file usage functions.', 'group' => 'File', ); } /** * Tests file_usage_list(). */ function testGetUsage() { $file = $this->createFile(); db_insert('file_usage') ->fields(array( 'fid' => $file->fid, 'module' => 'testing', 'type' => 'foo', 'id' => 1, 'count' => 1 )) ->execute(); db_insert('file_usage') ->fields(array( 'fid' => $file->fid, 'module' => 'testing', 'type' => 'bar', 'id' => 2, 'count' => 2 )) ->execute(); $usage = file_usage_list($file); $this->assertEqual(count($usage['testing']), 2, t('Returned the correct number of items.')); $this->assertTrue(isset($usage['testing']['foo'][1]), t('Returned the correct id.')); $this->assertTrue(isset($usage['testing']['bar'][2]), t('Returned the correct id.')); $this->assertEqual($usage['testing']['foo'][1], 1, t('Returned the correct count.')); $this->assertEqual($usage['testing']['bar'][2], 2, t('Returned the correct count.')); } /** * Tests file_usage_add(). */ function testAddUsage() { $file = $this->createFile(); file_usage_add($file, 'testing', 'foo', 1); // Add the file twice to ensure that the count is incremented rather than // creating additional records. file_usage_add($file, 'testing', 'bar', 2); file_usage_add($file, 'testing', 'bar', 2); $usage = db_select('file_usage', 'f') ->fields('f') ->condition('f.fid', $file->fid) ->execute() ->fetchAllAssoc('id'); $this->assertEqual(count($usage), 2, t('Created two records')); $this->assertEqual($usage[1]->module, 'testing', t('Correct module')); $this->assertEqual($usage[2]->module, 'testing', t('Correct module')); $this->assertEqual($usage[1]->type, 'foo', t('Correct type')); $this->assertEqual($usage[2]->type, 'bar', t('Correct type')); $this->assertEqual($usage[1]->count, 1, t('Correct count')); $this->assertEqual($usage[2]->count, 2, t('Correct count')); } /** * Tests file_usage_delete(). */ function testRemoveUsage() { $file = $this->createFile(); db_insert('file_usage') ->fields(array( 'fid' => $file->fid, 'module' => 'testing', 'type' => 'bar', 'id' => 2, 'count' => 3, )) ->execute(); // Normal decrement. file_usage_delete($file, 'testing', 'bar', 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) ->execute() ->fetchField(); $this->assertEqual(2, $count, t('The count was decremented correctly.')); // Multiple decrement and removal. file_usage_delete($file, 'testing', 'bar', 2, 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) ->execute() ->fetchField(); $this->assertIdentical(FALSE, $count, t('The count was removed entirely when empty.')); // Non-existent decrement. file_usage_delete($file, 'testing', 'bar', 2); $count = db_select('file_usage', 'f') ->fields('f', array('count')) ->condition('f.fid', $file->fid) ->execute() ->fetchField(); $this->assertIdentical(FALSE, $count, t('Decrementing non-exist record complete.')); } } /** * Tests the file_validate() function.. */ class FileValidateTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File validate', 'description' => 'Tests the file_validate() function.', 'group' => 'File API', ); } /** * Test that the validators passed into are checked. */ function testCallerValidation() { $file = $this->createFile(); // Empty validators. $this->assertEqual(file_validate($file, array()), array(), t('Validating an empty array works successfully.')); $this->assertFileHooksCalled(array('validate')); // Use the file_test.module's test validator to ensure that passing tests // return correctly. file_test_reset(); file_test_set_return('validate', array()); $passing = array('file_test_validator' => array(array())); $this->assertEqual(file_validate($file, $passing), array(), t('Validating passes.')); $this->assertFileHooksCalled(array('validate')); // Now test for failures in validators passed in and by hook_validate. file_test_reset(); file_test_set_return('validate', array('Epic fail')); $failing = array('file_test_validator' => array(array('Failed', 'Badly'))); $this->assertEqual(file_validate($file, $failing), array('Failed', 'Badly', 'Epic fail'), t('Validating returns errors.')); $this->assertFileHooksCalled(array('validate')); } } /** * Tests the file_save_data() function. */ class FileSaveDataTest extends FileHookTestCase { public static function getInfo() { return array( 'name' => 'File save data', 'description' => 'Tests the file save data function.', 'group' => 'File API', ); } /** * Test the file_save_data() function when no filename is provided. */ function testWithoutFilename() { $contents = $this->randomName(8); $result = file_save_data($contents); $this->assertTrue($result, t('Unnamed file saved correctly.')); $this->assertEqual(file_default_scheme(), file_uri_scheme($result->uri), t("File was placed in Drupal's files directory.")); $this->assertEqual($result->filename, drupal_basename($result->uri), t("Filename was set to the file's basename.")); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.')); $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.')); $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent.")); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('insert')); // Verify that what was returned is what's in the database. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); } /** * Test the file_save_data() function when a filename is provided. */ function testWithFilename() { $contents = $this->randomName(8); // Using filename with non-latin characters. $filename = 'Текстовый файл.txt'; $result = file_save_data($contents, 'public://' . $filename); $this->assertTrue($result, t('Unnamed file saved correctly.')); $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory.")); $this->assertEqual($filename, drupal_basename($result->uri), t('File was named correctly.')); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.')); $this->assertEqual($result->filemime, 'text/plain', t('A MIME type was set.')); $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent.")); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('insert')); // Verify that what was returned is what's in the database. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); } /** * Test file_save_data() when renaming around an existing file. */ function testExistingRename() { // Setup a file to overwrite. $existing = $this->createFile(); $contents = $this->randomName(8); $result = file_save_data($contents, $existing->uri, FILE_EXISTS_RENAME); $this->assertTrue($result, t("File saved successfully.")); $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory.")); $this->assertEqual($result->filename, $existing->filename, t("Filename was set to the basename of the source, rather than that of the renamed file.")); $this->assertEqual($contents, file_get_contents($result->uri), t("Contents of the file are correct.")); $this->assertEqual($result->filemime, 'application/octet-stream', t("A MIME type was set.")); $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent.")); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('insert')); // Ensure that the existing file wasn't overwritten. $this->assertDifferentFile($existing, $result); $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE)); // Verify that was returned is what's in the database. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); } /** * Test file_save_data() when replacing an existing file. */ function testExistingReplace() { // Setup a file to overwrite. $existing = $this->createFile(); $contents = $this->randomName(8); $result = file_save_data($contents, $existing->uri, FILE_EXISTS_REPLACE); $this->assertTrue($result, t('File saved successfully.')); $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory.")); $this->assertEqual($result->filename, $existing->filename, t('Filename was set to the basename of the existing file, rather than preserving the original name.')); $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.')); $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.')); $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent.")); // Check that the correct hooks were called. $this->assertFileHooksCalled(array('load', 'update')); // Verify that the existing file was re-used. $this->assertSameFile($existing, $result); // Verify that what was returned is what's in the database. $this->assertFileUnchanged($result, file_load($result->fid, TRUE)); } /** * Test that file_save_data() fails overwriting an existing file. */ function testExistingError() { $contents = $this->randomName(8); $existing = $this->createFile(NULL, $contents); // Check the overwrite error. $result = file_save_data('asdf', $existing->uri, FILE_EXISTS_ERROR); $this->assertFalse($result, t('Overwriting a file fails when FILE_EXISTS_ERROR is specified.')); $this->assertEqual($contents, file_get_contents($existing->uri), t('Contents of existing file were unchanged.')); // Check that no hooks were called while failing. $this->assertFileHooksCalled(array()); // Ensure that the existing file wasn't overwritten. $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE)); } } /** * Tests for download/file transfer functions. */ class FileDownloadTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File download', 'description' => 'Tests for file download/transfer functions.', 'group' => 'File API', ); } function setUp() { parent::setUp('file_test'); // Clear out any hook calls. file_test_reset(); } /** * Test the public file transfer system. */ function testPublicFileTransfer() { // Test generating an URL to a created file. $file = $this->createFile(); $url = file_create_url($file->uri); // URLs can't contain characters outside the ASCII set so $filename has to be // encoded. $filename = $GLOBALS['base_url'] . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . rawurlencode($file->filename); $this->assertEqual($filename, $url, t('Correctly generated a URL for a created file.')); $this->drupalHead($url); $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the created file.')); // Test generating an URL to a shipped file (i.e. a file that is part of // Drupal core, a module or a theme, for example a JavaScript file). $filepath = 'misc/jquery.js'; $url = file_create_url($filepath); $this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.')); $this->drupalHead($url); $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the shipped file.')); } /** * Test the private file transfer system. */ function testPrivateFileTransfer() { // Set file downloads to private so handler functions get called. // Create a file. $contents = $this->randomName(8); $file = $this->createFile(NULL, $contents, 'private'); $url = file_create_url($file->uri); // Set file_test access header to allow the download. file_test_set_return('download', array('x-foo' => 'Bar')); $this->drupalGet($url); $headers = $this->drupalGetHeaders(); $this->assertEqual($headers['x-foo'], 'Bar', t('Found header set by file_test module on private download.')); $this->assertResponse(200, t('Correctly allowed access to a file when file_test provides headers.')); // Test that the file transfered correctly. $this->assertEqual($contents, $this->content, t('Contents of the file are correct.')); // Deny access to all downloads via a -1 header. file_test_set_return('download', -1); $this->drupalHead($url); $this->assertResponse(403, t('Correctly denied access to a file when file_test sets the header to -1.')); // Try non-existent file. $url = file_create_url('private://' . $this->randomName()); $this->drupalHead($url); $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.')); } /** * Test file_create_url(). */ function testFileCreateUrl() { global $base_url; // Tilde (~) is excluded from this test because it is encoded by // rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986. // @see http://www.php.net/manual/en/function.rawurlencode.php#86506 $basename = " -._!$'\"()*@[]?&+%#,;=:\n\x00" . // "Special" ASCII characters. "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string. "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets. $basename_encoded = '%20-._%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' . '%2523%2525%2526%252B%252F%253F' . '%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E'; $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded); $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0'); } /** * Download a file from the URL generated by file_create_url(). * * Create a file with the specified scheme, directory and filename; check that * the URL generated by file_create_url() for the specified file equals the * specified URL; fetch the URL and then compare the contents to the file. * * @param $scheme * A scheme, e.g. "public" * @param $directory * A directory, possibly "" * @param $filename * A filename * @param $expected_url * The expected URL * @param $clean_url * The value of the clean_url setting */ private function checkUrl($scheme, $directory, $filename, $expected_url, $clean_url = '1') { variable_set('clean_url', $clean_url); // Convert $filename to a valid filename, i.e. strip characters not // supported by the filesystem, and create the file in the specified // directory. $filepath = file_create_filename($filename, $directory); $directory_uri = $scheme . '://' . dirname($filepath); file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY); $file = $this->createFile($filepath, NULL, $scheme); $url = file_create_url($file->uri); $this->assertEqual($url, $expected_url, t('Generated URL matches expected URL.')); if ($scheme == 'private') { // Tell the implementation of hook_file_download() in file_test.module // that this file may be downloaded. file_test_set_return('download', array('x-foo' => 'Bar')); } $this->drupalGet($url); if ($this->assertResponse(200) == 'pass') { $this->assertRaw(file_get_contents($file->uri), t('Contents of the file are correct.')); } file_delete($file); } } /** * Tests for file URL rewriting. */ class FileURLRewritingTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File URL rewriting', 'description' => 'Tests for file URL rewriting.', 'group' => 'File', ); } function setUp() { parent::setUp('file_test'); } /** * Test the generating of rewritten shipped file URLs. */ function testShippedFileURL() { // Test generating an URL to a shipped file (i.e. a file that is part of // Drupal core, a module or a theme, for example a JavaScript file). // Test alteration of file URLs to use a CDN. variable_set('file_test_hook_file_url_alter', 'cdn'); $filepath = 'misc/jquery.js'; $url = file_create_url($filepath); $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.')); $filepath = 'misc/favicon.ico'; $url = file_create_url($filepath); $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.')); // Test alteration of file URLs to use root-relative URLs. variable_set('file_test_hook_file_url_alter', 'root-relative'); $filepath = 'misc/jquery.js'; $url = file_create_url($filepath); $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.')); $filepath = 'misc/favicon.ico'; $url = file_create_url($filepath); $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.')); // Test alteration of file URLs to use protocol-relative URLs. variable_set('file_test_hook_file_url_alter', 'protocol-relative'); $filepath = 'misc/jquery.js'; $url = file_create_url($filepath); $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.')); $filepath = 'misc/favicon.ico'; $url = file_create_url($filepath); $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.')); } /** * Test the generating of rewritten public created file URLs. */ function testPublicCreatedFileURL() { // Test generating an URL to a created file. // Test alteration of file URLs to use a CDN. variable_set('file_test_hook_file_url_alter', 'cdn'); $file = $this->createFile(); $url = file_create_url($file->uri); $public_directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath(); $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a CDN URL for a created file.')); // Test alteration of file URLs to use root-relative URLs. variable_set('file_test_hook_file_url_alter', 'root-relative'); $file = $this->createFile(); $url = file_create_url($file->uri); $this->assertEqual(base_path() . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a root-relative URL for a created file.')); // Test alteration of file URLs to use a protocol-relative URLs. variable_set('file_test_hook_file_url_alter', 'protocol-relative'); $file = $this->createFile(); $url = file_create_url($file->uri); $this->assertEqual('/' . base_path() . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a protocol-relative URL for a created file.')); } } /** * Tests for file_munge_filename() and file_unmunge_filename(). */ class FileNameMungingTest extends FileTestCase { public static function getInfo() { return array( 'name' => 'File naming', 'description' => 'Test filename munging and unmunging.', 'group' => 'File API', ); } function setUp() { parent::setUp(); $this->bad_extension = 'php'; $this->name = $this->randomName() . '.' . $this->bad_extension . '.txt'; } /** * Create a file and munge/unmunge the name. */ function testMunging() { // Disable insecure uploads. variable_set('allow_insecure_uploads', 0); $munged_name = file_munge_filename($this->name, '', TRUE); $messages = drupal_get_messages(); $this->assertTrue(in_array(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $munged_name)), $messages['status']), t('Alert properly set when a file is renamed.')); $this->assertNotEqual($munged_name, $this->name, t('The new filename (%munged) has been modified from the original (%original)', array('%munged' => $munged_name, '%original' => $this->name))); } /** * Tests munging with a null byte in the filename. */ function testMungeNullByte() { $prefix = $this->randomName(); $filename = $prefix . '.' . $this->bad_extension . "\0.txt"; $this->assertEqual(file_munge_filename($filename, ''), $prefix . '.' . $this->bad_extension . '_.txt', 'A filename with a null byte is correctly munged to remove the null byte.'); } /** * If the allow_insecure_uploads variable evaluates to true, the file should * come out untouched, no matter how evil the filename. */ function testMungeIgnoreInsecure() { variable_set('allow_insecure_uploads', 1); $munged_name = file_munge_filename($this->name, ''); $this->assertIdentical($munged_name, $this->name, t('The original filename (%original) matches the munged filename (%munged) when insecure uploads are enabled.', array('%munged' => $munged_name, '%original' => $this->name))); } /** * White listed extensions are ignored by file_munge_filename(). */ function testMungeIgnoreWhitelisted() { // Declare our extension as whitelisted. $munged_name = file_munge_filename($this->name, $this->bad_extension); $this->assertIdentical($munged_name, $this->name, t('The new filename (%munged) matches the original (%original) once the extension has been whitelisted.', array('%munged' => $munged_name, '%original' => $this->name))); } /** * Ensure that unmunge gets your name back. */ function testUnMunge() { $munged_name = file_munge_filename($this->name, '', FALSE); $unmunged_name = file_unmunge_filename($munged_name); $this->assertIdentical($unmunged_name, $this->name, t('The unmunged (%unmunged) filename matches the original (%original)', array('%unmunged' => $unmunged_name, '%original' => $this->name))); } } /** * Tests for file_get_mimetype(). */ class FileMimeTypeTest extends DrupalWebTestCase { function setUp() { parent::setUp('file_test'); } public static function getInfo() { return array( 'name' => 'File mimetypes', 'description' => 'Test filename mimetype detection.', 'group' => 'File API', ); } /** * Test mapping of mimetypes from filenames. */ public function testFileMimeTypeDetection() { $prefix = 'public://'; $test_case = array( 'test.jar' => 'application/java-archive', 'test.jpeg' => 'image/jpeg', 'test.JPEG' => 'image/jpeg', 'test.jpg' => 'image/jpeg', 'test.jar.jpg' => 'image/jpeg', 'test.jpg.jar' => 'application/java-archive', 'test.pcf.Z' => 'application/x-font', 'pcf.z' => 'application/octet-stream', 'jar' => 'application/octet-stream', 'some.junk' => 'application/octet-stream', 'foo.file_test_1' => 'madeup/file_test_1', 'foo.file_test_2' => 'madeup/file_test_2', 'foo.doc' => 'madeup/doc', 'test.ogg' => 'audio/ogg', ); // Test using default mappings. foreach ($test_case as $input => $expected) { // Test stream [URI]. $output = file_get_mimetype($prefix . $input); $this->assertIdentical($output, $expected, t('Mimetype for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); // Test normal path equivalent $output = file_get_mimetype($input); $this->assertIdentical($output, $expected, t('Mimetype (using default mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); } // Now test passing in the map. $mapping = array( 'mimetypes' => array( 0 => 'application/java-archive', 1 => 'image/jpeg', ), 'extensions' => array( 'jar' => 0, 'jpg' => 1, ) ); $test_case = array( 'test.jar' => 'application/java-archive', 'test.jpeg' => 'application/octet-stream', 'test.jpg' => 'image/jpeg', 'test.jar.jpg' => 'image/jpeg', 'test.jpg.jar' => 'application/java-archive', 'test.pcf.z' => 'application/octet-stream', 'pcf.z' => 'application/octet-stream', 'jar' => 'application/octet-stream', 'some.junk' => 'application/octet-stream', 'foo.file_test_1' => 'application/octet-stream', 'foo.file_test_2' => 'application/octet-stream', 'foo.doc' => 'application/octet-stream', 'test.ogg' => 'application/octet-stream', ); foreach ($test_case as $input => $expected) { $output = file_get_mimetype($input, $mapping); $this->assertIdentical($output, $expected, t('Mimetype (using passed-in mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected))); } } } /** * Tests stream wrapper functions. */ class StreamWrapperTest extends DrupalWebTestCase { protected $scheme = 'dummy'; protected $classname = 'DrupalDummyStreamWrapper'; public static function getInfo() { return array( 'name' => 'Stream wrappers', 'description' => 'Tests stream wrapper functions.', 'group' => 'File API', ); } function setUp() { parent::setUp('file_test'); drupal_static_reset('file_get_stream_wrappers'); } function tearDown() { parent::tearDown(); stream_wrapper_unregister($this->scheme); } /** * Test the getClassName() function. */ function testGetClassName() { // Check the dummy scheme. $this->assertEqual($this->classname, file_stream_wrapper_get_class($this->scheme), t('Got correct class name for dummy scheme.')); // Check core's scheme. $this->assertEqual('DrupalPublicStreamWrapper', file_stream_wrapper_get_class('public'), t('Got correct class name for public scheme.')); } /** * Test the file_stream_wrapper_get_instance_by_scheme() function. */ function testGetInstanceByScheme() { $instance = file_stream_wrapper_get_instance_by_scheme($this->scheme); $this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy scheme.')); $instance = file_stream_wrapper_get_instance_by_scheme('public'); $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public scheme.')); } /** * Test the URI and target functions. */ function testUriFunctions() { $instance = file_stream_wrapper_get_instance_by_uri($this->scheme . '://foo'); $this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy URI.')); $instance = file_stream_wrapper_get_instance_by_uri('public://foo'); $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public URI.')); // Test file_uri_target(). $this->assertEqual(file_uri_target('public://foo/bar.txt'), 'foo/bar.txt', t('Got a valid stream target from public://foo/bar.txt.')); $this->assertFalse(file_uri_target('foo/bar.txt'), t('foo/bar.txt is not a valid stream.')); // Test file_build_uri() and DrupalLocalStreamWrapper::getDirectoryPath(). $this->assertEqual(file_build_uri('foo/bar.txt'), 'public://foo/bar.txt', t('Expected scheme was added.')); $this->assertEqual(file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath(), variable_get('file_public_path'), t('Expected default directory path was returned.')); $this->assertEqual(file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath(), variable_get('file_temporary_path'), t('Expected temporary directory path was returned.')); variable_set('file_default_scheme', 'private'); $this->assertEqual(file_build_uri('foo/bar.txt'), 'private://foo/bar.txt', t('Got a valid URI from foo/bar.txt.')); } /** * Test the scheme functions. */ function testGetValidStreamScheme() { $this->assertEqual('foo', file_uri_scheme('foo://pork//chops'), t('Got the correct scheme from foo://asdf')); $this->assertTrue(file_stream_wrapper_valid_scheme(file_uri_scheme('public://asdf')), t('Got a valid stream scheme from public://asdf')); $this->assertFalse(file_stream_wrapper_valid_scheme(file_uri_scheme('foo://asdf')), t('Did not get a valid stream scheme from foo://asdf')); } }