Projet

Général

Profil

Paste
Télécharger (109 ko) Statistiques
| Branche: | Révision:

root / drupal7 / modules / user / user.test @ cd5c298a

1
<?php
2

    
3
/**
4
 * @file
5
 * Tests for user.module.
6
 */
7

    
8
class UserRegistrationTestCase extends DrupalWebTestCase {
9
  public static function getInfo() {
10
    return array(
11
      'name' => 'User registration',
12
      'description' => 'Test registration of user under different configurations.',
13
      'group' => 'User'
14
    );
15
  }
16

    
17
  function setUp() {
18
    parent::setUp('field_test');
19
  }
20

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

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

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

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

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

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

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

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

    
77
    // Allow registration by site visitors, but require administrator approval.
78
    variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
79
    $edit = array();
80
    $edit['name'] = $name = $this->randomName();
81
    $edit['mail'] = $mail = $edit['name'] . '@example.com';
82
    $edit['pass[pass1]'] = $pass = $this->randomName();
83
    $edit['pass[pass2]'] = $pass;
84
    $this->drupalPost('user/register', $edit, t('Create new account'));
85
    $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');
86

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

    
95
    // Activate the new account.
96
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
97
    $new_user = reset($accounts);
98
    $admin_user = $this->drupalCreateUser(array('administer users'));
99
    $this->drupalLogin($admin_user);
100
    $edit = array(
101
      'status' => 1,
102
    );
103
    $this->drupalPost('user/' . $new_user->uid . '/edit', $edit, t('Save'));
104
    $this->drupalLogout();
105

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

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

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

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

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

    
125
    // Attempt to create a new account using an existing e-mail address.
126
    $this->drupalPost('user/register', $edit, t('Create new account'));
127
    $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');
128

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

    
132
    $this->drupalPost('user/register', $edit, t('Create new account'));
133
    $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');
134
  }
135

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

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

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

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

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

    
159
    // Check user fields.
160
    $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
161
    $new_user = reset($accounts);
162
    $this->assertEqual($new_user->name, $name, 'Username matches.');
163
    $this->assertEqual($new_user->mail, $mail, 'E-mail address matches.');
164
    $this->assertEqual($new_user->theme, '', 'Correct theme field.');
165
    $this->assertEqual($new_user->signature, '', 'Correct signature field.');
166
    $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), 'Correct creation time.');
167
    $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, 'Correct status field.');
168
    $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), 'Correct time zone field.');
169
    $this->assertEqual($new_user->language, '', 'Correct language field.');
170
    $this->assertEqual($new_user->picture, '', 'Correct picture field.');
171
    $this->assertEqual($new_user->init, $mail, 'Correct init field.');
172
  }
173

    
174
  /**
175
   * Tests Field API fields on user registration forms.
176
   */
177
  function testRegistrationWithUserFields() {
178
    // Create a field, and an instance on 'user' entity type.
179
    $field = array(
180
      'type' => 'test_field',
181
      'field_name' => 'test_user_field',
182
      'cardinality' => 1,
183
    );
184
    field_create_field($field);
185
    $instance = array(
186
      'field_name' => 'test_user_field',
187
      'entity_type' => 'user',
188
      'label' => 'Some user field',
189
      'bundle' => 'user',
190
      'required' => TRUE,
191
      'settings' => array('user_register_form' => FALSE),
192
    );
193
    field_create_instance($instance);
194

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

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

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

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

    
227
    // Check that the 'add more' button works.
228
    $field['cardinality'] = FIELD_CARDINALITY_UNLIMITED;
229
    field_update_field($field);
230
    foreach (array('js', 'nojs') as $js) {
231
      $this->drupalGet('user/register');
232
      // Add two inputs.
233
      $value = rand(1, 255);
234
      $edit = array();
235
      $edit['test_user_field[und][0][value]'] = $value;
236
      if ($js == 'js') {
237
        $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more');
238
        $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more');
239
      }
240
      else {
241
        $this->drupalPost(NULL, $edit, t('Add another item'));
242
        $this->drupalPost(NULL, $edit, t('Add another item'));
243
      }
244
      // Submit with three values.
245
      $edit['test_user_field[und][1][value]'] = $value + 1;
246
      $edit['test_user_field[und][2][value]'] = $value + 2;
247
      $edit['name'] = $name = $this->randomName();
248
      $edit['mail'] = $mail = $edit['name'] . '@example.com';
249
      $this->drupalPost(NULL, $edit, t('Create new account'));
250
      // Check user fields.
251
      $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail));
252
      $new_user = reset($accounts);
253
      $this->assertEqual($new_user->test_user_field[LANGUAGE_NONE][0]['value'], $value, format_string('@js : The field value was correclty saved.', array('@js' => $js)));
254
      $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)));
255
      $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)));
256
    }
257
  }
258
}
259

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

    
269
  // Username validation.
270
  function testUsernames() {
271
    $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'),
272
      'foo'                    => array('Valid username', 'assertNull'),
273
      'FOO'                    => array('Valid username', 'assertNull'),
274
      'Foo O\'Bar'             => array('Valid username', 'assertNull'),
275
      'foo@bar'                => array('Valid username', 'assertNull'),
276
      'foo@example.com'        => array('Valid username', 'assertNull'),
277
      'foo@-example.com'       => array('Valid username', 'assertNull'), // invalid domains are allowed in usernames
278
      'þòøÇߪř€'               => array('Valid username', 'assertNull'),
279
      'foo+bar'                => array('Valid username', 'assertNull'), // '+' symbol is allowed
280
      'ᚠᛇᚻ᛫ᛒᛦᚦ'                => array('Valid UTF8 username', 'assertNull'), // runes
281
      ' foo'                   => array('Invalid username that starts with a space', 'assertNotNull'),
282
      'foo '                   => array('Invalid username that ends with a space', 'assertNotNull'),
283
      'foo  bar'               => array('Invalid username that contains 2 spaces \'&nbsp;&nbsp;\'', 'assertNotNull'),
284
      ''                       => array('Invalid empty username', 'assertNotNull'),
285
      'foo/'                   => array('Invalid username containing invalid chars', 'assertNotNull'),
286
      'foo' . chr(0) . 'bar'   => array('Invalid username containing chr(0)', 'assertNotNull'), // NULL
287
      'foo' . chr(13) . 'bar'  => array('Invalid username containing chr(13)', 'assertNotNull'), // CR
288
      str_repeat('x', USERNAME_MAX_LENGTH + 1) => array('Invalid excessively long username', 'assertNotNull'),
289
    );
