<?php

/**
 * @file
 * Test integration for the file_entity module.
 */

class FileEntityTestHelper extends DrupalWebTestCase {
  protected $files = array();

  function setUp($modules = array()) {
    $modules[] = 'file_entity';
    parent::setUp($modules);

    $this->setUpFiles();
  }

  function setUpFiles() {
    $types = array('text', 'image');
    foreach ($types as $type) {
      foreach ($this->drupalGetTestFiles($type) as $file) {
        $this->files[$type][] = file_save($file);
      }
    }
  }

  protected function createFileType($overrides = array()) {
    $type = new stdClass();
    $type->type = 'test';
    $type->label = "Test";
    $type->description = '';
    $type->mimetypes = array('image/jpeg', 'image/gif', 'image/png', 'image/tiff');
    $type->streams = array('public', 'private');

    foreach ($overrides as $k => $v) {
      $type->$k = $v;
    }

    file_type_save($type);
    return $type;
  }
}

class FileEntityUnitTestCase extends FileEntityTestHelper {
  public static function getInfo() {
    return array(
      'name' => 'File entity unit tests',
      'description' => 'Test basic file entity funcitonality.',
      'group' => 'File entity',
    );
  }

  /**
   * Regression tests for core issue http://drupal.org/node/1239376.
   */
  function testMimeTypeMappings() {
    $tests = array(
      'public://test.ogg' => 'audio/ogg',
      'public://test.mkv' => 'video/x-m4v',
      'public://test.mka' => 'audio/x-matroska',
      'public://test.mkv' => 'video/x-matroska',
      'public://test.webp' => 'image/webp',
    );
    foreach ($tests as $input => $expected) {
      $this->assertEqual(file_get_mimetype($input), $expected);
    }
  }

  function testFileEntity() {
    $file = reset($this->files['text']);

    // Test entity ID, revision ID, and bundle.
    $ids = entity_extract_ids('file', $file);
    $this->assertIdentical($ids, array($file->fid, NULL, 'document'));

    // Test the entity URI callback.
    $uri = entity_uri('file', $file);
    $this->assertEqual($uri['path'], "file/{$file->fid}");
  }

  function testImageDimensions() {
    $images_dimensions = array();
    $text_fids = array();
    // Test hook_file_insert().
    // Files have been saved as part of setup (in FileEntityTestHelper::setUpFiles).
    foreach ($this->files['image'] as $file) {
      $images_dimensions[$file->fid] = $file->image_dimensions;
      $this->assertTrue(isset($file->image_dimensions), 'Image dimensions retrieved on file_save() for an image file.');
    }
    foreach ($this->files['text'] as $file) {
      $text_fids[] = $file->fid;
      $this->assertFalse(isset($file->image_dimensions), 'No image dimensions retrieved on file_save() for an text file.');
    }

    // Test hook_file_load().
    // Clear the cache and load fresh files objects to test file_load behavior.
    entity_get_controller('file')->resetCache();
    foreach (file_load_multiple(array_keys($images_dimensions)) as $file) {
      $this->assertTrue(isset($file->image_dimensions), 'Image dimensions retrieved on file_load() for an image file.');
      $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'], 'Loaded image height is equal to saved image height.');
      $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'], 'Loaded image width is equal to saved image width.');
    }
    foreach (file_load_multiple($text_fids) as $file) {
      $this->assertFalse(isset($file->image_dimensions), 'No image dimensions retrieved on file_load() for an text file.');
    }

    // Test hook_file_update().
    // Load the first image file and resize it.
    $file = file_load(reset(array_keys($images_dimensions)));
    $image = image_load($file->uri);
    image_resize($image, $file->image_dimensions['width'] / 2, $file->image_dimensions['height'] / 2);
    image_save($image);
    file_save($file);
    $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'] / 2, 'Image file height updated by file_save().');
    $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'] / 2, 'Image file width updated by file_save().');
    // Clear the cache and reload the file.
    entity_get_controller('file')->resetCache();
    $file = file_load($file->fid);
    $this->assertEqual($file->image_dimensions['height'], $images_dimensions[$file->fid]['height'] / 2, 'Updated image height retrieved by file_load().');
    $this->assertEqual($file->image_dimensions['width'], $images_dimensions[$file->fid]['width'] / 2, 'Updated image width retrieved by file_load().');

    //Test hook_file_delete().
    file_delete($file, TRUE);
    $this->assertFalse(db_query('SELECT COUNT(*) FROM {image_dimensions} WHERE fid = :fid', array(':fid' => 'fid'))->fetchField(), 'Row deleted in {file_dimensions} on file_delete().');
  }
}

