summaryrefslogtreecommitdiff
path: root/kolab.org/www/drupal-7.18/sites/all/modules/captcha/captcha.test
blob: bccefe3739a0701ddbceadd863ddde19df5e740a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
<?php

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

// TODO: write test for CAPTCHAs on admin pages
// TODO: test for default challenge type
// TODO: test about placement (comment form, node forms, log in form, etc)
// TODO: test if captcha_cron does it work right
// TODO: test custom CAPTCHA validation stuff
// TODO: test if entry on status report (Already X blocked form submissions) works
// TODO: test space ignoring validation of image CAPTCHA

// TODO: refactor the 'comment_body[' . LANGUAGE_NONE . '][0][value]' stuff

// Some constants for better reuse.
define('CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE',
  'The answer you entered for the CAPTCHA was not correct.');

define('CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE',
  'CAPTCHA session reuse attack detected.');

define('CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE',
  'CAPTCHA validation error: unknown CAPTCHA session ID. Contact the site administrator if this problem persists.');



/**
 * Base class for CAPTCHA tests.
 *
 * Provides common setup stuff and various helper functions
 */
abstract class CaptchaBaseWebTestCase extends DrupalWebTestCase {

  /**
   * User with various administrative permissions.
   * @var Drupal user
   */
  protected $admin_user;

  /**
   * Normal visitor with limited permissions
   * @var Drupal user;
   */
  protected $normal_user;

  /**
   * Form ID of comment form on standard (page) node
   * @var string
   */
  const COMMENT_FORM_ID = 'comment_node_page_form';

  /**
   * Drupal path of the (general) CAPTCHA admin page
   */
  const CAPTCHA_ADMIN_PATH = 'admin/config/people/captcha';


  function setUp() {
    // Load two modules: the captcha module itself and the comment module for testing anonymous comments.
    parent::setUp('captcha', 'comment');
    module_load_include('inc', 'captcha');

    // Create a normal user.
    $permissions = array(
      'access comments', 'post comments', 'skip comment approval',
      'access content', 'create page content', 'edit own page content',
    );
    $this->normal_user = $this->drupalCreateUser($permissions);

    // Create an admin user.
    $permissions[] = 'administer CAPTCHA settings';
    $permissions[] = 'skip CAPTCHA';
    $permissions[] = 'administer permissions';
    $permissions[] = 'administer content types';
    $this->admin_user = $this->drupalCreateUser($permissions);

    // Put comments on page nodes on a separate page (default in D7: below post).
    variable_set('comment_form_location_page', COMMENT_FORM_SEPARATE_PAGE);

  }

  /**
   * Assert that the response is accepted:
   * no "unknown CSID" message, no "CSID reuse attack detection" message,
   * no "wrong answer" message.
   */
  protected function assertCaptchaResponseAccepted() {
    // There should be no error message about unknown CAPTCHA session ID.
    $this->assertNoText(t(CAPTCHA_UNKNOWN_CSID_ERROR_MESSAGE),
      'CAPTCHA response should be accepted (known CSID).',
      'CAPTCHA');
    // There should be no error message about CSID reuse attack.
    $this->assertNoText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE),
      'CAPTCHA response should be accepted (no CAPTCHA session reuse attack detection).',
      'CAPTCHA');
    // There should be no error message about wrong response.
    $this->assertNoText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
      'CAPTCHA response should be accepted (correct response).',
      'CAPTCHA');
  }

  /**
   * Assert that there is a CAPTCHA on the form or not.
   * @param bool $presence whether there should be a CAPTCHA or not.
   */
  protected function assertCaptchaPresence($presence) {
    if ($presence) {
      $this->assertText(_captcha_get_description(),
        'There should be a CAPTCHA on the form.', 'CAPTCHA');
    }
    else {
      $this->assertNoText(_captcha_get_description(),
        'There should be no CAPTCHA on the form.', 'CAPTCHA');
    }
  }

  /**
   * Helper function to create a node with comments enabled.
   *
   * @return
   *   Created node object.
   */
  protected function createNodeWithCommentsEnabled($type='page') {
    $node_settings = array(
      'type' => $type,
      'comment' => COMMENT_NODE_OPEN,
    );
    $node = $this->drupalCreateNode($node_settings);
    return $node;
  }

  /**
   * Helper function to generate a form values array for comment forms
   */
  protected function getCommentFormValues() {
    $edit = array(
      'subject' => 'comment_subject ' . $this->randomName(32),
      'comment_body[' . LANGUAGE_NONE . '][0][value]' => 'comment_body ' . $this->randomName(256),
    );
    return $edit;
  }

  /**
   * Helper function to generate a form values array for node forms
   */
  protected function getNodeFormValues() {
    $edit = array(
      'title' => 'node_title ' . $this->randomName(32),
      'body[' . LANGUAGE_NONE . '][0][value]' => 'node_body ' . $this->randomName(256),
    );
    return $edit;
  }


  /**
   * Get the CAPTCHA session id from the current form in the browser.
   */
  protected function getCaptchaSidFromForm() {
    $elements = $this->xpath('//input[@name="captcha_sid"]');
    $captcha_sid = (int) $elements[0]['value'];
    return $captcha_sid;
  }
  /**
   * Get the CAPTCHA token from the current form in the browser.
   */
  protected function getCaptchaTokenFromForm() {
    $elements = $this->xpath('//input[@name="captcha_token"]');
    $captcha_token = (int) $elements[0]['value'];
    return $captcha_token;
  }

  /**
   * Get the solution of the math CAPTCHA from the current form in the browser.
   */
  protected function getMathCaptchaSolutionFromForm() {
    // Get the math challenge.
    $elements = $this->xpath('//div[@class="form-item form-type-textfield form-item-captcha-response"]/span[@class="field-prefix"]');
    $challenge = (string) $elements[0];
    // Extract terms and operator from challenge.
    $matches = array();
    $ret = preg_match('/\\s*(\\d+)\\s*(-|\\+)\\s*(\\d+)\\s*=\\s*/', $challenge, $matches);
    // Solve the challenge
    $a = (int) $matches[1];
    $b = (int) $matches[3];
    $solution = $matches[2] == '-' ? $a - $b : $a + $b;
    return $solution;
  }

  /**
   * Helper function to allow comment posting for anonymous users.
   */
  protected function allowCommentPostingForAnonymousVisitors() {
    // Log in as admin.
    $this->drupalLogin($this->admin_user);
    // Post user permissions form
    $edit = array(
      '1[access comments]' => true,
      '1[post comments]' => true,
      '1[skip comment approval]' => true,
    );
    $this->drupalPost('admin/people/permissions', $edit, 'Save permissions');
    $this->assertText('The changes have been saved.');
    // Log admin out
    $this->drupalLogout();
  }

}



