<?php
// $Id: user.test,v 1.89 2010/04/20 09:48:06 webchick Exp $

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

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

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

    // Allow registration by site visitors without administrator approval.
    variable_set('user_register', 1);
    $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.'), t('User registered successfully.'));
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
    $new_user = reset($accounts);
    $this->assertTrue($new_user->status, t('New account is active after registration.'));

    // Allow registration by site visitors, but require administrator approval.
    variable_set('user_register', 2);
    $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, t('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', 1);
    $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.'), t('Type mismatched passwords display 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.'), t('Users are logged in after registering.'));
    $this->drupalLogout();

    // Allow registration by site visitors, but require administrator approval.
    variable_set('user_register', 2);
    $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.'), t('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)), t('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'), t('User can log in after administrator approval.'));
  }

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

    // 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>', t('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, t('Username matches.'));
    $this->assertEqual($new_user->mail, $mail, t('E-mail address matches.'));
    $this->assertEqual($new_user->theme, '', t('Correct theme field.'));
    $this->assertEqual($new_user->signature, '', t('Correct signature field.'));
    $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.'));
    $this->assertEqual($new_user->status, variable_get('user_register', 1) == 1 ? 1 : 0, t('Correct status field.'));
    $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.'));
    $this->assertEqual($new_user->language, '', t('Correct language field.'));
    $this->assertEqual($new_user->picture, '', t('Correct picture field.'));
    $this->assertEqual($new_user->init, $mail, t('Correct init field.'));

    // Make the user timezone configurable, which will create a second fieldset
    // on the registration page and cause the account information elements to
    // be put in a fieldset.
    variable_set('user_default_timezone', DRUPAL_USER_TIMEZONE_SELECT);
    $this->drupalLogout();
    $this->drupalGet('user/register');
    $this->assertText(t('Account information'), t('Account settings fieldset was not hidden.'));
  }
}

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');
  }

  /**
   * 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'));
    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?'));
    }
  }
}

/**
 * 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'), t('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));
    $this->assertResponse(403, t('Bogus cancelling request rejected.'));
    $account = user_load($account->uid);
    $this->assertTrue($account->status == 1, t('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), t('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, t('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.'), t('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));
    $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Bogus cancelling request rejected.'));
    $account = user_load($account->uid);
    $this->assertTrue($account->status == 1, t('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));
    $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Expired cancel account request rejected.'));
    $accounts = user_load_multiple(array($account->uid), array('status' => 1));
    $this->assertTrue(reset($accounts), t('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), t('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?'), t('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.'), t('Informs that all content will be remain as is.'));
    $this->assertNoText(t('Select the method to cancel the account above.'), t('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.'), t('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 = user_load($account->uid, TRUE);
    $this->assertTrue($account->status == 0, t('User has been blocked.'));

    // Confirm user is logged out.
    $this->assertNoText($account->name, t('Logged out.'));
  }

  /**
   * 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?'), t('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.'), t('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.'), t('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 = user_load($account->uid, TRUE);
    $this->assertTrue($account->status == 0, t('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, t('Node of the user has been unpublished.'));
    $test_node = node_load($node->nid, $node->vid, TRUE);
    $this->assertTrue($test_node->status == 0, t('Node revision of the user has been unpublished.'));

    // Confirm user is logged out.
    $this->assertNoText($account->name, t('Logged out.'));
  }

  /**
   * 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?'), t('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')))), t('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.'), t('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));
    $this->assertFalse(user_load($account->uid, TRUE), t('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), t('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), t('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), t("Current revision of the user's node was not attributed to anonymous user."));

    // Confirm that user is logged out.
    $this->assertNoText($account->name, t('Logged out.'));
  }

  /**
   * 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', 'post comments without 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, t('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?'), t('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.'), t('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.'), t('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));
    $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.'));

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

    // Confirm that user is logged out.
    $this->assertNoText($account->name, t('Logged out.'));
  }

  /**
   * 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)), t('Confirmation form to cancel account displayed.'));
    $this->assertText(t('Select the method to cancel the account above.'), t('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)), t('User deleted.'));
    $this->assertFalse(user_load($account->uid), t('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?'), t('Confirmation form to cancel accounts displayed.'));
    $this->assertText(t('When cancelling these accounts'), t('Allows to select account cancellation method.'));
    $this->assertText(t('Require e-mail confirmation to cancel account.'), t('Allows to send confirmation mail.'));
    $this->assertText(t('Notify user when account is canceled.'), t('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, t('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.'), t('Account cancellation request mailed message displayed.'));
    $admin_user = user_load($admin_user->uid);
    $this->assertTrue($admin_user->status == 1, t('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, t('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.'), t('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, t('Image was resized.'));
      $alt = t("@user's picture", array('@user' => format_username($this->user)));
      $style = variable_get('user_picture_style', '');
      $this->assertRaw(image_style_url($style, $pic_path), t("Image is displayed in user's edit page"));

      // Check if file is located in proper directory.
      $this->assertTrue(is_file($pic_path), t("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, t('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, t('File size cited as reason for failure.'));

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), t('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, t('Upload failed.'));
      $text = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $test_dim));
      $this->assertRaw($text, t('Checking response on invalid image (dimensions).'));

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), t('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, t('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, t('File size cited as reason for failure.'));

      // Check if file is not uploaded.
      $this->assertFalse(is_file($pic_path), t('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), t("Image is displayed in user's profile page"));

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

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

    $img_info    = image_get_info($image->uri);
    $picture_dir = variable_get('user_picture_path', 'pictures');
    $pic_path    = 'public://' . $picture_dir . '/picture-' . $this->user->uid . '.' . $img_info['extension'];

    return $pic_path;
  }
}


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), t('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.'), t('Successful save message displayed.'));
    drupal_static_reset('user_access');
    drupal_static_reset('user_role_permissions');
    $this->assertTrue(user_access('administer nodes', $account), t('User now has "administer nodes" permission.'));

    // Remove a permission.
    $this->assertTrue(user_access('access user profiles', $account), t('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.'), t('Successful save message displayed.'));
    drupal_static_reset('user_access');
    drupal_static_reset('user_role_permissions');
    $this->assertFalse(user_access('access user profiles', $account), t('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), t('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), t('User does not have "administer nodes" permission.'));
    $this->assertTrue(user_access('access user profiles', $account), t('User has "access user profiles" permission.'));
    $this->assertTrue(user_access('administer site configuration', $account), t('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), t('User now has "administer nodes" permission.'));
    $this->assertFalse(user_access('access user profiles', $account), t('User no longer has "access user profiles" permission.'));
    $this->assertTrue(user_access('administer site configuration', $account), t('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, t('Found user A on admin users page'));
    $this->assertText($user_b->name, t('Found user B on admin users page'));
    $this->assertText($user_c->name, t('Found user C on admin users page'));
    $this->assertText($admin_user->name, t('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, t('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, t('User A not on filtered by perm  admin users page'));
    $this->assertText($user_b->name, t('Found user B on filtered by perm admin users page'));
    $this->assertText($user_c->name, t('Found user C on filtered by perm admin users page'));

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

/**
 * 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', t('Date should be PST.'));
    $this->drupalGet("node/$node2->nid");
    $this->assertText('2007-03-11 01:00 PST', t('Date should be PST.'));
    $this->drupalGet("node/$node3->nid");
    $this->assertText('2007-03-20 21:00 PDT', t('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.'), t('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', t('Date should be Chile summer time; five hours ahead of PST.'));
    $this->drupalGet("node/$node2->nid");
    $this->assertText('2007-03-11 05:00 CLT', t('Date should be Chile time; four hours ahead of PST'));
    $this->drupalGet("node/$node3->nid");
    $this->assertText('2007-03-21 00:00 CLT', t('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, t('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, t('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, t('User name found in autocompletion results.'));
  }
}

/**
 * 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'), t('Logged in.'));

    // Check that we are still on the same page.
    $this->assertEqual(url('admin/people/permissions', array('absolute' => TRUE)), $this->getUrl(), t('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'), t('Logged in.'));
    $this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', t('Still on the same page after login for allowed page'));
  }

  /**
   * 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, t('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, t('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'), t('Correct number of online users (2 users).'));
    $this->assertText($user1->name, t('Active user 1 found in online list.'));
    $this->assertText($user2->name, t('Active user 2 found in online list.'));
    $this->assertNoText($user3->name, t("Inactive user not found in online list."));
    $this->assertTrue(strpos($this->drupalGetContent(), $user1->name) > strpos($this->drupalGetContent(), $user2->name), t('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' => md5(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, t('Session record inserted.'));
  }
}

/**
 * Test case to test user_save() behaviour.
 */
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, t('Loading user by return of user_save().'));

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

    $user_by_name = user_load_by_name($test_name);
    $this->assertTrue($user_by_name, t('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 creat 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');
    }
  }
}

/**
 * Test case to test user_save() behaviour.
 */
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'])));

    // 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 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', 0);

    // 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.',
      '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.'), t('The role has been added.'));
    $role = user_role_load_by_name($role_name);
    $this->assertTrue(is_object($role), t('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)), t('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.'), t('The role has been renamed.'));
    $this->assertFalse(user_role_load_by_name($old_name), t('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)), t('The role can be retrieved from the database using its new name.'));

    // Test deleting a role.
    $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.'), t('The role has been deleted'));
    $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", t('Role edit link removed.'));
    $this->assertFalse(user_role_load_by_name($role_name), t('A deleted role can no longer be loaded.'));

    // 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, t('Access denied when trying to edit the built-in anonymous role.'));
    $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_AUTHENTICATED_RID);
    $this->assertResponse(403, t('Access denied when trying to edit the built-in authenticated role.'));
  }
}

/**
 * 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 $user;

    // Generate and test sanitized tokens.
    $tests = array();
    $tests['[user:uid]'] = $account->uid;
    $tests['[user:name]'] = filter_xss($account->name);
    $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($user->name);

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

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

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

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