Drupal PHP Cross Reference Content Management Systems

Source: /modules/simpletest/tests/file.test - 2773 lines - 112073 bytes - Summary - Text - Print

   1  <?php
   2  
   3  /**
   4   *  @file
   5   *  This provides SimpleTests for the core file handling functionality.
   6   *  These include FileValidateTest and FileSaveTest.
   7   */
   8  
   9  /**
  10   * Helper validator that returns the $errors parameter.
  11   */
  12  function file_test_validator($file, $errors) {
  13    return $errors;
  14  }
  15  
  16  /**
  17   * Helper function for testing file_scan_directory().
  18   *
  19   * Each time the function is called the file is stored in a static variable.
  20   * When the function is called with no $filepath parameter, the results are
  21   * returned.
  22   *
  23   * @param $filepath
  24   *   File path
  25   * @return
  26   *   If $filepath is NULL, an array of all previous $filepath parameters
  27   */
  28  function file_test_file_scan_callback($filepath = NULL) {
  29    $files = &drupal_static(__FUNCTION__, array());
  30    if (isset($filepath)) {
  31      $files[] = $filepath;
  32    }
  33    else {
  34      return $files;
  35    }
  36  }
  37  
  38  /**
  39   * Reset static variables used by file_test_file_scan_callback().
  40   */
  41  function file_test_file_scan_callback_reset() {
  42    drupal_static_reset('file_test_file_scan_callback');
  43  }
  44  
  45  /**
  46   * Base class for file tests that adds some additional file specific
  47   * assertions and helper functions.
  48   */
  49  class FileTestCase extends DrupalWebTestCase {
  50    /**
  51     * Check that two files have the same values for all fields other than the
  52     * timestamp.
  53     *
  54     * @param $before
  55     *   File object to compare.
  56     * @param $after
  57     *   File object to compare.
  58     */
  59    function assertFileUnchanged($before, $after) {
  60      $this->assertEqual($before->fid, $after->fid, t('File id is the same: %file1 == %file2.', array('%file1' => $before->fid, '%file2' => $after->fid)), 'File unchanged');
  61      $this->assertEqual($before->uid, $after->uid, t('File owner is the same: %file1 == %file2.', array('%file1' => $before->uid, '%file2' => $after->uid)), 'File unchanged');
  62      $this->assertEqual($before->filename, $after->filename, t('File name is the same: %file1 == %file2.', array('%file1' => $before->filename, '%file2' => $after->filename)), 'File unchanged');
  63      $this->assertEqual($before->uri, $after->uri, t('File path is the same: %file1 == %file2.', array('%file1' => $before->uri, '%file2' => $after->uri)), 'File unchanged');
  64      $this->assertEqual($before->filemime, $after->filemime, t('File MIME type is the same: %file1 == %file2.', array('%file1' => $before->filemime, '%file2' => $after->filemime)), 'File unchanged');
  65      $this->assertEqual($before->filesize, $after->filesize, t('File size is the same: %file1 == %file2.', array('%file1' => $before->filesize, '%file2' => $after->filesize)), 'File unchanged');
  66      $this->assertEqual($before->status, $after->status, t('File status is the same: %file1 == %file2.', array('%file1' => $before->status, '%file2' => $after->status)), 'File unchanged');
  67    }
  68  
  69    /**
  70     * Check that two files are not the same by comparing the fid and filepath.
  71     *
  72     * @param $file1
  73     *   File object to compare.
  74     * @param $file2
  75     *   File object to compare.
  76     */
  77    function assertDifferentFile($file1, $file2) {
  78      $this->assertNotEqual($file1->fid, $file2->fid, t('Files have different ids: %file1 != %file2.', array('%file1' => $file1->fid, '%file2' => $file2->fid)), 'Different file');
  79      $this->assertNotEqual($file1->uri, $file2->uri, t('Files have different paths: %file1 != %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Different file');
  80    }
  81  
  82    /**
  83     * Check that two files are the same by comparing the fid and filepath.
  84     *
  85     * @param $file1
  86     *   File object to compare.
  87     * @param $file2
  88     *   File object to compare.
  89     */
  90    function assertSameFile($file1, $file2) {
  91      $this->assertEqual($file1->fid, $file2->fid, t('Files have the same ids: %file1 == %file2.', array('%file1' => $file1->fid, '%file2-fid' => $file2->fid)), 'Same file');
  92      $this->assertEqual($file1->uri, $file2->uri, t('Files have the same path: %file1 == %file2.', array('%file1' => $file1->uri, '%file2' => $file2->uri)), 'Same file');
  93    }
  94  
  95    /**
  96     * Helper function to test the permissions of a file.
  97     *
  98     * @param $filepath
  99     *   String file path.
 100     * @param $expected_mode
 101     *   Octal integer like 0664 or 0777.
 102     * @param $message
 103     *   Optional message.
 104     */
 105    function assertFilePermissions($filepath, $expected_mode, $message = NULL) {
 106      // Clear out PHP's file stat cache to be sure we see the current value.
 107      clearstatcache();
 108  
 109      // Mask out all but the last three octets.
 110      $actual_mode = fileperms($filepath) & 0777;
 111  
 112      // PHP on Windows has limited support for file permissions. Usually each of
 113      // "user", "group" and "other" use one octal digit (3 bits) to represent the
 114      // read/write/execute bits. On Windows, chmod() ignores the "group" and
 115      // "other" bits, and fileperms() returns the "user" bits in all three
 116      // positions. $expected_mode is updated to reflect this.
 117      if (substr(PHP_OS, 0, 3) == 'WIN') {
 118        // Reset the "group" and "other" bits.
 119        $expected_mode = $expected_mode & 0700;
 120        // Shift the "user" bits to the "group" and "other" positions also.
 121        $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
 122      }
 123  
 124      if (!isset($message)) {
 125        $message = t('Expected file permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
 126      }
 127      $this->assertEqual($actual_mode, $expected_mode, $message);
 128    }
 129  
 130    /**
 131     * Helper function to test the permissions of a directory.
 132     *
 133     * @param $directory
 134     *   String directory path.
 135     * @param $expected_mode
 136     *   Octal integer like 0664 or 0777.
 137     * @param $message
 138     *   Optional message.
 139     */
 140    function assertDirectoryPermissions($directory, $expected_mode, $message = NULL) {
 141      // Clear out PHP's file stat cache to be sure we see the current value.
 142      clearstatcache();
 143  
 144      // Mask out all but the last three octets.
 145      $actual_mode = fileperms($directory) & 0777;
 146  
 147      // PHP on Windows has limited support for file permissions. Usually each of
 148      // "user", "group" and "other" use one octal digit (3 bits) to represent the
 149      // read/write/execute bits. On Windows, chmod() ignores the "group" and
 150      // "other" bits, and fileperms() returns the "user" bits in all three
 151      // positions. $expected_mode is updated to reflect this.
 152      if (substr(PHP_OS, 0, 3) == 'WIN') {
 153        // Reset the "group" and "other" bits.
 154        $expected_mode = $expected_mode & 0700;
 155        // Shift the "user" bits to the "group" and "other" positions also.
 156        $expected_mode = $expected_mode | $expected_mode >> 3 | $expected_mode >> 6;
 157      }
 158  
 159      if (!isset($message)) {
 160        $message = t('Expected directory permission to be %expected, actually were %actual.', array('%actual' => decoct($actual_mode), '%expected' => decoct($expected_mode)));
 161      }
 162      $this->assertEqual($actual_mode, $expected_mode, $message);
 163    }
 164  
 165    /**
 166     * Create a directory and assert it exists.
 167     *
 168     * @param $path
 169     *   Optional string with a directory path. If none is provided, a random
 170     *   name in the site's files directory will be used.
 171     * @return
 172     *   The path to the directory.
 173     */
 174    function createDirectory($path = NULL) {
 175      // A directory to operate on.
 176      if (!isset($path)) {
 177        $path = file_default_scheme() . '://' . $this->randomName();
 178      }
 179      $this->assertTrue(drupal_mkdir($path) && is_dir($path), t('Directory was created successfully.'));
 180      return $path;
 181    }
 182  
 183    /**
 184     * Create a file and save it to the files table and assert that it occurs
 185     * correctly.
 186     *
 187     * @param $filepath
 188     *   Optional string specifying the file path. If none is provided then a
 189     *   randomly named file will be created in the site's files directory.
 190     * @param $contents
 191     *   Optional contents to save into the file. If a NULL value is provided an
 192     *   arbitrary string will be used.
 193     * @param $scheme
 194     *   Optional string indicating the stream scheme to use. Drupal core includes
 195     *   public, private, and temporary. The public wrapper is the default.
 196     * @return
 197     *   File object.
 198     */
 199    function createFile($filepath = NULL, $contents = NULL, $scheme = NULL) {
 200      if (!isset($filepath)) {
 201        // Prefix with non-latin characters to ensure that all file-related
 202        // tests work with international filenames.
 203        $filepath = 'Файл для тестирования ' . $this->randomName();
 204      }
 205      if (!isset($scheme)) {
 206        $scheme = file_default_scheme();
 207      }
 208      $filepath = $scheme . '://' . $filepath;
 209  
 210      if (!isset($contents)) {
 211        $contents = "file_put_contents() doesn't seem to appreciate empty strings so let's put in some data.";
 212      }
 213  
 214      file_put_contents($filepath, $contents);
 215      $this->assertTrue(is_file($filepath), t('The test file exists on the disk.'), 'Create test file');
 216  
 217      $file = new stdClass();
 218      $file->uri = $filepath;
 219      $file->filename = drupal_basename($file->uri);
 220      $file->filemime = 'text/plain';
 221      $file->uid = 1;
 222      $file->timestamp = REQUEST_TIME;
 223      $file->filesize = filesize($file->uri);
 224      $file->status = 0;
 225      // Write the record directly rather than calling file_save() so we don't
 226      // invoke the hooks.
 227      $this->assertNotIdentical(drupal_write_record('file_managed', $file), FALSE, t('The file was added to the database.'), 'Create test file');
 228  
 229      return $file;
 230    }
 231  }
 232  
 233  /**
 234   * Base class for file tests that use the file_test module to test uploads and
 235   * hooks.
 236   */
 237  class FileHookTestCase extends FileTestCase {
 238    function setUp() {
 239      // Install file_test module
 240      parent::setUp('file_test');
 241      // Clear out any hook calls.
 242      file_test_reset();
 243    }
 244  
 245    /**
 246     * Assert that all of the specified hook_file_* hooks were called once, other
 247     * values result in failure.
 248     *
 249     * @param $expected
 250     *   Array with string containing with the hook name, e.g. 'load', 'save',
 251     *   'insert', etc.
 252     */
 253    function assertFileHooksCalled($expected) {
 254      // Determine which hooks were called.
 255      $actual = array_keys(array_filter(file_test_get_all_calls()));
 256  
 257      // Determine if there were any expected that were not called.
 258      $uncalled = array_diff($expected, $actual);
 259      if (count($uncalled)) {
 260        $this->assertTrue(FALSE, t('Expected hooks %expected to be called but %uncalled was not called.', array('%expected' => implode(', ', $expected), '%uncalled' => implode(', ', $uncalled))));
 261      }
 262      else {
 263        $this->assertTrue(TRUE, t('All the expected hooks were called: %expected', array('%expected' => empty($expected) ? t('(none)') : implode(', ', $expected))));
 264      }
 265  
 266      // Determine if there were any unexpected calls.
 267      $unexpected = array_diff($actual, $expected);
 268      if (count($unexpected)) {
 269        $this->assertTrue(FALSE, t('Unexpected hooks were called: %unexpected.', array('%unexpected' => empty($unexpected) ? t('(none)') : implode(', ', $unexpected))));
 270      }
 271      else {
 272        $this->assertTrue(TRUE, t('No unexpected hooks were called.'));
 273      }
 274    }
 275  
 276    /**
 277     * Assert that a hook_file_* hook was called a certain number of times.
 278     *
 279     * @param $hook
 280     *   String with the hook name, e.g. 'load', 'save', 'insert', etc.
 281     * @param $expected_count
 282     *   Optional integer count.
 283     * @param $message
 284     *   Optional translated string message.
 285     */
 286    function assertFileHookCalled($hook, $expected_count = 1, $message = NULL) {
 287      $actual_count = count(file_test_get_calls($hook));
 288  
 289      if (!isset($message)) {
 290        if ($actual_count == $expected_count) {
 291          $message = t('hook_file_@name was called correctly.', array('@name' => $hook));
 292        }
 293        elseif ($expected_count == 0) {
 294          $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));
 295        }
 296        else {
 297          $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));
 298        }
 299      }
 300      $this->assertEqual($actual_count, $expected_count, $message);
 301    }
 302  }
 303  
 304  
 305  /**
 306   *  This will run tests against the file_space_used() function.
 307   */
 308  class FileSpaceUsedTest extends FileTestCase {
 309    public static function getInfo() {
 310      return array(
 311        'name' => 'File space used tests',
 312        'description' => 'Tests the file_space_used() function.',
 313        'group' => 'File API',
 314      );
 315    }
 316  
 317    function setUp() {
 318      parent::setUp();
 319  
 320      // Create records for a couple of users with different sizes.
 321      $file = array('uid' => 2, 'uri' => 'public://example1.txt', 'filesize' => 50, 'status' => FILE_STATUS_PERMANENT);
 322      drupal_write_record('file_managed', $file);
 323      $file = array('uid' => 2, 'uri' => 'public://example2.txt', 'filesize' => 20, 'status' => FILE_STATUS_PERMANENT);
 324      drupal_write_record('file_managed', $file);
 325      $file = array('uid' => 3, 'uri' => 'public://example3.txt', 'filesize' => 100, 'status' => FILE_STATUS_PERMANENT);
 326      drupal_write_record('file_managed', $file);
 327      $file = array('uid' => 3, 'uri' => 'public://example4.txt', 'filesize' => 200, 'status' => FILE_STATUS_PERMANENT);
 328      drupal_write_record('file_managed', $file);
 329  
 330      // Now create some non-permanent files.
 331      $file = array('uid' => 2, 'uri' => 'public://example5.txt', 'filesize' => 1, 'status' => 0);
 332      drupal_write_record('file_managed', $file);
 333      $file = array('uid' => 3, 'uri' => 'public://example6.txt', 'filesize' => 3, 'status' => 0);
 334      drupal_write_record('file_managed', $file);
 335    }
 336  
 337    /**
 338     * Test different users with the default status.
 339     */
 340    function testFileSpaceUsed() {
 341      // Test different users with default status.
 342      $this->assertEqual(file_space_used(2), 70);
 343      $this->assertEqual(file_space_used(3), 300);
 344      $this->assertEqual(file_space_used(), 370);
 345  
 346      // Test the status fields
 347      $this->assertEqual(file_space_used(NULL, 0), 4);
 348      $this->assertEqual(file_space_used(NULL, FILE_STATUS_PERMANENT), 370);
 349  
 350      // Test both the user and status.
 351      $this->assertEqual(file_space_used(1, 0), 0);
 352      $this->assertEqual(file_space_used(1, FILE_STATUS_PERMANENT), 0);
 353      $this->assertEqual(file_space_used(2, 0), 1);
 354      $this->assertEqual(file_space_used(2, FILE_STATUS_PERMANENT), 70);
 355      $this->assertEqual(file_space_used(3, 0), 3);
 356      $this->assertEqual(file_space_used(3, FILE_STATUS_PERMANENT), 300);
 357    }
 358  }
 359  
 360  /**
 361   *  This will run tests against the file validation functions (file_validate_*).
 362   */
 363  class FileValidatorTest extends DrupalWebTestCase {
 364    public static function getInfo() {
 365      return array(
 366        'name' => 'File validator tests',
 367        'description' => 'Tests the functions used to validate uploaded files.',
 368        'group' => 'File API',
 369      );
 370    }
 371  
 372    function setUp() {
 373      parent::setUp();
 374  
 375      $this->image = new stdClass();
 376      $this->image->uri = 'misc/druplicon.png';
 377      $this->image->filename = drupal_basename($this->image->uri);
 378  
 379      $this->non_image = new stdClass();
 380      $this->non_image->uri = 'misc/jquery.js';
 381      $this->non_image->filename = drupal_basename($this->non_image->uri);
 382    }
 383  
 384    /**
 385     * Test the file_validate_extensions() function.
 386     */
 387    function testFileValidateExtensions() {
 388      $file = new stdClass();
 389      $file->filename = 'asdf.txt';
 390      $errors = file_validate_extensions($file, 'asdf txt pork');
 391      $this->assertEqual(count($errors), 0, t('Valid extension accepted.'), 'File');
 392  
 393      $file->filename = 'asdf.txt';
 394      $errors = file_validate_extensions($file, 'exe png');
 395      $this->assertEqual(count($errors), 1, t('Invalid extension blocked.'), 'File');
 396    }
 397  
 398    /**
 399     *  This ensures a specific file is actually an image.
 400     */
 401    function testFileValidateIsImage() {
 402      $this->assertTrue(file_exists($this->image->uri), t('The image being tested exists.'), 'File');
 403      $errors = file_validate_is_image($this->image);
 404      $this->assertEqual(count($errors), 0, t('No error reported for our image file.'), 'File');
 405  
 406      $this->assertTrue(file_exists($this->non_image->uri), t('The non-image being tested exists.'), 'File');
 407      $errors = file_validate_is_image($this->non_image);
 408      $this->assertEqual(count($errors), 1, t('An error reported for our non-image file.'), 'File');
 409    }
 410  
 411    /**
 412     *  This ensures the resolution of a specific file is within bounds.
 413     *  The image will be resized if it's too large.
 414     */
 415    function testFileValidateImageResolution() {
 416      // Non-images.
 417      $errors = file_validate_image_resolution($this->non_image);
 418      $this->assertEqual(count($errors), 0, t("Shouldn't get any errors for a non-image file."), 'File');
 419      $errors = file_validate_image_resolution($this->non_image, '50x50', '100x100');
 420      $this->assertEqual(count($errors), 0, t("Don't check the resolution on non files."), 'File');
 421  
 422      // Minimum size.
 423      $errors = file_validate_image_resolution($this->image);
 424      $this->assertEqual(count($errors), 0, t('No errors for an image when there is no minimum or maximum resolution.'), 'File');
 425      $errors = file_validate_image_resolution($this->image, 0, '200x1');
 426      $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't wide enough."), 'File');
 427      $errors = file_validate_image_resolution($this->image, 0, '1x200');
 428      $this->assertEqual(count($errors), 1, t("Got an error for an image that wasn't tall enough."), 'File');
 429      $errors = file_validate_image_resolution($this->image, 0, '200x200');
 430      $this->assertEqual(count($errors), 1, t('Small images report an error.'), 'File');
 431  
 432      // Maximum size.
 433      if (image_get_toolkit()) {
 434        // Copy the image so that the original doesn't get resized.
 435        copy('misc/druplicon.png', 'temporary://druplicon.png');
 436        $this->image->uri = 'temporary://druplicon.png';
 437  
 438        $errors = file_validate_image_resolution($this->image, '10x5');
 439        $this->assertEqual(count($errors), 0, t('No errors should be reported when an oversized image can be scaled down.'), 'File');
 440  
 441        $info = image_get_info($this->image->uri);
 442        $this->assertTrue($info['width'] <= 10, t('Image scaled to correct width.'), 'File');
 443        $this->assertTrue($info['height'] <= 5, t('Image scaled to correct height.'), 'File');
 444  
 445        drupal_unlink('temporary://druplicon.png');
 446      }
 447      else {
 448        // TODO: should check that the error is returned if no toolkit is available.
 449        $errors = file_validate_image_resolution($this->image, '5x10');
 450        $this->assertEqual(count($errors), 1, t("Oversize images that can't be scaled get an error."), 'File');
 451      }
 452    }
 453  
 454    /**
 455     *  This will ensure the filename length is valid.
 456     */
 457    function testFileValidateNameLength() {
 458      // Create a new file object.
 459      $file = new stdClass();
 460  
 461      // Add a filename with an allowed length and test it.
 462      $file->filename = str_repeat('x', 240);
 463      $this->assertEqual(strlen($file->filename), 240);
 464      $errors = file_validate_name_length($file);
 465      $this->assertEqual(count($errors), 0, t('No errors reported for 240 length filename.'), 'File');
 466  
 467      // Add a filename with a length too long and test it.
 468      $file->filename = str_repeat('x', 241);
 469      $errors = file_validate_name_length($file);
 470      $this->assertEqual(count($errors), 1, t('An error reported for 241 length filename.'), 'File');
 471  
 472      // Add a filename with an empty string and test it.
 473      $file->filename = '';
 474      $errors = file_validate_name_length($file);
 475      $this->assertEqual(count($errors), 1, t('An error reported for 0 length filename.'), 'File');
 476    }
 477  
 478  
 479    /**
 480     * Test file_validate_size().
 481     */
 482    function testFileValidateSize() {
 483      global $user;
 484      $original_user = $user;
 485      drupal_save_session(FALSE);
 486  
 487      // Run these test as uid = 1.
 488      $user = user_load(1);
 489  
 490      $file = new stdClass();
 491      $file->filesize = 999999;
 492      $errors = file_validate_size($file, 1, 1);
 493      $this->assertEqual(count($errors), 0, t('No size limits enforced on uid=1.'), 'File');
 494  
 495      // Run these tests as a regular user.
 496      $user = $this->drupalCreateUser();
 497  
 498      // Create a file with a size of 1000 bytes, and quotas of only 1 byte.
 499      $file = new stdClass();
 500      $file->filesize = 1000;
 501      $errors = file_validate_size($file, 0, 0);
 502      $this->assertEqual(count($errors), 0, t('No limits means no errors.'), 'File');
 503      $errors = file_validate_size($file, 1, 0);
 504      $this->assertEqual(count($errors), 1, t('Error for the file being over the limit.'), 'File');
 505      $errors = file_validate_size($file, 0, 1);
 506      $this->assertEqual(count($errors), 1, t('Error for the user being over their limit.'), 'File');
 507      $errors = file_validate_size($file, 1, 1);
 508      $this->assertEqual(count($errors), 2, t('Errors for both the file and their limit.'), 'File');
 509  
 510      $user = $original_user;
 511      drupal_save_session(TRUE);
 512    }
 513  }
 514  
 515  
 516  
 517  /**
 518   *  Tests the file_unmanaged_save_data() function.
 519   */
 520  class FileUnmanagedSaveDataTest extends FileTestCase {
 521    public static function getInfo() {
 522      return array(
 523        'name' => 'Unmanaged file save data',
 524        'description' => 'Tests the unmanaged file save data function.',
 525        'group' => 'File API',
 526      );
 527    }
 528  
 529    /**
 530     * Test the file_unmanaged_save_data() function.
 531     */
 532    function testFileSaveData() {
 533      $contents = $this->randomName(8);
 534  
 535      // No filename.
 536      $filepath = file_unmanaged_save_data($contents);
 537      $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
 538      $this->assertEqual(file_uri_scheme($filepath), file_default_scheme(), t("File was placed in Drupal's files directory."));
 539      $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
 540  
 541      // Provide a filename.
 542      $filepath = file_unmanaged_save_data($contents, 'public://asdf.txt', FILE_EXISTS_REPLACE);
 543      $this->assertTrue($filepath, t('Unnamed file saved correctly.'));
 544      $this->assertEqual('asdf.txt', drupal_basename($filepath), t('File was named correctly.'));
 545      $this->assertEqual($contents, file_get_contents($filepath), t('Contents of the file are correct.'));
 546      $this->assertFilePermissions($filepath, variable_get('file_chmod_file', 0664));
 547    }
 548  }
 549  
 550  /**
 551   *  Tests the file_unmanaged_save_data() function on remote filesystems.
 552   */
 553  class RemoteFileUnmanagedSaveDataTest extends FileUnmanagedSaveDataTest {
 554    public static function getInfo() {
 555      $info = parent::getInfo();
 556      $info['group'] = 'File API (remote)';
 557      return $info;
 558    }
 559  
 560    function setUp() {
 561      parent::setUp('file_test');
 562      variable_set('file_default_scheme', 'dummy-remote');
 563    }
 564  }
 565  
 566  /**
 567   * Test the file_save_upload() function.
 568   */
 569  class FileSaveUploadTest extends FileHookTestCase {
 570    /**
 571     * An image file path for uploading.
 572     */
 573    protected $image;
 574  
 575    /**
 576     * A PHP file path for upload security testing.
 577     */
 578    protected $phpfile;
 579  
 580    /**
 581     * The largest file id when the test starts.
 582     */
 583    protected $maxFidBefore;
 584  
 585    public static function getInfo() {
 586      return array(
 587        'name' => 'File uploading',
 588        'description' => 'Tests the file uploading functions.',
 589        'group' => 'File API',
 590      );
 591    }
 592  
 593    function setUp() {
 594      parent::setUp();
 595      $account = $this->drupalCreateUser(array('access content'));
 596      $this->drupalLogin($account);
 597  
 598      $image_files = $this->drupalGetTestFiles('image');
 599      $this->image = current($image_files);
 600  
 601      list(, $this->image_extension) = explode('.', $this->image->filename);
 602      $this->assertTrue(is_file($this->image->uri), t("The image file we're going to upload exists."));
 603  
 604      $this->phpfile = current($this->drupalGetTestFiles('php'));
 605      $this->assertTrue(is_file($this->phpfile->uri), t("The PHP file we're going to upload exists."));
 606  
 607      $this->maxFidBefore = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
 608  
 609      // Upload with replace to guarantee there's something there.
 610      $edit = array(
 611        'file_test_replace' => FILE_EXISTS_REPLACE,
 612        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 613      );
 614      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 615      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 616      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 617  
 618      // Check that the correct hooks were called then clean out the hook
 619      // counters.
 620      $this->assertFileHooksCalled(array('validate', 'insert'));
 621      file_test_reset();
 622    }
 623  
 624    /**
 625     * Test the file_save_upload() function.
 626     */
 627    function testNormal() {
 628      $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
 629      $this->assertTrue($max_fid_after > $this->maxFidBefore, t('A new file was created.'));
 630      $file1 = file_load($max_fid_after);
 631      $this->assertTrue($file1, t('Loaded the file.'));
 632      // MIME type of the uploaded image may be either image/jpeg or image/png.
 633      $this->assertEqual(substr($file1->filemime, 0, 5), 'image', 'A MIME type was set.');
 634  
 635      // Reset the hook counters to get rid of the 'load' we just called.
 636      file_test_reset();
 637  
 638      // Upload a second file.
 639      $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
 640      $image2 = current($this->drupalGetTestFiles('image'));
 641      $edit = array('files[file_test_upload]' => drupal_realpath($image2->uri));
 642      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 643      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 644      $this->assertRaw(t('You WIN!'));
 645      $max_fid_after = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField();
 646  
 647      // Check that the correct hooks were called.
 648      $this->assertFileHooksCalled(array('validate', 'insert'));
 649  
 650      $file2 = file_load($max_fid_after);
 651      $this->assertTrue($file2);
 652      // MIME type of the uploaded image may be either image/jpeg or image/png.
 653      $this->assertEqual(substr($file2->filemime, 0, 5), 'image', 'A MIME type was set.');
 654  
 655      // Load both files using file_load_multiple().
 656      $files = file_load_multiple(array($file1->fid, $file2->fid));
 657      $this->assertTrue(isset($files[$file1->fid]), t('File was loaded successfully'));
 658      $this->assertTrue(isset($files[$file2->fid]), t('File was loaded successfully'));
 659  
 660      // Upload a third file to a subdirectory.
 661      $image3 = current($this->drupalGetTestFiles('image'));
 662      $image3_realpath = drupal_realpath($image3->uri);
 663      $dir = $this->randomName();
 664      $edit = array(
 665        'files[file_test_upload]' => $image3_realpath,
 666        'file_subdir' => $dir,
 667      );
 668      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 669      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 670      $this->assertRaw(t('You WIN!'));
 671      $this->assertTrue(is_file('temporary://' . $dir . '/' . trim(drupal_basename($image3_realpath))));
 672  
 673      // Check that file_load_multiple() with no arguments returns FALSE.
 674      $this->assertFalse(file_load_multiple(), t('No files were loaded.'));
 675    }
 676  
 677    /**
 678     * Test extension handling.
 679     */
 680    function testHandleExtension() {
 681      // The file being tested is a .gif which is in the default safe list
 682      // of extensions to allow when the extension validator isn't used. This is
 683      // implicitly tested at the testNormal() test. Here we tell
 684      // file_save_upload() to only allow ".foo".
 685      $extensions = 'foo';
 686      $edit = array(
 687        'file_test_replace' => FILE_EXISTS_REPLACE,
 688        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 689        'extensions' => $extensions,
 690      );
 691  
 692      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 693      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 694      $message = t('Only files with the following extensions are allowed:') . ' <em class="placeholder">' . $extensions . '</em>';
 695      $this->assertRaw($message, t('Can\'t upload a disallowed extension'));
 696      $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.'));
 697  
 698      // Check that the correct hooks were called.
 699      $this->assertFileHooksCalled(array('validate'));
 700  
 701      // Reset the hook counters.
 702      file_test_reset();
 703  
 704      $extensions = 'foo ' . $this->image_extension;
 705      // Now tell file_save_upload() to allow the extension of our test image.
 706      $edit = array(
 707        'file_test_replace' => FILE_EXISTS_REPLACE,
 708        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 709        'extensions' => $extensions,
 710      );
 711  
 712      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 713      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 714      $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload an allowed extension.'));
 715      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 716  
 717      // Check that the correct hooks were called.
 718      $this->assertFileHooksCalled(array('validate', 'load', 'update'));
 719  
 720      // Reset the hook counters.
 721      file_test_reset();
 722  
 723      // Now tell file_save_upload() to allow any extension.
 724      $edit = array(
 725        'file_test_replace' => FILE_EXISTS_REPLACE,
 726        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 727        'allow_all_extensions' => TRUE,
 728      );
 729      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 730      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 731      $this->assertNoRaw(t('Only files with the following extensions are allowed:'), t('Can upload any extension.'));
 732      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 733  
 734      // Check that the correct hooks were called.
 735      $this->assertFileHooksCalled(array('validate', 'load', 'update'));
 736    }
 737  
 738    /**
 739     * Test dangerous file handling.
 740     */
 741    function testHandleDangerousFile() {
 742      // Allow the .php extension and make sure it gets renamed to .txt for
 743      // safety. Also check to make sure its MIME type was changed.
 744      $edit = array(
 745        'file_test_replace' => FILE_EXISTS_REPLACE,
 746        'files[file_test_upload]' => drupal_realpath($this->phpfile->uri),
 747        'is_image_file' => FALSE,
 748        'extensions' => 'php',
 749      );
 750  
 751      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 752      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 753      $message = t('For security reasons, your upload has been renamed to') . ' <em class="placeholder">' . $this->phpfile->filename . '.txt' . '</em>';
 754      $this->assertRaw($message, t('Dangerous file was renamed.'));
 755      $this->assertRaw(t('File MIME type is text/plain.'), t('Dangerous file\'s MIME type was changed.'));
 756      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 757  
 758      // Check that the correct hooks were called.
 759      $this->assertFileHooksCalled(array('validate', 'insert'));
 760  
 761      // Ensure dangerous files are not renamed when insecure uploads is TRUE.
 762      // Turn on insecure uploads.
 763      variable_set('allow_insecure_uploads', 1);
 764      // Reset the hook counters.
 765      file_test_reset();
 766  
 767      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 768      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 769      $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.'));
 770      $this->assertRaw(t('File name is !filename', array('!filename' => $this->phpfile->filename)), t('Dangerous file was not renamed when insecure uploads is TRUE.'));
 771      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 772  
 773      // Check that the correct hooks were called.
 774      $this->assertFileHooksCalled(array('validate', 'insert'));
 775  
 776      // Turn off insecure uploads.
 777      variable_set('allow_insecure_uploads', 0);
 778    }
 779  
 780    /**
 781     * Test file munge handling.
 782     */
 783    function testHandleFileMunge() {
 784      // Ensure insecure uploads are disabled for this test.
 785      variable_set('allow_insecure_uploads', 0);
 786      $this->image = file_move($this->image, $this->image->uri . '.foo.' . $this->image_extension);
 787  
 788      // Reset the hook counters to get rid of the 'move' we just called.
 789      file_test_reset();
 790  
 791      $extensions = $this->image_extension;
 792      $edit = array(
 793        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 794        'extensions' => $extensions,
 795      );
 796  
 797      $munged_filename = $this->image->filename;
 798      $munged_filename = substr($munged_filename, 0, strrpos($munged_filename, '.'));
 799      $munged_filename .= '_.' . $this->image_extension;
 800  
 801      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 802      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 803      $this->assertRaw(t('For security reasons, your upload has been renamed'), t('Found security message.'));
 804      $this->assertRaw(t('File name is !filename', array('!filename' => $munged_filename)), t('File was successfully munged.'));
 805      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 806  
 807      // Check that the correct hooks were called.
 808      $this->assertFileHooksCalled(array('validate', 'insert'));
 809  
 810      // Ensure we don't munge files if we're allowing any extension.
 811      // Reset the hook counters.
 812      file_test_reset();
 813  
 814      $edit = array(
 815        'files[file_test_upload]' => drupal_realpath($this->image->uri),
 816        'allow_all_extensions' => TRUE,
 817      );
 818  
 819      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 820      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 821      $this->assertNoRaw(t('For security reasons, your upload has been renamed'), t('Found no security message.'));
 822      $this->assertRaw(t('File name is !filename', array('!filename' => $this->image->filename)), t('File was not munged when allowing any extension.'));
 823      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 824  
 825      // Check that the correct hooks were called.
 826      $this->assertFileHooksCalled(array('validate', 'insert'));
 827    }
 828  
 829    /**
 830     * Test renaming when uploading over a file that already exists.
 831     */
 832    function testExistingRename() {
 833      $edit = array(
 834        'file_test_replace' => FILE_EXISTS_RENAME,
 835        'files[file_test_upload]' => drupal_realpath($this->image->uri)
 836      );
 837      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 838      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 839      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 840  
 841      // Check that the correct hooks were called.
 842      $this->assertFileHooksCalled(array('validate', 'insert'));
 843    }
 844  
 845    /**
 846     * Test replacement when uploading over a file that already exists.
 847     */
 848    function testExistingReplace() {
 849      $edit = array(
 850        'file_test_replace' => FILE_EXISTS_REPLACE,
 851        'files[file_test_upload]' => drupal_realpath($this->image->uri)
 852      );
 853      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 854      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 855      $this->assertRaw(t('You WIN!'), t('Found the success message.'));
 856  
 857      // Check that the correct hooks were called.
 858      $this->assertFileHooksCalled(array('validate', 'load', 'update'));
 859    }
 860  
 861    /**
 862     * Test for failure when uploading over a file that already exists.
 863     */
 864    function testExistingError() {
 865      $edit = array(
 866        'file_test_replace' => FILE_EXISTS_ERROR,
 867        'files[file_test_upload]' => drupal_realpath($this->image->uri)
 868      );
 869      $this->drupalPost('file-test/upload', $edit, t('Submit'));
 870      $this->assertResponse(200, t('Received a 200 response for posted test file.'));
 871      $this->assertRaw(t('Epic upload FAIL!'), t('Found the failure message.'));
 872  
 873      // Check that the no hooks were called while failing.
 874      $this->assertFileHooksCalled(array());
 875    }
 876  
 877    /**
 878     * Test for no failures when not uploading a file.
 879     */
 880    function testNoUpload() {
 881      $this->drupalPost('file-test/upload', array(), t('Submit'));
 882      $this->assertNoRaw(t('Epic upload FAIL!'), t('Failure message not found.'));
 883    }
 884  }
 885  
 886  /**
 887   * Test the file_save_upload() function on remote filesystems.
 888   */
 889  class RemoteFileSaveUploadTest extends FileSaveUploadTest {
 890    public static function getInfo() {
 891      $info = parent::getInfo();
 892      $info['group'] = 'File API (remote)';
 893      return $info;
 894    }
 895  
 896    function setUp() {
 897      parent::setUp('file_test');
 898      variable_set('file_default_scheme', 'dummy-remote');
 899    }
 900  }
 901  
 902  /**
 903   * Directory related tests.
 904   */
 905  class FileDirectoryTest extends FileTestCase {
 906    public static function getInfo() {
 907      return array(
 908        'name' => 'File paths and directories',
 909        'description' => 'Tests operations dealing with directories.',
 910        'group' => 'File API',
 911      );
 912    }
 913  
 914    /**
 915     * Test directory handling functions.
 916     */
 917    function testFileCheckDirectoryHandling() {
 918      // A directory to operate on.
 919      $directory = file_default_scheme() . '://' . $this->randomName() . '/' . $this->randomName();
 920      $this->assertFalse(is_dir($directory), t('Directory does not exist prior to testing.'));
 921  
 922      // Non-existent directory.
 923      $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for non-existing directory.'), 'File');
 924  
 925      // Make a directory.
 926      $this->assertTrue(file_prepare_directory($directory, FILE_CREATE_DIRECTORY), t('No error reported when creating a new directory.'), 'File');
 927  
 928      // Make sure directory actually exists.
 929      $this->assertTrue(is_dir($directory), t('Directory actually exists.'), 'File');
 930  
 931      if (substr(PHP_OS, 0, 3) != 'WIN') {
 932        // PHP on Windows doesn't support any kind of useful read-only mode for
 933        // directories. When executing a chmod() on a directory, PHP only sets the
 934        // read-only flag, which doesn't prevent files to actually be written
 935        // in the directory on any recent version of Windows.
 936  
 937        // Make directory read only.
 938        @drupal_chmod($directory, 0444);
 939        $this->assertFalse(file_prepare_directory($directory, 0), t('Error reported for a non-writeable directory.'), 'File');
 940  
 941        // Test directory permission modification.
 942        $this->assertTrue(file_prepare_directory($directory, FILE_MODIFY_PERMISSIONS), t('No error reported when making directory writeable.'), 'File');
 943      }
 944  
 945      // Test that the directory has the correct permissions.
 946      $this->assertDirectoryPermissions($directory, variable_get('file_chmod_directory', 0775));
 947  
 948      // Remove .htaccess file to then test that it gets re-created.
 949      @drupal_unlink(file_default_scheme() . '://.htaccess');
 950      $this->assertFalse(is_file(file_default_scheme() . '://.htaccess'), t('Successfully removed the .htaccess file in the files directory.'), 'File');
 951      file_ensure_htaccess();
 952      $this->assertTrue(is_file(file_default_scheme() . '://.htaccess'), t('Successfully re-created the .htaccess file in the files directory.'), 'File');
 953      // Verify contents of .htaccess file.
 954      $file = file_get_contents(file_default_scheme() . '://.htaccess');
 955      $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');
 956    }
 957  
 958    /**
 959     * This will take a directory and path, and find a valid filepath that is not
 960     * taken by another file.
 961     */
 962    function testFileCreateNewFilepath() {
 963      // First we test against an imaginary file that does not exist in a
 964      // directory.
 965      $basename = 'xyz.txt';
 966      $directory = 'misc';
 967      $original = $directory . '/' . $basename;
 968      $path = file_create_filename($basename, $directory);
 969      $this->assertEqual($path, $original, t('New filepath %new equals %original.', array('%new' => $path, '%original' => $original)), 'File');
 970  
 971      // Then we test against a file that already exists within that directory.
 972      $basename = 'druplicon.png';
 973      $original = $directory . '/' . $basename;
 974      $expected = $directory . '/druplicon_0.png';
 975      $path = file_create_filename($basename, $directory);
 976      $this->assertEqual($path, $expected, t('Creating a new filepath from %original equals %new.', array('%new' => $path, '%original' => $original)), 'File');
 977  
 978      // @TODO: Finally we copy a file into a directory several times, to ensure a properly iterating filename suffix.
 979    }
 980  
 981    /**
 982     * This will test the filepath for a destination based on passed flags and
 983     * whether or not the file exists.
 984     *
 985     * If a file exists, file_destination($destination, $replace) will either
 986     * return:
 987     * - the existing filepath, if $replace is FILE_EXISTS_REPLACE
 988     * - a new filepath if FILE_EXISTS_RENAME
 989     * - an error (returning FALSE) if FILE_EXISTS_ERROR.
 990     * If the file doesn't currently exist, then it will simply return the
 991     * filepath.
 992     */
 993    function testFileDestination() {
 994      // First test for non-existent file.
 995      $destination = 'misc/xyz.txt';
 996      $path = file_destination($destination, FILE_EXISTS_REPLACE);
 997      $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_REPLACE.'), 'File');
 998      $path = file_destination($destination, FILE_EXISTS_RENAME);
 999      $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_RENAME.'), 'File');
1000      $path = file_destination($destination, FILE_EXISTS_ERROR);
1001      $this->assertEqual($path, $destination, t('Non-existing filepath destination is correct with FILE_EXISTS_ERROR.'), 'File');
1002  
1003      $destination = 'misc/druplicon.png';
1004      $path = file_destination($destination, FILE_EXISTS_REPLACE);
1005      $this->assertEqual($path, $destination, t('Existing filepath destination remains the same with FILE_EXISTS_REPLACE.'), 'File');
1006      $path = file_destination($destination, FILE_EXISTS_RENAME);
1007      $this->assertNotEqual($path, $destination, t('A new filepath destination is created when filepath destination already exists with FILE_EXISTS_RENAME.'), 'File');
1008      $path = file_destination($destination, FILE_EXISTS_ERROR);
1009      $this->assertEqual($path, FALSE, t('An error is returned when filepath destination already exists with FILE_EXISTS_ERROR.'), 'File');
1010    }
1011  
1012    /**
1013     * Ensure that the file_directory_temp() function always returns a value.
1014     */
1015    function testFileDirectoryTemp() {
1016      // Start with an empty variable to ensure we have a clean slate.
1017      variable_set('file_temporary_path', '');
1018      $tmp_directory = file_directory_temp();
1019      $this->assertEqual(empty($tmp_directory), FALSE, t('file_directory_temp() returned a non-empty value.'));
1020      $setting = variable_get('file_temporary_path', '');
1021      $this->assertEqual($setting, $tmp_directory, t("The 'file_temporary_path' variable has the same value that file_directory_temp() returned."));
1022    }
1023  }
1024  
1025  /**
1026   * Directory related tests.
1027   */
1028  class RemoteFileDirectoryTest extends FileDirectoryTest {
1029    public static function getInfo() {
1030      $info = parent::getInfo();
1031      $info['group'] = 'File API (remote)';
1032      return $info;
1033    }
1034  
1035    function setUp() {
1036      parent::setUp('file_test');
1037      variable_set('file_default_scheme', 'dummy-remote');
1038    }
1039  }
1040  
1041  /**
1042   * Tests the file_scan_directory() function.
1043   */
1044  class FileScanDirectoryTest extends FileTestCase {
1045    public static function getInfo() {
1046      return array(
1047        'name' => 'File scan directory',
1048        'description' => 'Tests the file_scan_directory() function.',
1049        'group' => 'File API',
1050      );
1051    }
1052  
1053    function setUp() {
1054      parent::setUp();
1055      $this->path = drupal_get_path('module', 'simpletest') . '/files';
1056    }
1057  
1058    /**
1059     * Check the format of the returned values.
1060     */
1061    function testReturn() {
1062      // Grab a listing of all the JavaSscript files and check that they're
1063      // passed to the callback.
1064      $all_files = file_scan_directory($this->path, '/^javascript-/');
1065      ksort($all_files);
1066      $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.'));
1067  
1068      // Check the first file.
1069      $file = reset($all_files);
1070      $this->assertEqual(key($all_files), $file->uri, t('Correct array key was used for the first returned file.'));
1071      $this->assertEqual($file->uri, $this->path . '/javascript-1.txt', t('First file name was set correctly.'));
1072      $this->assertEqual($file->filename, 'javascript-1.txt', t('First basename was set correctly'));
1073      $this->assertEqual($file->name, 'javascript-1', t('First name was set correctly.'));
1074  
1075      // Check the second file.
1076      $file = next($all_files);
1077      $this->assertEqual(key($all_files), $file->uri, t('Correct array key was used for the second returned file.'));
1078      $this->assertEqual($file->uri, $this->path . '/javascript-2.script', t('Second file name was set correctly.'));
1079      $this->assertEqual($file->filename, 'javascript-2.script', t('Second basename was set correctly'));
1080      $this->assertEqual($file->name, 'javascript-2', t('Second name was set correctly.'));
1081    }
1082  
1083    /**
1084     * Check that the callback function is called correctly.
1085     */
1086    function testOptionCallback() {
1087      // When nothing is matched nothing should be passed to the callback.
1088      $all_files = file_scan_directory($this->path, '/^NONEXISTINGFILENAME/', array('callback' => 'file_test_file_scan_callback'));
1089      $this->assertEqual(0, count($all_files), t('No files were found.'));
1090      $results = file_test_file_scan_callback();
1091      file_test_file_scan_callback_reset();
1092      $this->assertEqual(0, count($results), t('No files were passed to the callback.'));
1093  
1094      // Grab a listing of all the JavaSscript files and check that they're
1095      // passed to the callback.
1096      $all_files = file_scan_directory($this->path, '/^javascript-/', array('callback' => 'file_test_file_scan_callback'));
1097      $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.'));
1098      $results = file_test_file_scan_callback();
1099      file_test_file_scan_callback_reset();
1100      $this->assertEqual(2, count($results), t('Files were passed to the callback.'));
1101    }
1102  
1103    /**
1104     * Check that the no-mask parameter is honored.
1105     */
1106    function testOptionNoMask() {
1107      // Grab a listing of all the JavaSscript files.
1108      $all_files = file_scan_directory($this->path, '/^javascript-/');
1109      $this->assertEqual(2, count($all_files), t('Found two, expected javascript files.'));
1110  
1111      // Now use the nomast parameter to filter out the .script file.
1112      $filtered_files = file_scan_directory($this->path, '/^javascript-/', array('nomask' => '/.script$/'));
1113      $this->assertEqual(1, count($filtered_files), t('Filtered correctly.'));
1114    }
1115  
1116    /**
1117     * Check that key parameter sets the return value's key.
1118     */
1119    function testOptionKey() {
1120      // "filename", for the path starting with $dir.
1121      $expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script');
1122      $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filepath')));
1123      sort($actual);
1124      $this->assertEqual($expected, $actual, t('Returned the correct values for the filename key.'));
1125  
1126      // "basename", for the basename of the file.
1127      $expected = array('javascript-1.txt', 'javascript-2.script');
1128      $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'filename')));
1129      sort($actual);
1130      $this->assertEqual($expected, $actual, t('Returned the correct values for the basename key.'));
1131  
1132      // "name" for the name of the file without an extension.
1133      $expected = array('javascript-1', 'javascript-2');
1134      $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'name')));
1135      sort($actual);
1136      $this->assertEqual($expected, $actual, t('Returned the correct values for the name key.'));
1137  
1138      // Invalid option that should default back to "filename".
1139      $expected = array($this->path . '/javascript-1.txt', $this->path . '/javascript-2.script');
1140      $actual = array_keys(file_scan_directory($this->path, '/^javascript-/', array('key' => 'INVALID')));
1141      sort($actual);
1142      $this->assertEqual($expected, $actual, t('An invalid key defaulted back to the default.'));
1143    }
1144  
1145    /**
1146     * Check that the recurse option decends into subdirectories.
1147     */
1148    function testOptionRecurse() {
1149      $files = file_scan_directory(drupal_get_path('module', 'simpletest'), '/^javascript-/', array('recurse' => FALSE));
1150      $this->assertTrue(empty($files), t("Without recursion couldn't find javascript files."));
1151  
1152      $files = file_scan_directory(drupal_get_path('module', 'simpletest'), '/^javascript-/', array('recurse' => TRUE));
1153      $this->assertEqual(2, count($files), t('With recursion we found the expected javascript files.'));
1154    }
1155  
1156  
1157    /**
1158     * Check that the min_depth options lets us ignore files in the starting
1159     * directory.
1160     */
1161    function testOptionMinDepth() {
1162      $files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 0));
1163      $this->assertEqual(2, count($files), t('No minimum-depth gets files in current directory.'));
1164  
1165      $files = file_scan_directory($this->path, '/^javascript-/', array('min_depth' => 1));
1166      $this->assertTrue(empty($files), t("Minimum-depth of 1 successfully excludes files from current directory."));
1167    }
1168  }
1169  
1170  /**
1171   * Tests the file_scan_directory() function on remote filesystems.
1172   */
1173  class RemoteFileScanDirectoryTest extends FileScanDirectoryTest {
1174    public static function getInfo() {
1175      $info = parent::getInfo();
1176      $info['group'] = 'File API (remote)';
1177      return $info;
1178    }
1179  
1180    function setUp() {
1181      parent::setUp('file_test');
1182      variable_set('file_default_scheme', 'dummy-remote');
1183    }
1184  }
1185  
1186  /**
1187   * Deletion related tests.
1188   */
1189  class FileUnmanagedDeleteTest extends FileTestCase {
1190    public static function getInfo() {
1191      return array(
1192        'name' => 'Unmanaged file delete',
1193        'description' => 'Tests the unmanaged file delete function.',
1194        'group' => 'File API',
1195      );
1196    }
1197  
1198    /**
1199     * Delete a normal file.
1200     */
1201    function testNormal() {
1202      // Create a file for testing
1203      $file = $this->createFile();
1204  
1205      // Delete a regular file
1206      $this->assertTrue(file_unmanaged_delete($file->uri), t('Deleted worked.'));
1207      $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.'));
1208    }
1209  
1210    /**
1211     * Try deleting a missing file.
1212     */
1213    function testMissing() {
1214      // Try to delete a non-existing file
1215      $this->assertTrue(file_unmanaged_delete(file_default_scheme() . '/' . $this->randomName()), t('Returns true when deleting a non-existent file.'));
1216    }
1217  
1218    /**
1219     * Try deleting a directory.
1220     */
1221    function testDirectory() {
1222      // A directory to operate on.
1223      $directory = $this->createDirectory();
1224  
1225      // Try to delete a directory
1226      $this->assertFalse(file_unmanaged_delete($directory), t('Could not delete the delete directory.'));
1227      $this->assertTrue(file_exists($directory), t('Directory has not been deleted.'));
1228    }
1229  }
1230  
1231  /**
1232   * Deletion related tests on remote filesystems.
1233   */
1234  class RemoteFileUnmanagedDeleteTest extends FileUnmanagedDeleteTest {
1235    public static function getInfo() {
1236      $info = parent::getInfo();
1237      $info['group'] = 'File API (remote)';
1238      return $info;
1239    }
1240  
1241    function setUp() {
1242      parent::setUp('file_test');
1243      variable_set('file_default_scheme', 'dummy-remote');
1244    }
1245  }
1246  
1247  /**
1248   * Deletion related tests.
1249   */
1250  class FileUnmanagedDeleteRecursiveTest extends FileTestCase {
1251    public static function getInfo() {
1252      return array(
1253        'name' => 'Unmanaged recursive file delete',
1254        'description' => 'Tests the unmanaged file delete recursive function.',
1255        'group' => 'File API',
1256      );
1257    }
1258  
1259    /**
1260     * Delete a normal file.
1261     */
1262    function testSingleFile() {
1263      // Create a file for testing
1264      $filepath = file_default_scheme() . '://' . $this->randomName();
1265      file_put_contents($filepath, '');
1266  
1267      // Delete the file.
1268      $this->assertTrue(file_unmanaged_delete_recursive($filepath), t('Function reported success.'));
1269      $this->assertFalse(file_exists($filepath), t('Test file has been deleted.'));
1270    }
1271  
1272    /**
1273     * Try deleting an empty directory.
1274     */
1275    function testEmptyDirectory() {
1276      // A directory to operate on.
1277      $directory = $this->createDirectory();
1278  
1279      // Delete the directory.
1280      $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.'));
1281      $this->assertFalse(file_exists($directory), t('Directory has been deleted.'));
1282    }
1283  
1284    /**
1285     * Try deleting a directory with some files.
1286     */
1287    function testDirectory() {
1288      // A directory to operate on.
1289      $directory = $this->createDirectory();
1290      $filepathA = $directory . '/A';
1291      $filepathB = $directory . '/B';
1292      file_put_contents($filepathA, '');
1293      file_put_contents($filepathB, '');
1294  
1295      // Delete the directory.
1296      $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.'));
1297      $this->assertFalse(file_exists($filepathA), t('Test file A has been deleted.'));
1298      $this->assertFalse(file_exists($filepathB), t('Test file B has been deleted.'));
1299      $this->assertFalse(file_exists($directory), t('Directory has been deleted.'));
1300    }
1301  
1302    /**
1303     * Try deleting subdirectories with some files.
1304     */
1305    function testSubDirectory() {
1306      // A directory to operate on.
1307      $directory = $this->createDirectory();
1308      $subdirectory = $this->createDirectory($directory . '/sub');
1309      $filepathA = $directory . '/A';
1310      $filepathB = $subdirectory . '/B';
1311      file_put_contents($filepathA, '');
1312      file_put_contents($filepathB, '');
1313  
1314      // Delete the directory.
1315      $this->assertTrue(file_unmanaged_delete_recursive($directory), t('Function reported success.'));
1316      $this->assertFalse(file_exists($filepathA), t('Test file A has been deleted.'));
1317      $this->assertFalse(file_exists($filepathB), t('Test file B has been deleted.'));
1318      $this->assertFalse(file_exists($subdirectory), t('Subdirectory has been deleted.'));
1319      $this->assertFalse(file_exists($directory), t('Directory has been deleted.'));
1320    }
1321  }
1322  
1323  /**
1324   * Deletion related tests on remote filesystems.
1325   */
1326  class RemoteFileUnmanagedDeleteRecursiveTest extends FileUnmanagedDeleteRecursiveTest {
1327    public static function getInfo() {
1328      $info = parent::getInfo();
1329      $info['group'] = 'File API (remote)';
1330      return $info;
1331    }
1332  
1333    function setUp() {
1334      parent::setUp('file_test');
1335      variable_set('file_default_scheme', 'dummy-remote');
1336    }
1337  }
1338  
1339  /**
1340   * Unmanaged move related tests.
1341   */
1342  class FileUnmanagedMoveTest extends FileTestCase {
1343    public static function getInfo() {
1344      return array(
1345        'name' => 'Unmanaged file moving',
1346        'description' => 'Tests the unmanaged file move function.',
1347        'group' => 'File API',
1348      );
1349    }
1350  
1351    /**
1352     * Move a normal file.
1353     */
1354    function testNormal() {
1355      // Create a file for testing
1356      $file = $this->createFile();
1357  
1358      // Moving to a new name.
1359      $desired_filepath = 'public://' . $this->randomName();
1360      $new_filepath = file_unmanaged_move($file->uri, $desired_filepath, FILE_EXISTS_ERROR);
1361      $this->assertTrue($new_filepath, t('Move was successful.'));
1362      $this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.'));
1363      $this->assertTrue(file_exists($new_filepath), t('File exists at the new location.'));
1364      $this->assertFalse(file_exists($file->uri), t('No file remains at the old location.'));
1365      $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
1366  
1367      // Moving with rename.
1368      $desired_filepath = 'public://' . $this->randomName();
1369      $this->assertTrue(file_exists($new_filepath), t('File exists before moving.'));
1370      $this->assertTrue(file_put_contents($desired_filepath, ' '), t('Created a file so a rename will have to happen.'));
1371      $newer_filepath = file_unmanaged_move($new_filepath, $desired_filepath, FILE_EXISTS_RENAME);
1372      $this->assertTrue($newer_filepath, t('Move was successful.'));
1373      $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
1374      $this->assertTrue(file_exists($newer_filepath), t('File exists at the new location.'));
1375      $this->assertFalse(file_exists($new_filepath), t('No file remains at the old location.'));
1376      $this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664));
1377  
1378      // TODO: test moving to a directory (rather than full directory/file path)
1379      // TODO: test creating and moving normal files (rather than streams)
1380    }
1381  
1382    /**
1383     * Try to move a missing file.
1384     */
1385    function testMissing() {
1386      // Move non-existent file.
1387      $new_filepath = file_unmanaged_move($this->randomName(), $this->randomName());
1388      $this->assertFalse($new_filepath, t('Moving a missing file fails.'));
1389    }
1390  
1391    /**
1392     * Try to move a file onto itself.
1393     */
1394    function testOverwriteSelf() {
1395      // Create a file for testing.
1396      $file = $this->createFile();
1397  
1398      // Move the file onto itself without renaming shouldn't make changes.
1399      $new_filepath = file_unmanaged_move($file->uri, $file->uri, FILE_EXISTS_REPLACE);
1400      $this->assertFalse($new_filepath, t('Moving onto itself without renaming fails.'));
1401      $this->assertTrue(file_exists($file->uri), t('File exists after moving onto itself.'));
1402  
1403      // Move the file onto itself with renaming will result in a new filename.
1404      $new_filepath = file_unmanaged_move($file->uri, $file->uri, FILE_EXISTS_RENAME);
1405      $this->assertTrue($new_filepath, t('Moving onto itself with renaming works.'));
1406      $this->assertFalse(file_exists($file->uri), t('Original file has been removed.'));
1407      $this->assertTrue(file_exists($new_filepath), t('File exists after moving onto itself.'));
1408    }
1409  }
1410  
1411  /**
1412   * Unmanaged move related tests on remote filesystems.
1413   */
1414  class RemoteFileUnmanagedMoveTest extends FileUnmanagedMoveTest {
1415    public static function getInfo() {
1416      $info = parent::getInfo();
1417      $info['group'] = 'File API (remote)';
1418      return $info;
1419    }
1420  
1421    function setUp() {
1422      parent::setUp('file_test');
1423      variable_set('file_default_scheme', 'dummy-remote');
1424    }
1425  }
1426  
1427  /**
1428   * Unmanaged copy related tests.
1429   */
1430  class FileUnmanagedCopyTest extends FileTestCase {
1431    public static function getInfo() {
1432      return array(
1433        'name' => 'Unmanaged file copying',
1434        'description' => 'Tests the unmanaged file copy function.',
1435        'group' => 'File API',
1436      );
1437    }
1438  
1439    /**
1440     * Copy a normal file.
1441     */
1442    function testNormal() {
1443      // Create a file for testing
1444      $file = $this->createFile();
1445  
1446      // Copying to a new name.
1447      $desired_filepath = 'public://' . $this->randomName();
1448      $new_filepath = file_unmanaged_copy($file->uri, $desired_filepath, FILE_EXISTS_ERROR);
1449      $this->assertTrue($new_filepath, t('Copy was successful.'));
1450      $this->assertEqual($new_filepath, $desired_filepath, t('Returned expected filepath.'));
1451      $this->assertTrue(file_exists($file->uri), t('Original file remains.'));
1452      $this->assertTrue(file_exists($new_filepath), t('New file exists.'));
1453      $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
1454  
1455      // Copying with rename.
1456      $desired_filepath = 'public://' . $this->randomName();
1457      $this->assertTrue(file_put_contents($desired_filepath, ' '), t('Created a file so a rename will have to happen.'));
1458      $newer_filepath = file_unmanaged_copy($file->uri, $desired_filepath, FILE_EXISTS_RENAME);
1459      $this->assertTrue($newer_filepath, t('Copy was successful.'));
1460      $this->assertNotEqual($newer_filepath, $desired_filepath, t('Returned expected filepath.'));
1461      $this->assertTrue(file_exists($file->uri), t('Original file remains.'));
1462      $this->assertTrue(file_exists($newer_filepath), t('New file exists.'));
1463      $this->assertFilePermissions($newer_filepath, variable_get('file_chmod_file', 0664));
1464  
1465      // TODO: test copying to a directory (rather than full directory/file path)
1466      // TODO: test copying normal files using normal paths (rather than only streams)
1467    }
1468  
1469    /**
1470     * Copy a non-existent file.
1471     */
1472    function testNonExistent() {
1473      // Copy non-existent file
1474      $desired_filepath = $this->randomName();
1475      $this->assertFalse(file_exists($desired_filepath), t("Randomly named file doesn't exists."));
1476      $new_filepath = file_unmanaged_copy($desired_filepath, $this->randomName());
1477      $this->assertFalse($new_filepath, t('Copying a missing file fails.'));
1478    }
1479  
1480    /**
1481     * Copy a file onto itself.
1482     */
1483    function testOverwriteSelf() {
1484      // Create a file for testing
1485      $file = $this->createFile();
1486  
1487      // Copy the file onto itself with renaming works.
1488      $new_filepath = file_unmanaged_copy($file->uri, $file->uri, FILE_EXISTS_RENAME);
1489      $this->assertTrue($new_filepath, t('Copying onto itself with renaming works.'));
1490      $this->assertNotEqual($new_filepath, $file->uri, t('Copied file has a new name.'));
1491      $this->assertTrue(file_exists($file->uri), t('Original file exists after copying onto itself.'));
1492      $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
1493      $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
1494  
1495      // Copy the file onto itself without renaming fails.
1496      $new_filepath = file_unmanaged_copy($file->uri, $file->uri, FILE_EXISTS_ERROR);
1497      $this->assertFalse($new_filepath, t('Copying onto itself without renaming fails.'));
1498      $this->assertTrue(file_exists($file->uri), t('File exists after copying onto itself.'));
1499  
1500      // Copy the file into same directory without renaming fails.
1501      $new_filepath = file_unmanaged_copy($file->uri, drupal_dirname($file->uri), FILE_EXISTS_ERROR);
1502      $this->assertFalse($new_filepath, t('Copying onto itself fails.'));
1503      $this->assertTrue(file_exists($file->uri), t('File exists after copying onto itself.'));
1504  
1505      // Copy the file into same directory with renaming works.
1506      $new_filepath = file_unmanaged_copy($file->uri, drupal_dirname($file->uri), FILE_EXISTS_RENAME);
1507      $this->assertTrue($new_filepath, t('Copying into same directory works.'));
1508      $this->assertNotEqual($new_filepath, $file->uri, t('Copied file has a new name.'));
1509      $this->assertTrue(file_exists($file->uri), t('Original file exists after copying onto itself.'));
1510      $this->assertTrue(file_exists($new_filepath), t('Copied file exists after copying onto itself.'));
1511      $this->assertFilePermissions($new_filepath, variable_get('file_chmod_file', 0664));
1512    }
1513  }
1514  
1515  /**
1516   * Unmanaged copy related tests on remote filesystems.
1517   */
1518  class RemoteFileUnmanagedCopyTest extends FileUnmanagedCopyTest {
1519    public static function getInfo() {
1520      $info = parent::getInfo();
1521      $info['group'] = 'File API (remote)';
1522      return $info;
1523    }
1524  
1525    function setUp() {
1526      parent::setUp('file_test');
1527      variable_set('file_default_scheme', 'dummy-remote');
1528    }
1529  }
1530  
1531  /**
1532   * Deletion related tests.
1533   */
1534  class FileDeleteTest extends FileHookTestCase {
1535    public static function getInfo() {
1536      return array(
1537        'name' => 'File delete',
1538        'description' => 'Tests the file delete function.',
1539        'group' => 'File API',
1540      );
1541    }
1542  
1543    /**
1544     * Tries deleting a normal file (as opposed to a directory, symlink, etc).
1545     */
1546    function testUnused() {
1547      $file = $this->createFile();
1548  
1549      // Check that deletion removes the file and database record.
1550      $this->assertTrue(is_file($file->uri), t('File exists.'));
1551      $this->assertIdentical(file_delete($file), TRUE, t('Delete worked.'));
1552      $this->assertFileHooksCalled(array('delete'));
1553      $this->assertFalse(file_exists($file->uri), t('Test file has actually been deleted.'));
1554      $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
1555    }
1556  
1557    /**
1558     * Tries deleting a file that is in use.
1559     */
1560    function testInUse() {
1561      $file = $this->createFile();
1562      file_usage_add($file, 'testing', 'test', 1);
1563      file_usage_add($file, 'testing', 'test', 1);
1564  
1565      file_usage_delete($file, 'testing', 'test', 1);
1566      file_delete($file);
1567      $usage = file_usage_list($file);
1568      $this->assertEqual($usage['testing']['test'], array(1 => 1), t('Test file is still in use.'));
1569      $this->assertTrue(file_exists($file->uri), t('File still exists on the disk.'));
1570      $this->assertTrue(file_load($file->fid), t('File still exists in the database.'));
1571  
1572      // Clear out the call to hook_file_load().
1573      file_test_reset();
1574  
1575      file_usage_delete($file, 'testing', 'test', 1);
1576      file_delete($file);
1577      $usage = file_usage_list($file);
1578      $this->assertFileHooksCalled(array('delete'));
1579      $this->assertTrue(empty($usage), t('File usage data was removed.'));
1580      $this->assertFalse(file_exists($file->uri), t('File has been deleted after its last usage was removed.'));
1581      $this->assertFalse(file_load($file->fid), t('File was removed from the database.'));
1582    }
1583  }
1584  
1585  
1586  /**
1587   * Move related tests
1588   */
1589  class FileMoveTest extends FileHookTestCase {
1590    public static function getInfo() {
1591      return array(
1592        'name' => 'File moving',
1593        'description' => 'Tests the file move function.',
1594        'group' => 'File API',
1595      );
1596    }
1597  
1598    /**
1599     * Move a normal file.
1600     */
1601    function testNormal() {
1602      $contents = $this->randomName(10);
1603      $source = $this->createFile(NULL, $contents);
1604      $desired_filepath = 'public://' . $this->randomName();
1605  
1606      // Clone the object so we don't have to worry about the function changing
1607      // our reference copy.
1608      $result = file_move(clone $source, $desired_filepath, FILE_EXISTS_ERROR);
1609  
1610      // Check the return status and that the contents changed.
1611      $this->assertTrue($result, t('File moved successfully.'));
1612      $this->assertFalse(file_exists($source->uri));
1613      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file correctly written.'));
1614  
1615      // Check that the correct hooks were called.
1616      $this->assertFileHooksCalled(array('move', 'load', 'update'));
1617  
1618      // Make sure we got the same file back.
1619      $this->assertEqual($source->fid, $result->fid, t("Source file id's' %fid is unchanged after move.", array('%fid' => $source->fid)));
1620  
1621      // Reload the file from the database and check that the changes were
1622      // actually saved.
1623      $loaded_file = file_load($result->fid, TRUE);
1624      $this->assertTrue($loaded_file, t('File can be loaded from the database.'));
1625      $this->assertFileUnchanged($result, $loaded_file);
1626    }
1627  
1628    /**
1629     * Test renaming when moving onto a file that already exists.
1630     */
1631    function testExistingRename() {
1632      // Setup a file to overwrite.
1633      $contents = $this->randomName(10);
1634      $source = $this->createFile(NULL, $contents);
1635      $target = $this->createFile();
1636      $this->assertDifferentFile($source, $target);
1637  
1638      // Clone the object so we don't have to worry about the function changing
1639      // our reference copy.
1640      $result = file_move(clone $source, $target->uri, FILE_EXISTS_RENAME);
1641  
1642      // Check the return status and that the contents changed.
1643      $this->assertTrue($result, t('File moved successfully.'));
1644      $this->assertFalse(file_exists($source->uri));
1645      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file correctly written.'));
1646  
1647      // Check that the correct hooks were called.
1648      $this->assertFileHooksCalled(array('move', 'load', 'update'));
1649  
1650      // Compare the returned value to what made it into the database.
1651      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
1652      // The target file should not have been altered.
1653      $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
1654      // Make sure we end up with two distinct files afterwards.
1655      $this->assertDifferentFile($target, $result);
1656  
1657      // Compare the source and results.
1658      $loaded_source = file_load($source->fid, TRUE);
1659      $this->assertEqual($loaded_source->fid, $result->fid, t("Returned file's id matches the source."));
1660      $this->assertNotEqual($loaded_source->uri, $source->uri, t("Returned file path has changed from the original."));
1661    }
1662  
1663    /**
1664     * Test replacement when moving onto a file that already exists.
1665     */
1666    function testExistingReplace() {
1667      // Setup a file to overwrite.
1668      $contents = $this->randomName(10);
1669      $source = $this->createFile(NULL, $contents);
1670      $target = $this->createFile();
1671      $this->assertDifferentFile($source, $target);
1672  
1673      // Clone the object so we don't have to worry about the function changing
1674      // our reference copy.
1675      $result = file_move(clone $source, $target->uri, FILE_EXISTS_REPLACE);
1676  
1677      // Look at the results.
1678      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were overwritten.'));
1679      $this->assertFalse(file_exists($source->uri));
1680      $this->assertTrue($result, t('File moved successfully.'));
1681  
1682      // Check that the correct hooks were called.
1683      $this->assertFileHooksCalled(array('move', 'update', 'delete', 'load'));
1684  
1685      // Reload the file from the database and check that the changes were
1686      // actually saved.
1687      $loaded_result = file_load($result->fid, TRUE);
1688      $this->assertFileUnchanged($result, $loaded_result);
1689      // Check that target was re-used.
1690      $this->assertSameFile($target, $loaded_result);
1691      // Source and result should be totally different.
1692      $this->assertDifferentFile($source, $loaded_result);
1693    }
1694  
1695    /**
1696     * Test replacement when moving onto itself.
1697     */
1698    function testExistingReplaceSelf() {
1699      // Setup a file to overwrite.
1700      $contents = $this->randomName(10);
1701      $source = $this->createFile(NULL, $contents);
1702  
1703      // Copy the file over itself. Clone the object so we don't have to worry
1704      // about the function changing our reference copy.
1705      $result = file_move(clone $source, $source->uri, FILE_EXISTS_REPLACE);
1706      $this->assertFalse($result, t('File move failed.'));
1707      $this->assertEqual($contents, file_get_contents($source->uri), t('Contents of file were not altered.'));
1708  
1709      // Check that no hooks were called while failing.
1710      $this->assertFileHooksCalled(array());
1711  
1712      // Load the file from the database and make sure it is identical to what
1713      // was returned.
1714      $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
1715    }
1716  
1717    /**
1718     * Test that moving onto an existing file fails when FILE_EXISTS_ERROR is
1719     * specified.
1720     */
1721    function testExistingError() {
1722      $contents = $this->randomName(10);
1723      $source = $this->createFile();
1724      $target = $this->createFile(NULL, $contents);
1725      $this->assertDifferentFile($source, $target);
1726  
1727      // Clone the object so we don't have to worry about the function changing
1728      // our reference copy.
1729      $result = file_move(clone $source, $target->uri, FILE_EXISTS_ERROR);
1730  
1731      // Check the return status and that the contents did not change.
1732      $this->assertFalse($result, t('File move failed.'));
1733      $this->assertTrue(file_exists($source->uri));
1734      $this->assertEqual($contents, file_get_contents($target->uri), t('Contents of file were not altered.'));
1735  
1736      // Check that no hooks were called while failing.
1737      $this->assertFileHooksCalled(array());
1738  
1739      // Load the file from the database and make sure it is identical to what
1740      // was returned.
1741      $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
1742      $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
1743    }
1744  }
1745  
1746  
1747  /**
1748   * Copy related tests.
1749   */
1750  class FileCopyTest extends FileHookTestCase {
1751    public static function getInfo() {
1752      return array(
1753        'name' => 'File copying',
1754        'description' => 'Tests the file copy function.',
1755        'group' => 'File API',
1756      );
1757    }
1758  
1759    /**
1760     * Test file copying in the normal, base case.
1761     */
1762    function testNormal() {
1763      $contents = $this->randomName(10);
1764      $source = $this->createFile(NULL, $contents);
1765      $desired_uri = 'public://' . $this->randomName();
1766  
1767      // Clone the object so we don't have to worry about the function changing
1768      // our reference copy.
1769      $result = file_copy(clone $source, $desired_uri, FILE_EXISTS_ERROR);
1770  
1771      // Check the return status and that the contents changed.
1772      $this->assertTrue($result, t('File copied successfully.'));
1773      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were copied correctly.'));
1774  
1775      // Check that the correct hooks were called.
1776      $this->assertFileHooksCalled(array('copy', 'insert'));
1777  
1778      $this->assertDifferentFile($source, $result);
1779      $this->assertEqual($result->uri, $desired_uri, t('The copied file object has the desired filepath.'));
1780      $this->assertTrue(file_exists($source->uri), t('The original file still exists.'));
1781      $this->assertTrue(file_exists($result->uri), t('The copied file exists.'));
1782  
1783      // Reload the file from the database and check that the changes were
1784      // actually saved.
1785      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
1786    }
1787  
1788    /**
1789     * Test renaming when copying over a file that already exists.
1790     */
1791    function testExistingRename() {
1792      // Setup a file to overwrite.
1793      $contents = $this->randomName(10);
1794      $source = $this->createFile(NULL, $contents);
1795      $target = $this->createFile();
1796      $this->assertDifferentFile($source, $target);
1797  
1798      // Clone the object so we don't have to worry about the function changing
1799      // our reference copy.
1800      $result = file_copy(clone $source, $target->uri, FILE_EXISTS_RENAME);
1801  
1802      // Check the return status and that the contents changed.
1803      $this->assertTrue($result, t('File copied successfully.'));
1804      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were copied correctly.'));
1805      $this->assertNotEqual($result->uri, $source->uri, t('Returned file path has changed from the original.'));
1806  
1807      // Check that the correct hooks were called.
1808      $this->assertFileHooksCalled(array('copy', 'insert'));
1809  
1810      // Load all the affected files to check the changes that actually made it
1811      // to the database.
1812      $loaded_source = file_load($source->fid, TRUE);
1813      $loaded_target = file_load($target->fid, TRUE);
1814      $loaded_result = file_load($result->fid, TRUE);
1815  
1816      // Verify that the source file wasn't changed.
1817      $this->assertFileUnchanged($source, $loaded_source);
1818  
1819      // Verify that what was returned is what's in the database.
1820      $this->assertFileUnchanged($result, $loaded_result);
1821  
1822      // Make sure we end up with three distinct files afterwards.
1823      $this->assertDifferentFile($loaded_source, $loaded_target);
1824      $this->assertDifferentFile($loaded_target, $loaded_result);
1825      $this->assertDifferentFile($loaded_source, $loaded_result);
1826    }
1827  
1828    /**
1829     * Test replacement when copying over a file that already exists.
1830     */
1831    function testExistingReplace() {
1832      // Setup a file to overwrite.
1833      $contents = $this->randomName(10);
1834      $source = $this->createFile(NULL, $contents);
1835      $target = $this->createFile();
1836      $this->assertDifferentFile($source, $target);
1837  
1838      // Clone the object so we don't have to worry about the function changing
1839      // our reference copy.
1840      $result = file_copy(clone $source, $target->uri, FILE_EXISTS_REPLACE);
1841  
1842      // Check the return status and that the contents changed.
1843      $this->assertTrue($result, t('File copied successfully.'));
1844      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of file were overwritten.'));
1845      $this->assertDifferentFile($source, $result);
1846  
1847      // Check that the correct hooks were called.
1848      $this->assertFileHooksCalled(array('load', 'copy', 'update'));
1849  
1850      // Load all the affected files to check the changes that actually made it
1851      // to the database.
1852      $loaded_source = file_load($source->fid, TRUE);
1853      $loaded_target = file_load($target->fid, TRUE);
1854      $loaded_result = file_load($result->fid, TRUE);
1855  
1856      // Verify that the source file wasn't changed.
1857      $this->assertFileUnchanged($source, $loaded_source);
1858  
1859      // Verify that what was returned is what's in the database.
1860      $this->assertFileUnchanged($result, $loaded_result);
1861  
1862      // Target file was reused for the result.
1863      $this->assertFileUnchanged($loaded_target, $loaded_result);
1864    }
1865  
1866    /**
1867     * Test that copying over an existing file fails when FILE_EXISTS_ERROR is
1868     * specified.
1869     */
1870    function testExistingError() {
1871      $contents = $this->randomName(10);
1872      $source = $this->createFile();
1873      $target = $this->createFile(NULL, $contents);
1874      $this->assertDifferentFile($source, $target);
1875  
1876      // Clone the object so we don't have to worry about the function changing
1877      // our reference copy.
1878      $result = file_copy(clone $source, $target->uri, FILE_EXISTS_ERROR);
1879  
1880      // Check the return status and that the contents were not changed.
1881      $this->assertFalse($result, t('File copy failed.'));
1882      $this->assertEqual($contents, file_get_contents($target->uri), t('Contents of file were not altered.'));
1883  
1884      // Check that the correct hooks were called.
1885      $this->assertFileHooksCalled(array());
1886  
1887      $this->assertFileUnchanged($source, file_load($source->fid, TRUE));
1888      $this->assertFileUnchanged($target, file_load($target->fid, TRUE));
1889    }
1890  }
1891  
1892  
1893  /**
1894   * Tests the file_load() function.
1895   */
1896  class FileLoadTest extends FileHookTestCase {
1897    public static function getInfo() {
1898      return array(
1899        'name' => 'File loading',
1900        'description' => 'Tests the file_load() function.',
1901        'group' => 'File API',
1902      );
1903    }
1904  
1905    /**
1906     * Try to load a non-existent file by fid.
1907     */
1908    function testLoadMissingFid() {
1909      $this->assertFalse(file_load(-1), t("Try to load an invalid fid fails."));
1910      $this->assertFileHooksCalled(array());
1911    }
1912  
1913    /**
1914     * Try to load a non-existent file by URI.
1915     */
1916    function testLoadMissingFilepath() {
1917      $files = file_load_multiple(array(), array('uri' => 'foobar://misc/druplicon.png'));
1918      $this->assertFalse(reset($files), t("Try to load a file that doesn't exist in the database fails."));
1919      $this->assertFileHooksCalled(array());
1920    }
1921  
1922    /**
1923     * Try to load a non-existent file by status.
1924     */
1925    function testLoadInvalidStatus() {
1926      $files = file_load_multiple(array(), array('status' => -99));
1927      $this->assertFalse(reset($files), t("Trying to load a file with an invalid status fails."));
1928      $this->assertFileHooksCalled(array());
1929    }
1930  
1931    /**
1932     * Load a single file and ensure that the correct values are returned.
1933     */
1934    function testSingleValues() {
1935      // Create a new file object from scratch so we know the values.
1936      $file = $this->createFile('druplicon.txt', NULL, 'public');
1937  
1938      $by_fid_file = file_load($file->fid);
1939      $this->assertFileHookCalled('load');
1940      $this->assertTrue(is_object($by_fid_file), t('file_load() returned an object.'));
1941      $this->assertEqual($by_fid_file->fid, $file->fid, t("Loading by fid got the same fid."), 'File');
1942      $this->assertEqual($by_fid_file->uri, $file->uri, t("Loading by fid got the correct filepath."), 'File');
1943      $this->assertEqual($by_fid_file->filename, $file->filename, t("Loading by fid got the correct filename."), 'File');
1944      $this->assertEqual($by_fid_file->filemime, $file->filemime, t("Loading by fid got the correct MIME type."), 'File');
1945      $this->assertEqual($by_fid_file->status, $file->status, t("Loading by fid got the correct status."), 'File');
1946      $this->assertTrue($by_fid_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.'));
1947    }
1948  
1949    /**
1950     * This will test loading file data from the database.
1951     */
1952    function testMultiple() {
1953      // Create a new file object.
1954      $file = $this->createFile('druplicon.txt', NULL, 'public');
1955  
1956      // Load by path.
1957      file_test_reset();
1958      $by_path_files = file_load_multiple(array(), array('uri' => $file->uri));
1959      $this->assertFileHookCalled('load');
1960      $this->assertEqual(1, count($by_path_files), t('file_load_multiple() returned an array of the correct size.'));
1961      $by_path_file = reset($by_path_files);
1962      $this->assertTrue($by_path_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.'));
1963      $this->assertEqual($by_path_file->fid, $file->fid, t("Loading by filepath got the correct fid."), 'File');
1964  
1965      // Load by fid.
1966      file_test_reset();
1967      $by_fid_files = file_load_multiple(array($file->fid), array());
1968      $this->assertFileHookCalled('load');
1969      $this->assertEqual(1, count($by_fid_files), t('file_load_multiple() returned an array of the correct size.'));
1970      $by_fid_file = reset($by_fid_files);
1971      $this->assertTrue($by_fid_file->file_test['loaded'], t('file_test_file_load() was able to modify the file during load.'));
1972      $this->assertEqual($by_fid_file->uri, $file->uri, t("Loading by fid got the correct filepath."), 'File');
1973    }
1974  }
1975  
1976  /**
1977   * Tests the file_save() function.
1978   */
1979  class FileSaveTest extends FileHookTestCase {
1980    public static function getInfo() {
1981      return array(
1982        'name' => 'File saving',
1983        'description' => 'Tests the file_save() function.',
1984        'group' => 'File API',
1985      );
1986    }
1987  
1988    function testFileSave() {
1989      // Create a new file object.
1990      $file = array(
1991        'uid' => 1,
1992        'filename' => 'druplicon.txt',
1993        'uri' => 'public://druplicon.txt',
1994        'filemime' => 'text/plain',
1995        'timestamp' => 1,
1996        'status' => FILE_STATUS_PERMANENT,
1997      );
1998      $file = (object) $file;
1999      file_put_contents($file->uri, 'hello world');
2000  
2001      // Save it, inserting a new record.
2002      $saved_file = file_save($file);
2003  
2004      // Check that the correct hooks were called.
2005      $this->assertFileHooksCalled(array('insert'));
2006  
2007      $this->assertNotNull($saved_file, t("Saving the file should give us back a file object."), 'File');
2008      $this->assertTrue($saved_file->fid > 0, t("A new file ID is set when saving a new file to the database."), 'File');
2009      $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
2010      $this->assertNotNull($loaded_file, t("Record exists in the database."));
2011      $this->assertEqual($loaded_file->status, $file->status, t("Status was saved correctly."));
2012      $this->assertEqual($saved_file->filesize, filesize($file->uri), t("File size was set correctly."), 'File');
2013      $this->assertTrue($saved_file->timestamp > 1, t("File size was set correctly."), 'File');
2014  
2015  
2016      // Resave the file, updating the existing record.
2017      file_test_reset();
2018      $saved_file->status = 7;
2019      $resaved_file = file_save($saved_file);
2020  
2021      // Check that the correct hooks were called.
2022      $this->assertFileHooksCalled(array('load', 'update'));
2023  
2024      $this->assertEqual($resaved_file->fid, $saved_file->fid, t("The file ID of an existing file is not changed when updating the database."), 'File');
2025      $this->assertTrue($resaved_file->timestamp >= $saved_file->timestamp, t("Timestamp didn't go backwards."), 'File');
2026      $loaded_file = db_query('SELECT * FROM {file_managed} f WHERE f.fid = :fid', array(':fid' => $saved_file->fid))->fetch(PDO::FETCH_OBJ);
2027      $this->assertNotNull($loaded_file, t("Record still exists in the database."), 'File');
2028      $this->assertEqual($loaded_file->status, $saved_file->status, t("Status was saved correctly."));
2029  
2030      // Try to insert a second file with the same name apart from case insensitivity
2031      // to ensure the 'uri' index allows for filenames with different cases.
2032      $file = (object) array(
2033        'uid' => 1,
2034        'filename' => 'DRUPLICON.txt',
2035        'uri' => 'public://DRUPLICON.txt',
2036        'filemime' => 'text/plain',
2037        'timestamp' => 1,
2038        'status' => FILE_STATUS_PERMANENT,
2039      );
2040      file_put_contents($file->uri, 'hello world');
2041      file_save($file);
2042    }
2043  }
2044  
2045  /**
2046   * Tests file usage functions.
2047   */
2048  class FileUsageTest extends FileTestCase {
2049    public static function getInfo() {
2050      return array(
2051        'name' => 'File usage',
2052        'description' => 'Tests the file usage functions.',
2053        'group' => 'File',
2054      );
2055    }
2056  
2057    /**
2058     * Tests file_usage_list().
2059     */
2060    function testGetUsage() {
2061      $file = $this->createFile();
2062      db_insert('file_usage')
2063        ->fields(array(
2064          'fid' => $file->fid,
2065          'module' => 'testing',
2066          'type' => 'foo',
2067          'id' => 1,
2068          'count' => 1
2069        ))
2070        ->execute();
2071      db_insert('file_usage')
2072        ->fields(array(
2073          'fid' => $file->fid,
2074          'module' => 'testing',
2075          'type' => 'bar',
2076          'id' => 2,
2077          'count' => 2
2078        ))
2079        ->execute();
2080  
2081      $usage = file_usage_list($file);
2082  
2083      $this->assertEqual(count($usage['testing']), 2, t('Returned the correct number of items.'));
2084      $this->assertTrue(isset($usage['testing']['foo'][1]), t('Returned the correct id.'));
2085      $this->assertTrue(isset($usage['testing']['bar'][2]), t('Returned the correct id.'));
2086      $this->assertEqual($usage['testing']['foo'][1], 1, t('Returned the correct count.'));
2087      $this->assertEqual($usage['testing']['bar'][2], 2, t('Returned the correct count.'));
2088    }
2089  
2090    /**
2091     * Tests file_usage_add().
2092     */
2093    function testAddUsage() {
2094      $file = $this->createFile();
2095      file_usage_add($file, 'testing', 'foo', 1);
2096      // Add the file twice to ensure that the count is incremented rather than
2097      // creating additional records.
2098      file_usage_add($file, 'testing', 'bar', 2);
2099      file_usage_add($file, 'testing', 'bar', 2);
2100  
2101      $usage = db_select('file_usage', 'f')
2102        ->fields('f')
2103        ->condition('f.fid', $file->fid)
2104        ->execute()
2105        ->fetchAllAssoc('id');
2106      $this->assertEqual(count($usage), 2, t('Created two records'));
2107      $this->assertEqual($usage[1]->module, 'testing', t('Correct module'));
2108      $this->assertEqual($usage[2]->module, 'testing', t('Correct module'));
2109      $this->assertEqual($usage[1]->type, 'foo', t('Correct type'));
2110      $this->assertEqual($usage[2]->type, 'bar', t('Correct type'));
2111      $this->assertEqual($usage[1]->count, 1, t('Correct count'));
2112      $this->assertEqual($usage[2]->count, 2, t('Correct count'));
2113    }
2114  
2115    /**
2116     * Tests file_usage_delete().
2117     */
2118    function testRemoveUsage() {
2119      $file = $this->createFile();
2120      db_insert('file_usage')
2121        ->fields(array(
2122          'fid' => $file->fid,
2123          'module' => 'testing',
2124          'type' => 'bar',
2125          'id' => 2,
2126          'count' => 3,
2127        ))
2128        ->execute();
2129  
2130      // Normal decrement.
2131      file_usage_delete($file, 'testing', 'bar', 2);
2132      $count = db_select('file_usage', 'f')
2133        ->fields('f', array('count'))
2134        ->condition('f.fid', $file->fid)
2135        ->execute()
2136        ->fetchField();
2137      $this->assertEqual(2, $count, t('The count was decremented correctly.'));
2138  
2139      // Multiple decrement and removal.
2140      file_usage_delete($file, 'testing', 'bar', 2, 2);
2141      $count = db_select('file_usage', 'f')
2142        ->fields('f', array('count'))
2143        ->condition('f.fid', $file->fid)
2144        ->execute()
2145        ->fetchField();
2146      $this->assertIdentical(FALSE, $count, t('The count was removed entirely when empty.'));
2147  
2148      // Non-existent decrement.
2149      file_usage_delete($file, 'testing', 'bar', 2);
2150      $count = db_select('file_usage', 'f')
2151        ->fields('f', array('count'))
2152        ->condition('f.fid', $file->fid)
2153        ->execute()
2154        ->fetchField();
2155      $this->assertIdentical(FALSE, $count, t('Decrementing non-exist record complete.'));
2156    }
2157  }
2158  
2159  /**
2160   * Tests the file_validate() function..
2161   */
2162  class FileValidateTest extends FileHookTestCase {
2163    public static function getInfo() {
2164      return array(
2165        'name' => 'File validate',
2166        'description' => 'Tests the file_validate() function.',
2167        'group' => 'File API',
2168      );
2169    }
2170  
2171    /**
2172     * Test that the validators passed into are checked.
2173     */
2174    function testCallerValidation() {
2175      $file = $this->createFile();
2176  
2177      // Empty validators.
2178      $this->assertEqual(file_validate($file, array()), array(), t('Validating an empty array works successfully.'));
2179      $this->assertFileHooksCalled(array('validate'));
2180  
2181      // Use the file_test.module's test validator to ensure that passing tests
2182      // return correctly.
2183      file_test_reset();
2184      file_test_set_return('validate', array());
2185      $passing = array('file_test_validator' => array(array()));
2186      $this->assertEqual(file_validate($file, $passing), array(), t('Validating passes.'));
2187      $this->assertFileHooksCalled(array('validate'));
2188  
2189      // Now test for failures in validators passed in and by hook_validate.
2190      file_test_reset();
2191      file_test_set_return('validate', array('Epic fail'));
2192      $failing = array('file_test_validator' => array(array('Failed', 'Badly')));
2193      $this->assertEqual(file_validate($file, $failing), array('Failed', 'Badly', 'Epic fail'), t('Validating returns errors.'));
2194      $this->assertFileHooksCalled(array('validate'));
2195    }
2196  }
2197  
2198  /**
2199   *  Tests the file_save_data() function.
2200   */
2201  class FileSaveDataTest extends FileHookTestCase {
2202    public static function getInfo() {
2203      return array(
2204        'name' => 'File save data',
2205        'description' => 'Tests the file save data function.',
2206        'group' => 'File API',
2207      );
2208    }
2209  
2210    /**
2211     * Test the file_save_data() function when no filename is provided.
2212     */
2213    function testWithoutFilename() {
2214      $contents = $this->randomName(8);
2215  
2216      $result = file_save_data($contents);
2217      $this->assertTrue($result, t('Unnamed file saved correctly.'));
2218  
2219      $this->assertEqual(file_default_scheme(), file_uri_scheme($result->uri), t("File was placed in Drupal's files directory."));
2220      $this->assertEqual($result->filename, drupal_basename($result->uri), t("Filename was set to the file's basename."));
2221      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.'));
2222      $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.'));
2223      $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
2224  
2225      // Check that the correct hooks were called.
2226      $this->assertFileHooksCalled(array('insert'));
2227  
2228      // Verify that what was returned is what's in the database.
2229      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
2230    }
2231  
2232    /**
2233     * Test the file_save_data() function when a filename is provided.
2234     */
2235    function testWithFilename() {
2236      $contents = $this->randomName(8);
2237  
2238      // Using filename with non-latin characters.
2239      $filename = 'Текстовый файл.txt';
2240  
2241      $result = file_save_data($contents, 'public://' . $filename);
2242      $this->assertTrue($result, t('Unnamed file saved correctly.'));
2243  
2244      $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory."));
2245      $this->assertEqual($filename, drupal_basename($result->uri), t('File was named correctly.'));
2246      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.'));
2247      $this->assertEqual($result->filemime, 'text/plain', t('A MIME type was set.'));
2248      $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
2249  
2250      // Check that the correct hooks were called.
2251      $this->assertFileHooksCalled(array('insert'));
2252  
2253      // Verify that what was returned is what's in the database.
2254      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
2255    }
2256  
2257    /**
2258     * Test file_save_data() when renaming around an existing file.
2259     */
2260    function testExistingRename() {
2261      // Setup a file to overwrite.
2262      $existing = $this->createFile();
2263      $contents = $this->randomName(8);
2264  
2265      $result = file_save_data($contents, $existing->uri, FILE_EXISTS_RENAME);
2266      $this->assertTrue($result, t("File saved successfully."));
2267  
2268      $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory."));
2269      $this->assertEqual($result->filename, $existing->filename, t("Filename was set to the basename of the source, rather than that of the renamed file."));
2270      $this->assertEqual($contents, file_get_contents($result->uri), t("Contents of the file are correct."));
2271      $this->assertEqual($result->filemime, 'application/octet-stream', t("A MIME type was set."));
2272      $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
2273  
2274      // Check that the correct hooks were called.
2275      $this->assertFileHooksCalled(array('insert'));
2276  
2277      // Ensure that the existing file wasn't overwritten.
2278      $this->assertDifferentFile($existing, $result);
2279      $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE));
2280  
2281      // Verify that was returned is what's in the database.
2282      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
2283    }
2284  
2285    /**
2286     * Test file_save_data() when replacing an existing file.
2287     */
2288    function testExistingReplace() {
2289      // Setup a file to overwrite.
2290      $existing = $this->createFile();
2291      $contents = $this->randomName(8);
2292  
2293      $result = file_save_data($contents, $existing->uri, FILE_EXISTS_REPLACE);
2294      $this->assertTrue($result, t('File saved successfully.'));
2295  
2296      $this->assertEqual('public', file_uri_scheme($result->uri), t("File was placed in Drupal's files directory."));
2297      $this->assertEqual($result->filename, $existing->filename, t('Filename was set to the basename of the existing file, rather than preserving the original name.'));
2298      $this->assertEqual($contents, file_get_contents($result->uri), t('Contents of the file are correct.'));
2299      $this->assertEqual($result->filemime, 'application/octet-stream', t('A MIME type was set.'));
2300      $this->assertEqual($result->status, FILE_STATUS_PERMANENT, t("The file's status was set to permanent."));
2301  
2302      // Check that the correct hooks were called.
2303      $this->assertFileHooksCalled(array('load', 'update'));
2304  
2305      // Verify that the existing file was re-used.
2306      $this->assertSameFile($existing, $result);
2307  
2308      // Verify that what was returned is what's in the database.
2309      $this->assertFileUnchanged($result, file_load($result->fid, TRUE));
2310    }
2311  
2312    /**
2313     * Test that file_save_data() fails overwriting an existing file.
2314     */
2315    function testExistingError() {
2316      $contents = $this->randomName(8);
2317      $existing = $this->createFile(NULL, $contents);
2318  
2319      // Check the overwrite error.
2320      $result = file_save_data('asdf', $existing->uri, FILE_EXISTS_ERROR);
2321      $this->assertFalse($result, t('Overwriting a file fails when FILE_EXISTS_ERROR is specified.'));
2322      $this->assertEqual($contents, file_get_contents($existing->uri), t('Contents of existing file were unchanged.'));
2323  
2324      // Check that no hooks were called while failing.
2325      $this->assertFileHooksCalled(array());
2326  
2327      // Ensure that the existing file wasn't overwritten.
2328      $this->assertFileUnchanged($existing, file_load($existing->fid, TRUE));
2329    }
2330  }
2331  
2332  /**
2333   * Tests for download/file transfer functions.
2334   */
2335  class FileDownloadTest extends FileTestCase {
2336    public static function getInfo() {
2337      return array(
2338        'name' => 'File download',
2339        'description' => 'Tests for file download/transfer functions.',
2340        'group' => 'File API',
2341      );
2342    }
2343  
2344    function setUp() {
2345      parent::setUp('file_test');
2346      // Clear out any hook calls.
2347      file_test_reset();
2348    }
2349  
2350    /**
2351     * Test the public file transfer system.
2352     */
2353    function testPublicFileTransfer() {
2354      // Test generating an URL to a created file.
2355      $file = $this->createFile();
2356      $url = file_create_url($file->uri);
2357      // URLs can't contain characters outside the ASCII set so $filename has to be
2358      // encoded.
2359      $filename = $GLOBALS['base_url'] . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . rawurlencode($file->filename);
2360      $this->assertEqual($filename, $url, t('Correctly generated a URL for a created file.'));
2361      $this->drupalHead($url);
2362      $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the created file.'));
2363  
2364      // Test generating an URL to a shipped file (i.e. a file that is part of
2365      // Drupal core, a module or a theme, for example a JavaScript file).
2366      $filepath = 'misc/jquery.js';
2367      $url = file_create_url($filepath);
2368      $this->assertEqual($GLOBALS['base_url'] . '/' . $filepath, $url, t('Correctly generated a URL for a shipped file.'));
2369      $this->drupalHead($url);
2370      $this->assertResponse(200, t('Confirmed that the generated URL is correct by downloading the shipped file.'));
2371    }
2372  
2373    /**
2374     * Test the private file transfer system.
2375     */
2376    function testPrivateFileTransfer() {
2377      // Set file downloads to private so handler functions get called.
2378  
2379      // Create a file.
2380      $contents = $this->randomName(8);
2381      $file = $this->createFile(NULL, $contents, 'private');
2382      $url  = file_create_url($file->uri);
2383  
2384      // Set file_test access header to allow the download.
2385      file_test_set_return('download', array('x-foo' => 'Bar'));
2386      $this->drupalGet($url);
2387      $headers = $this->drupalGetHeaders();
2388      $this->assertEqual($headers['x-foo'], 'Bar', t('Found header set by file_test module on private download.'));
2389      $this->assertResponse(200, t('Correctly allowed access to a file when file_test provides headers.'));
2390  
2391      // Test that the file transfered correctly.
2392      $this->assertEqual($contents, $this->content, t('Contents of the file are correct.'));
2393  
2394      // Deny access to all downloads via a -1 header.
2395      file_test_set_return('download', -1);
2396      $this->drupalHead($url);
2397      $this->assertResponse(403, t('Correctly denied access to a file when file_test sets the header to -1.'));
2398  
2399      // Try non-existent file.
2400      $url = file_create_url('private://' . $this->randomName());
2401      $this->drupalHead($url);
2402      $this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.'));
2403    }
2404  
2405    /**
2406     * Test file_create_url().
2407     */
2408    function testFileCreateUrl() {
2409      global $base_url;
2410  
2411      // Tilde (~) is excluded from this test because it is encoded by
2412      // rawurlencode() in PHP 5.2 but not in PHP 5.3, as per RFC 3986.
2413      // @see http://www.php.net/manual/en/function.rawurlencode.php#86506
2414      $basename = " -._!$'\"()*@[]?&+%#,;=:\n\x00" . // "Special" ASCII characters.
2415        "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
2416        "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
2417      $basename_encoded = '%20-._%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' .
2418        '%2523%2525%2526%252B%252F%253F' .
2419        '%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E';
2420  
2421      $this->checkUrl('public', '', $basename, $base_url . '/' . file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath() . '/' . $basename_encoded);
2422      $this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded);
2423      $this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0');
2424    }
2425  
2426    /**
2427     * Download a file from the URL generated by file_create_url().
2428     *
2429     * Create a file with the specified scheme, directory and filename; check that
2430     * the URL generated by file_create_url() for the specified file equals the
2431     * specified URL; fetch the URL and then compare the contents to the file.
2432     *
2433     * @param $scheme
2434     *   A scheme, e.g. "public"
2435     * @param $directory
2436     *   A directory, possibly ""
2437     * @param $filename
2438     *   A filename
2439     * @param $expected_url
2440     *   The expected URL
2441     * @param $clean_url
2442     *   The value of the clean_url setting
2443     */
2444    private function checkUrl($scheme, $directory, $filename, $expected_url, $clean_url = '1') {
2445      variable_set('clean_url', $clean_url);
2446  
2447      // Convert $filename to a valid filename, i.e. strip characters not
2448      // supported by the filesystem, and create the file in the specified
2449      // directory.
2450      $filepath = file_create_filename($filename, $directory);
2451      $directory_uri = $scheme . '://' . dirname($filepath);
2452      file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
2453      $file = $this->createFile($filepath, NULL, $scheme);
2454  
2455      $url = file_create_url($file->uri);
2456      $this->assertEqual($url, $expected_url, t('Generated URL matches expected URL.'));
2457  
2458      if ($scheme == 'private') {
2459        // Tell the implementation of hook_file_download() in file_test.module
2460        // that this file may be downloaded.
2461        file_test_set_return('download', array('x-foo' => 'Bar'));
2462      }
2463  
2464      $this->drupalGet($url);
2465      if ($this->assertResponse(200) == 'pass') {
2466        $this->assertRaw(file_get_contents($file->uri), t('Contents of the file are correct.'));
2467      }
2468  
2469      file_delete($file);
2470    }
2471  }
2472  
2473  /**
2474   * Tests for file URL rewriting.
2475   */
2476  class FileURLRewritingTest extends FileTestCase {
2477    public static function getInfo() {
2478      return array(
2479        'name' => 'File URL rewriting',
2480        'description' => 'Tests for file URL rewriting.',
2481        'group' => 'File',
2482      );
2483    }
2484  
2485    function setUp() {
2486      parent::setUp('file_test');
2487    }
2488  
2489    /**
2490     * Test the generating of rewritten shipped file URLs.
2491     */
2492    function testShippedFileURL()  {
2493      // Test generating an URL to a shipped file (i.e. a file that is part of
2494      // Drupal core, a module or a theme, for example a JavaScript file).
2495  
2496      // Test alteration of file URLs to use a CDN.
2497      variable_set('file_test_hook_file_url_alter', 'cdn');
2498      $filepath = 'misc/jquery.js';
2499      $url = file_create_url($filepath);
2500      $this->assertEqual(FILE_URL_TEST_CDN_1 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.'));
2501      $filepath = 'misc/favicon.ico';
2502      $url = file_create_url($filepath);
2503      $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $filepath, $url, t('Correctly generated a CDN URL for a shipped file.'));
2504  
2505      // Test alteration of file URLs to use root-relative URLs.
2506      variable_set('file_test_hook_file_url_alter', 'root-relative');
2507      $filepath = 'misc/jquery.js';
2508      $url = file_create_url($filepath);
2509      $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.'));
2510      $filepath = 'misc/favicon.ico';
2511      $url = file_create_url($filepath);
2512      $this->assertEqual(base_path() . '/' . $filepath, $url, t('Correctly generated a root-relative URL for a shipped file.'));
2513  
2514      // Test alteration of file URLs to use protocol-relative URLs.
2515      variable_set('file_test_hook_file_url_alter', 'protocol-relative');
2516      $filepath = 'misc/jquery.js';
2517      $url = file_create_url($filepath);
2518      $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.'));
2519      $filepath = 'misc/favicon.ico';
2520      $url = file_create_url($filepath);
2521      $this->assertEqual('/' . base_path() . '/' . $filepath, $url, t('Correctly generated a protocol-relative URL for a shipped file.'));
2522    }
2523  
2524    /**
2525     * Test the generating of rewritten public created file URLs.
2526     */
2527    function testPublicCreatedFileURL() {
2528      // Test generating an URL to a created file.
2529  
2530      // Test alteration of file URLs to use a CDN.
2531      variable_set('file_test_hook_file_url_alter', 'cdn');
2532      $file = $this->createFile();
2533      $url = file_create_url($file->uri);
2534      $public_directory_path = file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath();
2535      $this->assertEqual(FILE_URL_TEST_CDN_2 . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a CDN URL for a created file.'));
2536  
2537      // Test alteration of file URLs to use root-relative URLs.
2538      variable_set('file_test_hook_file_url_alter', 'root-relative');
2539      $file = $this->createFile();
2540      $url = file_create_url($file->uri);
2541      $this->assertEqual(base_path() . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a root-relative URL for a created file.'));
2542  
2543      // Test alteration of file URLs to use a protocol-relative URLs.
2544      variable_set('file_test_hook_file_url_alter', 'protocol-relative');
2545      $file = $this->createFile();
2546      $url = file_create_url($file->uri);
2547      $this->assertEqual('/' . base_path() . '/' . $public_directory_path . '/' . $file->filename, $url, t('Correctly generated a protocol-relative URL for a created file.'));
2548    }
2549  }
2550  
2551  /**
2552   * Tests for file_munge_filename() and file_unmunge_filename().
2553   */
2554  class FileNameMungingTest extends FileTestCase {
2555    public static function getInfo() {
2556      return array(
2557        'name' => 'File naming',
2558        'description' => 'Test filename munging and unmunging.',
2559        'group' => 'File API',
2560      );
2561    }
2562  
2563    function setUp() {
2564      parent::setUp();
2565      $this->bad_extension = 'php';
2566      $this->name = $this->randomName() . '.' . $this->bad_extension . '.txt';
2567    }
2568  
2569    /**
2570     * Create a file and munge/unmunge the name.
2571     */
2572    function testMunging() {
2573      // Disable insecure uploads.
2574      variable_set('allow_insecure_uploads', 0);
2575      $munged_name = file_munge_filename($this->name, '', TRUE);
2576      $messages = drupal_get_messages();
2577      $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.'));
2578      $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)));
2579    }
2580  
2581    /**
2582     * If the allow_insecure_uploads variable evaluates to true, the file should
2583     * come out untouched, no matter how evil the filename.
2584     */
2585    function testMungeIgnoreInsecure() {
2586      variable_set('allow_insecure_uploads', 1);
2587      $munged_name = file_munge_filename($this->name, '');
2588      $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)));
2589    }
2590  
2591    /**
2592     * White listed extensions are ignored by file_munge_filename().
2593     */
2594    function testMungeIgnoreWhitelisted() {
2595      // Declare our extension as whitelisted.
2596      $munged_name = file_munge_filename($this->name, $this->bad_extension);
2597      $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)));
2598    }
2599  
2600    /**
2601     * Ensure that unmunge gets your name back.
2602     */
2603    function testUnMunge() {
2604      $munged_name = file_munge_filename($this->name, '', FALSE);
2605      $unmunged_name = file_unmunge_filename($munged_name);
2606      $this->assertIdentical($unmunged_name, $this->name, t('The unmunged (%unmunged) filename matches the original (%original)', array('%unmunged' => $unmunged_name, '%original' => $this->name)));
2607    }
2608  }
2609  
2610  /**
2611   * Tests for file_get_mimetype().
2612   */
2613  class FileMimeTypeTest extends DrupalWebTestCase {
2614    function setUp() {
2615      parent::setUp('file_test');
2616    }
2617  
2618    public static function getInfo() {
2619      return array(
2620        'name' => 'File mimetypes',
2621        'description' => 'Test filename mimetype detection.',
2622        'group' => 'File API',
2623      );
2624    }
2625  
2626    /**
2627     * Test mapping of mimetypes from filenames.
2628     */
2629    public function testFileMimeTypeDetection() {
2630      $prefix = 'public://';
2631  
2632      $test_case = array(
2633        'test.jar' => 'application/java-archive',
2634        'test.jpeg' => 'image/jpeg',
2635        'test.JPEG' => 'image/jpeg',
2636        'test.jpg' => 'image/jpeg',
2637        'test.jar.jpg' => 'image/jpeg',
2638        'test.jpg.jar' => 'application/java-archive',
2639        'test.pcf.Z' => 'application/x-font',
2640        'pcf.z' => 'application/octet-stream',
2641        'jar' => 'application/octet-stream',
2642        'some.junk' => 'application/octet-stream',
2643        'foo.file_test_1' => 'madeup/file_test_1',
2644        'foo.file_test_2' => 'madeup/file_test_2',
2645        'foo.doc' => 'madeup/doc',
2646        'test.ogg' => 'audio/ogg',
2647      );
2648  
2649      // Test using default mappings.
2650      foreach ($test_case as $input => $expected) {
2651        // Test stream [URI].
2652        $output = file_get_mimetype($prefix . $input);
2653        $this->assertIdentical($output, $expected, t('Mimetype for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected)));
2654  
2655        // Test normal path equivalent
2656        $output = file_get_mimetype($input);
2657        $this->assertIdentical($output, $expected, t('Mimetype (using default mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected)));
2658      }
2659  
2660      // Now test passing in the map.
2661      $mapping = array(
2662        'mimetypes' => array(
2663          0 => 'application/java-archive',
2664          1 => 'image/jpeg',
2665        ),
2666        'extensions' => array(
2667           'jar' => 0,
2668           'jpg' => 1,
2669        )
2670      );
2671  
2672      $test_case = array(
2673        'test.jar' => 'application/java-archive',
2674        'test.jpeg' => 'application/octet-stream',
2675        'test.jpg' => 'image/jpeg',
2676        'test.jar.jpg' => 'image/jpeg',
2677        'test.jpg.jar' => 'application/java-archive',
2678        'test.pcf.z' => 'application/octet-stream',
2679        'pcf.z' => 'application/octet-stream',
2680        'jar' => 'application/octet-stream',
2681        'some.junk' => 'application/octet-stream',
2682        'foo.file_test_1' => 'application/octet-stream',
2683        'foo.file_test_2' => 'application/octet-stream',
2684        'foo.doc' => 'application/octet-stream',
2685        'test.ogg' => 'application/octet-stream',
2686      );
2687  
2688      foreach ($test_case as $input => $expected) {
2689        $output = file_get_mimetype($input, $mapping);
2690        $this->assertIdentical($output, $expected, t('Mimetype (using passed-in mappings) for %input is %output (expected: %expected).', array('%input' => $input, '%output' => $output, '%expected' => $expected)));
2691      }
2692    }
2693  }
2694  
2695  /**
2696   * Tests stream wrapper functions.
2697   */
2698  class StreamWrapperTest extends DrupalWebTestCase {
2699  
2700    protected $scheme = 'dummy';
2701    protected $classname = 'DrupalDummyStreamWrapper';
2702  
2703    public static function getInfo() {
2704      return array(
2705        'name' => 'Stream wrappers',
2706        'description' => 'Tests stream wrapper functions.',
2707        'group' => 'File API',
2708      );
2709    }
2710  
2711    function setUp() {
2712      parent::setUp('file_test');
2713      drupal_static_reset('file_get_stream_wrappers');
2714    }
2715  
2716    function tearDown() {
2717      parent::tearDown();
2718      stream_wrapper_unregister($this->scheme);
2719    }
2720  
2721    /**
2722     * Test the getClassName() function.
2723     */
2724    function testGetClassName() {
2725      // Check the dummy scheme.
2726      $this->assertEqual($this->classname, file_stream_wrapper_get_class($this->scheme), t('Got correct class name for dummy scheme.'));
2727      // Check core's scheme.
2728      $this->assertEqual('DrupalPublicStreamWrapper', file_stream_wrapper_get_class('public'), t('Got correct class name for public scheme.'));
2729    }
2730  
2731    /**
2732     * Test the file_stream_wrapper_get_instance_by_scheme() function.
2733     */
2734    function testGetInstanceByScheme() {
2735      $instance = file_stream_wrapper_get_instance_by_scheme($this->scheme);
2736      $this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy scheme.'));
2737  
2738      $instance = file_stream_wrapper_get_instance_by_scheme('public');
2739      $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public scheme.'));
2740    }
2741  
2742    /**
2743     * Test the URI and target functions.
2744     */
2745    function testUriFunctions() {
2746      $instance = file_stream_wrapper_get_instance_by_uri($this->scheme . '://foo');
2747      $this->assertEqual($this->classname, get_class($instance), t('Got correct class type for dummy URI.'));
2748  
2749      $instance = file_stream_wrapper_get_instance_by_uri('public://foo');
2750      $this->assertEqual('DrupalPublicStreamWrapper', get_class($instance), t('Got correct class type for public URI.'));
2751  
2752      // Test file_uri_target().
2753      $this->assertEqual(file_uri_target('public://foo/bar.txt'), 'foo/bar.txt', t('Got a valid stream target from public://foo/bar.txt.'));
2754      $this->assertFalse(file_uri_target('foo/bar.txt'), t('foo/bar.txt is not a valid stream.'));
2755  
2756      // Test file_build_uri() and DrupalLocalStreamWrapper::getDirectoryPath().
2757      $this->assertEqual(file_build_uri('foo/bar.txt'), 'public://foo/bar.txt', t('Expected scheme was added.'));
2758      $this->assertEqual(file_stream_wrapper_get_instance_by_scheme('public')->getDirectoryPath(), variable_get('file_public_path'), t('Expected default directory path was returned.'));
2759      $this->assertEqual(file_stream_wrapper_get_instance_by_scheme('temporary')->getDirectoryPath(), variable_get('file_temporary_path'), t('Expected temporary directory path was returned.'));
2760  
2761      variable_set('file_default_scheme', 'private');
2762      $this->assertEqual(file_build_uri('foo/bar.txt'), 'private://foo/bar.txt', t('Got a valid URI from foo/bar.txt.'));
2763    }
2764  
2765    /**
2766     * Test the scheme functions.
2767     */
2768    function testGetValidStreamScheme() {
2769      $this->assertEqual('foo', file_uri_scheme('foo://pork//chops'), t('Got the correct scheme from foo://asdf'));
2770      $this->assertTrue(file_stream_wrapper_valid_scheme(file_uri_scheme('public://asdf')), t('Got a valid stream scheme from public://asdf'));
2771      $this->assertFalse(file_stream_wrapper_valid_scheme(file_uri_scheme('foo://asdf')), t('Did not get a valid stream scheme from foo://asdf'));
2772    }
2773  }

title

Description

title

Description

title

Description

title

title

Body