<?php

/**
 * @file
 * Tests for user.module.
 */

class UserRegistrationTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User registration',
      'description' => 'Test registration of user under different configurations.',
      'group' => 'User'
    );
  }

  function setUp() {
    parent::setUp('field_test');
  }

  function testRegistrationWithEmailVerification() {
    // Require e-mail verification.
    variable_set('user_email_verification', TRUE);

    // Set registration to administrator only.
    variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY);
    $this->drupalGet('user/register');
    $this->assertResponse(403, 'Registration page is inaccessible when only administrators can create accounts.');

    // Allow registration by site visitors without administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS);
    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $this->assertText(t('A welcome message with further instructions has been sent to your e-mail address.'), 'User registered successfully.');
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertTrue($new_user->status, 'New account is active after registration.');

    // Allow registration by site visitors, but require administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertFalse($new_user->status, 'New account is blocked until approved by an administrator.');
  }

  function testRegistrationWithoutEmailVerification() {
    // Don't require e-mail verification.
    variable_set('user_email_verification', FALSE);

    // Allow registration by site visitors without administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS);
    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';

    // Try entering a mismatching password.
    $edit['pass[pass1]'] = '99999.0';
    $edit['pass[pass2]'] = '99999';
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $this->assertText(t('The specified passwords do not match.'), 'Typing mismatched passwords displays an error message.');

    // Enter a correct password.
    $edit['pass[pass1]'] = $new_pass = $this->randomName();
    $edit['pass[pass2]'] = $new_pass;
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertText(t('Registration successful. You are now logged in.'), 'Users are logged in after registering.');
    $this->drupalLogout();

    // Allow registration by site visitors, but require administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
    $edit['pass[pass1]'] = $pass = $this->randomName();
    $edit['pass[pass2]'] = $pass;
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $this->assertText(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.'), 'Users are notified of pending approval');

    // Try to login before administrator approval.
    $auth = array(
      'name' => $name,
      'pass' => $pass,
    );
    $this->drupalPost('user/login', $auth, t('Log in'));
    $this->assertText(t('The username @name has not been activated or is blocked.', array('@name' => $name)), 'User cannot login yet.');

    // Activate the new account.
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $admin_user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($admin_user);
    $edit = array(
      'status' => 1,
    );
    $this->drupalPost('user/' . $new_user->uid . '/edit', $edit, t('Save'));
    $this->drupalLogout();

    // Login after administrator approval.
    $this->drupalPost('user/login', $auth, t('Log in'));
    $this->assertText(t('Member for'), 'User can log in after administrator approval.');
  }

  function testRegistrationEmailDuplicates() {
    // Don't require e-mail verification.
    variable_set('user_email_verification', FALSE);

    // Allow registration by site visitors without administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS);

    // Set up a user to check for duplicates.
    $duplicate_user = $this->drupalCreateUser();

    $edit = array();
    $edit['name'] = $this->randomName();
    $edit['mail'] = $duplicate_user->mail;

    // Attempt to create a new account using an existing e-mail address.
    $this->drupalPost('user/register', $edit, t('Create new account'));
    $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), 'Supplying an exact duplicate email address displays an error message');

    // Attempt to bypass duplicate email registration validation by adding spaces.
    $edit['mail'] = '   ' . $duplicate_user->mail . '   ';

    $this->drupalPost('user/register', $edit, t('Create new account'));
    $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), 'Supplying a duplicate email address with added whitespace displays an error message');
  }

  function testRegistrationDefaultValues() {
    // Allow registration by site visitors without administrator approval.
    variable_set('user_register', USER_REGISTER_VISITORS);

    // Don't require e-mail verification.
    variable_set('user_email_verification', FALSE);

    // Set the default timezone to Brussels.
    variable_set('configurable_timezones', 1);
    variable_set('date_default_timezone', 'Europe/Brussels');

    // Check that the account information fieldset's options are not displayed
    // is a fieldset if there is not more than one fieldset in the form.
    $this->drupalGet('user/register');
    $this->assertNoRaw('<fieldset id="edit-account"><legend>Account information</legend>', 'Account settings fieldset was hidden.');

    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
    $edit['pass[pass1]'] = $new_pass = $this->randomName();
    $edit['pass[pass2]'] = $new_pass;
    $this->drupalPost(NULL, $edit, t('Create new account'));

    // Check user fields.
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertEqual($new_user->name, $name, 'Username matches.');
    $this->assertEqual($new_user->mail, $mail, 'E-mail address matches.');
    $this->assertEqual($new_user->theme, '', 'Correct theme field.');
    $this->assertEqual($new_user->signature, '', 'Correct signature field.');
    $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), 'Correct creation time.');
    $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, 'Correct status field.');
    $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), 'Correct time zone field.');
    $this->assertEqual($new_user->language, '', 'Correct language field.');
    $this->assertEqual($new_user->picture, '', 'Correct picture field.');
    $this->assertEqual($new_user->init, $mail, 'Correct init field.');
  }

  /**
   * Tests Field API fields on user registration forms.
   */
  function testRegistrationWithUserFields() {
    // Create a field, and an instance on 'user' entity type.
    $field = array(
      'type' => 'test_field',
      'field_name' => 'test_user_field',
      'cardinality' => 1,
    );
    field_create_field($field);
    $instance = array(
      'field_name' => 'test_user_field',
      'entity_type' => 'user',
      'label' => 'Some user field',
      'bundle' => 'user',
      'required' => TRUE,
      'settings' => array('user_register_form' => FALSE),
    );
    field_create_instance($instance);

    // Check that the field does not appear on the registration form.
    $this->drupalGet('user/register');
    $this->assertNoText($instance['label'], 'The field does not appear on user registration form');

    // Have the field appear on the registration form.
    $instance['settings']['user_register_form'] = TRUE;
    field_update_instance($instance);
    $this->drupalGet('user/register');
    $this->assertText($instance['label'], 'The field appears on user registration form');

    // Check that validation errors are correctly reported.
    $edit = array();
    $edit['name'] = $name = $this->randomName();
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
    // Missing input in required field.
    $edit['test_user_field[und][0][value]'] = '';
    $this->drupalPost(NULL, $edit, t('Create new account'));
    $this->assertRaw(t('@name field is required.', array('@name' => $instance['label'])), 'Field validation error was correctly reported.');
    // Invalid input.
    $edit['test_user_field[und][0][value]'] = '-1';
    $this->drupalPost(NULL, $edit, t('Create new account'));
    $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $instance['label'])), 'Field validation error was correctly reported.');

    // Submit with valid data.
    $value = rand(1, 255);
    $edit['test_user_field[und][0][value]'] = $value;
    $this->drupalPost(NULL, $edit, t('Create new account'));
    // Check user fields.
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertEqual($new_user->test_user_field[LANGUAGE_NONE][0]['value'], $value, 'The field value was correctly saved.');

    // Check that the 'add more' button works.
    $field['cardinality'] = FIELD_CARDINALITY_UNLIMITED;
    field_update_field($field);
    foreach (array('js', 'nojs') as $js) {
      $this->drupalGet('user/register');
      // Add two inputs.
      $value = rand(1, 255);
      $edit = array();
      $edit['test_user_field[und][0][value]'] = $value;
      if ($js == 'js') {
        $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more');
        $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more');
      }
      else {
        $this->drupalPost(NULL, $edit, t('Add another item'));
        $this->drupalPost(NULL, $edit, t('Add another item'));
      }
      // Submit with three values.
      $edit['test_user_field[und][1][value]'] = $value + 1;
      $edit['test_user_field[und][2][value]'] = $value + 2;
      $edit['name'] = $name = $this->randomName();
      $edit['mail'] = $mail = $edit['name'] . '@example.com';
      $this->drupalPost(NULL, $edit, t('Create new account'));
      // Check user fields.
      $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
      $new_user = reset($accounts);
      $this->assertEqual($new_user->test_user_field[LANGUAGE_NONE][0]['value'], $value, format_string('@js : The field value was correclty saved.', array('@js' => $js)));
      $this->assertEqual($new_user->test_user_field[LANGUAGE_NONE][1]['value'], $value + 1, format_string('@js : The field value was correclty saved.', array('@js' => $js)));
      $this->assertEqual($new_user->test_user_field[LANGUAGE_NONE][2]['value'], $value + 2, format_string('@js : The field value was correclty saved.', array('@js' => $js)));
    }
  }
}

class UserValidationTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Username/e-mail validation',
      'description' => 'Verify that username/email validity checks behave as designed.',
      'group' => 'User'
    );
  }

  // Username validation.
  function testUsernames() {
    $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'),
      'foo'                    => array('Valid username', 'assertNull'),
      'FOO'                    => array('Valid username', 'assertNull'),
      'Foo O\'Bar'             => array('Valid username', 'assertNull'),
      'foo@bar'                => array('Valid username', 'assertNull'),
      'foo@example.com'        => array('Valid username', 'assertNull'),
      'foo@-example.com'       => array('Valid username', 'assertNull'), // invalid domains are allowed in usernames
      'þòøÇßªř€'               => array('Valid username', 'assertNull'),
      'ᚠᛇᚻ᛫ᛒᛦᚦ'                => array('Valid UTF8 username', 'assertNull'), // runes
      ' foo'                   => array('Invalid username that starts with a space', 'assertNotNull'),
      'foo '                   => array('Invalid username that ends with a space', 'assertNotNull'),
      'foo  bar'               => array('Invalid username that contains 2 spaces \'&nbsp;&nbsp;\'', 'assertNotNull'),
      ''                       => array('Invalid empty username', 'assertNotNull'),
      'foo/'                   => array('Invalid username containing invalid chars', 'assertNotNull'),
      'foo' . chr(0) . 'bar'   => array('Invalid username containing chr(0)', 'assertNotNull'), // NULL
      'foo' . chr(13) . 'bar'  => array('Invalid username containing chr(13)', 'assertNotNull'), // CR
      str_repeat('x', USERNAME_MAX_LENGTH + 1) => array('Invalid excessively long username', 'assertNotNull'),
    );
    foreach ($test_cases as $name => $test_case) {
      list($description, $test) = $test_case;
      $result = user_validate_name($name);
      $this->$test($result, $description . ' (' . $name . ')');
    }
  }

  // Mail validation. More extensive tests can be found at common.test
  function testMailAddresses() {
    $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'),
      ''                => array('Empty mail address', 'assertNotNull'),
      'foo'             => array('Invalid mail address', 'assertNotNull'),
      'foo@example.com' => array('Valid mail address', 'assertNull'),
    );
    foreach ($test_cases as $name => $test_case) {
      list($description, $test) = $test_case;
      $result = user_validate_mail($name);
      $this->$test($result, $description . ' (' . $name . ')');
    }
  }
}