class CaptchaTestCase extends CaptchaBaseWebTestCase {

  public static function getInfo() {
    return array(
      'name' => t('General CAPTCHA functionality'),
      'description' => t('Testing of the basic CAPTCHA functionality.'),
      'group' => t('CAPTCHA'),
    );
  }

  /**
   * Testing the protection of the user log in form.
   */
  function testCaptchaOnLoginForm() {
    // Create user and test log in without CAPTCHA.
    $user = $this->drupalCreateUser();
    $this->drupalLogin($user);
    // Log out again.
    $this->drupalLogout();

    // Set a CAPTCHA on login form
    captcha_set_form_id_setting('user_login', 'captcha/Math');

    // Check if there is a CAPTCHA on the login form (look for the title).
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);

    // Try to log in, which should fail.
    $edit = array(
      'name' => $user->name,
      'pass' => $user->pass_raw,
      'captcha_response' => '?',
    );
    $this->drupalPost('user', $edit, t('Log in'));
    // Check for error message.
    $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
      'CAPTCHA should block user login form', 'CAPTCHA');

    // And make sure that user is not logged in: check for name and password fields on ?q=user
    $this->drupalGet('user');
    $this->assertField('name', t('Username field found.'), 'CAPTCHA');
    $this->assertField('pass', t('Password field found.'), 'CAPTCHA');

  }


  /**
   * Assert function for testing if comment posting works as it should.
   *
   * Creates node with comment writing enabled, tries to post comment
   * with given CAPTCHA response (caller should enable the desired
   * challenge on page node comment forms) and checks if the result is as expected.
   *
   * @param $captcha_response the response on the CAPTCHA
   * @param $should_pass boolean describing if the posting should pass or should be blocked
   * @param $message message to prefix to nested asserts
   */
  protected function assertCommentPosting($captcha_response, $should_pass, $message) {
    // Make sure comments on pages can be saved directely without preview.
    variable_set('comment_preview_page', DRUPAL_OPTIONAL);

    // Create a node with comments enabled.
    $node = $this->createNodeWithCommentsEnabled();

    // Post comment on node.
    $edit = $this->getCommentFormValues();
    $comment_subject = $edit['subject'];
    $comment_body = $edit['comment_body[' . LANGUAGE_NONE . '][0][value]'];
    $edit['captcha_response'] = $captcha_response;
    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Save'));

    if ($should_pass) {
      // There should be no error message.
      $this->assertCaptchaResponseAccepted();
      // Get node page and check that comment shows up.
      $this->drupalGet('node/' . $node->nid);
      $this->assertText($comment_subject, $message .' Comment should show up on node page.', 'CAPTCHA');
      $this->assertText($comment_body, $message . ' Comment should show up on node page.', 'CAPTCHA');
    }
    else {
      // Check for error message.
      $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE), $message .' Comment submission should be blocked.', 'CAPTCHA');
      // Get node page and check that comment is not present.
      $this->drupalGet('node/' . $node->nid);
      $this->assertNoText($comment_subject, $message .' Comment should not show up on node page.', 'CAPTCHA');
      $this->assertNoText($comment_body, $message . ' Comment should not show up on node page.', 'CAPTCHA');
    }
  }

  /*
   * Testing the case sensistive/insensitive validation.
   */
  function testCaseInsensitiveValidation() {
    // Set Test CAPTCHA on comment form
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');

    // Log in as normal user.
    $this->drupalLogin($this->normal_user);

    // Test case sensitive posting.
    variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_SENSITIVE);
    $this->assertCommentPosting('Test 123', TRUE, 'Case sensitive validation of right casing.');
    $this->assertCommentPosting('test 123', FALSE, 'Case sensitive validation of wrong casing.');
    $this->assertCommentPosting('TEST 123', FALSE, 'Case sensitive validation of wrong casing.');

    // Test case insensitive posting (the default)
    variable_set('captcha_default_validation', CAPTCHA_DEFAULT_VALIDATION_CASE_INSENSITIVE);
    $this->assertCommentPosting('Test 123', TRUE, 'Case insensitive validation of right casing.');
    $this->assertCommentPosting('test 123', TRUE, 'Case insensitive validation of wrong casing.');
    $this->assertCommentPosting('TEST 123', TRUE, 'Case insensitive validation of wrong casing.');

  }

  /**
   * Test if the CAPTCHA description is only shown if there are challenge widgets to show.
   * For example, when a comment is previewed with correct CAPTCHA answer,
   * a challenge is generated and added to the form but removed in the pre_render phase.
   * The CAPTCHA description should not show up either.
   *
   * \see testCaptchaSessionReuseOnNodeForms()
   */
  function testCaptchaDescriptionAfterCommentPreview() {
    // Set Test CAPTCHA on comment form.
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');

    // Log in as normal user.
    $this->drupalLogin($this->normal_user);

    // Create a node with comments enabled.
    $node = $this->createNodeWithCommentsEnabled();

    // Preview comment with correct CAPTCHA answer.
    $edit = $this->getCommentFormValues();
    $edit['captcha_response'] = 'Test 123';
    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));

    // Check that there is no CAPTCHA after preview.
    $this->assertCaptchaPresence(FALSE);
  }

  /**
   * Test if the CAPTCHA session ID is reused when previewing nodes:
   * node preview after correct response should not show CAPTCHA anymore.
   * The preview functionality of comments and nodes works slightly different under the hood.
   * CAPTCHA module should be able to handle both.
   *
   * \see testCaptchaDescriptionAfterCommentPreview()
   */
  function testCaptchaSessionReuseOnNodeForms() {
    // Set Test CAPTCHA on page form.
    captcha_set_form_id_setting('page_node_form', 'captcha/Test');

    // Log in as normal user.
    $this->drupalLogin($this->normal_user);

    // Page settings to post, with correct CAPTCHA answer.
    $edit = $this->getNodeFormValues();
    $edit['captcha_response'] = 'Test 123';
    // Preview the node
    $this->drupalPost('node/add/page', $edit, t('Preview'));

    // Check that there is no CAPTCHA after preview.
    $this->assertCaptchaPresence(FALSE);
  }


  /**
   * CAPTCHA should also be put on admin pages even if visitor
   * has no access
   */
  function testCaptchaOnLoginBlockOnAdminPagesIssue893810() {
    // Set a CAPTCHA on login block form
    captcha_set_form_id_setting('user_login_block', 'captcha/Math');

    // Check if there is a CAPTCHA on home page.
    $this->drupalGet('node');
    $this->assertCaptchaPresence(TRUE);

    // Check there is a CAPTCHA on "forbidden" admin pages
    $this->drupalGet('admin');
    $this->assertCaptchaPresence(TRUE);
  }

}