290
    foreach ($test_cases as $name => $test_case) {
291
      list($description, $test) = $test_case;
292
      $result = user_validate_name($name);
293
      $this->$test($result, $description . ' (' . $name . ')');
294
    }
295
  }
296

    
297
  // Mail validation. More extensive tests can be found at common.test
298
  function testMailAddresses() {
299
    $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'),
300
      ''                => array('Empty mail address', 'assertNotNull'),
301
      'foo'             => array('Invalid mail address', 'assertNotNull'),
302
      'foo@example.com' => array('Valid mail address', 'assertNull'),
303
    );
304
    foreach ($test_cases as $name => $test_case) {
305
      list($description, $test) = $test_case;
306
      $result = user_validate_mail($name);
307
      $this->$test($result, $description . ' (' . $name . ')');
308
    }
309
  }
310
}
311

    
312
/**
313
 * Functional tests for user logins, including rate limiting of login attempts.
314
 */
315
class UserLoginTestCase extends DrupalWebTestCase {
316
  public static function getInfo() {
317
    return array(
318
      'name' => 'User login',
319
      'description' => 'Ensure that login works as expected.',
320
      'group' => 'User',
321
    );
322
  }
323

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
399
  /**
400
   * Test that user password is re-hashed upon login after changing $count_log2.
401
   */
402
  function testPasswordRehashOnLogin() {
403
    // Load password hashing API.
404
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
405
    // Set initial $count_log2 to the default, DRUPAL_HASH_COUNT.
406
    variable_set('password_count_log2', DRUPAL_HASH_COUNT);
407
    // Create a new user and authenticate.
408
    $account = $this->drupalCreateUser(array());
409
    $password = $account->pass_raw;
410
    $this->drupalLogin($account);
411
    $this->drupalLogout();
412
    // Load the stored user. The password hash should reflect $count_log2.
413
    $account = user_load($account->uid);
414
    $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT);
415
    // Change $count_log2 and log in again.
416
    variable_set('password_count_log2', DRUPAL_HASH_COUNT + 1);
417
    $account->pass_raw = $password;
418
    $this->drupalLogin($account);
419
    // Load the stored user, which should have a different password hash now.
420
    $account = user_load($account->uid, TRUE);
421
    $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT + 1);
422
  }
423

    
424
  /**
425
   * Make an unsuccessful login attempt.
426
   *
427
   * @param $account
428
   *   A user object with name and pass_raw attributes for the login attempt.
429
   * @param $flood_trigger
430
   *   Whether or not to expect that the flood control mechanism will be
431
   *   triggered.
432
   */
433
  function assertFailedLogin($account, $flood_trigger = NULL) {
434
    $edit = array(
435
      'name' => $account->name,
436
      'pass' => $account->pass_raw,
437
    );
438
    $this->drupalPost('user', $edit, t('Log in'));
439
    $this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, 'Password value attribute is blank.');
440
    if (isset($flood_trigger)) {
441
      if ($flood_trigger == 'user') {
442
        $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'))));
443
      }
444
      else {
445
        // No uid, so the limit is IP-based.
446
        $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'))));
447
      }
448
    }
449
    else {
450
      $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?'));
451
    }
452
  }
453
}
454

    
455
/**
456
 * Tests resetting a user password.
457
 */