/**
 * Functional tests for user logins, including rate limiting of login attempts.
 */
class UserLoginTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User login',
      'description' => 'Ensure that login works as expected.',
      'group' => 'User',
    );
  }

  /**
   * Test the global login flood control.
   */
  function testGlobalLoginFloodControl() {
    // Set the global login limit.
    variable_set('user_failed_login_ip_limit', 10);
    // Set a high per-user limit out so that it is not relevant in the test.
    variable_set('user_failed_login_user_limit', 4000);

    $user1 = $this->drupalCreateUser(array());
    $incorrect_user1 = clone $user1;
    $incorrect_user1->pass_raw .= 'incorrect';

    // Try 2 failed logins.
    for ($i = 0; $i < 2; $i++) {
      $this->assertFailedLogin($incorrect_user1);
    }

    // A successful login will not reset the IP-based flood control count.
    $this->drupalLogin($user1);
    $this->drupalLogout();

    // Try 8 more failed logins, they should not trigger the flood control
    // mechanism.
    for ($i = 0; $i < 8; $i++) {
      $this->assertFailedLogin($incorrect_user1);
    }

    // The next login trial should result in an IP-based flood error message.
    $this->assertFailedLogin($incorrect_user1, 'ip');

    // A login with the correct password should also result in a flood error
    // message.
    $this->assertFailedLogin($user1, 'ip');
  }

  /**
   * Test the per-user login flood control.
   */
  function testPerUserLoginFloodControl() {
    // Set a high global limit out so that it is not relevant in the test.
    variable_set('user_failed_login_ip_limit', 4000);
    // Set the per-user login limit.
    variable_set('user_failed_login_user_limit', 3);

    $user1 = $this->drupalCreateUser(array());
    $incorrect_user1 = clone $user1;
    $incorrect_user1->pass_raw .= 'incorrect';

    $user2 = $this->drupalCreateUser(array());

    // Try 2 failed logins.
    for ($i = 0; $i < 2; $i++) {
      $this->assertFailedLogin($incorrect_user1);
    }

    // A successful login will reset the per-user flood control count.
    $this->drupalLogin($user1);
    $this->drupalLogout();

    // Try 3 failed logins for user 1, they will not trigger flood control.
    for ($i = 0; $i < 3; $i++) {
      $this->assertFailedLogin($incorrect_user1);
    }

    // Try one successful attempt for user 2, it should not trigger any
    // flood control.
    $this->drupalLogin($user2);
    $this->drupalLogout();

    // Try one more attempt for user 1, it should be rejected, even if the
    // correct password has been used.
    $this->assertFailedLogin($user1, 'user');
  }

  /**
   * Test that user password is re-hashed upon login after changing $count_log2.
   */
  function testPasswordRehashOnLogin() {
    // Load password hashing API.
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
    // Set initial $count_log2 to the default, DRUPAL_HASH_COUNT.
    variable_set('password_count_log2', DRUPAL_HASH_COUNT);
    // Create a new user and authenticate.
    $account = $this->drupalCreateUser(array());
    $password = $account->pass_raw;
    $this->drupalLogin($account);
    $this->drupalLogout();
    // Load the stored user. The password hash should reflect $count_log2.
    $account = user_load($account->uid);
    $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT);
    // Change $count_log2 and log in again.
    variable_set('password_count_log2', DRUPAL_HASH_COUNT + 1);
    $account->pass_raw = $password;
    $this->drupalLogin($account);
    // Load the stored user, which should have a different password hash now.
    $account = user_load($account->uid, TRUE);
    $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT + 1);
  }

  /**
   * Make an unsuccessful login attempt.
   *
   * @param $account
   *   A user object with name and pass_raw attributes for the login attempt.
   * @param $flood_trigger
   *   Whether or not to expect that the flood control mechanism will be
   *   triggered.
   */
  function assertFailedLogin($account, $flood_trigger = NULL) {
    $edit = array(
      'name' => $account->name,
      'pass' => $account->pass_raw,
    );
    $this->drupalPost('user', $edit, t('Log in'));
    $this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, 'Password value attribute is blank.');
    if (isset($flood_trigger)) {
      if ($flood_trigger == 'user') {
        $this->assertRaw(format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
      }
      else {
        // No uid, so the limit is IP-based.
        $this->assertRaw(t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
      }
    }
    else {
      $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?'));
    }
  }
}

/**
 * Tests resetting a user password.
 */
class UserPasswordResetTestCase extends DrupalWebTestCase {
  protected $profile = 'standard';

  public static function getInfo() {
    return array(
      'name' => 'Reset password',
      'description' => 'Ensure that password reset methods work as expected.',
      'group' => 'User',
    );
  }

  /**
   * Tests password reset functionality.
   */
  function testUserPasswordReset() {
    // Create a user.
    $account = $this->drupalCreateUser();
    $this->drupalLogin($account);
    $this->drupalLogout();
    // Attempt to reset password.
    $edit = array('name' => $account->name);
    $this->drupalPost('user/password', $edit, t('E-mail new password'));
    // Confirm the password reset.
    $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.');
  }

  /**
   * Attempts login using an expired password reset link.
   */
  function testUserPasswordResetExpired() {
    // Set password reset timeout variable to 43200 seconds = 12 hours.
    $timeout = 43200;
    variable_set('user_password_reset_timeout', $timeout);

    // Create a user.
    $account = $this->drupalCreateUser();
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);
    $this->drupalLogout();

    // To attempt an expired password reset, create a password reset link as if
    // its request time was 60 seconds older than the allowed limit of timeout.
    $bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60;
    $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
    $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.');
  }

  /**
   * Prefill the text box on incorrect login via link to password reset page.
   */
  function testUserPasswordTextboxFilled() {
    $this->drupalGet('user/login');
    $edit = array(
      'name' => $this->randomName(),
      'pass' => $this->randomName(),
    );
    $this->drupalPost('user', $edit, t('Log in'));
    $this->assertRaw(t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>',
      array('@password' => url('user/password', array('query' => array('name' => $edit['name']))))));
    unset($edit['pass']);
    $this->drupalGet('user/password', array('query' => array('name' => $edit['name'])));
    $this->assertFieldByName('name', $edit['name'], 'User name found.');
  }

  /**
   * Make sure that users cannot forge password reset URLs of other users.
   */
  function testResetImpersonation() {
    // Make sure user 1 has a valid password, so it does not interfere with the
    // test user accounts that are created below.
    $account = user_load(1);
    user_save($account, array('pass' => user_password()));

    // Create two identical user accounts except for the user name. They must
    // have the same empty password, so we can't use $this->drupalCreateUser().
    $edit = array();
    $edit['name'] = $this->randomName();
    $edit['mail'] = $edit['name'] . '@example.com';
    $edit['status'] = 1;

    $user1 = user_save(drupal_anonymous_user(), $edit);

    $edit['name'] = $this->randomName();
    $user2 = user_save(drupal_anonymous_user(), $edit);

    // The password reset URL must not be valid for the second user when only
    // the user ID is changed in the URL.
    $reset_url = user_pass_reset_url($user1);
    $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
    $this->drupalGet($attack_reset_url);
    $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
    $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');

    // When legacy code calls user_pass_rehash() without providing the $uid
    // parameter, neither password reset URL should be valid since it is
    // impossible for the system to determine which user account the token was
    // intended for.
    $timestamp = REQUEST_TIME;
    // Pass an explicit NULL for the $uid parameter of user_pass_rehash()
    // rather than not passing it at all, to avoid triggering PHP warnings in
    // the test.
    $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
    $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
    $this->drupalGet($reset_url);
    $this->assertNoText($user1->name, 'The invalid password reset page does not show the user name.');
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
    $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
    $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
    $this->drupalGet($attack_reset_url);
    $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
    $this->assertText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');

    // To verify that user_pass_rehash() never returns a valid result in the
    // above situation (even if legacy code also called it to attempt to
    // validate the token, rather than just to generate the URL), check that a
    // second call with the same parameters produces a different result.
    $new_reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
    $this->assertNotEqual($reset_url_token, $new_reset_url_token);

    // However, when the duplicate account is removed, the password reset URL
    // should be valid.
    user_delete($user2->uid);
    $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
    $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
    $this->drupalGet($reset_url);
    $this->assertText($user1->name, 'The valid password reset page shows the user name.');
    $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.');
    $this->assertNoText('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.');
  }

}

/**
 * Test cancelling a user.
 */
class UserCancelTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Cancel account',
      'description' => 'Ensure that account cancellation methods work as expected.',
      'group' => 'User',
    );
  }

  function setUp() {
    parent::setUp('comment');
  }

  /**
   * Attempt to cancel account without permission.
   */
  function testUserCancelWithoutPermission() {
    variable_set('user_cancel_method', 'user_cancel_reassign');

    // Create a user.
    $account = $this->drupalCreateUser(array());
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);

    // Create a node.
    $node = $this->drupalCreateNode(array('uid' => $account->uid));

    // Attempt to cancel account.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->assertNoRaw(t('Cancel account'), 'No cancel account button displayed.');

    // Attempt bogus account cancellation request confirmation.
    $timestamp = $account->login;
    $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
    $this->assertResponse(403, 'Bogus cancelling request rejected.');
    $account = user_load($account->uid);
    $this->assertTrue($account->status == 1, 'User account was not canceled.');

    // Confirm user's content has not been altered.
    $test_node = node_load($node->nid, NULL, TRUE);
    $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), 'Node of the user has not been altered.');
  }

  /**
   * Tests that user account for uid 1 cannot be cancelled.
   *
   * This should never be possible, or the site owner would become unable to
   * administer the site.
   */
  function testUserCancelUid1() {
    // Update uid 1's name and password to we know it.
    $password = user_password();
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
    $account = array(
      'name' => 'user1',
      'pass' => user_hash_password(trim($password)),
    );
    // We cannot use user_save() here or the password would be hashed again.
    db_update('users')
      ->fields($account)
      ->condition('uid', 1)
      ->execute();

    // Reload and log in uid 1.
    $user1 = user_load(1, TRUE);
    $user1->pass_raw = $password;

    // Try to cancel uid 1's account with a different user.
    $this->admin_user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($this->admin_user);
    $edit = array(
      'operation' => 'cancel',
      'accounts[1]' => TRUE,
    );
    $this->drupalPost('admin/people', $edit, t('Update'));

    // Verify that uid 1's account was not cancelled.
    $user1 = user_load(1, TRUE);
    $this->assertEqual($user1->status, 1, 'User #1 still exists and is not blocked.');
  }

  /**
   * Attempt invalid account cancellations.
   */
  function testUserCancelInvalid() {
    variable_set('user_cancel_method', 'user_cancel_reassign');

    // Create a user.
    $account = $this->drupalCreateUser(array('cancel account'));
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);

    // Create a node.
    $node = $this->drupalCreateNode(array('uid' => $account->uid));

    // Attempt to cancel account.
    $this->drupalPost('user/' . $account->uid . '/edit', NULL, t('Cancel account'));

    // Confirm account cancellation.
    $timestamp = time();
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');

    // Attempt bogus account cancellation request confirmation.
    $bogus_timestamp = $timestamp + 60;
    $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
    $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Bogus cancelling request rejected.');
    $account = user_load($account->uid);
    $this->assertTrue($account->status == 1, 'User account was not canceled.');

    // Attempt expired account cancellation request confirmation.
    $bogus_timestamp = $timestamp - 86400 - 60;
    $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
    $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), 'Expired cancel account request rejected.');
    $accounts = user_load_multiple(array($account->uid), array('status' => 1));
    $this->assertTrue(reset($accounts), 'User account was not canceled.');

    // Confirm user's content has not been altered.
    $test_node = node_load($node->nid, NULL, TRUE);
    $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), 'Node of the user has not been altered.');
  }

  /**
   * Disable account and keep all content.
   */
  function testUserBlock() {
    variable_set('user_cancel_method', 'user_cancel_block');

    // Create a user.
    $web_user = $this->drupalCreateUser(array('cancel account'));
    $this->drupalLogin($web_user);

    // Load real user object.
    $account = user_load($web_user->uid, TRUE);

    // Attempt to cancel account.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
    $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your user name.'), 'Informs that all content will be remain as is.');
    $this->assertNoText(t('Select the method to cancel the account above.'), 'Does not allow user to select account cancellation method.');

    // Confirm account cancellation.
    $timestamp = time();

    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');

    // Confirm account cancellation request.
    $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
    $account = user_load($account->uid, TRUE);
    $this->assertTrue($account->status == 0, 'User has been blocked.');

    // Confirm that the confirmation message made it through to the end user.
    $this->assertRaw(t('%name has been disabled.', array('%name' => $account->name)), "Confirmation message displayed to user.");
  }

  /**
   * Disable account and unpublish all content.
   */
  function testUserBlockUnpublish() {
    variable_set('user_cancel_method', 'user_cancel_block_unpublish');

    // Create a user.
    $account = $this->drupalCreateUser(array('cancel account'));
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);

    // Create a node with two revisions.
    $node = $this->drupalCreateNode(array('uid' => $account->uid));
    $settings = get_object_vars($node);
    $settings['revision'] = 1;
    $node = $this->drupalCreateNode($settings);

    // Attempt to cancel account.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
    $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators.'), 'Informs that all content will be unpublished.');

    // Confirm account cancellation.
    $timestamp = time();
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');

    // Confirm account cancellation request.
    $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
    $account = user_load($account->uid, TRUE);
    $this->assertTrue($account->status == 0, 'User has been blocked.');

    // Confirm user's content has been unpublished.
    $test_node = node_load($node->nid, NULL, TRUE);
    $this->assertTrue($test_node->status == 0, 'Node of the user has been unpublished.');
    $test_node = node_load($node->nid, $node->vid, TRUE);
    $this->assertTrue($test_node->status == 0, 'Node revision of the user has been unpublished.');

    // Confirm that the confirmation message made it through to the end user.
    $this->assertRaw(t('%name has been disabled.', array('%name' => $account->name)), "Confirmation message displayed to user.");
  }

  /**
   * Delete account and anonymize all content.
   */
  function testUserAnonymize() {
    variable_set('user_cancel_method', 'user_cancel_reassign');

    // Create a user.
    $account = $this->drupalCreateUser(array('cancel account'));
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);

    // Create a simple node.
    $node = $this->drupalCreateNode(array('uid' => $account->uid));

    // Create a node with two revisions, the initial one belonging to the
    // cancelling user.
    $revision_node = $this->drupalCreateNode(array('uid' => $account->uid));
    $revision = $revision_node->vid;
    $settings = get_object_vars($revision_node);
    $settings['revision'] = 1;
    $settings['uid'] = 1; // Set new/current revision to someone else.
    $revision_node = $this->drupalCreateNode($settings);

    // Attempt to cancel account.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
    $this->assertRaw(t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')))), 'Informs that all content will be attributed to anonymous account.');

    // Confirm account cancellation.
    $timestamp = time();
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');

    // Confirm account cancellation request.
    $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
    $this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.');

    // Confirm that user's content has been attributed to anonymous user.
    $test_node = node_load($node->nid, NULL, TRUE);
    $this->assertTrue(($test_node->uid == 0 && $test_node->status == 1), 'Node of the user has been attributed to anonymous user.');
    $test_node = node_load($revision_node->nid, $revision, TRUE);
    $this->assertTrue(($test_node->revision_uid == 0 && $test_node->status == 1), 'Node revision of the user has been attributed to anonymous user.');
    $test_node = node_load($revision_node->nid, NULL, TRUE);
    $this->assertTrue(($test_node->uid != 0 && $test_node->status == 1), "Current revision of the user's node was not attributed to anonymous user.");

    // Confirm that the confirmation message made it through to the end user.
    $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), "Confirmation message displayed to user.");
  }

  /**
   * Delete account and remove all content.
   */
  function testUserDelete() {
    variable_set('user_cancel_method', 'user_cancel_delete');

    // Create a user.
    $account = $this->drupalCreateUser(array('cancel account', 'post comments', 'skip comment approval'));
    $this->drupalLogin($account);
    // Load real user object.
    $account = user_load($account->uid, TRUE);

    // Create a simple node.
    $node = $this->drupalCreateNode(array('uid' => $account->uid));

    // Create comment.
    $langcode = LANGUAGE_NONE;
    $edit = array();
    $edit['subject'] = $this->randomName(8);
    $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16);

    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
    $this->drupalPost(NULL, array(), t('Save'));
    $this->assertText(t('Your comment has been posted.'));
    $comments = comment_load_multiple(array(), array('subject' => $edit['subject']));
    $comment = reset($comments);
    $this->assertTrue($comment->cid, 'Comment found.');

    // Create a node with two revisions, the initial one belonging to the
    // cancelling user.
    $revision_node = $this->drupalCreateNode(array('uid' => $account->uid));
    $revision = $revision_node->vid;
    $settings = get_object_vars($revision_node);
    $settings['revision'] = 1;
    $settings['uid'] = 1; // Set new/current revision to someone else.
    $revision_node = $this->drupalCreateNode($settings);

    // Attempt to cancel account.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
    $this->assertText(t('Your account will be removed and all account information deleted. All of your content will also be deleted.'), 'Informs that all content will be deleted.');

    // Confirm account cancellation.
    $timestamp = time();
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');

    // Confirm account cancellation request.
    $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid));
    $this->assertFalse(user_load($account->uid, TRUE), 'User is not found in the database.');

    // Confirm that user's content has been deleted.
    $this->assertFalse(node_load($node->nid, NULL, TRUE), 'Node of the user has been deleted.');
    $this->assertFalse(node_load($node->nid, $revision, TRUE), 'Node revision of the user has been deleted.');
    $this->assertTrue(node_load($revision_node->nid, NULL, TRUE), "Current revision of the user's node was not deleted.");
    $this->assertFalse(comment_load($comment->cid), 'Comment of the user has been deleted.');

    // Confirm that the confirmation message made it through to the end user.
    $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), "Confirmation message displayed to user.");
  }

  /**
   * Create an administrative user and delete another user.
   */
  function testUserCancelByAdmin() {
    variable_set('user_cancel_method', 'user_cancel_reassign');

    // Create a regular user.
    $account = $this->drupalCreateUser(array());

    // Create administrative user.
    $admin_user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($admin_user);

    // Delete regular user.
    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertRaw(t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)), 'Confirmation form to cancel account displayed.');
    $this->assertText(t('Select the method to cancel the account above.'), 'Allows to select account cancellation method.');

    // Confirm deletion.
    $this->drupalPost(NULL, NULL, t('Cancel account'));
    $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), 'User deleted.');
    $this->assertFalse(user_load($account->uid), 'User is not found in the database.');
  }

  /**
   * Create an administrative user and mass-delete other users.
   */
  function testMassUserCancelByAdmin() {
    variable_set('user_cancel_method', 'user_cancel_reassign');
    // Enable account cancellation notification.
    variable_set('user_mail_status_canceled_notify', TRUE);

    // Create administrative user.
    $admin_user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($admin_user);

    // Create some users.
    $users = array();
    for ($i = 0; $i < 3; $i++) {
      $account = $this->drupalCreateUser(array());
      $users[$account->uid] = $account;
    }

    // Cancel user accounts, including own one.
    $edit = array();
    $edit['operation'] = 'cancel';
    foreach ($users as $uid => $account) {
      $edit['accounts[' . $uid . ']'] = TRUE;
    }
    $edit['accounts[' . $admin_user->uid . ']'] = TRUE;
    // Also try to cancel uid 1.
    $edit['accounts[1]'] = TRUE;
    $this->drupalPost('admin/people', $edit, t('Update'));
    $this->assertText(t('Are you sure you want to cancel these user accounts?'), 'Confirmation form to cancel accounts displayed.');
    $this->assertText(t('When cancelling these accounts'), 'Allows to select account cancellation method.');
    $this->assertText(t('Require e-mail confirmation to cancel account.'), 'Allows to send confirmation mail.');
    $this->assertText(t('Notify user when account is canceled.'), 'Allows to send notification mail.');

    // Confirm deletion.
    $this->drupalPost(NULL, NULL, t('Cancel accounts'));
    $status = TRUE;
    foreach ($users as $account) {
      $status = $status && (strpos($this->content, t('%name has been deleted.', array('%name' => $account->name))) !== FALSE);
      $status = $status && !user_load($account->uid, TRUE);
    }
    $this->assertTrue($status, 'Users deleted and not found in the database.');

    // Ensure that admin account was not cancelled.
    $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), 'Account cancellation request mailed message displayed.');
    $admin_user = user_load($admin_user->uid);
    $this->assertTrue($admin_user->status == 1, 'Administrative user is found in the database and enabled.');

    // Verify that uid 1's account was not cancelled.
    $user1 = user_load(1, TRUE);
    $this->assertEqual($user1->status, 1, 'User #1 still exists and is not blocked.');
  }
}