class CaptchaAdminTestCase extends CaptchaBaseWebTestCase {

  public static function getInfo() {
    return array(
      'name' => t('CAPTCHA administration functionality'),
      'description' => t('Testing of the CAPTCHA administration interface and functionality.'),
      'group' => t('CAPTCHA'),
    );
  }

  /**
   * Test access to the admin pages.
   */
  function testAdminAccess() {
    $this->drupalLogin($this->normal_user);
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
    file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
    $this->assertText(t('Access denied'), 'Normal users should not be able to access the CAPTCHA admin pages', 'CAPTCHA');

    $this->drupalLogin($this->admin_user);
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH);
    $this->assertNoText(t('Access denied'), 'Admin users should be able to access the CAPTCHA admin pages', 'CAPTCHA');
  }

  /**
   * Test the CAPTCHA point setting getter/setter.
   */
  function testCaptchaPointSettingGetterAndSetter() {
    $comment_form_id = self::COMMENT_FORM_ID;
    // Set to 'none'.
    captcha_set_form_id_setting($comment_form_id, 'none');
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
    $this->assertNull($result->module, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
    $this->assertNull($result->captcha_type, 'Setting and getting CAPTCHA point: none', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'none', 'Setting and symbolic getting CAPTCHA point: "none"', 'CAPTCHA');
    // Set to 'default'
    captcha_set_form_id_setting($comment_form_id, 'default');
    variable_set('captcha_default_challenge', 'foo/bar');
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
    $this->assertEqual($result->module, 'foo', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
    $this->assertEqual($result->captcha_type, 'bar', 'Setting and getting CAPTCHA point: default', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'default', 'Setting and symbolic getting CAPTCHA point: "default"', 'CAPTCHA');
    // Set to 'baz/boo'.
    captcha_set_form_id_setting($comment_form_id, 'baz/boo');
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
    $this->assertEqual($result->module, 'baz', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
    $this->assertEqual($result->captcha_type, 'boo', 'Setting and getting CAPTCHA point: baz/boo', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'baz/boo', 'Setting and symbolic getting CAPTCHA point: "baz/boo"', 'CAPTCHA');
    // Set to NULL (which should delete the CAPTCHA point setting entry).
    captcha_set_form_id_setting($comment_form_id, NULL);
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNull($result, 'Setting and getting CAPTCHA point: NULL', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertNull($result, 'Setting and symbolic getting CAPTCHA point: NULL', 'CAPTCHA');
    // Set with object.
    $captcha_type = new stdClass;
    $captcha_type->module = 'baba';
    $captcha_type->captcha_type = 'fofo';
    captcha_set_form_id_setting($comment_form_id, $captcha_type);
    $result = captcha_get_form_id_setting($comment_form_id);
    $this->assertNotNull($result, 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
    $this->assertEqual($result->module, 'baba', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
    $this->assertEqual($result->captcha_type, 'fofo', 'Setting and getting CAPTCHA point: baba/fofo', 'CAPTCHA');
    $result = captcha_get_form_id_setting($comment_form_id, TRUE);
    $this->assertEqual($result, 'baba/fofo', 'Setting and symbolic getting CAPTCHA point: "baba/fofo"', 'CAPTCHA');

  }


  /**
   * Helper function for checking CAPTCHA setting of a form.
   *
   * @param $form_id the form_id of the form to investigate.
   * @param $challenge_type what the challenge type should be:
   *   NULL, 'none', 'default' or something like 'captcha/Math'
   */
  protected function assertCaptchaSetting($form_id, $challenge_type) {
    $result = captcha_get_form_id_setting(self::COMMENT_FORM_ID, TRUE);
    $this->assertEqual($result, $challenge_type,
      t('Check CAPTCHA setting for form: expected: @expected, received: @received.',
      array('@expected' => var_export($challenge_type, TRUE), '@received' => var_export($result, TRUE))),
      'CAPTCHA');
  }

  /**
   * Testing of the CAPTCHA administration links.
   */
  function testCaptchAdminLinks() {
    // Log in as admin
    $this->drupalLogin($this->admin_user);

    // Enable CAPTCHA administration links.
    $edit = array(
      'captcha_administration_mode' => TRUE,
    );
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');

    // Create a node with comments enabled.
    $node = $this->createNodeWithCommentsEnabled();

    // Go to node page
    $this->drupalGet('node/' . $node->nid);

    // Click the add new comment link
    $this->clickLink(t('Add new comment'));
    $add_comment_url = $this->getUrl();
    // Remove fragment part from comment URL to avoid problems with later asserts
    $add_comment_url = strtok($add_comment_url, "#");

    ////////////////////////////////////////////////////////////
    // Click the CAPTCHA admin link to enable a challenge.
    $this->clickLink(t('Place a CAPTCHA here for untrusted users.'));
    // Enable Math CAPTCHA.
    $edit = array('captcha_type' => 'captcha/Math');
    $this->drupalPost($this->getUrl(), $edit, t('Save'));

    // Check if returned to original comment form.
    $this->assertUrl($add_comment_url, array(),
      'After setting CAPTCHA with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
    // Check if CAPTCHA was successfully enabled (on CAPTCHA admin links fieldset).
    $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
      'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
    // Check if CAPTCHA was successfully enabled (through API).
    $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'captcha/Math');

    //////////////////////////////////////////////////////
    // Edit challenge type through CAPTCHA admin links.
    $this->clickLink(t('change'));
    // Enable Math CAPTCHA.
    $edit = array('captcha_type' => 'default');
    $this->drupalPost($this->getUrl(), $edit, t('Save'));

    // Check if returned to original comment form.
    $this->assertEqual($add_comment_url, $this->getUrl(),
      'After editing challenge type CAPTCHA admin links: should return to original form.', 'CAPTCHA');
    // Check if CAPTCHA was successfully changed (on CAPTCHA admin links fieldset).
    // This is actually the same as the previous setting because the captcha/Math is the
    // default for the default challenge. TODO Make sure the edit is a real change.
    $this->assertText(t('CAPTCHA: challenge "@type" enabled', array('@type' => 'Math')),
      'Enable a challenge through the CAPTCHA admin links', 'CAPTCHA');
    // Check if CAPTCHA was successfully edited (through API).
    $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'default');



    //////////////////////////////////////////////////////
    // Disable challenge through CAPTCHA admin links.
    $this->clickLink(t('disable'));
    // And confirm.
    $this->drupalPost($this->getUrl(), array(), 'Disable');

    // Check if returned to original comment form.
    $this->assertEqual($add_comment_url, $this->getUrl(),
      'After disablin challenge with CAPTCHA admin links: should return to original form.', 'CAPTCHA');
    // Check if CAPTCHA was successfully disabled (on CAPTCHA admin links fieldset).
    $this->assertText(t('CAPTCHA: no challenge enabled'),
      'Disable challenge through the CAPTCHA admin links', 'CAPTCHA');
    // Check if CAPTCHA was successfully disabled (through API).
    $this->assertCaptchaSetting(self::COMMENT_FORM_ID, 'none');

  }


  function testUntrustedUserPosting() {
    // Set CAPTCHA on comment form.
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');

    // Create a node with comments enabled.
    $node = $this->createNodeWithCommentsEnabled();

    // Log in as normal (untrusted) user.
    $this->drupalLogin($this->normal_user);

    // Go to node page and click the "add comment" link.
    $this->drupalGet('node/' . $node->nid);
    $this->clickLink(t('Add new comment'));
    $add_comment_url = $this->getUrl();

    // Check if CAPTCHA is visible on form.
    $this->assertCaptchaPresence(TRUE);
    // Try to post a comment with wrong answer.
    $edit = $this->getCommentFormValues();
    $edit['captcha_response'] = 'xx';
    $this->drupalPost($add_comment_url, $edit, t('Preview'));
    $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
      'wrong CAPTCHA should block form submission.', 'CAPTCHA');

    //TODO: more testing for untrusted posts.
  }



  /**
   * Test XSS vulnerability on CAPTCHA description.
   */
  function testXssOnCaptchaDescription() {
    // Set CAPTCHA on user register form.
    captcha_set_form_id_setting('user_register', 'captcha/Math');

    // Put Javascript snippet in CAPTCHA description.
    $this->drupalLogin($this->admin_user);
    $xss = '<script type="text/javascript">alert("xss")</script>';
    $edit = array('captcha_description' => $xss);
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');

    // Visit user register form and check if Javascript snippet is there.
    $this->drupalLogout();
    $this->drupalGet('user/register');
    $this->assertNoRaw($xss, 'Javascript should not be allowed in CAPTCHA description.', 'CAPTCHA');

  }

  /**
   * Test the CAPTCHA placement clearing.
   */
  function testCaptchaPlacementCacheClearing() {
    // Set CAPTCHA on user register form.
    captcha_set_form_id_setting('user_register_form', 'captcha/Math');
    // Visit user register form to fill the CAPTCHA placement cache.
    $this->drupalGet('user/register');
    // Check if there is CAPTCHA placement cache.
    $placement_map = variable_get('captcha_placement_map_cache', NULL);
    $this->assertNotNull($placement_map, 'CAPTCHA placement cache should be set.');
    // Clear the cache
    $this->drupalLogin($this->admin_user);
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH, array(), t('Clear the CAPTCHA placement cache'));
    // Check that the placement cache is unset
    $placement_map = variable_get('captcha_placement_map_cache', NULL);
    $this->assertNull($placement_map, 'CAPTCHA placement cache should be unset after cache clear.');
  }

  /**
   * Helper function to get the CAPTCHA point setting straight from the database.
   * @param string $form_id
   * @return stdClass object
   */
  private function getCaptchaPointSettingFromDatabase($form_id) {
    $result = db_query(
      "SELECT * FROM {captcha_points} WHERE form_id = :form_id",
      array(':form_id' => $form_id)
    )->fetchObject();
    return $result;
  }

  /**
   * Method for testing the CAPTCHA point administration
   */
  function testCaptchaPointAdministration() {
    // Generate CAPTCHA point data:
    // Drupal form ID should consist of lowercase alphanumerics and underscore)
    $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
    // the Math CAPTCHA by the CAPTCHA module is always available, so let's use it
    $captcha_point_module = 'captcha';
    $captcha_point_type = 'Math';

    // Log in as admin
    $this->drupalLogin($this->admin_user);

    // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point
    $form_values = array(
      'captcha_point_form_id' => $captcha_point_form_id,
      'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
    );
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point', $form_values, t('Save'));
    $this->assertText(t('Saved CAPTCHA point settings.'),
      'Saving of CAPTCHA point settings');

    // Check in database
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->module, $captcha_point_module,
      'Enabled CAPTCHA point should have module set');
    $this->assertEqual($result->captcha_type, $captcha_point_type,
      'Enabled CAPTCHA point should have type set');

    // Disable CAPTCHA point again
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable', array(), t('Disable'));
    $this->assertRaw(t('Disabled CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)), 'Disabling of CAPTCHA point');

    // Check in database
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertNull($result->module,
      'Disabled CAPTCHA point should have NULL as module');
    $this->assertNull($result->captcha_type,
      'Disabled CAPTCHA point should have NULL as type');

    // Set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
    $form_values = array(
      'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
    );
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id, $form_values, t('Save'));
    $this->assertText(t('Saved CAPTCHA point settings.'),
      'Saving of CAPTCHA point settings');

    // Check in database
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->module, $captcha_point_module,
      'Enabled CAPTCHA point should have module set');
    $this->assertEqual($result->captcha_type, $captcha_point_type,
      'Enabled CAPTCHA point should have type set');

    // Delete CAPTCHA point
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
    $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
      'Deleting of CAPTCHA point');

    // Check in database
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertFalse($result, 'Deleted CAPTCHA point should be in database');
  }

  /**
   * Method for testing the CAPTCHA point administration
   */
  function testCaptchaPointAdministrationByNonAdmin() {
    // First add a CAPTCHA point (as admin)
    $this->drupalLogin($this->admin_user);
    $captcha_point_form_id = 'form_' . strtolower($this->randomName(32));
    $captcha_point_module = 'captcha';
    $captcha_point_type = 'Math';
    $form_values = array(
      'captcha_point_form_id' => $captcha_point_form_id,
      'captcha_type' => $captcha_point_module .'/'. $captcha_point_type,
    );
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/', $form_values, t('Save'));
    $this->assertText(t('Saved CAPTCHA point settings.'),
      'Saving of CAPTCHA point settings');

    // Switch from admin to nonadmin
    $this->drupalGet(url('logout', array('absolute' => TRUE)));
    $this->drupalLogin($this->normal_user);


    // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to set a CAPTCHA point');

    // Try to set CAPTCHA point through admin/user/captcha/captcha/captcha_point/$form_id
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/' . 'form_' . strtolower($this->randomName(32)));
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to set a CAPTCHA point');

    // Try to disable the CAPTCHA point
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/disable');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to disable a CAPTCHA point');

    // Try to delete the CAPTCHA point
    $this->drupalGet(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete');
    $this->assertText(t('You are not authorized to access this page.'),
      'Non admin should not be able to delete a CAPTCHA point');

    // Switch from nonadmin to admin again
    $this->drupalGet(url('logout', array('absolute' => TRUE)));
    $this->drupalLogin($this->admin_user);

    // Check if original CAPTCHA point still exists in database
    $result = $this->getCaptchaPointSettingFromDatabase($captcha_point_form_id);
    $this->assertEqual($result->module, $captcha_point_module,
      'Enabled CAPTCHA point should still have module set');
    $this->assertEqual($result->captcha_type, $captcha_point_type,
      'Enabled CAPTCHA point should still have type set');

    // Delete CAPTCHA point
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH . '/captcha/captcha_point/'. $captcha_point_form_id .'/delete', array(), t('Delete'));
    $this->assertRaw(t('Deleted CAPTCHA for form %form_id.', array('%form_id' => $captcha_point_form_id)),
      'Deleting of CAPTCHA point');
  }



}