458
class UserPasswordResetTestCase extends DrupalWebTestCase {
459
  protected $profile = 'standard';
460

    
461
  public static function getInfo() {
462
    return array(
463
      'name' => 'Reset password',
464
      'description' => 'Ensure that password reset methods work as expected.',
465
      'group' => 'User',
466
    );
467
  }
468

    
469
  /**
470
   * Retrieves password reset email and extracts the login link.
471
   */
472
  public function getResetURL() {
473
    // Assume the most recent email.
474
    $_emails = $this->drupalGetMails();
475
    $email = end($_emails);
476
    $urls = array();
477
    preg_match('#.+user/reset/.+#', $email['body'], $urls);
478

    
479
    return $urls[0];
480
  }
481

    
482
  /**
483
   * Tests password reset functionality.
484
   */
485
  function testUserPasswordReset() {
486
    // Create a user.
487
    $account = $this->drupalCreateUser();
488
    $this->drupalLogin($account);
489
    $this->drupalLogout();
490
    // Attempt to reset password.
491
    $edit = array('name' => $account->name);
492
    $this->drupalPost('user/password', $edit, t('E-mail new password'));
493
    // Confirm the password reset.
494
    $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.');
495

    
496
    // Create an image field to enable an Ajax request on the user profile page.
497
    $field = array(
498
      'field_name' => 'field_avatar',
499
      'type' => 'image',
500
      'settings' => array(),
501
      'cardinality' => 1,
502
    );
503
    field_create_field($field);
504

    
505
    $instance = array(
506
      'field_name' => $field['field_name'],
507
      'entity_type' => 'user',
508
      'label' => 'Avatar',
509
      'bundle' => 'user',
510
      'required' => FALSE,
511
      'settings' => array(),
512
      'widget' => array(
513
        'type' => 'image_image',
514
        'settings' => array(),
515
      ),
516
    );
517
    field_create_instance($instance);
518

    
519
    $resetURL = $this->getResetURL();
520
    $this->drupalGet($resetURL);
521

    
522
    // Check successful login.
523
    $this->drupalPost(NULL, NULL, t('Log in'));
524

    
525
    // Make sure the Ajax request from uploading a file does not invalidate the
526
    // reset token.
527
    $image = current($this->drupalGetTestFiles('image'));
528
    $edit = array(
529
      'files[field_avatar_und_0]' => drupal_realpath($image->uri),
530
    );
531
    $this->drupalPostAJAX(NULL, $edit, 'field_avatar_und_0_upload_button');
532

    
533
    // Change the forgotten password.
534
    $password = user_password();
535
    $edit = array('pass[pass1]' => $password, 'pass[pass2]' => $password);
536
    $this->drupalPost(NULL, $edit, t('Save'));
537
    $this->assertText(t('The changes have been saved.'), 'Forgotten password changed.');
538
  }
539

    
540
  /**
541
   * Test user password reset while logged in.
542
   */
543
  function testUserPasswordResetLoggedIn() {
544
    $account = $this->drupalCreateUser();
545
    $this->drupalLogin($account);
546
    // Make sure the test account has a valid password.
547
    user_save($account, array('pass' => user_password()));
548

    
549
    // Generate one time login link.
550
    $reset_url = user_pass_reset_url($account);
551
    $this->drupalGet($reset_url);
552

    
553
    $this->assertText('Reset password');
554
    $this->drupalPost(NULL, NULL, t('Log in'));
555

    
556
    $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.');
557

    
558
    $pass = user_password();
559
    $edit = array(
560
      'pass[pass1]' => $pass,
561
      'pass[pass2]' => $pass,
562
    );
563
    $this->drupalPost(NULL, $edit, t('Save'));
564

    
565
    $this->assertText('The changes have been saved.');
566
  }
567

    
568
  /**
569
   * Attempts login using an expired password reset link.
570
   */
571
  function testUserPasswordResetExpired() {
572
    // Set password reset timeout variable to 43200 seconds = 12 hours.
573
    $timeout = 43200;
574
    variable_set('user_password_reset_timeout', $timeout);
575

    
576
    // Create a user.
577
    $account = $this->drupalCreateUser();
578
    $this->drupalLogin($account);
579
    // Load real user object.
580
    $account = user_load($account->uid, TRUE);
581
    $this->drupalLogout();
582

    
583
    // To attempt an expired password reset, create a password reset link as if
584
    // its request time was 60 seconds older than the allowed limit of timeout.
585
    $bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60;
586
    $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
587
    $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.');
588
  }
589

    
590
  /**
591
   * Prefill the text box on incorrect login via link to password reset page.
592
   */
593
  function testUserPasswordTextboxFilled() {
594
    $this->drupalGet('user/login');
595
    $edit = array(
596
      'name' => $this->randomName(),
597
      'pass' => $this->randomName(),
598
    );
599
    $this->drupalPost('user', $edit, t('Log in'));
600
    $this->assertRaw(t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>',
601
      array('@password' => url('user/password', array('query' => array('name' => $edit['name']))))));
602
    unset($edit['pass']);
603
    $this->drupalGet('user/password', array('query' => array('name' => $edit['name'])));
604
    $this->assertFieldByName('name', $edit['name'], 'User name found.');
605
  }
606

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

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

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

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

    
628
    // The password reset URL must not be valid for the second user when only
629
    // the user ID is changed in the URL.
630
    $reset_url = user_pass_reset_url($user1);
631
    $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
632
    $this->drupalGet($attack_reset_url);
633
    $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
634
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
635
    $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.');
636

    
637
    // When legacy code calls user_pass_rehash() without providing the $uid
638
    // parameter, neither password reset URL should be valid since it is
639
    // impossible for the system to determine which user account the token was
640
    // intended for.
641
    $timestamp = REQUEST_TIME;
642
    // Pass an explicit NULL for the $uid parameter of user_pass_rehash()
643
    // rather than not passing it at all, to avoid triggering PHP warnings in
644
    // the test.
645
    $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
646
    $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
647
    $this->drupalGet($reset_url);
648
    $this->assertNoText($user1->name, 'The invalid password reset page does not show the user name.');
649
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
650
    $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.');
651
    $attack_reset_url = str_replace("user/reset/$user1->uid", "user/reset/$user2->uid", $reset_url);
652
    $this->drupalGet($attack_reset_url);
653
    $this->assertNoText($user2->name, 'The invalid password reset page does not show the user name.');
654
    $this->assertUrl('user/password', array(), 'The user is redirected to the password reset request page.');
655
    $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.');
656

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

    
664
    // However, when the duplicate account is removed, the password reset URL
665
    // should be valid.
666
    user_delete($user2->uid);
667
    $reset_url_token = user_pass_rehash($user1->pass, $timestamp, $user1->login, NULL);
668
    $reset_url = url("user/reset/$user1->uid/$timestamp/$reset_url_token", array('absolute' => TRUE));
669
    $this->drupalGet($reset_url);
670
    $this->assertText($user1->name, 'The valid password reset page shows the user name.');
671
    $this->assertUrl($reset_url, array(), 'The user remains on the password reset login page.');
672
    $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.');
673
  }
674

    
675
}
676

    
677
/**
678
 * Test cancelling a user.
679
 */