class UserPictureTestCase extends DrupalWebTestCase {
  protected $user;
  protected $_directory_test;

  public static function getInfo() {
    return array(
      'name' => 'Upload user picture',
      'description' => 'Assure that dimension check, extension check and image scaling work as designed.',
      'group' => 'User'
    );
  }

  function setUp() {
    parent::setUp();
    // Enable user pictures.
    variable_set('user_pictures', 1);

    $this->user = $this->drupalCreateUser();

    // Test if directories specified in settings exist in filesystem.
    $file_dir = 'public://';
    $file_check = file_prepare_directory($file_dir, FILE_CREATE_DIRECTORY);
    // TODO: Test public and private methods?

    $picture_dir = variable_get('user_picture_path', 'pictures');
    $picture_path = $file_dir . $picture_dir;

    $pic_check = file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY);
    $this->_directory_test = is_writable($picture_path);
    $this->assertTrue($this->_directory_test, "The directory $picture_path doesn't exist or is not writable. Further tests won't be made.");
  }

  function testNoPicture() {
    $this->drupalLogin($this->user);

    // Try to upload a file that is not an image for the user picture.
    $not_an_image = current($this->drupalGetTestFiles('html'));
    $this->saveUserPicture($not_an_image);
    $this->assertRaw(t('Only JPEG, PNG and GIF images are allowed.'), 'Non-image files are not accepted.');
  }

  /**
   * Do the test:
   *  GD Toolkit is installed
   *  Picture has invalid dimension
   *
   * results: The image should be uploaded because ImageGDToolkit resizes the picture
   */
  function testWithGDinvalidDimension() {
    if ($this->_directory_test && image_get_toolkit()) {
      $this->drupalLogin($this->user);

      $image = current($this->drupalGetTestFiles('image'));
      $info = image_get_info($image->uri);

      // Set new variables: invalid dimensions, valid filesize (0 = no limit).
      $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10);
      variable_set('user_picture_dimensions', $test_dim);
      variable_set('user_picture_file_size', 0);

      $pic_path = $this->saveUserPicture($image);
      // Check that the image was resized and is being displayed on the
      // user's profile page.
      $text = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $test_dim));
      $this->assertRaw($text, 'Image was resized.');
      $alt = t("@user's picture", array('@user' => format_username($this->user)));
      $style = variable_get('user_picture_style', '');
      $this->assertRaw(check_plain(image_style_url($style, $pic_path)), "Image is displayed in user's edit page");

      // Check if file is located in proper directory.
      $this->assertTrue(is_file($pic_path), "File is located in proper directory");
    }
  }

  /**
   * Do the test:
   *  GD Toolkit is installed
   *  Picture has invalid size
   *
   * results: The image should be uploaded because ImageGDToolkit resizes the picture
   */
  function testWithGDinvalidSize() {
    if ($this->_directory_test && image_get_toolkit()) {
      $this->drupalLogin($this->user);

      // Images are sorted first by size then by name. We need an image
      // bigger than 1 KB so we'll grab the last one.
      $files = $this->drupalGetTestFiles('image');
      $image = end($files);
      $info = image_get_info($image->uri);

      // Set new variables: valid dimensions, invalid filesize.
      $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10);
      $test_size = 1;
      variable_set('user_picture_dimensions', $test_dim);
      variable_set('user_picture_file_size', $test_size);

      $pic_path = $this->saveUserPicture($image);

      // Test that the upload failed and that the correct reason was cited.
      $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename));
      $this->assertRaw($text, 'Upload failed.');
      $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024)));
      $this->assertRaw($text, 'File size cited as reason for failure.');

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), 'File was not uploaded.');
    }
  }

  /**
   * Do the test:
   *  GD Toolkit is not installed
   *  Picture has invalid size
   *
   * results: The image shouldn't be uploaded
   */
  function testWithoutGDinvalidDimension() {
    if ($this->_directory_test && !image_get_toolkit()) {
      $this->drupalLogin($this->user);

      $image = current($this->drupalGetTestFiles('image'));
      $info = image_get_info($image->uri);

      // Set new variables: invalid dimensions, valid filesize (0 = no limit).
      $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10);
      variable_set('user_picture_dimensions', $test_dim);
      variable_set('user_picture_file_size', 0);

      $pic_path = $this->saveUserPicture($image);

      // Test that the upload failed and that the correct reason was cited.
      $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename));
      $this->assertRaw($text, 'Upload failed.');
      $text = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $test_dim));
      $this->assertRaw($text, 'Checking response on invalid image (dimensions).');

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), 'File was not uploaded.');
    }
  }

  /**
   * Do the test:
   *  GD Toolkit is not installed
   *  Picture has invalid size
   *
   * results: The image shouldn't be uploaded
   */
  function testWithoutGDinvalidSize() {
    if ($this->_directory_test && !image_get_toolkit()) {
      $this->drupalLogin($this->user);

      $image = current($this->drupalGetTestFiles('image'));
      $info = image_get_info($image->uri);

      // Set new variables: valid dimensions, invalid filesize.
      $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10);
      $test_size = 1;
      variable_set('user_picture_dimensions', $test_dim);
      variable_set('user_picture_file_size', $test_size);

      $pic_path = $this->saveUserPicture($image);

      // Test that the upload failed and that the correct reason was cited.
      $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename));
      $this->assertRaw($text, 'Upload failed.');
      $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024)));
      $this->assertRaw($text, 'File size cited as reason for failure.');

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), 'File was not uploaded.');
    }
  }

  /**
   * Do the test:
   *  Picture is valid (proper size and dimension)
   *
   * results: The image should be uploaded
   */
  function testPictureIsValid() {
    if ($this->_directory_test) {
      $this->drupalLogin($this->user);

      $image = current($this->drupalGetTestFiles('image'));
      $info = image_get_info($image->uri);

      // Set new variables: valid dimensions, valid filesize (0 = no limit).
      $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10);
      variable_set('user_picture_dimensions', $test_dim);
      variable_set('user_picture_file_size', 0);

      $pic_path = $this->saveUserPicture($image);

      // Check if image is displayed in user's profile page.
      $this->drupalGet('user');
      $this->assertRaw(file_uri_target($pic_path), "Image is displayed in user's profile page");

      // Check if file is located in proper directory.
      $this->assertTrue(is_file($pic_path), 'File is located in proper directory');

      // Set new picture dimensions.
      $test_dim = ($info['width'] + 5) . 'x' . ($info['height'] + 5);
      variable_set('user_picture_dimensions', $test_dim);

      $pic_path2 = $this->saveUserPicture($image);
      $this->assertNotEqual($pic_path, $pic_path2, 'Filename of second picture is different.');

      // Check if user picture has a valid file ID after saving the user.
      $account = user_load($this->user->uid, TRUE);
      $this->assertTrue(is_object($account->picture), 'User picture object is valid after user load.');
      $this->assertNotNull($account->picture->fid, 'User picture object has a FID after user load.');
      $this->assertTrue(is_file($account->picture->uri), 'File is located in proper directory after user load.');
      user_save($account);
      // Verify that the user save does not destroy the user picture object.
      $this->assertTrue(is_object($account->picture), 'User picture object is valid after user save.');
      $this->assertNotNull($account->picture->fid, 'User picture object has a FID after user save.');
      $this->assertTrue(is_file($account->picture->uri), 'File is located in proper directory after user save.');
    }
  }

  /**
   * Test HTTP schema working with user pictures.
   */
  function testExternalPicture() {
    $this->drupalLogin($this->user);
    // Set the default picture to an URI with a HTTP schema.
    $images = $this->drupalGetTestFiles('image');
    $image = $images[0];
    $pic_path = file_create_url($image->uri);
    variable_set('user_picture_default', $pic_path);

    // Check if image is displayed in user's profile page.
    $this->drupalGet('user');

    // Get the user picture image via xpath.
    $elements = $this->xpath('//div[@class="user-picture"]/img');
    $this->assertEqual(count($elements), 1, "There is exactly one user picture on the user's profile page");
    $this->assertEqual($pic_path, (string) $elements[0]['src'], "User picture source is correct.");
  }

  /**
   * Tests deletion of user pictures.
   */
  function testDeletePicture() {
    $this->drupalLogin($this->user);

    $image = current($this->drupalGetTestFiles('image'));
    $info = image_get_info($image->uri);

    // Set new variables: valid dimensions, valid filesize (0 = no limit).
    $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10);
    variable_set('user_picture_dimensions', $test_dim);
    variable_set('user_picture_file_size', 0);

    // Save a new picture.
    $edit = array('files[picture_upload]' => drupal_realpath($image->uri));
    $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save'));

    // Load actual user data from database.
    $account = user_load($this->user->uid, TRUE);
    $pic_path = isset($account->picture) ? $account->picture->uri : NULL;

    // Check if image is displayed in user's profile page.
    $this->drupalGet('user');
    $this->assertRaw(file_uri_target($pic_path), "Image is displayed in user's profile page");

    // Check if file is located in proper directory.
    $this->assertTrue(is_file($pic_path), 'File is located in proper directory');

    $edit = array('picture_delete' => 1);
    $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save'));

    // Load actual user data from database.
    $account1 = user_load($this->user->uid, TRUE);
    $this->assertNull($account1->picture, 'User object has no picture');

    $file = file_load($account->picture->fid);
    $this->assertFalse($file, 'File is removed from database');

    // Clear out PHP's file stat cache so we see the current value.
    clearstatcache();
    $this->assertFalse(is_file($pic_path), 'File is removed from file system');
  }

  function saveUserPicture($image) {
    $edit = array('files[picture_upload]' => drupal_realpath($image->uri));
    $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save'));

    // Load actual user data from database.
    $account = user_load($this->user->uid, TRUE);
    return isset($account->picture) ? $account->picture->uri : NULL;
  }

  /**
   * Tests the admin form validates user picture settings.
   */
  function testUserPictureAdminFormValidation() {
    $this->drupalLogin($this->drupalCreateUser(array('administer users')));

    // The default values are valid.
    $this->drupalPost('admin/config/people/accounts', array(), t('Save configuration'));
    $this->assertText(t('The configuration options have been saved.'), 'The default values are valid.');

    // The form does not save with an invalid file size.
    $edit = array(
      'user_picture_file_size' => $this->randomName(),
    );
    $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration'));
    $this->assertNoText(t('The configuration options have been saved.'), 'The form does not save with an invalid file size.');
  }
}


