Projet

Général

Profil

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

root / drupal7 / modules / user / user.test @ 5587145e

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
      'ᚠᛇᚻ᛫ᛒᛦᚦ'                => array('Valid UTF8 username', 'assertNull'), // runes
280
      ' foo'                   => array('Invalid username that starts with a space', 'assertNotNull'),
281
      'foo '                   => array('Invalid username that ends with a space', 'assertNotNull'),
282
      'foo  bar'               => array('Invalid username that contains 2 spaces \'&nbsp;&nbsp;\'', 'assertNotNull'),
283
      ''                       => array('Invalid empty username', 'assertNotNull'),
284
      'foo/'                   => array('Invalid username containing invalid chars', 'assertNotNull'),
285
      'foo' . chr(0) . 'bar'   => array('Invalid username containing chr(0)', 'assertNotNull'), // NULL
286
      'foo' . chr(13) . 'bar'  => array('Invalid username containing chr(13)', 'assertNotNull'), // CR
287
      str_repeat('x', USERNAME_MAX_LENGTH + 1) => array('Invalid excessively long username', 'assertNotNull'),
288
    );
289
    foreach ($test_cases as $name => $test_case) {
290
      list($description, $test) = $test_case;
291
      $result = user_validate_name($name);
292
      $this->$test($result, $description . ' (' . $name . ')');
293
    }
294
  }
295

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
478
    return $urls[0];
479
  }
480

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

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

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

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

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

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

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

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

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

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

    
555
    $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.');
556

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
674
}
675

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1388

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1675

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1759
}
1760

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1943
    // Test that the password '0' is considered a password.
1944
    $name = $this->randomName();
1945
    $edit = array(
1946
      'name' => $name,
1947
      'mail' => $name . '@example.com',
1948
      'pass[pass1]' => 0,
1949
      'pass[pass2]' => 0,
1950
      'notify' => FALSE,
1951
    );
1952
    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
1953
    $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');
1954
    $this->assertNoText('Password field is required');
1955
  }
1956
}
1957

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
2388
  function testUserSearch() {
2389
    $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search'));
2390
    $this->drupalLogin($user1);
2391
    $keys = $user1->mail;
2392
    $edit = array('keys' => $keys);
2393
    $this->drupalPost('search/user/', $edit, t('Search'));
2394
    $this->assertNoText($keys);
2395
    $this->drupalLogout();
2396

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

    
2404
    // Verify that wildcard search works.
2405
    $keys = $user1->name;
2406
    $keys = substr($keys, 0, 2) . '*' . substr($keys, 4, 2);
2407
    $edit = array('keys' => $keys);
2408
    $this->drupalPost('search/user/', $edit, t('Search'));
2409
    $this->assertText($user1->name, 'Search for username wildcard resulted in user name on page for administrative user.');
2410

    
2411
    // Verify that wildcard search works for email.
2412
    $keys = $user1->mail;
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 email wildcard resulted in user name on page for administrative user.');
2417

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

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

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

    
2436
    $this->drupalLogout();
2437
  }
2438
}
2439

    
2440
/**
2441
 * Test role assignment.
2442
 */
2443
class UserRolesAssignmentTestCase extends DrupalWebTestCase {
2444
  protected $admin_user;
2445

    
2446
  public static function getInfo() {
2447
    return array(
2448
      'name' => 'Role assignment',
2449
      'description' => 'Tests that users can be assigned and unassigned roles.',
2450
      'group' => 'User'
2451
    );
2452
  }
2453

    
2454
  function setUp() {
2455
    parent::setUp();
2456
    $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users'));
2457
    $this->drupalLogin($this->admin_user);
2458
  }
2459

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

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

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

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

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

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

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

    
2533

    
2534
/**
2535
 * Unit test for authmap assignment.
2536
 */
2537
class UserAuthmapAssignmentTestCase extends DrupalWebTestCase {
2538
  public static function getInfo() {
2539
    return array(
2540
      'name' => 'Authmap assignment',
2541
      'description' => 'Tests that users can be assigned and unassigned authmaps.',
2542
      'group' => 'User'
2543
    );
2544
  }
2545

    
2546
  /**
2547
   * Test authmap assignment and retrieval.
2548
   */
2549
  function testAuthmapAssignment()  {
2550
    $account = $this->drupalCreateUser();
2551

    
2552
    // Assign authmaps to the user.
2553
    $authmaps = array(
2554
      'authname_poll' => 'external username one',
2555
      'authname_book' => 'external username two',
2556
    );
2557
    user_set_authmaps($account, $authmaps);
2558

    
2559
    // Test for expected authmaps.
2560
    $expected_authmaps = array(
2561
      'external username one' => array(
2562
        'poll' => 'external username one',
2563
      ),
2564
      'external username two' => array(
2565
        'book' => 'external username two',
2566
      ),
2567
    );
2568
    foreach ($expected_authmaps as $authname => $expected_output) {
2569
      $this->assertIdentical(user_get_authmaps($authname), $expected_output, format_string('Authmap for authname %authname was set correctly.', array('%authname' => $authname)));
2570
    }
2571

    
2572
    // Remove authmap for module poll, add authmap for module blog.
2573
    $authmaps = array(
2574
      'authname_poll' => NULL,
2575
      'authname_blog' => 'external username three',
2576
    );
2577
    user_set_authmaps($account, $authmaps);
2578

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

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

    
2593
/**
2594
 * Tests user_validate_current_pass on a custom form.
2595
 */
2596
class UserValidateCurrentPassCustomForm extends DrupalWebTestCase {
2597

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

    
2606
  /**
2607
   * User with permission to view content.
2608
   */
2609
  protected $accessUser;
2610

    
2611
  /**
2612
   * User permission to administer users.
2613
   */
2614
  protected $adminUser;
2615

    
2616
  function setUp() {
2617
    parent::setUp('user_form_test');
2618
    // Create two users
2619
    $this->accessUser = $this->drupalCreateUser(array('access content'));
2620
    $this->adminUser = $this->drupalCreateUser(array('administer users'));
2621
  }
2622

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

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