class CaptchaPersistenceTestCase extends CaptchaBaseWebTestCase {

  public static function getInfo() {
    return array(
      'name' => t('CAPTCHA persistence functionality'),
      'description' => t('Testing of the CAPTCHA persistence functionality.'),
      'group' => t('CAPTCHA'),
    );
  }

  /**
   * Set up the persistence and CAPTCHA settings.
   * @param int $persistence the persistence value.
   */
  private function setUpPersistence($persistence) {
    // Log in as admin
    $this->drupalLogin($this->admin_user);
    // Set persistence.
    $edit = array('captcha_persistence' => $persistence);
    $this->drupalPost(self::CAPTCHA_ADMIN_PATH, $edit, 'Save configuration');
    // Log admin out.
    $this->drupalLogout();

    // Set the Test123 CAPTCHA on user register and comment form.
    // We have to do this with the function captcha_set_form_id_setting()
    // (because the CATCHA admin form does not show the Test123 option).
    // We also have to do this after all usage of the CAPTCHA admin form
    // (because posting the CAPTCHA admin form would set the CAPTCHA to 'none').
    captcha_set_form_id_setting('user_login', 'captcha/Test');
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    captcha_set_form_id_setting('user_register_form', 'captcha/Test');
    $this->drupalGet('user/register');
    $this->assertCaptchaPresence(TRUE);
  }