class FileEntityReplaceTestCase extends FileEntityTestHelper {
  public static function getInfo() {
    return array(
      'name' => 'File replacement',
      'description' => 'Test file replace functionality.',
      'group' => 'File entity',
    );
  }

  /**
   * @todo Test image dimensions for an image field are reset when a file is replaced.
   * @todo Test image styles are cleared when an image is updated.
   */
  function testReplaceFile() {
    // Select the first text test file to use.
    $file = reset($this->files['text']);

    // Create a user with file edit permissions.
    $user = $this->drupalCreateUser(array('edit any files'));
    $this->drupalLogin($user);

    // Test that the Upload widget appears for a local file.
    $this->drupalGet('file/' . $file->fid . '/edit');
    $this->assertFieldByName('files[replace_upload]');

    // Test that file saves without uploading a file.
    $this->drupalPost(NULL, array(), t('Save'));
    $this->assertText(t('Document @file has been updated.', array('@file' => $file->filename)), 'File was updated without file upload.');

    // Get the next text file to use as a replacement.
    $original = clone $file;
    $replacement = next($this->files['text']);

    // Test that the file saves when uploading a replacement file.
    $edit = array();
    $edit['files[replace_upload]'] = drupal_realpath($replacement->uri);
    $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
    $this->assertText(t('Document @file has been updated.', array('@file' => $file->filename)), 'File was updated with file upload.');

    // Re-load the file from the database.
    $file = file_load($file->fid);

    // Test how file properties changed after the file has been replaced.
    $this->assertEqual($file->filename, $original->filename, 'Updated file name did not change.');
    $this->assertNotEqual($file->filesize, $original->filesize, 'Updated file size changed from previous file.');
    $this->assertEqual($file->filesize, $replacement->filesize, 'Updated file size matches uploaded file.');
    $this->assertEqual(file_get_contents($replacement->uri), file_get_contents($file->uri), 'Updated file contents matches uploaded file.');

    // Get an image file.
    $image = reset($this->files['image']);
    $edit['files[replace_upload]'] = drupal_realpath($image->uri);

    // Test that validation works by uploading a non-text file as a replacement.
    $this->drupalPost('file/' . $file->fid . '/edit', $edit, t('Save'));
    $this->assertRaw(t('The specified file %file could not be uploaded. Only files with the following extensions are allowed:', array('%file' => $image->filename)), 'File validation works, upload failed correctly.');

    // Create a non-local file record.
    $file2 = new stdClass();
    $file2->uri = 'oembed://' . $this->randomName();
    $file2->filename = drupal_basename($file2->uri);
    $file2->filemime = 'image/oembed';
    $file2->type = 'image';
    $file2->uid = 1;
    $file2->timestamp = REQUEST_TIME;
    $file2->filesize = 0;
    $file2->status = 0;
    // Write the record directly rather than calling file_save() so we don't
    // invoke the hooks.
    $this->assertTrue(drupal_write_record('file_managed', $file2), 'Non-local file was added to the database.');

    // Test that Upload widget does not appear for non-local file.
    $this->drupalGet('file/' . $file2->fid . '/edit');
    $this->assertNoFieldByName('files[replace_upload]');
  }
}

class FileEntityTokenTestCase extends FileEntityTestHelper {
  public static function getInfo() {
    return array(
      'name' => 'File entity tokens',
      'description' => 'Test the file entity tokens.',
      'group' => 'File entity',
    );
  }