class UserPermissionsTestCase extends DrupalWebTestCase {
  protected $admin_user;
  protected $rid;

  public static function getInfo() {
    return array(
      'name' => 'Role permissions',
      'description' => 'Verify that role permissions can be added and removed via the permissions page.',
      'group' => 'User'
    );
  }

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

    $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'access user profiles', 'administer site configuration', 'administer modules', 'administer users'));

    // Find the new role ID - it must be the maximum.
    $all_rids = array_keys($this->admin_user->roles);
    sort($all_rids);
    $this->rid = array_pop($all_rids);
  }

  /**
   * Change user permissions and check user_access().
   */
  function testUserPermissionChanges() {
    $this->drupalLogin($this->admin_user);
    $rid = $this->rid;
    $account = $this->admin_user;

    // Add a permission.
    $this->assertFalse(user_access('administer nodes', $account), 'User does not have "administer nodes" permission.');
    $edit = array();
    $edit[$rid . '[administer nodes]'] = TRUE;
    $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
    $this->assertText(t('The changes have been saved.'), 'Successful save message displayed.');
    drupal_static_reset('user_access');
    drupal_static_reset('user_role_permissions');
    $this->assertTrue(user_access('administer nodes', $account), 'User now has "administer nodes" permission.');

    // Remove a permission.
    $this->assertTrue(user_access('access user profiles', $account), 'User has "access user profiles" permission.');
    $edit = array();
    $edit[$rid . '[access user profiles]'] = FALSE;
    $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
    $this->assertText(t('The changes have been saved.'), 'Successful save message displayed.');
    drupal_static_reset('user_access');
    drupal_static_reset('user_role_permissions');
    $this->assertFalse(user_access('access user profiles', $account), 'User no longer has "access user profiles" permission.');
  }

  /**
   * Test assigning of permissions for the administrator role.
   */
  function testAdministratorRole() {
    $this->drupalLogin($this->admin_user);
    $this->drupalGet('admin/config/people/accounts');

    // Set the user's role to be the administrator role.
    $edit = array();
    $edit['user_admin_role'] = $this->rid;
    $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration'));

    // Enable aggregator module and ensure the 'administer news feeds'
    // permission is assigned by default.
    $edit = array();
    $edit['modules[Core][aggregator][enable]'] = TRUE;
    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
    $this->assertTrue(user_access('administer news feeds', $this->admin_user), 'The permission was automatically assigned to the administrator role');
  }

  /**
   * Verify proper permission changes by user_role_change_permissions().
   */
  function testUserRoleChangePermissions() {
    $rid = $this->rid;
    $account = $this->admin_user;

    // Verify current permissions.
    $this->assertFalse(user_access('administer nodes', $account), 'User does not have "administer nodes" permission.');
    $this->assertTrue(user_access('access user profiles', $account), 'User has "access user profiles" permission.');
    $this->assertTrue(user_access('administer site configuration', $account), 'User has "administer site configuration" permission.');

    // Change permissions.
    $permissions = array(
      'administer nodes' => 1,
      'access user profiles' => 0,
    );
    user_role_change_permissions($rid, $permissions);

    // Verify proper permission changes.
    $this->assertTrue(user_access('administer nodes', $account), 'User now has "administer nodes" permission.');
    $this->assertFalse(user_access('access user profiles', $account), 'User no longer has "access user profiles" permission.');
    $this->assertTrue(user_access('administer site configuration', $account), 'User still has "administer site configuration" permission.');
  }
}

class UserAdminTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User administration',
      'description' => 'Test user administration page functionality.',
      'group' => 'User'
    );
  }

  /**
   * Registers a user and deletes it.
   */
  function testUserAdmin() {

    $user_a = $this->drupalCreateUser(array());
    $user_b = $this->drupalCreateUser(array('administer taxonomy'));
    $user_c = $this->drupalCreateUser(array('administer taxonomy'));

    // Create admin user to delete registered user.
    $admin_user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($admin_user);
    $this->drupalGet('admin/people');
    $this->assertText($user_a->name, 'Found user A on admin users page');
    $this->assertText($user_b->name, 'Found user B on admin users page');
    $this->assertText($user_c->name, 'Found user C on admin users page');
    $this->assertText($admin_user->name, 'Found Admin user on admin users page');

    // Test for existence of edit link in table.
    $link = l(t('edit'), "user/$user_a->uid/edit", array('query' => array('destination' => 'admin/people')));
    $this->assertRaw($link, 'Found user A edit link on admin users page');

    // Filter the users by permission 'administer taxonomy'.
    $edit = array();
    $edit['permission'] = 'administer taxonomy';
    $this->drupalPost('admin/people', $edit, t('Filter'));

    // Check if the correct users show up.
    $this->assertNoText($user_a->name, 'User A not on filtered by perm admin users page');
    $this->assertText($user_b->name, 'Found user B on filtered by perm admin users page');
    $this->assertText($user_c->name, 'Found user C on filtered by perm admin users page');

    // Filter the users by role. Grab the system-generated role name for User C.
    $edit['role'] = max(array_flip($user_c->roles));
    $this->drupalPost('admin/people', $edit, t('Refine'));

    // Check if the correct users show up when filtered by role.
    $this->assertNoText($user_a->name, 'User A not on filtered by role on admin users page');
    $this->assertNoText($user_b->name, 'User B not on filtered by role on admin users page');
    $this->assertText($user_c->name, 'User C on filtered by role on admin users page');

    // Test blocking of a user.
    $account = user_load($user_c->uid);
    $this->assertEqual($account->status, 1, 'User C not blocked');
    $edit = array();
    $edit['operation'] = 'block';
    $edit['accounts[' . $account->uid . ']'] = TRUE;
    $this->drupalPost('admin/people', $edit, t('Update'));
    $account = user_load($user_c->uid, TRUE);
    $this->assertEqual($account->status, 0, 'User C blocked');

    // Test unblocking of a user from /admin/people page and sending of activation mail
    $editunblock = array();
    $editunblock['operation'] = 'unblock';
    $editunblock['accounts[' . $account->uid . ']'] = TRUE;
    $this->drupalPost('admin/people', $editunblock, t('Update'));
    $account = user_load($user_c->uid, TRUE);
    $this->assertEqual($account->status, 1, 'User C unblocked');
    $this->assertMail("to", $account->mail, "Activation mail sent to user C");

    // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail
    $user_d = $this->drupalCreateUser(array());
    $account1 = user_load($user_d->uid, TRUE);
    $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => 0), t('Save'));
    $account1 = user_load($user_d->uid, TRUE);
    $this->assertEqual($account1->status, 0, 'User D blocked');
    $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => TRUE), t('Save'));
    $account1 = user_load($user_d->uid, TRUE);
    $this->assertEqual($account1->status, 1, 'User D unblocked');
    $this->assertMail("to", $account1->mail, "Activation mail sent to user D");
  }
}

/**
 * Tests for user-configurable time zones.
 */
class UserTimeZoneFunctionalTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User time zones',
      'description' => 'Set a user time zone and verify that dates are displayed in local time.',
      'group' => 'User',
    );
  }

  /**
   * Tests the display of dates and time when user-configurable time zones are set.
   */
  function testUserTimeZone() {
    // Setup date/time settings for Los Angeles time.
    variable_set('date_default_timezone', 'America/Los_Angeles');
    variable_set('configurable_timezones', 1);
    variable_set('date_format_medium', 'Y-m-d H:i T');

    // Create a user account and login.
    $web_user = $this->drupalCreateUser();
    $this->drupalLogin($web_user);

    // Create some nodes with different authored-on dates.
    // Two dates in PST (winter time):
    $date1 = '2007-03-09 21:00:00 -0800';
    $date2 = '2007-03-11 01:00:00 -0800';
    // One date in PDT (summer time):
    $date3 = '2007-03-20 21:00:00 -0700';
    $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article'));
    $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article'));
    $node3 = $this->drupalCreateNode(array('created' => strtotime($date3), 'type' => 'article'));

    // Confirm date format and time zone.
    $this->drupalGet("node/$node1->nid");
    $this->assertText('2007-03-09 21:00 PST', 'Date should be PST.');
    $this->drupalGet("node/$node2->nid");
    $this->assertText('2007-03-11 01:00 PST', 'Date should be PST.');
    $this->drupalGet("node/$node3->nid");
    $this->assertText('2007-03-20 21:00 PDT', 'Date should be PDT.');

    // Change user time zone to Santiago time.
    $edit = array();
    $edit['mail'] = $web_user->mail;
    $edit['timezone'] = 'America/Santiago';
    $this->drupalPost("user/$web_user->uid/edit", $edit, t('Save'));
    $this->assertText(t('The changes have been saved.'), 'Time zone changed to Santiago time.');

    // Confirm date format and time zone.
    $this->drupalGet("node/$node1->nid");
    $this->assertText('2007-03-10 02:00 CLST', 'Date should be Chile summer time; five hours ahead of PST.');
    $this->drupalGet("node/$node2->nid");
    $this->assertText('2007-03-11 05:00 CLT', 'Date should be Chile time; four hours ahead of PST');
    $this->drupalGet("node/$node3->nid");
    $this->assertText('2007-03-21 00:00 CLT', 'Date should be Chile time; three hours ahead of PDT.');
  }
}

/**
 * Test user autocompletion.
 */
class UserAutocompleteTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User autocompletion',
      'description' => 'Test user autocompletion functionality.',
      'group' => 'User'
    );
  }

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

    // Set up two users with different permissions to test access.
    $this->unprivileged_user = $this->drupalCreateUser();
    $this->privileged_user = $this->drupalCreateUser(array('access user profiles'));
  }

  /**
   * Tests access to user autocompletion and verify the correct results.
   */
  function testUserAutocomplete() {
    // Check access from unprivileged user, should be denied.
    $this->drupalLogin($this->unprivileged_user);
    $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]);
    $this->assertResponse(403, 'Autocompletion access denied to user without permission.');

    // Check access from privileged user.
    $this->drupalLogout();
    $this->drupalLogin($this->privileged_user);
    $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]);
    $this->assertResponse(200, 'Autocompletion access allowed.');

    // Using first letter of the user's name, make sure the user's full name is in the results.
    $this->assertRaw($this->unprivileged_user->name, 'User name found in autocompletion results.');
  }
}


/**
 * Tests user links in the secondary menu.
 */
class UserAccountLinksUnitTests extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User account links',
      'description' => 'Test user-account links.',
      'group' => 'User'
    );
  }

  function setUp() {
    parent::setUp('menu');
  }

  /**
   * Tests the secondary menu.
   */
  function testSecondaryMenu() {
    // Create a regular user.
    $user = $this->drupalCreateUser(array());

    // Log in and get the homepage.
    $this->drupalLogin($user);
    $this->drupalGet('<front>');

    // For a logged-in user, expect the secondary menu to have links for "My
    // account" and "Log out".
    $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array(
      ':menu_id' => 'secondary-menu-links',
      ':href' => 'user',
      ':text' => 'My account',
    ));
    $this->assertEqual(count($link), 1, 'My account link is in secondary menu.');

    $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array(
      ':menu_id' => 'secondary-menu-links',
      ':href' => 'user/logout',
      ':text' => 'Log out',
    ));
    $this->assertEqual(count($link), 1, 'Log out link is in secondary menu.');

    // Log out and get the homepage.
    $this->drupalLogout();
    $this->drupalGet('<front>');

    // For a logged-out user, expect no secondary links.
    $element = $this->xpath('//ul[@id=:menu_id]', array(':menu_id' => 'secondary-menu-links'));
    $this->assertEqual(count($element), 0, 'No secondary-menu for logged-out users.');
  }

  /**
   * Tests disabling the 'My account' link.
   */
  function testDisabledAccountLink() {
    // Create an admin user and log in.
    $this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer menu')));

    // Verify that the 'My account' link is enabled.
    $this->drupalGet('admin/structure/menu/manage/user-menu');
    $label = $this->xpath('//label[contains(.,:text)]/@for', array(':text' => 'Enable My account menu link'));
    $this->assertFieldChecked((string) $label[0], "The 'My account' link is enabled by default.");

    // Disable the 'My account' link.
    $input = $this->xpath('//input[@id=:field_id]/@name', array(':field_id' => (string)$label[0]));
    $edit = array(
      (string) $input[0] => FALSE,
    );
    $this->drupalPost('admin/structure/menu/manage/user-menu', $edit, t('Save configuration'));

    // Get the homepage.
    $this->drupalGet('<front>');

    // Verify that the 'My account' link does not appear when disabled.
    $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array(
      ':menu_id' => 'secondary-menu-links',
      ':href' => 'user',
      ':text' => 'My account',
    ));
    $this->assertEqual(count($link), 0, 'My account link is not in the secondary menu.');
  }

}

/**
 * Test user blocks.
 */
class UserBlocksUnitTests extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User blocks',
      'description' => 'Test user blocks.',
      'group' => 'User'
    );
  }

  /**
   * Test the user login block.
   */
  function testUserLoginBlock() {
    // Create a user with some permission that anonymous users lack.
    $user = $this->drupalCreateUser(array('administer permissions'));

    // Log in using the block.
    $edit = array();
    $edit['name'] = $user->name;
    $edit['pass'] = $user->pass_raw;
    $this->drupalPost('admin/people/permissions', $edit, t('Log in'));
    $this->assertNoText(t('User login'), 'Logged in.');

    // Check that we are still on the same page.
    $this->assertEqual(url('admin/people/permissions', array('absolute' => TRUE)), $this->getUrl(), 'Still on the same page after login for access denied page');

    // Now, log out and repeat with a non-403 page.
    $this->drupalLogout();
    $this->drupalPost('filter/tips', $edit, t('Log in'));
    $this->assertNoText(t('User login'), 'Logged in.');
    $this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', 'Still on the same page after login for allowed page');

    // Check that the user login block is not vulnerable to information
    // disclosure to third party sites.
    $this->drupalLogout();
    $this->drupalPost('http://example.com/', $edit, t('Log in'), array('external' => FALSE));
    // Check that we remain on the site after login.
    $this->assertEqual(url('user/' . $user->uid, array('absolute' => TRUE)), $this->getUrl(), 'Redirected to user profile page after login from the frontpage');
  }

  /**
   * Test the Who's Online block.
   */
  function testWhosOnlineBlock() {
    // Generate users and make sure there are no current user sessions.
    $user1 = $this->drupalCreateUser(array());
    $user2 = $this->drupalCreateUser(array());
    $user3 = $this->drupalCreateUser(array());
    $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions}")->fetchField(), 0, 'Sessions table is empty.');

    // Insert a user with two sessions.
    $this->insertSession(array('uid' => $user1->uid));
    $this->insertSession(array('uid' => $user1->uid));
    $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid", array(':uid' => $user1->uid))->fetchField(), 2, 'Duplicate user session has been inserted.');

    // Insert a user with only one session.
    $this->insertSession(array('uid' => $user2->uid, 'timestamp' => REQUEST_TIME + 1));

    // Insert an inactive logged-in user who should not be seen in the block.
    $this->insertSession(array('uid' => $user3->uid, 'timestamp' => (REQUEST_TIME - variable_get('user_block_seconds_online', 900) - 1)));

    // Insert two anonymous user sessions.
    $this->insertSession();
    $this->insertSession();

    // Test block output.
    $block = user_block_view('online');
    $this->drupalSetContent($block['content']);
    $this->assertRaw(t('2 users'), 'Correct number of online users (2 users).');
    $this->assertText($user1->name, 'Active user 1 found in online list.');
    $this->assertText($user2->name, 'Active user 2 found in online list.');
    $this->assertNoText($user3->name, "Inactive user not found in online list.");
    $this->assertTrue(strpos($this->drupalGetContent(), $user1->name) > strpos($this->drupalGetContent(), $user2->name), 'Online users are ordered correctly.');
  }

  /**
   * Insert a user session into the {sessions} table. This function is used
   * since we cannot log in more than one user at the same time in tests.
   */
  private function insertSession(array $fields = array()) {
    $fields += array(
      'uid' => 0,
      'sid' => drupal_hash_base64(uniqid(mt_rand(), TRUE)),
      'timestamp' => REQUEST_TIME,
    );
    db_insert('sessions')
      ->fields($fields)
      ->execute();
    $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid AND sid = :sid AND timestamp = :timestamp", array(':uid' => $fields['uid'], ':sid' => $fields['sid'], ':timestamp' => $fields['timestamp']))->fetchField(), 1, 'Session record inserted.');
  }
}

/**
 * Tests saving a user account.
 */
class UserSaveTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User save test',
      'description' => 'Test user_save() for arbitrary new uid.',
      'group' => 'User',
    );
  }

  /**
   * Test creating a user with arbitrary uid.
   */
  function testUserImport() {
    // User ID must be a number that is not in the database.
    $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField();
    $test_uid = $max_uid + mt_rand(1000, 1000000);
    $test_name = $this->randomName();

    // Create the base user, based on drupalCreateUser().
    $user = array(
      'name' => $test_name,
      'uid' => $test_uid,
      'mail' => $test_name . '@example.com',
      'is_new' => TRUE,
      'pass' => user_password(),
      'status' => 1,
    );
    $user_by_return = user_save(drupal_anonymous_user(), $user);
    $this->assertTrue($user_by_return, 'Loading user by return of user_save().');

    // Test if created user exists.
    $user_by_uid = user_load($test_uid);
    $this->assertTrue($user_by_uid, 'Loading user by uid.');

    $user_by_name = user_load_by_name($test_name);
    $this->assertTrue($user_by_name, 'Loading user by name.');
  }
}