  protected function assertPreservedCsid($captcha_sid_initial) {
    $captcha_sid = $this->getCaptchaSidFromForm();
    $this->assertEqual($captcha_sid_initial, $captcha_sid,
      "CAPTCHA session ID should be preserved (expected: $captcha_sid_initial, found: $captcha_sid).");
  }

  protected function assertDifferentCsid($captcha_sid_initial) {
    $captcha_sid = $this->getCaptchaSidFromForm();
    $this->assertNotEqual($captcha_sid_initial, $captcha_sid,
      "CAPTCHA session ID should be different.");
  }

  function testPersistenceAlways(){
    // Set up of persistence and CAPTCHAs.
    $this->setUpPersistence(CAPTCHA_PERSISTENCE_SHOW_ALWAYS);

    // Go to login form and check if there is a CAPTCHA on the login form (look for the title).
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    $captcha_sid_initial = $this->getCaptchaSidFromForm();

    // Try to with wrong user name and password, but correct CAPTCHA.
    $edit = array(
      'name' => 'foobar',
      'pass' => 'bazlaz',
      'captcha_response' => 'Test 123',
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    // Check that there was no error message for the CAPTCHA.
    $this->assertCaptchaResponseAccepted();

    // Name and password were wrong, we should get an updated form with a fresh CAPTCHA.
    $this->assertCaptchaPresence(TRUE);
    $this->assertPreservedCsid($captcha_sid_initial);

    // Post from again.
    $this->drupalPost(NULL, $edit, t('Log in'));
    // Check that there was no error message for the CAPTCHA.
    $this->assertCaptchaResponseAccepted();
    $this->assertPreservedCsid($captcha_sid_initial);

  }

  function testPersistencePerFormInstance(){
    // Set up of persistence and CAPTCHAs.
    $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);

    // Go to login form and check if there is a CAPTCHA on the login form.
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    $captcha_sid_initial = $this->getCaptchaSidFromForm();

    // Try to with wrong user name and password, but correct CAPTCHA.
    $edit = array(
      'name' => 'foobar',
      'pass' => 'bazlaz',
      'captcha_response' => 'Test 123',
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    // Check that there was no error message for the CAPTCHA.
    $this->assertCaptchaResponseAccepted();
    // There shouldn't be a CAPTCHA on the new form.
    $this->assertCaptchaPresence(FALSE);
    $this->assertPreservedCsid($captcha_sid_initial);

    // Start a new form instance/session
    $this->drupalGet('node');
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    $this->assertDifferentCsid($captcha_sid_initial);

    // Check another form
    $this->drupalGet('user/register');
    $this->assertCaptchaPresence(TRUE);
    $this->assertDifferentCsid($captcha_sid_initial);

  }

  function testPersistencePerFormType(){
    // Set up of persistence and CAPTCHAs.
    $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_TYPE);

    // Go to login form and check if there is a CAPTCHA on the login form.
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    $captcha_sid_initial = $this->getCaptchaSidFromForm();

    // Try to with wrong user name and password, but correct CAPTCHA.
    $edit = array(
      'name' => 'foobar',
      'pass' => 'bazlaz',
      'captcha_response' => 'Test 123',
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    // Check that there was no error message for the CAPTCHA.
    $this->assertCaptchaResponseAccepted();
    // There shouldn't be a CAPTCHA on the new form.
    $this->assertCaptchaPresence(FALSE);
    $this->assertPreservedCsid($captcha_sid_initial);

    // Start a new form instance/session
    $this->drupalGet('node');
    $this->drupalGet('user');
    $this->assertCaptchaPresence(FALSE);
    $this->assertDifferentCsid($captcha_sid_initial);

    // Check another form
    $this->drupalGet('user/register');
    $this->assertCaptchaPresence(TRUE);
    $this->assertDifferentCsid($captcha_sid_initial);
  }

  function testPersistenceOnlyOnce(){
    // Set up of persistence and CAPTCHAs.
    $this->setUpPersistence(CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL);

    // Go to login form and check if there is a CAPTCHA on the login form.
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);
    $captcha_sid_initial = $this->getCaptchaSidFromForm();

    // Try to with wrong user name and password, but correct CAPTCHA.
    $edit = array(
      'name' => 'foobar',
      'pass' => 'bazlaz',
      'captcha_response' => 'Test 123',
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    // Check that there was no error message for the CAPTCHA.
    $this->assertCaptchaResponseAccepted();
    // There shouldn't be a CAPTCHA on the new form.
    $this->assertCaptchaPresence(FALSE);
    $this->assertPreservedCsid($captcha_sid_initial);

    // Start a new form instance/session
    $this->drupalGet('node');
    $this->drupalGet('user');
    $this->assertCaptchaPresence(FALSE);
    $this->assertDifferentCsid($captcha_sid_initial);

    // Check another form
    $this->drupalGet('user/register');
    $this->assertCaptchaPresence(FALSE);
    $this->assertDifferentCsid($captcha_sid_initial);
  }

}


class CaptchaSessionReuseAttackTestCase extends CaptchaBaseWebTestCase {