680
class UserCancelTestCase extends DrupalWebTestCase {
681
  public static function getInfo() {
682
    return array(
683
      'name' => 'Cancel account',
684
      'description' => 'Ensure that account cancellation methods work as expected.',
685
      'group' => 'User',
686
    );
687
  }
688

    
689
  function setUp() {
690
    parent::setUp('comment');
691
  }
692

    
693
  /**
694
   * Attempt to cancel account without permission.
695
   */
696
  function testUserCancelWithoutPermission() {
697
    variable_set('user_cancel_method', 'user_cancel_reassign');
698

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

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

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

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

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

    
724
  /**
725
   * Tests that user account for uid 1 cannot be cancelled.
726
   *
727
   * This should never be possible, or the site owner would become unable to
728
   * administer the site.
729
   */
730
  function testUserCancelUid1() {
731
    // Update uid 1's name and password to we know it.
732
    $password = user_password();
733
    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
734
    $account = array(
735
      'name' => 'user1',
736
      'pass' => user_hash_password(trim($password)),
737
    );
738
    // We cannot use user_save() here or the password would be hashed again.
739
    db_update('users')
740
      ->fields($account)
741
      ->condition('uid', 1)
742
      ->execute();
743

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

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

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

    
762
  /**
763
   * Attempt invalid account cancellations.
764
   */
765
  function testUserCancelInvalid() {
766
    variable_set('user_cancel_method', 'user_cancel_reassign');
767

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

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

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

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

    
785
    // Attempt bogus account cancellation request confirmation.
786
    $bogus_timestamp = $timestamp + 60;
787
    $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
788
    $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.');
789
    $account = user_load($account->uid);
790
    $this->assertTrue($account->status == 1, 'User account was not canceled.');
791

    
792
    // Attempt expired account cancellation request confirmation.
793
    $bogus_timestamp = $timestamp - 86400 - 60;
794
    $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login, $account->uid));
795
    $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.');
796
    $accounts = user_load_multiple(array($account->uid), array('status' => 1));
797
    $this->assertTrue(reset($accounts), 'User account was not canceled.');
798

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

    
804
  /**
805
   * Disable account and keep all content.
806
   */
807
  function testUserBlock() {
808
    variable_set('user_cancel_method', 'user_cancel_block');
809

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

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

    
817
    // Attempt to cancel account.
818
    $this->drupalGet('user/' . $account->uid . '/edit');
819
    $this->drupalPost(NULL, NULL, t('Cancel account'));
820
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
821
    $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.');
822
    $this->assertNoText(t('Select the method to cancel the account above.'), 'Does not allow user to select account cancellation method.');
823

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

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

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

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

    
839
  /**
840
   * Disable account and unpublish all content.
841
   */
842
  function testUserBlockUnpublish() {
843
    variable_set('user_cancel_method', 'user_cancel_block_unpublish');
844

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

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

    
857
    // Attempt to cancel account.
858
    $this->drupalGet('user/' . $account->uid . '/edit');
859
    $this->drupalPost(NULL, NULL, t('Cancel account'));
860
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
861
    $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.');
862

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

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

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

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

    
883
  /**
884
   * Delete account and anonymize all content.
885
   */
886
  function testUserAnonymize() {
887
    variable_set('user_cancel_method', 'user_cancel_reassign');
888

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

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

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

    
907
    // Attempt to cancel account.
908
    $this->drupalGet('user/' . $account->uid . '/edit');
909
    $this->drupalPost(NULL, NULL, t('Cancel account'));
910
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
911
    $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.');
912

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

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

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

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

    
934
  /**
935
   * Delete account and remove all content.
936
   */
937
  function testUserDelete() {
938
    variable_set('user_cancel_method', 'user_cancel_delete');
939

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

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

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

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

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

    
971
    // Attempt to cancel account.
972
    $this->drupalGet('user/' . $account->uid . '/edit');
973
    $this->drupalPost(NULL, NULL, t('Cancel account'));
974
    $this->assertText(t('Are you sure you want to cancel your account?'), 'Confirmation form to cancel account displayed.');
975
    $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.');
976

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

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

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

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

    
996
  /**
997
   * Create an administrative user and delete another user.
998
   */
999
  function testUserCancelByAdmin() {
1000
    variable_set('user_cancel_method', 'user_cancel_reassign');
1001

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

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

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

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

    
1021
  /**
1022
   * Create an administrative user and mass-delete other users.
1023
   */
1024
  function testMassUserCancelByAdmin() {
1025
    variable_set('user_cancel_method', 'user_cancel_reassign');
1026
    // Enable account cancellation notification.
1027
    variable_set('user_mail_status_canceled_notify', TRUE);
1028

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

    
1033
    // Create some users.
1034
    $users = array();
1035
    for ($i = 0; $i < 3; $i++) {
1036
      $account = $this->drupalCreateUser(array());
1037
      $users[$account->uid] = $account;
1038
    }
1039

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

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

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

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

    
1075
class UserPictureTestCase extends DrupalWebTestCase {
1076
  protected $user;
1077
  protected $_directory_test;
1078

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

    
1087
  function setUp() {
1088
    parent::setUp();
1089
    // Enable user pictures.
1090
    variable_set('user_pictures', 1);
1091

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

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

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

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

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

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

    
1116
  /**
1117
   * Do the test:
1118
   *  GD Toolkit is installed
1119
   *  Picture has invalid dimension
1120
   *
1121
   * results: The image should be uploaded because ImageGDToolkit resizes the picture
1122
   */
1123
  function testWithGDinvalidDimension() {
1124
    if ($this->_directory_test && image_get_toolkit()) {
1125
      $this->drupalLogin($this->user);
1126

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

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

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

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

    
1149
  /**
1150
   * Do the test:
1151
   *  GD Toolkit is installed
1152
   *  Picture has invalid size
1153
   *
1154
   * results: The image should be uploaded because ImageGDToolkit resizes the picture
1155
   */
1156
  function testWithGDinvalidSize() {
1157
    if ($this->_directory_test && image_get_toolkit()) {
1158
      $this->drupalLogin($this->user);
1159

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

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

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

    
1174
      // Test that the upload failed and that the correct reason was cited.
1175
      $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename));
1176
      $this->assertRaw($text, 'Upload failed.');
1177
      $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)));
1178
      $this->assertRaw($text, 'File size cited as reason for failure.');
1179

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

    
1185
  /**
1186
   * Do the test:
1187
   *  GD Toolkit is not installed
1188
   *  Picture has invalid size
1189
   *
1190
   * results: The image shouldn't be uploaded
1191
   */
1192
  function testWithoutGDinvalidDimension() {
1193
    if ($this->_directory_test && !image_get_toolkit()) {
1194
      $this->drupalLogin($this->user);
1195

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

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

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

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

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

    
1217
  /**
1218
   * Do the test:
1219
   *  GD Toolkit is not installed
1220
   *  Picture has invalid size
1221
   *
1222
   * results: The image shouldn't be uploaded
1223
   */
1224
  function testWithoutGDinvalidSize() {
1225
    if ($this->_directory_test && !image_get_toolkit()) {
1226
      $this->drupalLogin($this->user);
1227

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

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

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

    
1239
      // Test that the upload failed and that the correct reason was cited.
1240
      $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename));
1241
      $this->assertRaw($text, 'Upload failed.');
1242
      $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)));
1243
      $this->assertRaw($text, 'File size cited as reason for failure.');
1244

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

    
1250
  /**
1251
   * Do the test:
1252
   *  Picture is valid (proper size and dimension)
1253
   *
1254
   * results: The image should be uploaded
1255
   */
1256
  function testPictureIsValid() {
1257
    if ($this->_directory_test) {
1258
      $this->drupalLogin($this->user);
1259

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

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

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

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

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

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

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

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

    
1297
  /**
1298
   * Test HTTP schema working with user pictures.
1299
   */
1300
  function testExternalPicture() {
1301
    $this->drupalLogin($this->user);
1302
    // Set the default picture to an URI with a HTTP schema.
1303
    $images = $this->drupalGetTestFiles('image');
1304
    $image = $images[0];
1305
    $pic_path = file_create_url($image->uri);
1306
    variable_set('user_picture_default', $pic_path);
1307

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

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

    
1317
  /**
1318
   * Tests deletion of user pictures.
1319
   */
1320
  function testDeletePicture() {
1321
    $this->drupalLogin($this->user);
1322

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1389

    
1390
class UserPermissionsTestCase extends DrupalWebTestCase {
1391
  protected $admin_user;
1392
  protected $rid;
1393

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

    
1402
  function setUp() {
1403
    parent::setUp();
1404

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

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

    
1413
  /**
1414
   * Change user permissions and check user_access().
1415
   */
1416
  function testUserPermissionChanges() {
1417
    $this->drupalLogin($this->admin_user);
1418
    $rid = $this->rid;
1419
    $account = $this->admin_user;
1420

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

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

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

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

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

    
1462
  /**
1463
   * Verify proper permission changes by user_role_change_permissions().
1464
   */
1465
  function testUserRoleChangePermissions() {
1466
    $rid = $this->rid;
1467
    $account = $this->admin_user;
1468

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

    
1474
    // Change permissions.
1475
    $permissions = array(
1476
      'administer nodes' => 1,
1477
      'access user profiles' => 0,
1478
    );
1479
    user_role_change_permissions($rid, $permissions);
1480

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

    
1488
class UserAdminTestCase extends DrupalWebTestCase {
1489
  public static function getInfo() {
1490
    return array(
1491
      'name' => 'User administration',
1492
      'description' => 'Test user administration page functionality.',
1493
      'group' => 'User'
1494
    );
1495
  }
1496

    
1497
  /**
1498
   * Registers a user and deletes it.
1499
   */
1500
  function testUserAdmin() {
1501

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

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

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

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

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

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

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

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

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

    
1557
    // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail
1558
    $user_d = $this->drupalCreateUser(array());
1559
    $account1 = user_load($user_d->uid, TRUE);
1560
    $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => 0), t('Save'));
1561
    $account1 = user_load($user_d->uid, TRUE);
1562
    $this->assertEqual($account1->status, 0, 'User D blocked');
1563
    $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => TRUE), t('Save'));
1564
    $account1 = user_load($user_d->uid, TRUE);
1565
    $this->assertEqual($account1->status, 1, 'User D unblocked');
1566
    $this->assertMail("to", $account1->mail, "Activation mail sent to user D");
1567
  }
1568
}
1569

    
1570
/**
1571
 * Tests for user-configurable time zones.
1572
 */
1573
class UserTimeZoneFunctionalTest extends DrupalWebTestCase {
1574
  public static function getInfo() {
1575
    return array(
1576
      'name' => 'User time zones',
1577
      'description' => 'Set a user time zone and verify that dates are displayed in local time.',
1578
      'group' => 'User',
1579
    );
1580
  }
1581

    
1582
  /**
1583
   * Tests the display of dates and time when user-configurable time zones are set.
1584
   */
1585
  function testUserTimeZone() {
1586
    // Setup date/time settings for Los Angeles time.
1587
    variable_set('date_default_timezone', 'America/Los_Angeles');
1588
    variable_set('configurable_timezones', 1);
1589

    
1590
    // Override the 'medium' date format, which is the default for node
1591
    // creation time. Since we are testing time zones with Daylight Saving
1592
    // Time, and need to future proof against changes to the zoneinfo database,
1593
    // we choose the 'I' format placeholder instead of a human-readable zone
1594
    // name. With 'I', a 1 means the date is in DST, and 0 if not.
1595
    variable_set('date_format_medium', 'Y-m-d H:i I');
1596

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

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

    
1611
    // Confirm date format and time zone.
1612
    $this->drupalGet("node/$node1->nid");
1613
    $this->assertText('2007-03-09 21:00 0', 'Date should be PST.');
1614
    $this->drupalGet("node/$node2->nid");
1615
    $this->assertText('2007-03-11 01:00 0', 'Date should be PST.');
1616
    $this->drupalGet("node/$node3->nid");
1617
    $this->assertText('2007-03-20 21:00 1', 'Date should be PDT.');
1618

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

    
1626
    // Confirm date format and time zone.
1627
    $this->drupalGet("node/$node1->nid");
1628
    $this->assertText('2007-03-10 02:00 1', 'Date should be Chile summer time; five hours ahead of PST.');
1629
    $this->drupalGet("node/$node2->nid");
1630
    $this->assertText('2007-03-11 05:00 0', 'Date should be Chile time; four hours ahead of PST');
1631
    $this->drupalGet("node/$node3->nid");
1632
    $this->assertText('2007-03-21 00:00 0', 'Date should be Chile time; three hours ahead of PDT.');
1633
  }
1634
}
1635

    
1636
/**
1637
 * Test user autocompletion.
1638
 */
1639
class UserAutocompleteTestCase extends DrupalWebTestCase {
1640
  public static function getInfo() {
1641
    return array(
1642
      'name' => 'User autocompletion',
1643
      'description' => 'Test user autocompletion functionality.',
1644
      'group' => 'User'
1645
    );
1646
  }
1647

    
1648
  function setUp() {
1649
    parent::setUp();
1650

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

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

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

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

    
1676

    
1677
/**
1678
 * Tests user links in the secondary menu.
1679
 */
1680
class UserAccountLinksUnitTests extends DrupalWebTestCase {
1681
  public static function getInfo() {
1682
    return array(
1683
      'name' => 'User account links',
1684
      'description' => 'Test user-account links.',
1685
      'group' => 'User'
1686
    );
1687
  }
1688

    
1689
  function setUp() {
1690
    parent::setUp('menu');
1691
  }
1692

    
1693
  /**
1694
   * Tests the secondary menu.
1695
   */
1696
  function testSecondaryMenu() {
1697
    // Create a regular user.
1698
    $user = $this->drupalCreateUser(array());
1699

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

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

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

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

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

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

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

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

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

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

    
1760
}
1761

    
1762
/**
1763
 * Test user blocks.
1764
 */
1765
class UserBlocksUnitTests extends DrupalWebTestCase {
1766
  public static function getInfo() {
1767
    return array(
1768
      'name' => 'User blocks',
1769
      'description' => 'Test user blocks.',
1770
      'group' => 'User'
1771
    );
1772
  }
1773

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

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

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

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

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

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

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

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

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

    
1826
    // Insert two anonymous user sessions.
1827
    $this->insertSession();
1828
    $this->insertSession();
1829

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

    
1840
  /**
1841
   * Insert a user session into the {sessions} table. This function is used
1842
   * since we cannot log in more than one user at the same time in tests.
1843
   */
1844
  private function insertSession(array $fields = array()) {
1845
    $fields += array(
1846
      'uid' => 0,
1847
      'sid' => drupal_hash_base64(uniqid(mt_rand(), TRUE)),
1848
      'timestamp' => REQUEST_TIME,
1849
    );
1850
    db_insert('sessions')
1851
      ->fields($fields)
1852
      ->execute();
1853
    $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.');
1854
  }
1855
}
1856

    
1857
/**
1858
 * Tests saving a user account.
1859
 */
1860
class UserSaveTestCase extends DrupalWebTestCase {
1861

    
1862
  public static function getInfo() {
1863
    return array(
1864
      'name' => 'User save test',
1865
      'description' => 'Test user_save() for arbitrary new uid.',
1866
      'group' => 'User',
1867
    );
1868
  }
1869

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

    
1879
    // Create the base user, based on drupalCreateUser().
1880
    $user = array(
1881
      'name' => $test_name,
1882
      'uid' => $test_uid,
1883
      'mail' => $test_name . '@example.com',
1884
      'is_new' => TRUE,
1885
      'pass' => user_password(),
1886
      'status' => 1,
1887
    );
1888
    $user_by_return = user_save(drupal_anonymous_user(), $user);
1889
    $this->assertTrue($user_by_return, 'Loading user by return of user_save().');
1890

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

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

    
1900
/**
1901
 * Test the create user administration page.
1902
 */
1903
class UserCreateTestCase extends DrupalWebTestCase {
1904

    
1905
  public static function getInfo() {
1906
    return array(
1907
      'name' => 'User create',
1908
      'description' => 'Test the create user administration page.',
1909
      'group' => 'User',
1910
    );
1911
  }
1912

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

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

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

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

    
1944
    // Test that the password '0' is considered a password.
1945
    $name = $this->randomName();
1946
    $edit = array(
1947
      'name' => $name,
1948
      'mail' => $name . '@example.com',
1949
      'pass[pass1]' => 0,
1950
      'pass[pass2]' => 0,
1951
      'notify' => FALSE,
1952
    );
1953
    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
1954
    $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0');
1955
    $this->assertNoText('Password field is required');
1956
  }
1957
}
1958

    
1959
/**
1960
 * Tests editing a user account.
1961
 */
1962
class UserEditTestCase extends DrupalWebTestCase {
1963

    
1964
  public static function getInfo() {
1965
    return array(
1966
      'name' => 'User edit',
1967
      'description' => 'Test user edit page.',
1968
      'group' => 'User',
1969
    );
1970
  }
1971

    
1972
  /**
1973
   * Test user edit page.
1974
   */
1975
  function testUserEdit() {
1976
    // Test user edit functionality with user pictures disabled.
1977
    variable_set('user_pictures', 0);
1978
    $user1 = $this->drupalCreateUser(array('change own username'));
1979
    $user2 = $this->drupalCreateUser(array());
1980
    $this->drupalLogin($user1);
1981

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

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

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

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

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

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

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

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

    
2027
    // Make sure the user can log in with their new password.
2028
    $this->drupalLogout();
2029
    $user1->pass_raw = $new_pass;
2030
    $this->drupalLogin($user1);
2031
    $this->drupalLogout();
2032
  }
2033

    
2034
  /**
2035
   * Tests setting the password to "0".
2036
   */
2037
  public function testUserWith0Password() {
2038
    $admin = $this->drupalCreateUser(array('administer users'));
2039
    $this->drupalLogin($admin);
2040
    // Create a regular user.
2041
    $user1 = $this->drupalCreateUser(array());
2042

    
2043
    $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0');
2044
    $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save'));
2045
    $this->assertRaw(t("The changes have been saved."));
2046

    
2047
    $this->drupalLogout();
2048
    $user1->pass_raw = '0';
2049
    $this->drupalLogin($user1);
2050
    $this->drupalLogout();
2051
  }
2052
}
2053

    
2054
/**
2055
 * Tests editing a user account with and without a form rebuild.
2056
 */
2057
class UserEditRebuildTestCase extends DrupalWebTestCase {
2058

    
2059
  public static function getInfo() {
2060
    return array(
2061
      'name' => 'User edit with form rebuild',
2062
      'description' => 'Test user edit page when a form rebuild is triggered.',
2063
      'group' => 'User',
2064
    );
2065
  }
2066

    
2067
  function setUp() {
2068
    parent::setUp('user_form_test');
2069
  }
2070

    
2071
  /**
2072
   * Test user edit page when the form is set to rebuild.
2073
   */
2074
  function testUserEditFormRebuild() {
2075
    $user1 = $this->drupalCreateUser(array('change own username'));
2076
    $this->drupalLogin($user1);
2077

    
2078
    $roles = array_keys($user1->roles);
2079
    // Save the user form twice.
2080
    $edit = array();
2081
    $edit['current_pass'] = $user1->pass_raw;
2082
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
2083
    $this->assertRaw(t("The changes have been saved."));
2084
    $this->drupalPost(NULL, $edit, t('Save'));
2085
    $this->assertRaw(t("The changes have been saved."));
2086
    $saved_user1 = entity_load_unchanged('user', $user1->uid);
2087
    $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
2088
    $diff = array_diff(array_keys($saved_user1->roles), $roles);
2089
    $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
2090
    // Set variable that causes the form to be rebuilt in user_form_test.module.
2091
    variable_set('user_form_test_user_profile_form_rebuild', TRUE);
2092
    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
2093
    $this->assertRaw(t("The changes have been saved."));
2094
    $this->drupalPost(NULL, $edit, t('Save'));
2095
    $this->assertRaw(t("The changes have been saved."));
2096
    $saved_user1 = entity_load_unchanged('user', $user1->uid);
2097
    $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
2098
    $diff = array_diff(array_keys($saved_user1->roles), $roles);
2099
    $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
2100
  }
2101
}
2102

    
2103
/**
2104
 * Test case for user signatures.
2105
 */
2106
class UserSignatureTestCase extends DrupalWebTestCase {
2107
  public static function getInfo() {
2108
    return array(
2109
      'name' => 'User signatures',
2110
      'description' => 'Test user signatures.',
2111
      'group' => 'User',
2112
    );
2113
  }
2114

    
2115
  function setUp() {
2116
    parent::setUp('comment');
2117

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

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

    
2125
    // Create regular and administrative users.
2126
    $this->web_user = $this->drupalCreateUser(array());
2127
    $admin_permissions = array('administer comments');
2128
    foreach (filter_formats() as $format) {
2129
      if ($permission = filter_permission_name($format)) {
2130
        $admin_permissions[] = $permission;
2131
      }
2132
    }
2133
    $this->admin_user = $this->drupalCreateUser($admin_permissions);
2134
  }
2135

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

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

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

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

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

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

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

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

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

    
2192
  public static function getInfo() {
2193
    return array(
2194
      'name' => 'User edited own account',
2195
      'description' => 'Test user edited own account can still log in.',
2196
      'group' => 'User',
2197
    );
2198
  }
2199

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

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

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

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

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

    
2223
/**
2224
 * Test case to test adding, editing and deleting roles.
2225
 */
2226
class UserRoleAdminTestCase extends DrupalWebTestCase {
2227

    
2228
  public static function getInfo() {
2229
    return array(
2230
      'name' => 'User role administration',
2231
      'description' => 'Test adding, editing and deleting user roles and changing role weights.',
2232
      'group' => 'User',
2233
    );
2234
  }
2235

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

    
2241
  /**
2242
   * Test adding, renaming and deleting roles.
2243
   */
2244
  function testRoleAdministration() {
2245
    $this->drupalLogin($this->admin_user);
2246

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

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

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

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

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

    
2289
  /**
2290
   * Test user role weight change operation.
2291
   */
2292
  function testRoleWeightChange() {
2293
    $this->drupalLogin($this->admin_user);
2294

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

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

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

    
2312
/**
2313
 * Test user token replacement in strings.
2314
 */
2315
class UserTokenReplaceTestCase extends DrupalWebTestCase {
2316
  public static function getInfo() {
2317
    return array(
2318
      'name' => 'User token replacement',
2319
      'description' => 'Generates text using placeholders for dummy content to check user token replacement.',
2320
      'group' => 'User',
2321
    );
2322
  }
2323

    
2324
  /**
2325
   * Creates a user, then tests the tokens generated from it.
2326
   */
2327
  function testUserTokenReplacement() {
2328
    global $language;
2329
    $url_options = array(
2330
      'absolute' => TRUE,
2331
      'language' => $language,
2332
    );
2333

    
2334
    // Create two users and log them in one after another.
2335
    $user1 = $this->drupalCreateUser(array());
2336
    $user2 = $this->drupalCreateUser(array());
2337
    $this->drupalLogin($user1);
2338
    $this->drupalLogout();
2339
    $this->drupalLogin($user2);
2340

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

    
2344
    // Generate and test sanitized tokens.
2345
    $tests = array();
2346
    $tests['[user:uid]'] = $account->uid;
2347
    $tests['[user:name]'] = check_plain(format_username($account));
2348
    $tests['[user:mail]'] = check_plain($account->mail);
2349
    $tests['[user:url]'] = url("user/$account->uid", $url_options);
2350
    $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options);
2351
    $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language->language);
2352
    $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language->language);
2353
    $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language->language);
2354
    $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language->language);
2355
    $tests['[current-user:name]'] = check_plain(format_username($global_account));
2356

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

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

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

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

    
2377
/**
2378
 * Test user search.
2379
 */
2380
class UserUserSearchTestCase extends DrupalWebTestCase {
2381
  public static function getInfo() {
2382
    return array(
2383
      'name' => 'User search',
2384
      'description' => 'Tests the user search page and verifies that sensitive information is hidden from unauthorized users.',
2385
      'group' => 'User',
2386
    );
2387
  }
2388

    
2389
  function testUserSearch() {
2390
    // Verify that a user without 'administer users' permission cannot search
2391
    // for users by email address. Additionally, ensure that the username has a
2392
    // plus sign to ensure searching works with that.
2393
    $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search'));
2394
    $edit['name'] = 'foo+bar';
2395
    $edit['mail'] = $edit['name'] . '@example.com';
2396
    user_save($user1, $edit);
2397
    $this->drupalLogin($user1);
2398
    $keys = $user1->mail;
2399
    $edit = array('keys' => $keys);
2400
    $this->drupalPost('search/user/', $edit, t('Search'));
2401
    $this->assertNoText($keys);
2402
    $this->drupalLogout();
2403

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

    
2411
    // Verify that wildcard search works.
2412
    $keys = $user1->name;
2413
    $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
2414
    $edit = array('keys' => $keys);
2415
    $this->drupalPost('search/user/', $edit, t('Search'));
2416
    $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.');
2417

    
2418
    // Verify that wildcard search works for email.
2419
    $keys = $user1->mail;
2420
    $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
2421
    $edit = array('keys' => $keys);
2422
    $this->drupalPost('search/user/', $edit, t('Search'));
2423
    $this->assertText($user1->name, 'Search for email wildcard resulted in user name on page for administrative user.');
2424

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

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

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

    
2443
    $this->drupalLogout();
2444
  }
2445
}
2446

    
2447
/**
2448
 * Test role assignment.
2449
 */
2450
class UserRolesAssignmentTestCase extends DrupalWebTestCase {
2451
  protected $admin_user;
2452

    
2453
  public static function getInfo() {
2454
    return array(
2455
      'name' => 'Role assignment',
2456
      'description' => 'Tests that users can be assigned and unassigned roles.',
2457
      'group' => 'User'
2458
    );
2459
  }
2460

    
2461
  function setUp() {
2462
    parent::setUp();
2463
    $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users'));
2464
    $this->drupalLogin($this->admin_user);
2465
  }
2466

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

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

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

    
2488
  /**
2489
   * Tests that when creating a user the role can be assigned. And that it can
2490
   * be removed again.
2491
   */
2492
  function testCreateUserWithRole() {
2493
    $rid = $this->drupalCreateRole(array('administer content types'));
2494
    // Create a new user and add the role at the same time.
2495
    $edit = array(
2496
      'name' => $this->randomName(),
2497
      'mail' => $this->randomName() . '@example.com',
2498
      'pass[pass1]' => $pass = $this->randomString(),
2499
      'pass[pass2]' => $pass,
2500
      "roles[$rid]" => $rid,
2501
    );
2502
    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
2503
    $this->assertText(t('Created a new user account for !name.', array('!name' => $edit['name'])));
2504
    // Get the newly added user.
2505
    $account = user_load_by_name($edit['name']);
2506

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

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

    
2518
  /**
2519
   * Check role on user object.
2520
   *
2521
   * @param object $account
2522
   *   The user account to check.
2523
   * @param string $rid
2524
   *   The role ID to search for.
2525
   * @param bool $is_assigned
2526
   *   (optional) Whether to assert that $rid exists (TRUE) or not (FALSE).
2527
   *   Defaults to TRUE.
2528
   */
2529
  private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) {
2530
    $account = user_load($account->uid, TRUE);
2531
    if ($is_assigned) {
2532
      $this->assertTrue(array_key_exists($rid, $account->roles), 'The role is present in the user object.');
2533
    }
2534
    else {
2535
      $this->assertFalse(array_key_exists($rid, $account->roles), 'The role is not present in the user object.');
2536
    }
2537
  }
2538
}
2539

    
2540

    
2541
/**
2542
 * Unit test for authmap assignment.
2543
 */
2544
class UserAuthmapAssignmentTestCase extends DrupalWebTestCase {
2545
  public static function getInfo() {
2546
    return array(
2547
      'name' => 'Authmap assignment',
2548
      'description' => 'Tests that users can be assigned and unassigned authmaps.',
2549
      'group' => 'User'
2550
    );
2551
  }
2552

    
2553
  /**
2554
   * Test authmap assignment and retrieval.
2555
   */
2556
  function testAuthmapAssignment()  {
2557
    $account = $this->drupalCreateUser();
2558

    
2559
    // Assign authmaps to the user.
2560
    $authmaps = array(
2561
      'authname_poll' => 'external username one',
2562
      'authname_book' => 'external username two',
2563
    );
2564
    user_set_authmaps($account, $authmaps);
2565

    
2566
    // Test for expected authmaps.
2567
    $expected_authmaps = array(
2568
      'external username one' => array(
2569
        'poll' => 'external username one',
2570
      ),
2571
      'external username two' => array(
2572
        'book' => 'external username two',
2573
      ),
2574
    );
2575
    foreach ($expected_authmaps as $authname => $expected_output) {
2576
      $this->assertIdentical(user_get_authmaps($authname), $expected_output, format_string('Authmap for authname %authname was set correctly.', array('%authname' => $authname)));
2577
    }
2578

    
2579
    // Remove authmap for module poll, add authmap for module blog.
2580
    $authmaps = array(
2581
      'authname_poll' => NULL,
2582
      'authname_blog' => 'external username three',
2583
    );
2584
    user_set_authmaps($account, $authmaps);
2585

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

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

    
2600
/**
2601
 * Tests user_validate_current_pass on a custom form.
2602
 */
2603
class UserValidateCurrentPassCustomForm extends DrupalWebTestCase {
2604

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

    
2613
  /**
2614
   * User with permission to view content.
2615
   */
2616
  protected $accessUser;
2617

    
2618
  /**
2619
   * User permission to administer users.
2620
   */
2621
  protected $adminUser;
2622

    
2623
  function setUp() {
2624
    parent::setUp('user_form_test');
2625
    // Create two users
2626
    $this->accessUser = $this->drupalCreateUser(array('access content'));
2627
    $this->adminUser = $this->drupalCreateUser(array('administer users'));
2628
  }
2629

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

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