  function testFileEntityTokens() {
    $tokens = array(
      'type' => 'Document',
      'type:name' => 'Document',
      'type:machine-name' => 'document',
      'type:count' => count($this->files['text']),
    );
    $this->assertTokens('file', array('file' => $this->files['text'][0]), $tokens);

    $tokens = array(
      'type' => 'Image',
      'type:name' => 'Image',
      'type:machine-name' => 'image',
      'type:count' => count($this->files['image']),
    );
    $this->assertTokens('file', array('file' => $this->files['image'][0]), $tokens);
  }

  function assertTokens($type, array $data, array $tokens, array $options = array()) {
    $token_input = drupal_map_assoc(array_keys($tokens));
    $values = token_generate($type, $token_input, $data, $options);
    foreach ($tokens as $token => $expected) {
      if (!isset($expected)) {
        $this->assertTrue(!isset($values[$token]), t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
      }
      elseif (!isset($values[$token])) {
        $this->fail(t("Token value for [@type:@token] was not generated.", array('@type' => $type, '@token' => $token)));
      }
      elseif (!empty($options['regex'])) {
        $this->assertTrue(preg_match('/^' . $expected . '$/', $values[$token]), t("Token value for [@type:@token] was '@actual', matching regular expression pattern '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
      }
      else {
        $this->assertIdentical($values[$token], $expected, t("Token value for [@type:@token] was '@actual', expected value '@expected'.", array('@type' => $type, '@token' => $token, '@actual' => $values[$token], '@expected' => $expected)));
      }
    }

    return $values;
  }
}

class FileEntityTypeTestCase extends FileEntityTestHelper {
  public static function getInfo() {
    return array(
      'name' => 'File entity types',
      'description' => 'Test the file entity types.',
      'group' => 'File entity',
    );
  }

  /**
   * Test creating a new type. Basic CRUD.
   */
  function testCreate() {
    $type_machine_type = 'foo';
    $type_machine_label = 'foobar';
    $type = $this->createFileType(array('type' => $type_machine_type, 'label' => $type_machine_label));
    $loaded_type = file_type_load($type_machine_type);
    $this->assertEqual($loaded_type->label, $type_machine_label, "Was able to create a type and retreive it.");
  }


  /**
   * Ensures that the weight is respected when types are created.
   * @return unknown_type
   */
  function testOrder() {
//    $type = $this->createFileType(array('name' => 'last', 'label' => 'Last', 'weight' => 100));
//    $type = $this->createFileType(array('name' => 'first', 'label' => 'First'));
//    $types = media_type_get_types();
//    $keys = array_keys($types);
//    $this->assertTrue(isset($types['last']) && isset($types['first']), "Both types saved");
//    $this->assertTrue(array_search('last', $keys) > array_search('first', $keys), 'Type which was supposed to be first came first');
  }

  /**
   * Test view mode assignment.  Currently fails, don't know why.
   * @return unknown_type
   */
  function testViewModesAssigned() {
  }


}

class FileEntityAccessTestCase extends FileEntityTestHelper {

  public static function getInfo() {
    return array(
      'name' => 'File entity access',
      'description' => 'Test the access aspects of file entity.',
      'group' => 'File entity',
    );
  }

  /**
   * Asserts file_entity_access correctly grants or denies access.
   */
  function assertFileEntityAccess($ops, $file, $account) {
    foreach ($ops as $op => $result) {
      $msg = t("file_entity_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op));
      $this->assertEqual($result, file_entity_access($op, $file, $account), $msg);
    }
  }

  /**
   * Runs basic tests for file_entity_access function.
   */
  function testFileEntityAccess() {
    $file = reset($this->files['image']);

    // Ensures user with 'bypass file access' permission can do everything.
    $web_user = $this->drupalCreateUser(array('bypass file access'));
    $this->assertFileEntityAccess(array('create' => TRUE), NULL, $web_user);
    $this->assertFileEntityAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $file, $web_user);

    // User cannot 'view files'.
    $web_user = $this->drupalCreateUser(array('create files'));
    $this->assertFileEntityAccess(array('view' => FALSE), $file, $web_user);
    // But can upload new ones.
    $this->assertFileEntityAccess(array('create' => TRUE), NULL, $web_user);

    // User can view own files but no other files.
    $web_user = $this->drupalCreateUser(array('create files', 'view own files'));
    $this->assertFileEntityAccess(array('view' => FALSE), $file, $web_user);
    $file->uid = $web_user->uid;
    $file->status = FILE_STATUS_PERMANENT;
    file_save($file);
    $this->assertFileEntityAccess(array('view' => TRUE), $file, $web_user);

    // User can update own files but no other files.
    $web_user = $this->drupalCreateUser(array('create files', 'view own files', 'edit own files'));
    $this->assertFileEntityAccess(array('update' => FALSE), $file, $web_user);
    $file->uid = $web_user->uid;
    $file->status = FILE_STATUS_PERMANENT;
    file_save($file);
    $this->assertFileEntityAccess(array('update' => TRUE), $file, $web_user);

    // User can delete own files but no other files.
    $web_user = $this->drupalCreateUser(array('create files', 'view own files', 'edit own files', 'delete own files'));
    $this->assertFileEntityAccess(array('delete' => FALSE), $file, $web_user);
    $file->uid = $web_user->uid;
    $file->status = FILE_STATUS_PERMANENT;
    file_save($file);
    $this->assertFileEntityAccess(array('delete' => TRUE), $file, $web_user);

    // User can view any file.
    $web_user = $this->drupalCreateUser(array('create files', 'view files'));
    $this->assertFileEntityAccess(array('view' => TRUE), $file, $web_user);

    // User can edit any file.
    $web_user = $this->drupalCreateUser(array('create files', 'view files', 'edit any files'));
    $this->assertFileEntityAccess(array('update' => TRUE), $file, $web_user);

    // User can delete any file.
    $web_user = $this->drupalCreateUser(array('create files', 'view files', 'edit any files', 'delete any files'));
    $this->assertFileEntityAccess(array('delete' => TRUE), $file, $web_user);
  }

  /**
   * Test to see if we have access to view files when granted the permissions.
   * In this test we aim to prove the permissions work in the following pages:
   *  file/add
   *  file/%/view
   *  file/%/edit
   *  file/%/delete
   */
  function testFileEntityPageAccess() {
    $web_user = $this->drupalCreateUser(array());
    $this->drupalLogin($web_user);
    $this->drupalGet('file/add');
    $this->assertResponse(403, 'Users without access can not access the file add page');
    $web_user = $this->drupalCreateUser(array('create files'));
    $this->drupalLogin($web_user);
    $this->drupalGet('file/add');
    $this->assertResponse(200, 'Users with access can access the file add page');

    $file = reset($this->files['text']);
    $file->status = FILE_STATUS_PERMANENT;
    file_save($file);

    // This fails.. No clue why but, tested manually and works as should.
    //$web_user = $this->drupalCreateUser(array('view own files'));
    //$this->drupalLogin($web_user);
    //$this->drupalGet("file/{$file->fid}/view");
    //$this->assertResponse(403, 'Users without access can not access the file view page');
    $web_user = $this->drupalCreateUser(array('view files'));
    $this->drupalLogin($web_user);
    $this->drupalGet("file/{$file->fid}/view");
    $this->assertResponse(200, 'Users with access can access the file view page');

    $web_user = $this->drupalCreateUser(array());
    $this->drupalLogin($web_user);
    $this->drupalGet("file/{$file->fid}/edit");
    $this->assertResponse(403, 'Users without access can not access the file edit page');
    $web_user = $this->drupalCreateUser(array('edit any files'));
    $this->drupalLogin($web_user);
    $this->drupalGet("file/{$file->fid}/edit");
    $this->assertResponse(200, 'Users with access can access the file add page');

    $web_user = $this->drupalCreateUser(array());
    $this->drupalLogin($web_user);
    $this->drupalGet("file/{$file->fid}/delete");
    $this->assertResponse(403, 'Users without access can not access the file view page');
    $web_user = $this->drupalCreateUser(array('delete any files'));
    $this->drupalLogin($web_user);
    $this->drupalGet("file/{$file->fid}/delete");
    $this->assertResponse(200, 'Users with access can access the file add page');
  }
}