  public static function getInfo() {
    return array(
      'name' => t('CAPTCHA session reuse attack tests'),
      'description' => t('Testing of the protection against CAPTCHA session reuse attacks.'),
      'group' => t('CAPTCHA'),
    );
  }

  /**
   * Assert that the CAPTCHA session ID reuse attack was detected.
   */
  protected function assertCaptchaSessionIdReuseAttackDetection() {
    $this->assertText(t(CAPTCHA_SESSION_REUSE_ATTACK_ERROR_MESSAGE),
      'CAPTCHA session ID reuse attack should be detected.',
      'CAPTCHA');
    // There should be an error message about wrong response.
    $this->assertText(t(CAPTCHA_WRONG_RESPONSE_ERROR_MESSAGE),
      'CAPTCHA response should flagged as wrong.',
      'CAPTCHA');
  }

  function testCaptchaSessionReuseAttackDetectionOnCommentPreview() {
    // Create commentable node
    $node = $this->createNodeWithCommentsEnabled();
    // Set Test CAPTCHA on comment form.
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Math');
    variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);

    // Log in as normal user.
    $this->drupalLogin($this->normal_user);

    // Go to comment form of commentable node.
    $this->drupalGet('comment/reply/' . $node->nid);
    $this->assertCaptchaPresence(TRUE);

