? files ? themes ? sites/default/files ? sites/default/settings.php Index: includes/file.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/file.inc,v retrieving revision 1.155 diff -u -p -r1.155 file.inc --- includes/file.inc 22 Jan 2009 12:46:05 -0000 1.155 +++ includes/file.inc 30 Jan 2009 00:17:34 -0000 @@ -272,7 +272,7 @@ function file_check_location($source, $d * @see hook_file_load() * @see file_load() */ -function file_load_multiple($fids = array(), $conditions = array()) { +function file_load_multiple($fids = array(), $conditions = array()) { $query = db_select('files', 'f')->fields('f'); // If the $fids array is populated, add those to the query. @@ -282,15 +282,25 @@ function file_load_multiple($fids = arra // If the conditions array is populated, add those to the query. if ($conditions) { - foreach ($conditions as $field => $value) { + foreach ($conditions as $field => $value) { + // if the condition is a filepath, it may contain the path to the file_directory_path, so we need to remove this to allow the fid to be found + if ($field == 'filepath'){ + $value = (strpos($value, file_directory_path()) === 0) ? substr($value, strlen(file_directory_path()) + 1) : $value; + } $query->condition('f.' . $field, $value); } } - $files = $query->execute()->fetchAllAssoc('fid'); - + $files = $query->execute()->fetchAllAssoc('fid'); + // Invoke hook_file_load() on the terms loaded from the database // and add them to the static cache. if (!empty($files)) { + // prepend file_directory_path onto filepath + foreach ($files as $file){ + // add file_directory_path back in for the file_load hook, and return value + $file->filepath = file_directory_path() . '/' . $file->filepath; + } + foreach (module_implements('file_load') as $module) { $function = $module . '_file_load'; $function($files); @@ -332,9 +342,14 @@ function file_load($fid) { function file_save($file) { $file = (object)$file; $file->timestamp = REQUEST_TIME; - $file->filesize = filesize($file->filepath); - - if (empty($file->fid)) { + + $file->filesize = filesize($file->filepath); + + // remove file directory path for entering into the database + $old_filepath = $file->filepath; + $file->filepath = substr($file->filepath, strlen(file_directory_path()) + 1); + + if (empty($file->fid)) { drupal_write_record('files', $file); // Inform modules about the newly added file. module_invoke_all('file_insert', $file); @@ -344,7 +359,11 @@ function file_save($file) { // Inform modules that the file has been updated. module_invoke_all('file_update', $file); } - + + // and restore the original filepath dir for returning the file object + //should file_save just keep the filepath relevant to the files directory, and code outside of file.inc add the file_directory_path as necessary? + $file->filepath = $old_filepath; + return $file; } @@ -368,7 +387,7 @@ function file_save($file) { * @param $destination * A string containing the destination that $source should be copied to. This * can be a complete file path, a directory path or, if this value is omitted, - * Drupal's 'files' directory will be used. + * Drupal's 'files' directory will be used. N.B. it must be relative to the files directory * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE - Replace the existing file. If a managed file with @@ -384,7 +403,10 @@ function file_save($file) { * @see hook_file_copy() */ function file_copy($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { - $source = (object)$source; + $source = (object)$source; + + // ensure that the destination is inside the files dir + $destination = file_create_path($destination); if ($filepath = file_unmanaged_copy($source->filepath, $destination, $replace)) { $file = clone $source; @@ -410,7 +432,6 @@ function file_copy($source, $destination // Inform modules that the file has been copied. module_invoke_all('file_copy', $file, $source); - return $file; } return FALSE; @@ -452,7 +473,7 @@ function file_unmanaged_copy($source, $d return FALSE; } - $destination = file_create_path($destination); + $destination = file_create_path($destination); $directory = $destination; $basename = file_check_path($directory); @@ -565,7 +586,7 @@ function file_destination($destination, function file_move($source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { $source = (object)$source; - if ($filepath = file_unmanaged_move($source->filepath, $destination, $replace)) { + if ($filepath = file_unmanaged_move($source->filepath, $destination, $replace)) { $delete_source = FALSE; $file = clone $source; Index: modules/simpletest/tests/file.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/file.test,v retrieving revision 1.21 diff -u -p -r1.21 file.test --- modules/simpletest/tests/file.test 20 Jan 2009 02:56:05 -0000 1.21 +++ modules/simpletest/tests/file.test 30 Jan 2009 00:17:37 -0000 @@ -1,1775 +1,1792 @@ -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->filepath, $after->filepath, t('File path is the same: %file1 == %file2.', array('%file1' => $before->filepath, '%file2' => $after->filepath)), '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->filepath, $file2->filepath, t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->filepath, '%file2' => $file2->filepath)), '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->filepath, $file2->filepath, t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->filepath, '%file2' => $file2->filepath)), '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) { - // Mask out all but the last three octets. - $actual_mode = fileperms($filepath) & 511; - if (is_null($message)) { - if ($actual_mode == $expected_mode) { - $message = t('File permissions set correctly.'); - } - else { - $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); - } - - /** - * 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 (is_null($path)) { - $path = file_directory_path() . '/' . $this->randomName(); - } - $this->assertTrue(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. - * @return - * File object. - */ - function createFile($filepath = NULL, $contents = NULL) { - if (is_null($filepath)) { - $filepath = file_directory_path() . '/' . $this->randomName(); - } - - if (is_null($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->filepath = $filepath; - $file->filename = basename($file->filepath); - $file->filemime = 'text/plain'; - $file->uid = 1; - $file->timestamp = REQUEST_TIME; - $file->filesize = filesize($file->filepath); - $file->status = 0; - // Write the record directly rather than calling file_save() so we don't - // invoke the hooks. - $this->assertNotIdentical(drupal_write_record('files', $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' => 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' => 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 (is_null($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 { - function getInfo() { - return array( - 'name' => t('File space used tests'), - 'description' => t('Tests the file_space_used() function.'), - 'group' => t('File'), - ); - } - - function setUp() { - parent::setUp(); - - // Create records for a couple of users with different sizes. - drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 50, 'status' => FILE_STATUS_PERMANENT)); - drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 20, 'status' => FILE_STATUS_PERMANENT)); - drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 100, 'status' => FILE_STATUS_PERMANENT)); - drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 200, 'status' => FILE_STATUS_PERMANENT)); - - // Now create some with other statuses. These values were chosen arbitrarily - // for the sole purpose of testing that bitwise operators were used - // correctly on the field. - drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 1, 'status' => 2 | 8)); - drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 3, 'status' => 2 | 4)); - } - - /** - * Test different users with the default status. - */ - function testUser() { - $this->assertEqual(file_space_used(2), 70, t("Found the size of the first user's files.")); - $this->assertEqual(file_space_used(3), 300, t("Found the size of the second user's files.")); - $this->assertEqual(file_space_used(), 370, t("Found the size of all user's files.")); - } - - /** - * Test the status fields - */ - function testStatus() { - // Check selection with a single bit set. - $this->assertEqual(file_space_used(NULL, 2), 4, t("Found the size of all user's files with status 2.")); - $this->assertEqual(file_space_used(NULL, 4), 3, t("Found the size of all user's files with status 4.")); - // Check that the bitwise AND operator is used when selecting so that we - // only get files with the 2 AND 4 bits set. - $this->assertEqual(file_space_used(NULL, 2 | 4), 3, t("Found the size of all user's files with status 6.")); - } - - /** - * Test both the user and status. - */ - function testUserAndStatus() { - $this->assertEqual(file_space_used(1, 8), 0, t("Found the size of the admin user's files with status 8.")); - $this->assertEqual(file_space_used(2, 8), 1, t("Found the size of the first user's files with status 8.")); - $this->assertEqual(file_space_used(2, 2), 1, t("Found the size of the first user's files with status 2.")); - $this->assertEqual(file_space_used(3, 2), 3, t("Found the size of the second user's files with status 2.")); - } -} - -/** - * This will run tests against the file validation functions (file_validate_*). - */ -class FileValidatorTest extends DrupalWebTestCase { - function getInfo() { - return array( - 'name' => t('File validator tests'), - 'description' => t('Tests the functions used to validate uploaded files.'), - 'group' => t('File'), - ); - } - - function setUp() { - parent::setUp(); - - $this->image = new stdClass(); - $this->image->filepath = 'misc/druplicon.png'; - $this->image->filename = basename($this->image->filepath); - - $this->non_image = new stdClass(); - $this->non_image->filepath = 'misc/jquery.js'; - $this->non_image->filename = basename($this->non_image->filepath); - } - - /** - * 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->filepath), 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->filepath), 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. - $temp_dir = file_directory_temp(); - copy(realpath('misc/druplicon.png'), realpath($temp_dir) . '/druplicon.png'); - $this->image->filepath = $temp_dir . '/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->filepath); - $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File'); - $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File'); - - unlink(realpath($temp_dir . '/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', 255); - $this->assertEqual(strlen($file->filename), 255); - $errors = file_validate_name_length($file); - $this->assertEqual(count($errors), 0, t('No errors reported for 255 length filename.'), 'File'); - - // Add a filename with a length too long and test it. - $file->filename = str_repeat('x', 256); - $errors = file_validate_name_length($file); - $this->assertEqual(count($errors), 1, t('An error reported for 256 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(array('uid' => 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 { - function getInfo() { - return array( - 'name' => t('Unmanaged file save data'), - 'description' => t('Tests the unmanaged file save data function.'), - 'group' => t('File'), - ); - } - - /** - * 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_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); - $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); - - // Provide a filename. - $filepath = file_unmanaged_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE); - $this->assertTrue($filepath, t('Unnamed file saved correctly.')); - $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); - $this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.')); - $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); - $this->assertFilePermissions($filepath, 0664); - } -} - -/** - * Test the file_save_upload() function. - */ -class FileSaveUploadTest extends FileHookTestCase { - /** - * An image file path for uploading. - */ - var $image; - - /** - * The largest file id when the test starts. - */ - var $maxFidBefore; - - function getInfo() { - return array( - 'name' => t('File uploading'), - 'description' => t('Tests the file uploading functions.'), - 'group' => t('File'), - ); - } - - function setUp() { - parent::setUp(); - $account = $this->drupalCreateUser(array('access content')); - $this->drupalLogin($account); - - $this->image = current($this->drupalGetTestFiles('image')); - $this->assertTrue(is_file($this->image->filename), t("The file we're going to upload exists.")); - - $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField(); - - // Upload with replace to gurantee there's something there. - $edit = array( - 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => realpath($this->image->filename) - ); - $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_result(db_query('SELECT MAX(fid) AS fid FROM {files}')); - $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.')); - - // 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 {files}')->fetchField(); - $image2 = current($this->drupalGetTestFiles('image')); - $edit = array('files[file_test_upload]' => realpath($image2->filename)); - $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 {files}')->fetchField(); - - // Check that the correct hooks were called. - $this->assertFileHooksCalled(array('validate', 'insert')); - - $file2 = file_load($max_fid_after); - $this->assertTrue($file2); - - // 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')); - } - - - /** - * Test renaming when uploading over a file that already exists. - */ - function testExistingRename() { - $edit = array( - 'file_test_replace' => FILE_EXISTS_RENAME, - 'files[file_test_upload]' => realpath($this->image->filename) - ); - $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]' => realpath($this->image->filename) - ); - $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]' => realpath($this->image->filename) - ); - $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.')); - } -} - -/** - * Directory related tests. - */ -class FileDirectoryTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('File paths and directories'), - 'description' => t('Tests operations dealing with directories.'), - 'group' => t('File'), - ); - } - - /** - * Test the file_directory_path() function. - */ - function testFileCheckDirectory() { - // A directory to operate on. - $directory = file_directory_path() . '/' . $this->randomName(); - $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.')); - - // Non-existent directory. - $form_element = $this->randomName(); - $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for non-existing directory.'), 'File'); - - // Check that an error was set for the form element above. - $errors = form_get_errors(); - $this->assertEqual($errors[$form_element], t('The directory %directory does not exist.', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File'); - - // Make a directory. - $this->assertTrue(file_check_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'); - - // Make directory read only. - @chmod($directory, 0444); - $form_element = $this->randomName(); - $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for a non-writeable directory.'), 'File'); - - // Check if form error was set. - $errors = form_get_errors(); - $this->assertEqual($errors[$form_element], t('The directory %directory is not writable', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File'); - - // Test directory permission modification. - $this->assertTrue(file_check_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File'); - - // Verify directory actually is writeable. - $this->assertTrue(is_writeable($directory), t('Directory is writeable.'), 'File'); - - // Remove .htaccess file to then test that it gets re-created. - @unlink(file_directory_path() .'/.htaccess'); - $directory = file_directory_path(); - file_check_directory($directory); - $this->assertTrue(is_file(file_directory_path() . '/.htaccess'), t('Successfully created the .htaccess file in the files directory.'), 'File'); - - // Verify contents of .htaccess file. - $file = file_get_contents(file_directory_path() .'/.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'); - } - - /** - * Check file_directory_path() and file_directory_temp(). - */ - function testFileDirectoryPath() { - // Directory path. - $path = variable_get('file_directory_path', conf_path() . '/files'); - $this->assertEqual($path, file_directory_path(), t('Properly returns the stored file directory path.'), 'File'); - } - - /** - * Check file_directory_path() and file_directory_temp(). - */ - function testFileDirectoryTemp() { - // Temporary directory handling. - variable_set('file_directory_temp', NULL); - $temp = file_directory_temp(); - $this->assertTrue(!is_null($temp), t('Properly set and retrieved temp directory %directory.', array('%directory' => $temp)), 'File'); - } - - /** - * This tests that a file is actually in the specified directory, to prevent - * exploits. - */ - function testFileCheckLocation() { - $source = 'misc/xyz.txt'; - $directory = 'misc'; - $result = file_check_location($source, $directory); - $this->assertTrue($result, t('Non-existent file validates when checked for location in existing directory.'), 'File'); - - $source = 'fake/xyz.txt'; - $directory = 'fake'; - $result = file_check_location($source, $directory); - $this->assertTrue($result, t('Non-existent file validates when checked for location in non-existing directory.'), 'File'); - - $source = 'misc/../install.php'; - $directory = 'misc'; - $result = file_check_location($source, $directory); - $this->assertFalse($result, t('Existing file fails validation when it exists outside the directory path, using a /../ exploit.'), 'File'); - - $source = 'misc/druplicon.png'; - $directory = 'misc'; - $result = file_check_location($source, $directory); - $this->assertTrue($result, t('Existing file passes validation when checked for location in directory path, and filepath contains a subfolder of the checked path.'), 'File'); - - $result = file_check_location($source, $directory); - $this->assertTrue($result, t('Existing file passes validation, returning the source when checked for location in directory.'), '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'); - } -} - - -/** - * Tests the file_scan_directory() function. - */ -class FileScanDirectoryTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('File scan directory'), - 'description' => t('Tests the file_scan_directory() function.'), - 'group' => t('File'), - ); - } - - /** - * Check that the no-mask parameter is honored. - */ - function testNoMask() { - $path = $this->originalFileDirectory . '/simpletest'; - - // Grab a listing of all the JS files. - $all_files = file_scan_directory($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($path, '/javascript*/', '/.script$/'); - $this->assertEqual(1, count($filtered_files), t('Filtered correctly.')); - } -} - - -/** - * Deletion related tests. - */ -class FileUnmanagedDeleteTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('Unmanaged file delete'), - 'description' => t('Tests the unmanaged file delete function.'), - 'group' => t('File'), - ); - } - - /** - * Delete a normal file. - */ - function testNormal() { - // Create a file for testing - $file = $this->createFile(); - - // Delete a regular file - $this->assertTrue(file_unmanaged_delete($file->filepath), t('Deleted worked.')); - $this->assertFalse(file_exists($file->filepath), 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_directory_path() . '/' . $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.')); - } -} - - -/** - * Unmanaged move related tests. - */ -class FileUnmanagedMoveTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('Unmanaged file moving'), - 'description' => t('Tests the unmanaged file move function.'), - 'group' => t('File'), - ); - } - - /** - * Move a normal file. - */ - function testNormal() { - // Create a file for testing - $file = $this->createFile(); - - // Moving to a new name. - $desired_filepath = file_directory_path() . '/' . $this->randomName(); - $new_filepath = file_unmanaged_move($file->filepath, $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->filepath), t('No file remains at the old location.')); - $this->assertFilePermissions($new_filepath, 0664); - - // Moving with rename. - $desired_filepath = file_directory_path() . '/' . $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, 0664); - - // TODO: test moving to a directory (rather than full directory/file path) - } - - /** - * 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->filepath, $file->filepath, FILE_EXISTS_REPLACE); - $this->assertFalse($new_filepath, t('Moving onto itself without renaming fails.')); - $this->assertTrue(file_exists($file->filepath), 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->filepath, $file->filepath, FILE_EXISTS_RENAME); - $this->assertTrue($new_filepath, t('Moving onto itself with renaming works.')); - $this->assertFalse(file_exists($file->filepath), t('Original file has been removed.')); - $this->assertTrue(file_exists($new_filepath), t('File exists after moving onto itself.')); - } -} - - -/** - * Unmanaged copy related tests. - */ -class FileUnmanagedCopyTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('Unmanaged file copying'), - 'description' => t('Tests the unmanaged file copy function.'), - 'group' => t('File'), - ); - } - - /** - * Copy a normal file. - */ - function testNormal() { - // Create a file for testing - $file = $this->createFile(); - - // Copying to a new name. - $desired_filepath = file_directory_path() . '/' . $this->randomName(); - $new_filepath = file_unmanaged_copy($file->filepath, $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->filepath), t('Original file remains.')); - $this->assertTrue(file_exists($new_filepath), t('New file exists.')); - $this->assertFilePermissions($new_filepath, 0664); - - // Copying with rename. - $desired_filepath = file_directory_path() . '/' . $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($new_filepath, $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->filepath), t('Original file remains.')); - $this->assertTrue(file_exists($new_filepath), t('New file exists.')); - $this->assertFilePermissions($new_filepath, 0664); - - // TODO: test copying to a directory (rather than full directory/file path) - } - - /** - * 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->filepath, $file->filepath, FILE_EXISTS_RENAME); - $this->assertTrue($new_filepath, t('Copying onto itself with renaming works.')); - $this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.')); - $this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.')); - $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); - - // Copy the file onto itself without renaming fails. - $new_filepath = file_unmanaged_copy($file->filepath, $file->filepath, FILE_EXISTS_ERROR); - $this->assertFalse($new_filepath, t('Copying onto itself without renaming fails.')); - $this->assertTrue(file_exists($file->filepath), t('File exists after copying onto itself.')); - - // Copy the file into same directory without renaming fails. - $new_filepath = file_unmanaged_copy($file->filepath, dirname($file->filepath), FILE_EXISTS_ERROR); - $this->assertFalse($new_filepath, t('Copying onto itself fails.')); - $this->assertTrue(file_exists($file->filepath), t('File exists after copying onto itself.')); - - // Copy the file into same directory with renaming works. - $new_filepath = file_unmanaged_copy($file->filepath, dirname($file->filepath), FILE_EXISTS_RENAME); - $this->assertTrue($new_filepath, t('Copying into same directory works.')); - $this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.')); - $this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.')); - $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); - } -} - - - -/** - * Deletion related tests. - */ -class FileDeleteTest extends FileHookTestCase { - function getInfo() { - return array( - 'name' => t('File delete'), - 'description' => t('Tests the file delete function.'), - 'group' => t('File'), - ); - } - - /** - * Try deleting a normal file (as opposed to a directory, symlink, etc). - */ - function testNormal() { - $file = $this->createFile(); - - // Check that deletion removes the file and database record. - $this->assertTrue(is_file($file->filepath), t("File exists.")); - $this->assertIdentical(file_delete($file), TRUE, t("Delete worked.")); - $this->assertFileHooksCalled(array('references', 'delete')); - $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted.")); - $this->assertFalse(file_load($file->fid), t('File was removed from the database.')); - - // TODO: implement hook_file_references() in file_test.module and report a - // file in use and test the $force parameter. - } -} - - -/** - * Move related tests - */ -class FileMoveTest extends FileHookTestCase { - function getInfo() { - return array( - 'name' => t('File moving'), - 'description' => t('Tests the file move function.'), - 'group' => t('File'), - ); - } - - /** - * Move a normal file. - */ - function testNormal() { - $contents = $this->randomName(10); - $source = $this->createFile(NULL, $contents); - $desired_filepath = file_directory_path() . '/' . $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 sucessfully.')); - $this->assertFalse(file_exists($source->filepath)); - $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.')); - - // Check that the correct hooks were called. - $this->assertFileHooksCalled(array('move', '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->filepath, FILE_EXISTS_RENAME); - - // Check the return status and that the contents changed. - $this->assertTrue($result, t('File moved sucessfully.')); - $this->assertFalse(file_exists($source->filepath)); - $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.')); - - // Check that the correct hooks were called. - $this->assertFileHooksCalled(array('move', '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->filepath, $source->filepath, 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->filepath, FILE_EXISTS_REPLACE); - - // Look at the results. - $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were overwritten.')); - $this->assertFalse(file_exists($source->filepath)); - $this->assertTrue($result, t('File moved sucessfully.')); - - // Check that the correct hooks were called. - $this->assertFileHooksCalled(array('move', 'update', 'delete', 'references', '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->filepath, FILE_EXISTS_REPLACE); - $this->assertFalse($result, t('File move failed.')); - $this->assertEqual($contents, file_get_contents($source->filepath), 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->filepath, 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->filepath)); - $this->assertEqual($contents, file_get_contents($target->filepath), 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 { - function getInfo() { - return array( - 'name' => t('File copying'), - 'description' => t('Tests the file copy function.'), - 'group' => t('File'), - ); - } - - /** - * Test file copying in the normal, base case. - */ - function testNormal() { - $contents = $this->randomName(10); - $source = $this->createFile(NULL, $contents); - $desired_filepath = file_directory_path() . '/' . $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_filepath, FILE_EXISTS_ERROR); - - // Check the return status and that the contents changed. - $this->assertTrue($result, t('File copied sucessfully.')); - $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, $desired_filepath, t('The copied file object has the desired filepath.')); - $this->assertTrue(file_exists($source->filepath), t('The original file still exists.')); - $this->assertTrue(file_exists($result->filepath), 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->filepath, FILE_EXISTS_RENAME); - - // Check the return status and that the contents changed. - $this->assertTrue($result, t('File copied sucessfully.')); - $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were copied correctly.')); - $this->assertNotEqual($result->filepath, $source->filepath, 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->filepath, FILE_EXISTS_REPLACE); - - // Check the return status and that the contents changed. - $this->assertTrue($result, t('File copied sucessfully.')); - $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, 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->filepath), 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 { - function getInfo() { - return array( - 'name' => t('File loading'), - 'description' => t('Tests the file_load() function.'), - 'group' => t('File'), - ); - } - - /** - * 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 filepath. - */ - function testLoadMissingFilepath() { - $this->assertFalse(reset(file_load_multiple(array(), array('filepath' => 'misc/druplicon.png'))), 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() { - $this->assertFalse(reset(file_load_multiple(array(), array('status' => -99))), 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 = array( - 'uid' => 1, - 'filename' => 'druplicon.png', - 'filepath' => 'misc/druplicon.png', - 'filemime' => 'image/png', - 'timestamp' => 1, - 'status' => FILE_STATUS_PERMANENT, - ); - $file = file_save($file); - - $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->filepath, $file->filepath, 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 = array( - 'uid' => 1, - 'filename' => 'druplicon.png', - 'filepath' => 'misc/druplicon.png', - 'filemime' => 'image/png', - 'timestamp' => 1, - 'status' => FILE_STATUS_PERMANENT, - ); - $file = file_save($file); - - // Load by path. - file_test_reset(); - $by_path_files = file_load_multiple(array(), array('filepath' => $file->filepath)); - $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->filepath, $file->filepath, t("Loading by fid got the correct filepath."), 'File'); - } -} - -/** - * Tests the file_save() function. - */ -class FileSaveTest extends FileHookTestCase { - function getInfo() { - return array( - 'name' => t('File saving'), - 'description' => t('Tests the file_save() function.'), - 'group' => t('File'), - ); - } - - function testFileSave() { - // Create a new file object. - $file = array( - 'uid' => 1, - 'filename' => 'druplicon.png', - 'filepath' => 'misc/druplicon.png', - 'filemime' => 'image/png', - 'timestamp' => 1, - 'status' => FILE_STATUS_PERMANENT, - ); - $file = (object) $file; - - // 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 {files} 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->filepath), 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('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 {files} 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.")); - } -} - - -/** - * Tests the file_validate() function.. - */ -class FileValidateTest extends FileHookTestCase { - function getInfo() { - return array( - 'name' => t('File validate'), - 'description' => t('Tests the file_validate() function.'), - 'group' => t('File'), - ); - } - - /** - * 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 succesfully.')); - $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 { - function getInfo() { - return array( - 'name' => t('File save data'), - 'description' => t('Tests the file save data function.'), - 'group' => t('File'), - ); - } - - /** - * 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_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory.")); - $this->assertEqual($result->filename, basename($result->filepath), t("Filename was set to the file's basename.")); - $this->assertEqual($contents, file_get_contents($result->filepath), 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); - - $result = file_save_data($contents, 'asdf.txt'); - $this->assertTrue($result, t('Unnamed file saved correctly.')); - - $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory.")); - $this->assertEqual('asdf.txt', basename($result->filepath), t('File was named correctly.')); - $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, FILE_EXISTS_RENAME); - $this->assertTrue($result, t("File saved sucessfully.")); - - $this->assertEqual(file_directory_path(), dirname($result->filepath), 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->filepath), 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->filepath, FILE_EXISTS_REPLACE); - $this->assertTrue($result, t('File saved sucessfully.')); - - $this->assertEqual(file_directory_path(), dirname($result->filepath), 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->filepath), 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->filepath, FILE_EXISTS_ERROR); - $this->assertFalse($result, t('Overwriting a file fails when FILE_EXISTS_ERROR is specified.')); - $this->assertEqual($contents, file_get_contents($existing->filepath), 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 { - function getInfo() { - return array( - 'name' => t('File download'), - 'description' => t('Tests for file download/transfer functions.'), - 'group' => t('File'), - ); - } - - function setUp() { - parent::setUp('file_test'); - } - - /** - * Test the private file transfer system. - */ - function testPrivateFileTransfer() { - // Set file downloads to private so handler functions get called. - variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE); - - // Create a file. - $file = $this->createFile(); - $url = file_create_url($file->filename); - - // Set file_test access header to allow the download. - file_test_set_return('download', array('x-foo: Bar')); - $this->drupalHead($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.')); - - // 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($this->randomName()); - $this->drupalHead($url); - $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.')); - } -} - -/** - * Tests for file_munge_filename() and file_unmunge_filename(). - */ -class FileNameMungingTest extends FileTestCase { - function getInfo() { - return array( - 'name' => t('File naming'), - 'description' => t('Test filename munging and unmunging.'), - 'group' => t('File'), - ); - } - - 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))); - } - - /** - * 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))); - } -} +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->filepath, $after->filepath, t('File path is the same: %file1 == %file2.', array('%file1' => $before->filepath, '%file2' => $after->filepath)), '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->filepath, $file2->filepath, t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->filepath, '%file2' => $file2->filepath)), '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->filepath, $file2->filepath, t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->filepath, '%file2' => $file2->filepath)), '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) { + // Mask out all but the last three octets. + $actual_mode = fileperms($filepath) & 511; + if (is_null($message)) { + if ($actual_mode == $expected_mode) { + $message = t('File permissions set correctly.'); + } + else { + $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); + } + + /** + * 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 (is_null($path)) { + $path = file_directory_path() . '/' . $this->randomName(); + } + $this->assertTrue(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. + * @return + * File object. + */ + function createFile($filepath = NULL, $contents = NULL) { + if (is_null($filepath)) { + $filepath = file_directory_path() . '/' . $this->randomName(); + } + + if (is_null($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->filepath = substr($filepath, strlen(file_directory_path()) + 1); // we don't want a file_directory_path in the value that is written to the database + $file->filename = basename($file->filepath); + $file->filemime = 'text/plain'; + $file->uid = 1; + $file->timestamp = REQUEST_TIME; + $file->filesize = filesize($filepath); + $file->status = 0; + // Write the record directly rather than calling file_save() so we don't + // invoke the hooks. + $this->assertNotIdentical(drupal_write_record('files', $file), FALSE, t('The file was added to the database.'), 'Create test file'); + + // but we really do want the file directory path available to the tests and other functions, so we need to put it back + $file->filepath = $filepath; + 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' => 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' => 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 (is_null($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 { + function getInfo() { + return array( + 'name' => t('File space used tests'), + 'description' => t('Tests the file_space_used() function.'), + 'group' => t('File'), + ); + } + + function setUp() { + parent::setUp(); + + // Create records for a couple of users with different sizes. + drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 50, 'status' => FILE_STATUS_PERMANENT)); + drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 20, 'status' => FILE_STATUS_PERMANENT)); + drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 100, 'status' => FILE_STATUS_PERMANENT)); + drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 200, 'status' => FILE_STATUS_PERMANENT)); + + // Now create some with other statuses. These values were chosen arbitrarily + // for the sole purpose of testing that bitwise operators were used + // correctly on the field. + drupal_write_record('files', $file = array('uid' => 2, 'filesize' => 1, 'status' => 2 | 8)); + drupal_write_record('files', $file = array('uid' => 3, 'filesize' => 3, 'status' => 2 | 4)); + } + + /** + * Test different users with the default status. + */ + function testUser() { + $this->assertEqual(file_space_used(2), 70, t("Found the size of the first user's files.")); + $this->assertEqual(file_space_used(3), 300, t("Found the size of the second user's files.")); + $this->assertEqual(file_space_used(), 370, t("Found the size of all user's files.")); + } + + /** + * Test the status fields + */ + function testStatus() { + // Check selection with a single bit set. + $this->assertEqual(file_space_used(NULL, 2), 4, t("Found the size of all user's files with status 2.")); + $this->assertEqual(file_space_used(NULL, 4), 3, t("Found the size of all user's files with status 4.")); + // Check that the bitwise AND operator is used when selecting so that we + // only get files with the 2 AND 4 bits set. + $this->assertEqual(file_space_used(NULL, 2 | 4), 3, t("Found the size of all user's files with status 6.")); + } + + /** + * Test both the user and status. + */ + function testUserAndStatus() { + $this->assertEqual(file_space_used(1, 8), 0, t("Found the size of the admin user's files with status 8.")); + $this->assertEqual(file_space_used(2, 8), 1, t("Found the size of the first user's files with status 8.")); + $this->assertEqual(file_space_used(2, 2), 1, t("Found the size of the first user's files with status 2.")); + $this->assertEqual(file_space_used(3, 2), 3, t("Found the size of the second user's files with status 2.")); + } +} + +/** + * This will run tests against the file validation functions (file_validate_*). + */ +class FileValidatorTest extends DrupalWebTestCase { + function getInfo() { + return array( + 'name' => t('File validator tests'), + 'description' => t('Tests the functions used to validate uploaded files.'), + 'group' => t('File'), + ); + } + + function setUp() { + parent::setUp(); + + $this->image = new stdClass(); + $this->image->filepath = 'misc/druplicon.png'; + $this->image->filename = basename($this->image->filepath); + + $this->non_image = new stdClass(); + $this->non_image->filepath = 'misc/jquery.js'; + $this->non_image->filename = basename($this->non_image->filepath); + } + + /** + * 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->filepath), 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->filepath), 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. + $temp_dir = file_directory_temp(); + copy(realpath('misc/druplicon.png'), realpath($temp_dir) . '/druplicon.png'); + $this->image->filepath = $temp_dir . '/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->filepath); + $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File'); + $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File'); + + unlink(realpath($temp_dir . '/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', 255); + $this->assertEqual(strlen($file->filename), 255); + $errors = file_validate_name_length($file); + $this->assertEqual(count($errors), 0, t('No errors reported for 255 length filename.'), 'File'); + + // Add a filename with a length too long and test it. + $file->filename = str_repeat('x', 256); + $errors = file_validate_name_length($file); + $this->assertEqual(count($errors), 1, t('An error reported for 256 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(array('uid' => 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 { + function getInfo() { + return array( + 'name' => t('Unmanaged file save data'), + 'description' => t('Tests the unmanaged file save data function.'), + 'group' => t('File'), + ); + } + + /** + * 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_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); + $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); + + // Provide a filename. + $filepath = file_unmanaged_save_data($contents, 'asdf.txt', FILE_EXISTS_REPLACE); + $this->assertTrue($filepath, t('Unnamed file saved correctly.')); + $this->assertEqual(file_directory_path(), dirname($filepath), t("File was placed in Drupal's files directory.")); + $this->assertEqual('asdf.txt', basename($filepath), t('File was named correctly.')); + $this->assertEqual($contents, file_get_contents(realpath($filepath)), t('Contents of the file are correct.')); + $this->assertFilePermissions($filepath, 0664); + } +} + +/** + * Test the file_save_upload() function. + */ +class FileSaveUploadTest extends FileHookTestCase { + /** + * An image file path for uploading. + */ + var $image; + + /** + * The largest file id when the test starts. + */ + var $maxFidBefore; + + function getInfo() { + return array( + 'name' => t('File uploading'), + 'description' => t('Tests the file uploading functions.'), + 'group' => t('File'), + ); + } + + function setUp() { + parent::setUp(); + $account = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($account); + + $this->image = current($this->drupalGetTestFiles('image')); + $this->assertTrue(is_file($this->image->filename), t("The file we're going to upload exists.")); + + $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {files}')->fetchField(); + + // Upload with replace to gurantee there's something there. + $edit = array( + 'file_test_replace' => FILE_EXISTS_REPLACE, + 'files[file_test_upload]' => realpath($this->image->filename) + ); + $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_result(db_query('SELECT MAX(fid) AS fid FROM {files}')); + $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.')); + + // 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 {files}')->fetchField(); + $image2 = current($this->drupalGetTestFiles('image')); + $edit = array('files[file_test_upload]' => realpath($image2->filename)); + $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 {files}')->fetchField(); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('validate', 'insert')); + + $file2 = file_load($max_fid_after); + $this->assertTrue($file2); + + // 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')); + } + + + /** + * Test renaming when uploading over a file that already exists. + */ + function testExistingRename() { + $edit = array( + 'file_test_replace' => FILE_EXISTS_RENAME, + 'files[file_test_upload]' => realpath($this->image->filename) + ); + $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]' => realpath($this->image->filename) + ); + $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]' => realpath($this->image->filename) + ); + $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.')); + } +} + +/** + * Directory related tests. + */ +class FileDirectoryTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('File paths and directories'), + 'description' => t('Tests operations dealing with directories.'), + 'group' => t('File'), + ); + } + + /** + * Test the file_directory_path() function. + */ + function testFileCheckDirectory() { + // A directory to operate on. + $directory = file_directory_path() . '/' . $this->randomName(); + $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.')); + + // Non-existent directory. + $form_element = $this->randomName(); + $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for non-existing directory.'), 'File'); + + // Check that an error was set for the form element above. + $errors = form_get_errors(); + $this->assertEqual($errors[$form_element], t('The directory %directory does not exist.', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File'); + + // Make a directory. + $this->assertTrue(file_check_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'); + + // Make directory read only. + @chmod($directory, 0444); + $form_element = $this->randomName(); + $this->assertFalse(file_check_directory($directory, 0, $form_element), t('Error reported for a non-writeable directory.'), 'File'); + + // Check if form error was set. + $errors = form_get_errors(); + $this->assertEqual($errors[$form_element], t('The directory %directory is not writable', array('%directory' => $directory)), t('Properly generated an error for the passed form element.'), 'File'); + + // Test directory permission modification. + $this->assertTrue(file_check_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File'); + + // Verify directory actually is writeable. + $this->assertTrue(is_writeable($directory), t('Directory is writeable.'), 'File'); + + // Remove .htaccess file to then test that it gets re-created. + @unlink(file_directory_path() .'/.htaccess'); + $directory = file_directory_path(); + file_check_directory($directory); + $this->assertTrue(is_file(file_directory_path() . '/.htaccess'), t('Successfully created the .htaccess file in the files directory.'), 'File'); + + // Verify contents of .htaccess file. + $file = file_get_contents(file_directory_path() .'/.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'); + } + + /** + * Check file_directory_path() and file_directory_temp(). + */ + function testFileDirectoryPath() { + // Directory path. + $path = variable_get('file_directory_path', conf_path() . '/files'); + $this->assertEqual($path, file_directory_path(), t('Properly returns the stored file directory path.'), 'File'); + } + + /** + * Check file_directory_path() and file_directory_temp(). + */ + function testFileDirectoryTemp() { + // Temporary directory handling. + variable_set('file_directory_temp', NULL); + $temp = file_directory_temp(); + $this->assertTrue(!is_null($temp), t('Properly set and retrieved temp directory %directory.', array('%directory' => $temp)), 'File'); + } + + /** + * This tests that a file is actually in the specified directory, to prevent + * exploits. + */ + function testFileCheckLocation() { + $source = 'misc/xyz.txt'; + $directory = 'misc'; + $result = file_check_location($source, $directory); + $this->assertTrue($result, t('Non-existent file validates when checked for location in existing directory.'), 'File'); + + $source = 'fake/xyz.txt'; + $directory = 'fake'; + $result = file_check_location($source, $directory); + $this->assertTrue($result, t('Non-existent file validates when checked for location in non-existing directory.'), 'File'); + + $source = 'misc/../install.php'; + $directory = 'misc'; + $result = file_check_location($source, $directory); + $this->assertFalse($result, t('Existing file fails validation when it exists outside the directory path, using a /../ exploit.'), 'File'); + + $source = 'misc/druplicon.png'; + $directory = 'misc'; + $result = file_check_location($source, $directory); + $this->assertTrue($result, t('Existing file passes validation when checked for location in directory path, and filepath contains a subfolder of the checked path.'), 'File'); + + $result = file_check_location($source, $directory); + $this->assertTrue($result, t('Existing file passes validation, returning the source when checked for location in directory.'), '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'); + } +} + + +/** + * Tests the file_scan_directory() function. + */ +class FileScanDirectoryTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('File scan directory'), + 'description' => t('Tests the file_scan_directory() function.'), + 'group' => t('File'), + ); + } + + /** + * Check that the no-mask parameter is honored. + */ + function testNoMask() { + $path = $this->originalFileDirectory . '/simpletest'; + + // Grab a listing of all the JS files. + $all_files = file_scan_directory($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($path, '/javascript*/', '/.script$/'); + $this->assertEqual(1, count($filtered_files), t('Filtered correctly.')); + } +} + + +/** + * Deletion related tests. + */ +class FileUnmanagedDeleteTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('Unmanaged file delete'), + 'description' => t('Tests the unmanaged file delete function.'), + 'group' => t('File'), + ); + } + + /** + * Delete a normal file. + */ + function testNormal() { + // Create a file for testing + $file = $this->createFile(); + + // Delete a regular file + $this->assertTrue(file_unmanaged_delete($file->filepath), t('Deleted worked.')); + $this->assertFalse(file_exists($file->filepath), 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_directory_path() . '/' . $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.')); + } +} + + +/** + * Unmanaged move related tests. + */ +class FileUnmanagedMoveTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('Unmanaged file moving'), + 'description' => t('Tests the unmanaged file move function.'), + 'group' => t('File'), + ); + } + + /** + * Move a normal file. + */ + function testNormal() { + // Create a file for testing + $file = $this->createFile(); + + // Moving to a new name. + $desired_filepath = file_directory_path() . '/' . $this->randomName(); + $new_filepath = file_unmanaged_move($file->filepath, $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->filepath), t('No file remains at the old location.')); + $this->assertFilePermissions($new_filepath, 0664); + + // Moving with rename. + $desired_filepath = file_directory_path() . '/' . $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, 0664); + + // TODO: test moving to a directory (rather than full directory/file path) + } + + /** + * 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->filepath, $file->filepath, FILE_EXISTS_REPLACE); + $this->assertFalse($new_filepath, t('Moving onto itself without renaming fails.')); + $this->assertTrue(file_exists($file->filepath), 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->filepath, $file->filepath, FILE_EXISTS_RENAME); + $this->assertTrue($new_filepath, t('Moving onto itself with renaming works.')); + $this->assertFalse(file_exists($file->filepath), t('Original file has been removed.')); + $this->assertTrue(file_exists($new_filepath), t('File exists after moving onto itself.')); + } +} + + +/** + * Unmanaged copy related tests. + */ +class FileUnmanagedCopyTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('Unmanaged file copying'), + 'description' => t('Tests the unmanaged file copy function.'), + 'group' => t('File'), + ); + } + + /** + * Copy a normal file. + */ + function testNormal() { + // Create a file for testing + $file = $this->createFile(); + + // Copying to a new name. + $desired_filepath = file_directory_path() . '/' . $this->randomName(); + $new_filepath = file_unmanaged_copy($file->filepath, $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->filepath), t('Original file remains.')); + $this->assertTrue(file_exists($new_filepath), t('New file exists.')); + $this->assertFilePermissions($new_filepath, 0664); + + // Copying with rename. + $desired_filepath = file_directory_path() . '/' . $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($new_filepath, $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->filepath), t('Original file remains.')); + $this->assertTrue(file_exists($new_filepath), t('New file exists.')); + $this->assertFilePermissions($new_filepath, 0664); + + // TODO: test copying to a directory (rather than full directory/file path) + } + + /** + * 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->filepath, $file->filepath, FILE_EXISTS_RENAME); + $this->assertTrue($new_filepath, t('Copying onto itself with renaming works.')); + $this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.')); + $this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.')); + $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); + + // Copy the file onto itself without renaming fails. + $new_filepath = file_unmanaged_copy($file->filepath, $file->filepath, FILE_EXISTS_ERROR); + $this->assertFalse($new_filepath, t('Copying onto itself without renaming fails.')); + $this->assertTrue(file_exists($file->filepath), t('File exists after copying onto itself.')); + + // Copy the file into same directory without renaming fails. + $new_filepath = file_unmanaged_copy($file->filepath, dirname($file->filepath), FILE_EXISTS_ERROR); + $this->assertFalse($new_filepath, t('Copying onto itself fails.')); + $this->assertTrue(file_exists($file->filepath), t('File exists after copying onto itself.')); + + // Copy the file into same directory with renaming works. + $new_filepath = file_unmanaged_copy($file->filepath, dirname($file->filepath), FILE_EXISTS_RENAME); + $this->assertTrue($new_filepath, t('Copying into same directory works.')); + $this->assertNotEqual($new_filepath, $file->filepath, t('Copied file has a new name.')); + $this->assertTrue(file_exists($file->filepath), t('Original file exists after copying onto itself.')); + $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.')); + } +} + + + +/** + * Deletion related tests. + */ +class FileDeleteTest extends FileHookTestCase { + function getInfo() { + return array( + 'name' => t('File delete'), + 'description' => t('Tests the file delete function.'), + 'group' => t('File'), + ); + } + + /** + * Try deleting a normal file (as opposed to a directory, symlink, etc). + */ + function testNormal() { + $file = $this->createFile(); + + // Check that deletion removes the file and database record. + $this->assertTrue(is_file($file->filepath), t("File exists.")); + $this->assertIdentical(file_delete($file), TRUE, t("Delete worked.")); + $this->assertFileHooksCalled(array('references', 'delete')); + $this->assertFalse(file_exists($file->filepath), t("Test file has actually been deleted.")); + $this->assertFalse(file_load($file->fid), t('File was removed from the database.')); + + // TODO: implement hook_file_references() in file_test.module and report a + // file in use and test the $force parameter. + } +} + + +/** + * Move related tests + */ +class FileMoveTest extends FileHookTestCase { + function getInfo() { + return array( + 'name' => t('File moving'), + 'description' => t('Tests the file move function.'), + 'group' => t('File'), + ); + } + + /** + * Move a normal file. + */ + function testNormal() { + $contents = $this->randomName(10); + $source = $this->createFile(NULL, $contents); + $desired_filepath = file_directory_path() . '/' . $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 sucessfully.')); + $this->assertFalse(file_exists($source->filepath)); + $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('move', '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->filepath, FILE_EXISTS_RENAME); + + // Check the return status and that the contents changed. + $this->assertTrue($result, t('File moved sucessfully.')); + $this->assertFalse(file_exists($source->filepath)); + $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file correctly written.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('move', '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->filepath, $source->filepath, 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->filepath, FILE_EXISTS_REPLACE); + + // Look at the results. + $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were overwritten.')); + $this->assertFalse(file_exists($source->filepath)); + $this->assertTrue($result, t('File moved sucessfully.')); + + // Check that the correct hooks were called. + $this->assertFileHooksCalled(array('move', 'update', 'delete', 'references', '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->filepath, FILE_EXISTS_REPLACE); + $this->assertFalse($result, t('File move failed.')); + $this->assertEqual($contents, file_get_contents($source->filepath), 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->filepath, 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->filepath)); + $this->assertEqual($contents, file_get_contents($target->filepath), 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 { + function getInfo() { + return array( + 'name' => t('File copying'), + 'description' => t('Tests the file copy function.'), + 'group' => t('File'), + ); + } + + /** + * Test file copying in the normal, base case. + */ + function testNormal() { + $contents = $this->randomName(10); + $source = $this->createFile(NULL, $contents); + $desired_filepath = file_directory_path() . '/' . $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_filepath, FILE_EXISTS_ERROR); + + // Check the return status and that the contents changed. + $this->assertTrue($result, t('File copied sucessfully.')); + $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, $desired_filepath, t('The copied file object has the desired filepath.')); + $this->assertTrue(file_exists($source->filepath), t('The original file still exists.')); + $this->assertTrue(file_exists($result->filepath), 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->filepath, FILE_EXISTS_RENAME); + + // Check the return status and that the contents changed. + $this->assertTrue($result, t('File copied sucessfully.')); + $this->assertEqual($contents, file_get_contents($result->filepath), t('Contents of file were copied correctly.')); + $this->assertNotEqual($result->filepath, $source->filepath, 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->filepath, FILE_EXISTS_REPLACE); + + // Check the return status and that the contents changed. + $this->assertTrue($result, t('File copied sucessfully.')); + $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, 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->filepath), 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 { + function getInfo() { + return array( + 'name' => t('File loading'), + 'description' => t('Tests the file_load() function.'), + 'group' => t('File'), + ); + } + + /** + * 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 filepath. + */ + function testLoadMissingFilepath() { + $this->assertFalse(reset(file_load_multiple(array(), array('filepath' => 'misc/druplicon.png'))), 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() { + $this->assertFalse(reset(file_load_multiple(array(), array('status' => -99))), 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. + // as all paths are relative to the files dir, we can't use the old value of misc/druplicon.png because the path returned will include the path to the file_directory_path, where it doesn't exist + $filepath = file_directory_path() . '/' . $this->randomName(); + $contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data."; + file_put_contents($filepath, $contents); + + $file = array( + 'uid' => 1, + 'filename' => basename($filepath), + 'filepath' => $filepath, + 'filemime' => 'text/plain', + 'timestamp' => 1, + 'status' => FILE_STATUS_PERMANENT, + ); + $file = file_save($file); + + $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->filepath, $file->filepath, 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. + // as all paths are relative to the files dir, we can't use the old value of misc/druplicon.png because the path returned will include the path to the file_directory_path + $filepath = file_directory_path() . '/' . $this->randomName(); + $contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data."; + file_put_contents($filepath, $contents); + + $file = array( + 'uid' => 1, + 'filename' => basename($filepath), + 'filepath' => $filepath, + 'filemime' => 'text/plain', + 'timestamp' => 1, + 'status' => FILE_STATUS_PERMANENT, + ); + $file = file_save($file); + + // Load by path. + file_test_reset(); + $by_path_files = file_load_multiple(array(), array('filepath' => $file->filepath)); + $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->filepath, $file->filepath, t("Loading by fid got the correct filepath."), 'File'); + } +} + +/** + * Tests the file_save() function. + */ +class FileSaveTest extends FileHookTestCase { + function getInfo() { + return array( + 'name' => t('File saving'), + 'description' => t('Tests the file_save() function.'), + 'group' => t('File'), + ); + } + + function testFileSave() { + // Create a new file object. + // as all paths are relative to the files dir, we can't use the old value of misc/druplicon.png because the path returned will include the path to the file_directory_path + $filepath = file_directory_path() . '/' . $this->randomName(); + $contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data."; + file_put_contents($filepath, $contents); + + $file = array( + 'uid' => 1, + 'filename' => basename($filepath), + 'filepath' => $filepath, + 'filemime' => 'text/plain', + 'timestamp' => 1, + 'status' => FILE_STATUS_PERMANENT, + ); + $file = file_save($file); + + // 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 {files} 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->filepath), 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('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 {files} 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.")); + } +} + + +/** + * Tests the file_validate() function.. + */ +class FileValidateTest extends FileHookTestCase { + function getInfo() { + return array( + 'name' => t('File validate'), + 'description' => t('Tests the file_validate() function.'), + 'group' => t('File'), + ); + } + + /** + * 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 succesfully.')); + $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 { + function getInfo() { + return array( + 'name' => t('File save data'), + 'description' => t('Tests the file save data function.'), + 'group' => t('File'), + ); + } + + /** + * 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_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory.")); + $this->assertEqual($result->filename, basename($result->filepath), t("Filename was set to the file's basename.")); + $this->assertEqual($contents, file_get_contents($result->filepath), 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); + + $result = file_save_data($contents, 'asdf.txt'); + $this->assertTrue($result, t('Unnamed file saved correctly.')); + + $this->assertEqual(file_directory_path(), dirname($result->filepath), t("File was placed in Drupal's files directory.")); + $this->assertEqual('asdf.txt', basename($result->filepath), t('File was named correctly.')); + $this->assertEqual($contents, file_get_contents($result->filepath), 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->filepath, FILE_EXISTS_RENAME); + $this->assertTrue($result, t("File saved sucessfully.")); + + $this->assertEqual(file_directory_path(), dirname($result->filepath), 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->filepath), 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->filepath, FILE_EXISTS_REPLACE); + $this->assertTrue($result, t('File saved sucessfully.')); + + $this->assertEqual(file_directory_path(), dirname($result->filepath), 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->filepath), 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->filepath, FILE_EXISTS_ERROR); + $this->assertFalse($result, t('Overwriting a file fails when FILE_EXISTS_ERROR is specified.')); + $this->assertEqual($contents, file_get_contents($existing->filepath), 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 { + function getInfo() { + return array( + 'name' => t('File download'), + 'description' => t('Tests for file download/transfer functions.'), + 'group' => t('File'), + ); + } + + function setUp() { + parent::setUp('file_test'); + } + + /** + * Test the private file transfer system. + */ + function testPrivateFileTransfer() { + // Set file downloads to private so handler functions get called. + variable_set('file_downloads', FILE_DOWNLOADS_PRIVATE); + + // Create a file. + $file = $this->createFile(); + $url = file_create_url($file->filename); + + // Set file_test access header to allow the download. + file_test_set_return('download', array('x-foo: Bar')); + $this->drupalHead($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.')); + + // 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($this->randomName()); + $this->drupalHead($url); + $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.')); + } +} + +/** + * Tests for file_munge_filename() and file_unmunge_filename(). + */ +class FileNameMungingTest extends FileTestCase { + function getInfo() { + return array( + 'name' => t('File naming'), + 'description' => t('Test filename munging and unmunging.'), + 'group' => t('File'), + ); + } + + 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))); + } + + /** + * 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))); + } +}