Skip to content
file.test 110 KiB
Newer Older
Angie Byron's avatar
Angie Byron committed
<?php

/**
 *  @file
 *  This provides SimpleTests for the core file handling functionality.
 *  These include FileValidateTest and FileSaveTest.
 */

/**
 * Helper validator that returns the $errors parameter.
 */
function file_test_validator($file, $errors) {
  return $errors;
}

/**
 * Helper function for testing file_scan_directory().
 *
 * Each time the function is called the file is stored in a static variable.
 * When the function is called with no $filepath parameter, the results are
 * returned.
 *   If $filepath is NULL, an array of all previous $filepath parameters
function file_test_file_scan_callback($filepath = NULL) {
  $files = &drupal_static(__FUNCTION__, array());
  if (isset($filepath)) {
    $files[] = $filepath;
/**
 * Reset static variables used by file_test_file_scan_callback().
 */
function file_test_file_scan_callback_reset() {
  drupal_static_reset('file_test_file_scan_callback');
/**
 * Base class for file tests that adds some additional file specific
 * assertions and helper functions.
 */
class FileTestCase extends DrupalWebTestCase {
  /**
   * Check that two files have the same values for all fields other than the
   * timestamp.
   *
   * @param $before
   *   File object to compare.
   * @param $after
   *   File object to compare.
   */
  function assertFileUnchanged($before, $after) {
    $this->assertEqual($before->fid, $after->fid, t('File id is the same: %file1 == %file2.', array('%file1' => $before->fid, '%file2' => $after->fid)), 'File unchanged');
    $this->assertEqual($before->uid, $after->uid, t('File owner is the same: %file1 == %file2.', array('%file1' => $before->uid, '%file2' => $after->uid)), 'File unchanged');
    $this->assertEqual($before->filename, $after->filename, t('File name is the same: %file1 == %file2.', array('%file1' => $before->filename, '%file2' => $after->filename)), 'File unchanged');
    $this->assertEqual($before->uri, $after->uri, t('File path is the same: %file1 == %file2.', array('%file1' => $before->uri, '%file2' => $after->uri)), 'File unchanged');
    $this->assertEqual($before->filemime, $after->filemime, t('File MIME type is the same: %file1 == %file2.', array('%file1' => $before->filemime, '%file2' => $after->filemime)), 'File unchanged');
    $this->assertEqual($before->filesize, $after->filesize, t('File size is the same: %file1 == %file2.', array('%file1' => $before->filesize, '%file2' => $after->filesize)), 'File unchanged');
    $this->assertEqual($before->status, $after->status, t('File status is the same: %file1 == %file2.', array('%file1' => $before->status, '%file2' => $after->status)), 'File unchanged');
  }

  /**
   * Check that two files are not the same by comparing the fid and filepath.
   *
   * @param $file1
   *   File object to compare.
   * @param $file2
   *   File object to compare.
   */
  function assertDifferentFile($file1, $file2) {
    $this->assertNotEqual($file1->fid, $file2->fid, t('Files have different ids: %file1 != %file2.', array('%file1' => $file1->fid, '%file2' => $file2->fid)), 'Different file');
    $this->assertNotEqual($file1->uri, $file2->uri, t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Different file');
  }

  /**
   * Check that two files are the same by comparing the fid and filepath.
   *
   * @param $file1
   *   File object to compare.
   * @param $file2
   *   File object to compare.
   */
  function assertSameFile($file1, $file2) {
    $this->assertEqual($file1->fid, $file2->fid, t('Files have the same ids: %file1 == %file2.', array('%file1' => $file1->fid, '%file2-fid' => $file2->fid)), 'Same file');
    $this->assertEqual($file1->uri, $file2->uri, t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Same file');
  /**
   * Helper function to test the permissions of a file.
   *
   * @param $filepath
   *   String file path.
   * @param $expected_mode
   *   Octal integer like 0664 or 0777.
   * @param $message
   *   Optional message.
   */
  function assertFilePermissions($filepath, $expected_mode, $message = NULL) {
    // Clear out PHP's file stat cache to be sure we see the current value.
    clearstatcache();

    // Mask out all but the last three octets.
    $actual_mode = fileperms($filepath) & 0777;

    // PHP on Windows has limited support for file permissions. Usually each of
    // "user", "group" and "other" use one octal digit (3 bits) to represent the
    // read/write/execute bits. On Windows, chmod() ignores the "group" and
    // "other" bits, and fileperms() returns the "user" bits in all three
    // positions. $expected_mode is updated to reflect this.
    if (substr(PHP_OS, 0, 3) == 'WIN') {
      // Reset the "group" and "other" bits.
      $expected_mode = $expected_mode & 0700;
      // Shift the "user" bits to the "group" and "other" positions also.
      $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
    }

    if (!isset($message)) {
      $message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
    }
    $this->assertEqual($actual_mode, $expected_mode, $message);
  }

  /**
   * Helper function to test the permissions of a directory.
   *
   * @param $directory
   *   String directory path.
   * @param $expected_mode
   *   Octal integer like 0664 or 0777.
   * @param $message
   *   Optional message.
   */
  function assertDirectoryPermissions($directory, $expected_mode, $message = NULL) {
    // Clear out PHP's file stat cache to be sure we see the current value.
    clearstatcache();

    // Mask out all but the last three octets.
    $actual_mode = fileperms($directory) & 0777;

    // PHP on Windows has limited support for file permissions. Usually each of
    // "user", "group" and "other" use one octal digit (3 bits) to represent the
    // read/write/execute bits. On Windows, chmod() ignores the "group" and
    // "other" bits, and fileperms() returns the "user" bits in all three
    // positions. $expected_mode is updated to reflect this.
    if (substr(PHP_OS, 0, 3) == 'WIN') {
      // Reset the "group" and "other" bits.
      $expected_mode = $expected_mode & 0700;
      // Shift the "user" bits to the "group" and "other" positions also.
      $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
    }

    if (!isset($message)) {
      $message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
    }
    $this->assertEqual($actual_mode, $expected_mode, $message);
  }

  /**
   * Create a directory and assert it exists.
   *
   * @param $path
   *   Optional string with a directory path. If none is provided, a random
   *   name in the site's files directory will be used.
   * @return
   *   The path to the directory.
   */
  function createDirectory($path = NULL) {
    // A directory to operate on.
      $path = file_default_scheme() . '://' . $this->randomName();
    $this->assertTrue(drupal_mkdir($path) && is_dir($path), t('Directory was created successfully.'));
    return $path;
  }

  /**
   * Create a file and save it to the files table and assert that it occurs
   * correctly.
   *
   * @param $filepath
   *   Optional string specifying the file path. If none is provided then a
   *   randomly named file will be created in the site's files directory.
   * @param $contents
   *   Optional contents to save into the file. If a NULL value is provided an
   *   arbitrary string will be used.
   * @param $scheme
   *   Optional string indicating the stream scheme to use. Drupal core includes
   *   public, private, and temporary. The public wrapper is the default.
  function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
      // Prefix with non-latin characters to ensure that all file-related
      // tests work with international filenames.
      $filepath = 'Файл для тестирования ' . $this->randomName();
    if (!isset($scheme)) {
      $scheme = file_default_scheme();
    }
    $filepath = $scheme . '://' . $filepath;
      $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->filename = drupal_basename($file->uri);
    $file->filemime = 'text/plain';
    $file->uid = 1;
    $file->timestamp = REQUEST_TIME;
    $file->filesize = filesize($file->uri);
    // Write the record directly rather than calling file_save() so we don't
    // invoke the hooks.
    $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
/**
 * 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))));
      $this->assertTrue(TRUE, t('All the expected hooks were called: %expected', array('%expected' => empty($expected) ? t('(none)') : implode(', ', $expected))));
    }

    // Determine if there were any unexpected calls.
    $unexpected = array_diff($actual, $expected);
    if (count($unexpected)) {
      $this->assertTrue(FALSE, t('Unexpected hooks were called: %unexpected.', array('%unexpected' => empty($unexpected) ? t('(none)') : implode(', ', $unexpected))));
      $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 ($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 {
      'name' => 'File space used tests',
      'description' => 'Tests the file_space_used() function.',
    );
  }

  function setUp() {
    parent::setUp();

    // Create records for a couple of users with different sizes.
    $file = array('uid' => 2, 'uri' => 'public://example1.txt', 'filesize' => 50, 'status' => FILE_STATUS_PERMANENT);
    drupal_write_record('file_managed', $file);
    $file = array('uid' => 2, 'uri' => 'public://example2.txt', 'filesize' => 20, 'status' => FILE_STATUS_PERMANENT);
    drupal_write_record('file_managed', $file);
    $file = array('uid' => 3, 'uri' => 'public://example3.txt', 'filesize' => 100, 'status' => FILE_STATUS_PERMANENT);
    drupal_write_record('file_managed', $file);
    $file = array('uid' => 3, 'uri' => 'public://example4.txt', 'filesize' => 200, 'status' => FILE_STATUS_PERMANENT);
    drupal_write_record('file_managed', $file);
    // Now create some non-permanent files.
    $file = array('uid' => 2, 'uri' => 'public://example5.txt', 'filesize' => 1, 'status' => 0);
    drupal_write_record('file_managed', $file);
    $file = array('uid' => 3, 'uri' => 'public://example6.txt', 'filesize' => 3, 'status' => 0);
    drupal_write_record('file_managed', $file);
  }

  /**
   * Test different users with the default status.
   */
  function testFileSpaceUsed() {
    // Test different users with default status.
    $this->assertEqual(file_space_used(2), 70);
    $this->assertEqual(file_space_used(3), 300);
    $this->assertEqual(file_space_used(), 370);

    // Test the status fields
    $this->assertEqual(file_space_used(NULL, 0), 4);
    $this->assertEqual(file_space_used(NULL, FILE_STATUS_PERMANENT), 370);

    // Test both the user and status.
    $this->assertEqual(file_space_used(1, 0), 0);
    $this->assertEqual(file_space_used(1, FILE_STATUS_PERMANENT), 0);
    $this->assertEqual(file_space_used(2, 0), 1);
    $this->assertEqual(file_space_used(2, FILE_STATUS_PERMANENT), 70);
    $this->assertEqual(file_space_used(3, 0), 3);
    $this->assertEqual(file_space_used(3, FILE_STATUS_PERMANENT), 300);
Angie Byron's avatar
Angie Byron committed
/**
 *  This will run tests against the file validation functions (file_validate_*).
 */
class FileValidatorTest extends DrupalWebTestCase {
Angie Byron's avatar
Angie Byron committed
    return array(
      'name' => 'File validator tests',
      'description' => 'Tests the functions used to validate uploaded files.',
Angie Byron's avatar
Angie Byron committed
    );
  }

  function setUp() {
    parent::setUp();

    $this->image = new stdClass();
    $this->image->uri = 'misc/druplicon.png';
    $this->image->filename = drupal_basename($this->image->uri);
Angie Byron's avatar
Angie Byron committed

    $this->non_image = new stdClass();
    $this->non_image->uri = 'misc/jquery.js';
    $this->non_image->filename = drupal_basename($this->non_image->uri);
Angie Byron's avatar
Angie Byron committed
  }

  /**
   * 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');
Angie Byron's avatar
Angie Byron committed

    $file->filename = 'asdf.txt';
    $errors = file_validate_extensions($file, 'exe png');
    $this->assertEqual(count($errors), 1, t('Invalid extension blocked.'), 'File');
Angie Byron's avatar
Angie Byron committed
  }

  /**
   *  This ensures a specific file is actually an image.
   */
  function testFileValidateIsImage() {
    $this->assertTrue(file_exists($this->image->uri), t('The image being tested exists.'), 'File');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_is_image($this->image);
    $this->assertEqual(count($errors), 0, t('No error reported for our image file.'), 'File');
    $this->assertTrue(file_exists($this->non_image->uri), t('The non-image being tested exists.'), 'File');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_is_image($this->non_image);
    $this->assertEqual(count($errors), 1, t('An error reported for our non-image file.'), 'File');
Angie Byron's avatar
Angie Byron committed
  }

  /**
   *  This ensures the resolution of a specific file is within bounds.
   *  The image will be resized if it's too large.
   */
  function testFileValidateImageResolution() {
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_image_resolution($this->image, 0, '200x200');
    $this->assertEqual(count($errors), 1, t('Small images report an error.'), 'File');
Angie Byron's avatar
Angie Byron committed
    if (image_get_toolkit()) {
      // Copy the image so that the original doesn't get resized.
      copy('misc/druplicon.png', 'temporary://druplicon.png');
      $this->image->uri = 'temporary://druplicon.png';
Angie Byron's avatar
Angie Byron committed

      $errors = file_validate_image_resolution($this->image, '10x5');
      $this->assertEqual(count($errors), 0, t('No errors should be reported when an oversized image can be scaled down.'), 'File');
      $info = image_get_info($this->image->uri);
      $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File');
      $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File');
      drupal_unlink('temporary://druplicon.png');
Angie Byron's avatar
Angie Byron committed
    }
    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');
Angie Byron's avatar
Angie Byron committed
    }
  }

  /**
   *  This will ensure the filename length is valid.
   */
  function testFileValidateNameLength() {
    // Create a new file object.
    $file = new stdClass();

    // Add a filename with an allowed length and test it.
    $file->filename = str_repeat('x', 240);
    $this->assertEqual(strlen($file->filename), 240);
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_name_length($file);
    $this->assertEqual(count($errors), 0, t('No errors reported for 240 length filename.'), 'File');
Angie Byron's avatar
Angie Byron committed

    // Add a filename with a length too long and test it.
    $file->filename = str_repeat('x', 241);
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_name_length($file);
    $this->assertEqual(count($errors), 1, t('An error reported for 241 length filename.'), 'File');
Angie Byron's avatar
Angie Byron committed

    // 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');
Angie Byron's avatar
Angie Byron committed
  }


  /**
   * Test file_validate_size().
   */
  function testFileValidateSize() {
    global $user;
    $original_user = $user;
    drupal_save_session(FALSE);
Angie Byron's avatar
Angie Byron committed

    $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.
Angie Byron's avatar
Angie Byron committed
    $user = $this->drupalCreateUser();

    // Create a file with a size of 1000 bytes, and quotas of only 1 byte.
Angie Byron's avatar
Angie Byron committed
    $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');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_size($file, 1, 0);
    $this->assertEqual(count($errors), 1, t('Error for the file being over the limit.'), 'File');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_size($file, 0, 1);
    $this->assertEqual(count($errors), 1, t('Error for the user being over their limit.'), 'File');
Angie Byron's avatar
Angie Byron committed
    $errors = file_validate_size($file, 1, 1);
    $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File');
Angie Byron's avatar
Angie Byron committed

    $user = $original_user;
    drupal_save_session(TRUE);
Angie Byron's avatar
Angie Byron committed
/**
 *  Tests the file_unmanaged_save_data() function.
Angie Byron's avatar
Angie Byron committed
 */
class FileUnmanagedSaveDataTest extends FileTestCase {
Angie Byron's avatar
Angie Byron committed
    return array(
      'name' => 'Unmanaged file save data',
      'description' => 'Tests the unmanaged file save data function.',
   * Test the file_unmanaged_save_data() function.
Angie Byron's avatar
Angie Byron committed
  function testFileSaveData() {
    $contents = $this->randomName(8);

    $filepath = file_unmanaged_save_data($contents);
    $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
    $this->assertEqual(file_uri_scheme($filepath), file_default_scheme(), t("File was placed in Drupal's files directory."));
    $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
    $filepath = file_unmanaged_save_data($contents, 'public://asdf.txt', FILE_EXISTS_REPLACE);
    $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
    $this->assertEqual('asdf.txt', drupal_basename($filepath), t('File was named correctly.'));
    $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
    $this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664));
/**
 *  Tests the file_unmanaged_save_data() function on remote filesystems.
 */
class RemoteFileUnmanagedSaveDataTest extends FileUnmanagedSaveDataTest {
  public static function getInfo() {
    $info = parent::getInfo();
    $info['group'] = 'File API (remote)';
    return $info;
  }

  function setUp() {
    parent::setUp('file_test');
    variable_set('file_default_scheme', 'dummy-remote');
  }
}

Angie Byron's avatar
Angie Byron committed
/**
 * Test the file_save_upload() function.
Angie Byron's avatar
Angie Byron committed
 */
class FileSaveUploadTest extends FileHookTestCase {
  /**
   * An image file path for uploading.
   */
  protected $image;

  /**
   * A PHP file path for upload security testing.
   */
  protected $phpfile;

  /**
   * The largest file id when the test starts.
   */
Angie Byron's avatar
Angie Byron committed
    return array(
      'name' => 'File uploading',
      'description' => 'Tests the file uploading functions.',
  function setUp() {
    parent::setUp();
    $account = $this->drupalCreateUser(array('access content'));
    $this->drupalLogin($account);
    $image_files = $this->drupalGetTestFiles('image');
    $this->image = current($image_files);

    list(, $this->image_extension) = explode('.', $this->image->filename);
    $this->assertTrue(is_file($this->image->uri), t("The image file we're going to upload exists."));

    $this->phpfile = current($this->drupalGetTestFiles('php'));
    $this->assertTrue(is_file($this->phpfile->uri), t("The PHP file we're going to upload exists."));
    $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
    // Upload with replace to guarantee there's something there.
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));
    // Check that the correct hooks were called then clean out the hook
    // counters.
    $this->assertFileHooksCalled(array('validate', 'insert'));
    file_test_reset();
  }
  /**
   * Test the file_save_upload() function.
   */
  function testNormal() {
    $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
    $this->assertTrue($max_fid_after > $this->maxFidBefore, t('A new file was created.'));
    $this->assertTrue($file1, t('Loaded the file.'));
    // MIME type of the uploaded image may be either image/jpeg or image/png.
    $this->assertEqual(substr($file1->filemime, 0, 5), 'image', 'A MIME type was set.');
    // Reset the hook counters to get rid of the 'load' we just called.
    file_test_reset();

    $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
    $image2 = current($this->drupalGetTestFiles('image'));
    $edit = array('files[file_test_upload]' => drupal_realpath($image2->uri));
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('You WIN!'));
    $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));

    $file2 = file_load($max_fid_after);
    $this->assertTrue($file2);
    // MIME type of the uploaded image may be either image/jpeg or image/png.
    $this->assertEqual(substr($file2->filemime, 0, 5), 'image', 'A MIME type was set.');

    // Load both files using file_load_multiple().
    $files = file_load_multiple(array($file1->fid, $file2->fid));
    $this->assertTrue(isset($files[$file1->fid]), t('File was loaded successfully'));
    $this->assertTrue(isset($files[$file2->fid]), t('File was loaded successfully'));
    // Upload a third file to a subdirectory.
    $image3 = current($this->drupalGetTestFiles('image'));
    $image3_realpath = drupal_realpath($image3->uri);
    $dir = $this->randomName();
    $edit = array(
      'files[file_test_upload]' => $image3_realpath,
      'file_subdir' => $dir,
    );
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath))));
    // Check that file_load_multiple() with no arguments returns FALSE.
    $this->assertFalse(file_load_multiple(), t('No files were loaded.'));
  /**
   * Test extension handling.
   */
  function testHandleExtension() {
    // The file being tested is a .gif which is in the default safe list
    // of extensions to allow when the extension validator isn't used. This is
    // implicitly tested at the testNormal() test. Here we tell
    // file_save_upload() to only allow ".foo".
    $extensions = 'foo';
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
      'extensions' => $extensions,
    );

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $message = t('Only files with the following extensions are allowed:') . ' <em class="placeholder">' . $extensions . '</em>';
    $this->assertRaw($message, t('Can\'t upload a disallowed extension'));
    $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate'));

    // Reset the hook counters.
    file_test_reset();

    $extensions = 'foo ' . $this->image_extension;
    // Now tell file_save_upload() to allow the extension of our test image.
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
      'extensions' => $extensions,
    );

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload an allowed extension.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'load', 'update'));

    // Reset the hook counters.
    file_test_reset();

    // Now tell file_save_upload() to allow any extension.
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
      'allow_all_extensions' => TRUE,
    );
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload any extension.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'load', 'update'));
  }

  /**
   * Test dangerous file handling.
   */
  function testHandleDangerousFile() {
    // Allow the .php extension and make sure it gets renamed to .txt for
    // safety. Also check to make sure its MIME type was changed.
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->phpfile->uri),
      'is_image_file' => FALSE,
      'extensions' => 'php',
    );

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $message = t('For security reasons, your upload has been renamed to') . ' <em class="placeholder">' . $this->phpfile->filename . '.txt' . '</em>';
    $this->assertRaw($message, t('Dangerous file was renamed.'));
    $this->assertRaw(t('File MIME type is text/plain.'), t('Dangerous file\'s MIME type was changed.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));

    // Ensure dangerous files are not renamed when insecure uploads is TRUE.
    // Turn on insecure uploads.
    variable_set('allow_insecure_uploads', 1);
    // Reset the hook counters.
    file_test_reset();

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.'));
    $this->assertRaw(t('File name is !filename', array('!filename' => $this->phpfile->filename)), t('Dangerous file was not renamed when insecure uploads is TRUE.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));

    // Turn off insecure uploads.
    variable_set('allow_insecure_uploads', 0);
  }

  /**
   * Test file munge handling.
   */
  function testHandleFileMunge() {
    // Ensure insecure uploads are disabled for this test.
    variable_set('allow_insecure_uploads', 0);
    $this->image = file_move($this->image, $this->image->uri . '.foo.' . $this->image_extension);

    // Reset the hook counters to get rid of the 'move' we just called.
    file_test_reset();

    $edit = array(
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
      'extensions' => $extensions,
    );

    $munged_filename = $this->image->filename;
    $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.'));
    $munged_filename .= '_.' . $this->image_extension;

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('For security reasons, your upload has been renamed'), t('Found security message.'));
    $this->assertRaw(t('File name is !filename', array('!filename' => $munged_filename)), t('File was successfully munged.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));

    // Ensure we don't munge files if we're allowing any extension.
    // Reset the hook counters.
    file_test_reset();

    $edit = array(
      'files[file_test_upload]' => drupal_realpath($this->image->uri),
      'allow_all_extensions' => TRUE,
    );

    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.'));
    $this->assertRaw(t('File name is !filename', array('!filename' => $this->image->filename)), t('File was not munged when allowing any extension.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));
  }

  /**
   * Test renaming when uploading over a file that already exists.
   */
  function testExistingRename() {
    $edit = array(
      'file_test_replace' => FILE_EXISTS_RENAME,
      'files[file_test_upload]' => drupal_realpath($this->image->uri)
    );
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'insert'));
  }

  /**
   * Test replacement when uploading over a file that already exists.
   */
  function testExistingReplace() {
    $edit = array(
      'file_test_replace' => FILE_EXISTS_REPLACE,
      'files[file_test_upload]' => drupal_realpath($this->image->uri)
    );
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('You WIN!'), t('Found the success message.'));

    // Check that the correct hooks were called.
    $this->assertFileHooksCalled(array('validate', 'load', 'update'));
  }

  /**
   * Test for failure when uploading over a file that already exists.
   */
  function testExistingError() {
    $edit = array(
      'file_test_replace' => FILE_EXISTS_ERROR,
      'files[file_test_upload]' => drupal_realpath($this->image->uri)
    );
    $this->drupalPost('file-test/upload', $edit, t('Submit'));
    $this->assertResponse(200, t('Received a 200 response for posted test file.'));
    $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.'));

    // Check that the no hooks were called while failing.
    $this->assertFileHooksCalled(array());
  }

  /**
   * Test for no failures when not uploading a file.
   */
  function testNoUpload() {
    $this->drupalPost('file-test/upload', array(), t('Submit'));
    $this->assertNoRaw(t('Epic upload FAIL!'), t('Failure message not found.'));
/**
 * Test the file_save_upload() function on remote filesystems.
 */
class RemoteFileSaveUploadTest extends FileSaveUploadTest {
  public static function getInfo() {
    $info = parent::getInfo();
    $info['group'] = 'File API (remote)';
    return $info;
  }

  function setUp() {
    parent::setUp('file_test');
    variable_set('file_default_scheme', 'dummy-remote');
  }
}

/**
 * Directory related tests.
 */
class FileDirectoryTest extends FileTestCase {
      'name' => 'File paths and directories',
      'description' => 'Tests operations dealing with directories.',
   * Test directory handling functions.
Angie Byron's avatar
Angie Byron committed
   */
  function testFileCheckDirectoryHandling() {
    $directory = file_default_scheme() . '://' . $this->randomName() . '/' . $this->randomName();
    $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.'));
    $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for non-existing directory.'), 'File');
    $this->assertTrue(file_prepare_directory($directory, FILE_CREATE_DIRECTORY), t('No error reported when creating a new directory.'), 'File');
    // Make sure directory actually exists.
    $this->assertTrue(is_dir($directory), t('Directory actually exists.'), 'File');
    if (substr(PHP_OS, 0, 3) != 'WIN') {
      // PHP on Windows doesn't support any kind of useful read-only mode for
      // directories. When executing a chmod() on a directory, PHP only sets the
      // read-only flag, which doesn't prevent files to actually be written
      // in the directory on any recent version of Windows.

      // Make directory read only.
      @drupal_chmod($directory, 0444);
      $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for a non-writeable directory.'), 'File');
      // Test directory permission modification.
      $this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
    }
    // Test that the directory has the correct permissions.
    $this->assertDirectoryPermissions($directory, variable_get('file_chmod_directory', 0775));
    // Remove .htaccess file to then test that it gets re-created.
    @drupal_unlink(file_default_scheme() . '://.htaccess');
    $this->assertFalse(is_file(file_default_scheme() . '://.htaccess'), t('Successfully removed the .htaccess file in the files directory.'), 'File');
    $this->assertTrue(is_file(file_default_scheme() . '://.htaccess'), t('Successfully re-created the .htaccess file in the files directory.'), 'File');
    // Verify contents of .htaccess file.
    $file = file_get_contents(file_default_scheme() . '://.htaccess');
    $this->assertEqual($file, "SetHandler Drupal_Security_Do_Not_Remove_See_SA_2006_006\nOptions None\nOptions +FollowSymLinks", t('The .htaccess file contains the proper content.'), 'File');
   * This will take a directory and path, and find a valid filepath that is not
   * taken by another file.
Angie Byron's avatar
Angie Byron committed
   */
  function testFileCreateNewFilepath() {
    // First we test against an imaginary file that does not exist in a
    // directory.
Angie Byron's avatar
Angie Byron committed
    $basename = 'xyz.txt';
    $directory = 'misc';
    $original = $directory . '/' . $basename;
Angie Byron's avatar
Angie Byron committed
    $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.
Angie Byron's avatar
Angie Byron committed
    $basename = 'druplicon.png';
    $original = $directory . '/' . $basename;
    $expected = $directory . '/druplicon_0.png';
Angie Byron's avatar
Angie Byron committed
    $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.
Angie Byron's avatar
Angie Byron committed
  }

  /**
   * 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.
Angie Byron's avatar
Angie Byron committed
   */
  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');
Angie Byron's avatar
Angie Byron committed
    $path = file_destination($destination, FILE_EXISTS_RENAME);
    $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_RENAME.'), 'File');
Angie Byron's avatar
Angie Byron committed
    $path = file_destination($destination, FILE_EXISTS_ERROR);