/**
 * Test the create user administration page.
 */
class UserCreateTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User create',
      'description' => 'Test the create user administration page.',
      'group' => 'User',
    );
  }

  /**
   * Create a user through the administration interface and ensure that it
   * displays in the user list.
   */
  protected function testUserAdd() {
    $user = $this->drupalCreateUser(array('administer users'));
    $this->drupalLogin($user);

    foreach (array(FALSE, TRUE) as $notify) {
      $edit = array(
        'name' => $this->randomName(),
        'mail' => $this->randomName() . '@example.com',
        'pass[pass1]' => $pass = $this->randomString(),
        'pass[pass2]' => $pass,
        'notify' => $notify,
      );
      $this->drupalPost('admin/people/create', $edit, t('Create new account'));

      if ($notify) {
        $this->assertText(t('A welcome message with further instructions has been e-mailed to the new user @name.', array('@name' => $edit['name'])), 'User created');
        $this->assertEqual(count($this->drupalGetMails()), 1, 'Notification e-mail sent');
      }
      else {
        $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created');
        $this->assertEqual(count($this->drupalGetMails()), 0, 'Notification e-mail not sent');
      }

      $this->drupalGet('admin/people');
      $this->assertText($edit['name'], 'User found in list of users');
    }
  }
}

/**
 * Tests editing a user account.
 */
class UserEditTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User edit',
      'description' => 'Test user edit page.',
      'group' => 'User',
    );
  }

  /**
   * Test user edit page.
   */
  function testUserEdit() {
    // Test user edit functionality with user pictures disabled.
    variable_set('user_pictures', 0);
    $user1 = $this->drupalCreateUser(array('change own username'));
    $user2 = $this->drupalCreateUser(array());
    $this->drupalLogin($user1);

    // Test that error message appears when attempting to use a non-unique user name.
    $edit['name'] = $user2->name;
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name'])));

    // Repeat the test with user pictures enabled, which modifies the form.
    variable_set('user_pictures', 1);
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name'])));

    // Check that filling out a single password field does not validate.
    $edit = array();
    $edit['pass[pass1]'] = '';
    $edit['pass[pass2]'] = $this->randomName();
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertText(t("The specified passwords do not match."), 'Typing mismatched passwords displays an error message.');

    $edit['pass[pass1]'] = $this->randomName();
    $edit['pass[pass2]'] = '';
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertText(t("The specified passwords do not match."), 'Typing mismatched passwords displays an error message.');

    // Test that the error message appears when attempting to change the mail or
    // pass without the current password.
    $edit = array();
    $edit['mail'] = $this->randomName() . '@new.example.com';
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('E-mail address'))));

    $edit['current_pass'] = $user1->pass_raw;
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t("The changes have been saved."));

    // Test that the user must enter current password before changing passwords.
    $edit = array();
    $edit['pass[pass1]'] = $new_pass = $this->randomName();
    $edit['pass[pass2]'] = $new_pass;
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('Password'))));

    // Try again with the current password.
    $edit['current_pass'] = $user1->pass_raw;
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
    $this->assertRaw(t("The changes have been saved."));

    // Make sure the user can log in with their new password.
    $this->drupalLogout();
    $user1->pass_raw = $new_pass;
    $this->drupalLogin($user1);
    $this->drupalLogout();
  }
}

/**
 * Test case for user signatures.
 */
class UserSignatureTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User signatures',
      'description' => 'Test user signatures.',
      'group' => 'User',
    );
  }

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

    // Enable user signatures.
    variable_set('user_signatures', 1);

    // Prefetch text formats.
    $this->full_html_format = filter_format_load('full_html');
    $this->plain_text_format = filter_format_load('plain_text');

    // Create regular and administrative users.
    $this->web_user = $this->drupalCreateUser(array());
    $admin_permissions = array('administer comments');
    foreach (filter_formats() as $format) {
      if ($permission = filter_permission_name($format)) {
        $admin_permissions[] = $permission;
      }
    }
    $this->admin_user = $this->drupalCreateUser($admin_permissions);
  }

  /**
   * Test that a user can change their signature format and that it is respected
   * upon display.
   */
  function testUserSignature() {
    // Create a new node with comments on.
    $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN));

    // Verify that user signature field is not displayed on registration form.
    $this->drupalGet('user/register');
    $this->assertNoText(t('Signature'));

    // Log in as a regular user and create a signature.
    $this->drupalLogin($this->web_user);
    $signature_text = "<h1>" . $this->randomName() . "</h1>";
    $edit = array(
      'signature[value]' => $signature_text,
      'signature[format]' => $this->plain_text_format->format,
    );
    $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save'));

    // Verify that values were stored.
    $this->assertFieldByName('signature[value]', $edit['signature[value]'], 'Submitted signature text found.');
    $this->assertFieldByName('signature[format]', $edit['signature[format]'], 'Submitted signature format found.');

    // Create a comment.
    $langcode = LANGUAGE_NONE;
    $edit = array();
    $edit['subject'] = $this->randomName(8);
    $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16);
    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
    $this->drupalPost(NULL, array(), t('Save'));

    // Get the comment ID. (This technique is the same one used in the Comment
    // module's CommentHelperCase test case.)
    preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
    $comment_id = $match[1];

    // Log in as an administrator and edit the comment to use Full HTML, so
    // that the comment text itself is not filtered at all.
    $this->drupalLogin($this->admin_user);
    $edit['comment_body[' . $langcode . '][0][format]'] = $this->full_html_format->format;
    $this->drupalPost('comment/' . $comment_id . '/edit', $edit, t('Save'));

    // Assert that the signature did not make it through unfiltered.
    $this->drupalGet('node/' . $node->nid);
    $this->assertNoRaw($signature_text, 'Unfiltered signature text not found.');
    $this->assertRaw(check_markup($signature_text, $this->plain_text_format->format), 'Filtered signature text found.');
  }
}

/*
 * Test that a user, having editing their own account, can still log in.
 */
class UserEditedOwnAccountTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User edited own account',
      'description' => 'Test user edited own account can still log in.',
      'group' => 'User',
    );
  }

  function testUserEditedOwnAccount() {
    // Change account setting 'Who can register accounts?' to Administrators
    // only.
    variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY);

    // Create a new user account and log in.
    $account = $this->drupalCreateUser(array('change own username'));
    $this->drupalLogin($account);

    // Change own username.
    $edit = array();
    $edit['name'] = $this->randomName();
    $this->drupalPost('user/' . $account->uid . '/edit', $edit, t('Save'));

    // Log out.
    $this->drupalLogout();

    // Set the new name on the user account and attempt to log back in.
    $account->name = $edit['name'];
    $this->drupalLogin($account);
  }
}

/**
 * Test case to test adding, editing and deleting roles.
 */
class UserRoleAdminTestCase extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User role administration',
      'description' => 'Test adding, editing and deleting user roles and changing role weights.',
      'group' => 'User',
    );
  }

  function setUp() {
    parent::setUp();
    $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users'));
  }

  /**
   * Test adding, renaming and deleting roles.
   */
  function testRoleAdministration() {
    $this->drupalLogin($this->admin_user);

    // Test adding a role. (In doing so, we use a role name that happens to
    // correspond to an integer, to test that the role administration pages
    // correctly distinguish between role names and IDs.)
    $role_name = '123';
    $edit = array('name' => $role_name);
    $this->drupalPost('admin/people/permissions/roles', $edit, t('Add role'));
    $this->assertText(t('The role has been added.'), 'The role has been added.');
    $role = user_role_load_by_name($role_name);
    $this->assertTrue(is_object($role), 'The role was successfully retrieved from the database.');

    // Try adding a duplicate role.
    $this->drupalPost(NULL, $edit, t('Add role'));
    $this->assertRaw(t('The role name %name already exists. Choose another role name.', array('%name' => $role_name)), 'Duplicate role warning displayed.');

    // Test renaming a role.
    $old_name = $role_name;
    $role_name = '456';
    $edit = array('name' => $role_name);
    $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", $edit, t('Save role'));
    $this->assertText(t('The role has been renamed.'), 'The role has been renamed.');
    $this->assertFalse(user_role_load_by_name($old_name), 'The role can no longer be retrieved from the database using its old name.');
    $this->assertTrue(is_object(user_role_load_by_name($role_name)), 'The role can be retrieved from the database using its new name.');

    // Test deleting the default administrator role.
    $role_name = 'administrator';
    $role = user_role_load_by_name($role_name);
    $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role'));
    $this->drupalPost(NULL, NULL, t('Delete'));
    $this->assertText(t('The role has been deleted.'), 'The role has been deleted');
    $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", 'Role edit link removed.');
    $this->assertFalse(user_role_load_by_name($role_name), 'A deleted role can no longer be loaded.');
    // Make sure this role is no longer configured as the administrator role.
    $this->assertNull(variable_get('user_admin_role'), 'The administrator role is no longer configured as the administrator role.');

    // Make sure that the system-defined roles cannot be edited via the user
    // interface.
    $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_ANONYMOUS_RID);
    $this->assertResponse(403, 'Access denied when trying to edit the built-in anonymous role.');
    $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_AUTHENTICATED_RID);
    $this->assertResponse(403, 'Access denied when trying to edit the built-in authenticated role.');
  }

  /**
   * Test user role weight change operation.
   */
  function testRoleWeightChange() {
    $this->drupalLogin($this->admin_user);

    // Pick up a random role and get its weight.
    $rid = array_rand(user_roles());
    $role = user_role_load($rid);
    $old_weight = $role->weight;

    // Change the role weight and submit the form.
    $edit = array('roles['. $rid .'][weight]' => $old_weight + 1);
    $this->drupalPost('admin/people/permissions/roles', $edit, t('Save order'));
    $this->assertText(t('The role settings have been updated.'), 'The role settings form submitted successfully.');

    // Retrieve the saved role and compare its weight.
    $role = user_role_load($rid);
    $new_weight = $role->weight;
    $this->assertTrue(($old_weight + 1) == $new_weight, 'Role weight updated successfully.');
  }
}