    // Get CAPTCHA session ID and solution of the challenge.
    $captcha_sid = $this->getCaptchaSidFromForm();
    $captcha_token = $this->getCaptchaTokenFromForm();
    $solution = $this->getMathCaptchaSolutionFromForm();

    // Post the form with the solution.
    $edit = $this->getCommentFormValues();
    $edit['captcha_response'] = $solution;
    $this->drupalPost(NULL, $edit, t('Preview'));
    // Answer should be accepted and further CAPTCHA ommitted.
    $this->assertCaptchaResponseAccepted();
    $this->assertCaptchaPresence(FALSE);

    // Post a new comment, reusing the previous CAPTCHA session.
    $edit = $this->getCommentFormValues();
    $edit['captcha_sid'] = $captcha_sid;
    $edit['captcha_token'] = $captcha_token;
    $edit['captcha_response'] = $solution;
    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
    // CAPTCHA session reuse attack should be detected.
    $this->assertCaptchaSessionIdReuseAttackDetection();
    // There should be a CAPTCHA.
    $this->assertCaptchaPresence(TRUE);

  }

  function testCaptchaSessionReuseAttackDetectionOnNodeForm() {
    // Set CAPTCHA on page form.
    captcha_set_form_id_setting('page_node_form', 'captcha/Math');
    variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);