/**
 * Test user token replacement in strings.
 */
class UserTokenReplaceTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User token replacement',
      'description' => 'Generates text using placeholders for dummy content to check user token replacement.',
      'group' => 'User',
    );
  }

  /**
   * Creates a user, then tests the tokens generated from it.
   */
  function testUserTokenReplacement() {
    global $language;
    $url_options = array(
      'absolute' => TRUE,
      'language' => $language,
    );

    // Create two users and log them in one after another.
    $user1 = $this->drupalCreateUser(array());
    $user2 = $this->drupalCreateUser(array());
    $this->drupalLogin($user1);
    $this->drupalLogout();
    $this->drupalLogin($user2);

    $account = user_load($user1->uid);
    $global_account = user_load($GLOBALS['user']->uid);

    // Generate and test sanitized tokens.
    $tests = array();
    $tests['[user:uid]'] = $account->uid;
    $tests['[user:name]'] = check_plain(format_username($account));
    $tests['[user:mail]'] = check_plain($account->mail);
    $tests['[user:url]'] = url("user/$account->uid", $url_options);
    $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options);
    $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language->language);
    $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language->language);
    $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language->language);
    $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language->language);
    $tests['[current-user:name]'] = check_plain(format_username($global_account));

    // Test to make sure that we generated something for each token.
    $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('user' => $account), array('language' => $language));
      $this->assertEqual($output, $expected, format_string('Sanitized user token %token replaced.', array('%token' => $input)));
    }

    // Generate and test unsanitized tokens.
    $tests['[user:name]'] = format_username($account);
    $tests['[user:mail]'] = $account->mail;
    $tests['[current-user:name]'] = format_username($global_account);

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('user' => $account), array('language' => $language, 'sanitize' => FALSE));
      $this->assertEqual($output, $expected, format_string('Unsanitized user token %token replaced.', array('%token' => $input)));
    }
  }
}

/**
 * Test user search.
 */
class UserUserSearchTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'User search',
      'description' => 'Tests the user search page and verifies that sensitive information is hidden from unauthorized users.',
      'group' => 'User',
    );
  }

  function testUserSearch() {
    $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search'));
    $this->drupalLogin($user1);
    $keys = $user1->mail;
    $edit = array('keys' => $keys);
    $this->drupalPost('search/user/', $edit, t('Search'));
    $this->assertNoText($keys);
    $this->drupalLogout();

    $user2 = $this->drupalCreateUser(array('administer users', 'access user profiles', 'search content', 'use advanced search'));
    $this->drupalLogin($user2);
    $keys = $user2->mail;
    $edit = array('keys' => $keys);
    $this->drupalPost('search/user/', $edit, t('Search'));
    $this->assertText($keys);

    // Create a blocked user.
    $blocked_user = $this->drupalCreateUser();
    $edit = array('status' => 0);
    $blocked_user = user_save($blocked_user, $edit);

    // Verify that users with "administer users" permissions can see blocked
    // accounts in search results.
    $edit = array('keys' => $blocked_user->name);
    $this->drupalPost('search/user/', $edit, t('Search'));
    $this->assertText($blocked_user->name, 'Blocked users are listed on the user search results for users with the "administer users" permission.');

    // Verify that users without "administer users" permissions do not see
    // blocked accounts in search results.
    $this->drupalLogin($user1);
    $edit = array('keys' => $blocked_user->name);
    $this->drupalPost('search/user/', $edit, t('Search'));
    $this->assertNoText($blocked_user->name, 'Blocked users are hidden from the user search results.');

    $this->drupalLogout();
  }
}

/**
 * Test role assignment.
 */
class UserRolesAssignmentTestCase extends DrupalWebTestCase {
  protected $admin_user;

  public static function getInfo() {
    return array(
      'name' => 'Role assignment',
      'description' => 'Tests that users can be assigned and unassigned roles.',
      'group' => 'User'
    );
  }

  function setUp() {
    parent::setUp();
    $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users'));
    $this->drupalLogin($this->admin_user);
  }

  /**
   * Tests that a user can be assigned a role and that the role can be removed
   * again.
   */
  function testAssignAndRemoveRole()  {
    $rid = $this->drupalCreateRole(array('administer content types'));
    $account = $this->drupalCreateUser();

    // Assign the role to the user.
    $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => $rid), t('Save'));
    $this->assertText(t('The changes have been saved.'));
    $this->assertFieldChecked('edit-roles-' . $rid, 'Role is assigned.');
    $this->userLoadAndCheckRoleAssigned($account, $rid);

    // Remove the role from the user.
    $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save'));
    $this->assertText(t('The changes have been saved.'));
    $this->assertNoFieldChecked('edit-roles-' . $rid, 'Role is removed from user.');
    $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE);
  }

  /**
   * Tests that when creating a user the role can be assigned. And that it can
   * be removed again.
   */
  function testCreateUserWithRole() {
    $rid = $this->drupalCreateRole(array('administer content types'));
    // Create a new user and add the role at the same time.
    $edit = array(
      'name' => $this->randomName(),
      'mail' => $this->randomName() . '@example.com',
      'pass[pass1]' => $pass = $this->randomString(),
      'pass[pass2]' => $pass,
      "roles[$rid]" => $rid,
    );
    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
    $this->assertText(t('Created a new user account for !name.', array('!name' => $edit['name'])));
    // Get the newly added user.
    $account = user_load_by_name($edit['name']);

    $this->drupalGet('user/' . $account->uid . '/edit');
    $this->assertFieldChecked('edit-roles-' . $rid, 'Role is assigned.');
    $this->userLoadAndCheckRoleAssigned($account, $rid);

    // Remove the role again.
    $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save'));
    $this->assertText(t('The changes have been saved.'));
    $this->assertNoFieldChecked('edit-roles-' . $rid, 'Role is removed from user.');
    $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE);
  }

  /**
   * Check role on user object.
   *
   * @param object $account
   *   The user account to check.
   * @param string $rid
   *   The role ID to search for.
   * @param bool $is_assigned
   *   (optional) Whether to assert that $rid exists (TRUE) or not (FALSE).
   *   Defaults to TRUE.
   */
  private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) {
    $account = user_load($account->uid, TRUE);
    if ($is_assigned) {
      $this->assertTrue(array_key_exists($rid, $account->roles), 'The role is present in the user object.');
    }
    else {
      $this->assertFalse(array_key_exists($rid, $account->roles), 'The role is not present in the user object.');
    }
  }
}


/**
 * Unit test for authmap assignment.
 */
class UserAuthmapAssignmentTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Authmap assignment',
      'description' => 'Tests that users can be assigned and unassigned authmaps.',
      'group' => 'User'
    );
  }

  /**
   * Test authmap assignment and retrieval.
   */
  function testAuthmapAssignment()  {
    $account = $this->drupalCreateUser();

    // Assign authmaps to the user.
    $authmaps = array(
      'authname_poll' => 'external username one',
      'authname_book' => 'external username two',
    );
    user_set_authmaps($account, $authmaps);

    // Test for expected authmaps.
    $expected_authmaps = array(
      'external username one' => array(
        'poll' => 'external username one',
      ),
      'external username two' => array(
        'book' => 'external username two',
      ),
    );
    foreach ($expected_authmaps as $authname => $expected_output) {
      $this->assertIdentical(user_get_authmaps($authname), $expected_output, format_string('Authmap for authname %authname was set correctly.', array('%authname' => $authname)));
    }

    // Remove authmap for module poll, add authmap for module blog.
    $authmaps = array(
      'authname_poll' => NULL,
      'authname_blog' => 'external username three',
    );
    user_set_authmaps($account, $authmaps);

    // Assert that external username one does not have authmaps.
    $remove_username = 'external username one';
    unset($expected_authmaps[$remove_username]);
    $this->assertFalse(user_get_authmaps($remove_username), format_string('Authmap for %authname was removed.', array('%authname' => $remove_username)));

    // Assert that a new authmap was created for external username three, and
    // existing authmaps for external username two were unchanged.
    $expected_authmaps['external username three'] = array('blog' => 'external username three');
    foreach ($expected_authmaps as $authname => $expected_output) {
      $this->assertIdentical(user_get_authmaps($authname), $expected_output, format_string('Authmap for authname %authname was set correctly.', array('%authname' => $authname)));
    }
  }
}

/**
 * Tests user_validate_current_pass on a custom form.
 */
class UserValidateCurrentPassCustomForm extends DrupalWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'User validate current pass custom form',
      'description' => 'Test that user_validate_current_pass is usable on a custom form.',
      'group' => 'User',
    );
  }

  /**
   * User with permission to view content.
   */
  protected $accessUser;

  /**
   * User permission to administer users.
   */
  protected $adminUser;

  function setUp() {
    parent::setUp('user_form_test');
    // Create two users
    $this->accessUser = $this->drupalCreateUser(array('access content'));
    $this->adminUser = $this->drupalCreateUser(array('administer users'));
  }

  /**
   * Tests that user_validate_current_pass can be reused on a custom form.
   */
  function testUserValidateCurrentPassCustomForm() {
    $this->drupalLogin($this->adminUser);

    // Submit the custom form with the admin user using the access user's password.
    $edit = array();
    $edit['user_form_test_field'] = $this->accessUser->name;
    $edit['current_pass'] = $this->accessUser->pass_raw;
    $this->drupalPost('user_form_test_current_password/' . $this->accessUser->uid, $edit, t('Test'));
    $this->assertText(t('The password has been validated and the form submitted successfully.'));
  }
}