    // Log in as normal user.
    $this->drupalLogin($this->normal_user);

    // Go to node add form.
    $this->drupalGet('node/add/page');
    $this->assertCaptchaPresence(TRUE);

    // Get CAPTCHA session ID and solution of the challenge.
    $captcha_sid = $this->getCaptchaSidFromForm();
    $captcha_token = $this->getCaptchaTokenFromForm();
    $solution = $this->getMathCaptchaSolutionFromForm();

    // Page settings to post, with correct CAPTCHA answer.
    $edit = $this->getNodeFormValues();
    $edit['captcha_response'] = $solution;
    // Preview the node
    $this->drupalPost(NULL, $edit, t('Preview'));
    // Answer should be accepted.
    $this->assertCaptchaResponseAccepted();
    // Check that there is no CAPTCHA after preview.
    $this->assertCaptchaPresence(FALSE);

    // Post a new comment, reusing the previous CAPTCHA session.
    $edit = $this->getNodeFormValues();
    $edit['captcha_sid'] = $captcha_sid;
    $edit['captcha_token'] = $captcha_token;
    $edit['captcha_response'] = $solution;
    $this->drupalPost('node/add/page', $edit, t('Preview'));
    // CAPTCHA session reuse attack should be detected.
    $this->assertCaptchaSessionIdReuseAttackDetection();
    // There should be a CAPTCHA.
    $this->assertCaptchaPresence(TRUE);

  }

  function testCaptchaSessionReuseAttackDetectionOnLoginForm() {
    // Set CAPTCHA on login form.
    captcha_set_form_id_setting('user_login', 'captcha/Math');
    variable_set('captcha_persistence', CAPTCHA_PERSISTENCE_SKIP_ONCE_SUCCESSFUL_PER_FORM_INSTANCE);

    // Go to log in form.
    $this->drupalGet('user');
    $this->assertCaptchaPresence(TRUE);

    // Get CAPTCHA session ID and solution of the challenge.
    $captcha_sid = $this->getCaptchaSidFromForm();
    $captcha_token = $this->getCaptchaTokenFromForm();
    $solution = $this->getMathCaptchaSolutionFromForm();

    // Log in through form.
    $edit = array(
      'name' => $this->normal_user->name,
      'pass' => $this->normal_user->pass_raw,
      'captcha_response' => $solution,
    );
    $this->drupalPost(NULL, $edit, t('Log in'));
    $this->assertCaptchaResponseAccepted();
    $this->assertCaptchaPresence(FALSE);
    // If a "log out" link appears on the page, it is almost certainly because
    // the login was successful.
    $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $this->normal_user->name)), t('User login'));

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

    // Try to log in again, reusing the previous CAPTCHA session.
    $edit += array(
      'captcha_sid' => $captcha_sid,
      'captcha_token' => $captcha_token,
    );
    $this->drupalPost('user', $edit, t('Log in'));
    // CAPTCHA session reuse attack should be detected.
    $this->assertCaptchaSessionIdReuseAttackDetection();
    // There should be a CAPTCHA.
    $this->assertCaptchaPresence(TRUE);
  }


  public function testMultipleCaptchaProtectedFormsOnOnePage()
  {
    // Set Test CAPTCHA on comment form and login block
    captcha_set_form_id_setting(self::COMMENT_FORM_ID, 'captcha/Test');
    captcha_set_form_id_setting('user_login_block', 'captcha/Math');
    $this->allowCommentPostingForAnonymousVisitors();

    // Create a node with comments enabled.
    $node = $this->createNodeWithCommentsEnabled();

    // Preview comment with correct CAPTCHA answer.
    $edit = $this->getCommentFormValues();
    $comment_subject = $edit['subject'];
    $edit['captcha_response'] = 'Test 123';
    $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
    // Post should be accepted: no warnings,
    // no CAPTCHA reuse detection (which could be used by user log in block).
    $this->assertCaptchaResponseAccepted();
    $this->assertText($comment_subject);

  }

}


// Some tricks to debug:
// drupal_debug($data) // from devel module
// file_put_contents('tmp.simpletest.html', $this->drupalGetContent());