diff --git a/captcha.admin.inc b/captcha.admin.inc
index 0d095ae..66c1457 100644
--- a/captcha.admin.inc
+++ b/captcha.admin.inc
@@ -45,7 +45,7 @@ function captcha_admin_settings() {
 
   // Configuration of which forms to protect, with what challenge.
   $form['captcha_form_protection'] = array(
-    '#type' => 'fieldset',
+    '#type' => 'details',
     '#title' => t('Form protection'),
     '#description' => t("Select the challenge type you want for each of the listed forms (identified by their so called <em>form_id</em>'s). You can easily add arbitrary forms with the textfield at the bottom of the table or with the help of the option <em>Add CAPTCHA administration links to forms</em> below."),
   );
@@ -145,7 +145,7 @@ function captcha_admin_settings() {
   if (module_exists('locale')) {
     $langs = locale_language_list();
     $form['captcha_descriptions'] = array(
-      '#type' => 'fieldset',
+      '#type' => 'details',
       '#title' => t('CAPTCHA description'),
       '#description' => t('Configurable description of the CAPTCHA. An empty entry will reset the description to default.'),
       '#attributes' => array('id' => 'edit-captcha-description-wrapper'),
@@ -501,7 +501,7 @@ function captcha_examples($form, $form_state, $module, $challenge) {
       if ($challenges) {
         foreach ($challenges as $ckey => $challenge) {
           $form["captcha_{$mkey}_{$ckey}"] = array(
-            '#type' => 'fieldset',
+            '#type' => 'details',
             '#title' => t('Challenge %challenge by module %module', array('%challenge' => $challenge, '%module' => $module)),
             'challenge' => _captcha_generate_example_challenge($module, $challenge),
             'more_examples' => array(
diff --git a/captcha.inc b/captcha.inc
index 6af73a6..cac1df1 100644
--- a/captcha.inc
+++ b/captcha.inc
@@ -111,7 +111,7 @@ function _captcha_generate_captcha_session($form_id=NULL, $status=CAPTCHA_STATUS
   ->fields(array(
     'uid' => $user->uid,
     'sid' => session_id(),
-    'ip_address' => ip_address(),
+    'ip_address' => Drupal::request()->getClientIp(),
     'timestamp' => REQUEST_TIME,
     'form_id' => $form_id,
     'solution' => $solution,
@@ -191,8 +191,8 @@ function _captcha_required_for_user($captcha_sid, $form_id) {
  */
 function _captcha_get_description($lang_code=NULL) {
   // If no language code is given: use the language of the current page.
-  global $language;
-  $lang_code = isset($lang_code) ? $lang_code : $language->language;
+  $language = Drupal::languageManager()->getLanguage();
+  $lang_code = isset($lang_code) ? $lang_code : $language->langcode;
   // The hardcoded but localizable default.
   $default = t('This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.', array(), array('langcode' => $lang_code));
   // Look up the configured CAPTCHA description or fall back on the (localized) default.
diff --git a/captcha.info b/captcha.info
deleted file mode 100644
index 888094b..0000000
--- a/captcha.info
+++ /dev/null
@@ -1,11 +0,0 @@
-name = CAPTCHA
-description = Base CAPTCHA module for adding challenges to arbitrary forms.
-package = "Spam control"
-core = 7.x
-configure = admin/config/people/captcha
-
-files[] = captcha.module
-files[] = captcha.inc
-files[] = captcha.admin.inc
-files[] = captcha.install
-files[] = captcha.test
diff --git a/captcha.info.yml b/captcha.info.yml
new file mode 100644
index 0000000..6f32d91
--- /dev/null
+++ b/captcha.info.yml
@@ -0,0 +1,7 @@
+name: CAPTCHA
+type: module
+description: Base CAPTCHA module for adding challenges to arbitrary forms.
+package: Spam control
+version: 8.x
+core: 8.x
+configure: admin/config/people/captcha
\ No newline at end of file
diff --git a/captcha.install b/captcha.install
index f53f600..b3f68f7 100644
--- a/captcha.install
+++ b/captcha.install
@@ -113,11 +113,10 @@ function captcha_schema() {
  */
 function captcha_requirements($phase) {
   $requirements = array();
-  $t = get_t();
   if ($phase == 'runtime' && variable_get('captcha_enable_stats', FALSE)) {
     // Show the wrong response counter in the status report.
     $requirements['captcha_wrong_response_counter'] = array(
-      'title' => $t('CAPTCHA'),
+      'title' => t('CAPTCHA'),
       'value' => format_plural(
         variable_get('captcha_wrong_response_counter', 0),
         'Already 1 blocked form submission',
@@ -133,7 +132,6 @@ function captcha_requirements($phase) {
  * Implementation of hook_install().
  */
 function captcha_install() {
-  $t = get_t();
   // Insert some default CAPTCHA points.
   $form_ids = array(
     'contact_site_form', 'contact_personal_form',
@@ -155,7 +153,7 @@ function captcha_install() {
   }
 
   // Be friendly to your users: what to do after install?
-  drupal_set_message($t('You can now <a href="!captcha_admin">configure the CAPTCHA module</a> for your site.',
+  drupal_set_message(t('You can now <a href="!captcha_admin">configure the CAPTCHA module</a> for your site.',
     array('!captcha_admin' => url('admin/config/people/captcha'))), 'status');
 
   // Explain to users that page caching may be disabled.
diff --git a/captcha.module b/captcha.module
index 7d7c53c..3ea98d4 100644
--- a/captcha.module
+++ b/captcha.module
@@ -417,7 +417,7 @@ function captcha_form_alter(&$form, &$form_state, $form_id) {
 
   // Add a warning about caching on the Perfomance settings page.
   if ($form_id == 'system_performance_settings') {
-    $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'width' => 18, 'height' => 18, 'alt' => t('warning'), 'title' => t('warning')));
+    $icon = theme('image', array('uri' => 'core/misc/watchdog-warning.png', 'width' => 18, 'height' => 18, 'alt' => t('warning'), 'title' => t('warning')));
     $form['caching']['captcha'] = array(
       '#type' => 'item',
       '#title' => t('CAPTCHA'),
diff --git a/captcha.test b/captcha.test
deleted file mode 100644
index bccefe3..0000000
--- a/captcha.test
+++ /dev/null
@@ -1,1139 +0,0 @@
-<?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());
diff --git a/image_captcha/image_captcha.info b/image_captcha/image_captcha.info
deleted file mode 100644
index 0568ab8..0000000
--- a/image_captcha/image_captcha.info
+++ /dev/null
@@ -1,11 +0,0 @@
-name = "Image CAPTCHA"
-description = "Provides an image based CAPTCHA."
-package = "Spam control"
-dependencies[] = captcha
-core = 7.x
-configure = admin/config/people/captcha/image_captcha
-
-files[] = image_captcha.install
-files[] = image_captcha.module
-files[] = image_captcha.admin.inc
-files[] = image_captcha.user.inc
diff --git a/image_captcha/image_captcha.info.yml b/image_captcha/image_captcha.info.yml
new file mode 100644
index 0000000..651250a
--- /dev/null
+++ b/image_captcha/image_captcha.info.yml
@@ -0,0 +1,9 @@
+name: Image CAPTCHA
+type: module
+description: Provides an image based CAPTCHA.
+package: Spam control
+version: 8.x
+core: 8.x
+dependencies:
+- captcha
+configure: admin/config/people/captcha/image_captcha
diff --git a/lib/Drupal/captcha/Tests/CaptchaAdminTestCase.php b/lib/Drupal/captcha/Tests/CaptchaAdminTestCase.php
new file mode 100644
index 0000000..f585fed
--- /dev/null
+++ b/lib/Drupal/captcha/Tests/CaptchaAdminTestCase.php
@@ -0,0 +1,393 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\captcha\Tests\CaptchaAdminTestCase.
+ *
+ * Some tricks to debug:
+ * drupal_debug($data) // from devel module
+ * file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
+ */
+ 
+namespace Drupal\captcha\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+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');
+  }
+
+
+
+}
\ No newline at end of file
diff --git a/lib/Drupal/captcha/Tests/CaptchaBaseWebTestCase.php b/lib/Drupal/captcha/Tests/CaptchaBaseWebTestCase.php
new file mode 100644
index 0000000..8caebaf
--- /dev/null
+++ b/lib/Drupal/captcha/Tests/CaptchaBaseWebTestCase.php
@@ -0,0 +1,215 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\captcha\Tests\CaptchaBaseWebTestCase.
+ *
+ * Some tricks to debug:
+ * drupal_debug($data) // from devel module
+ * file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
+ */
+ 
+// 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
+
+namespace Drupal\captcha\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Base class for CAPTCHA tests.
+ *
+ * Provides common setup stuff and various helper functions
+ */
+abstract class CaptchaBaseWebTestCase extends DrupalWebTestCase {
+
+// 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.');
+  
+  /**
+   * 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();
+  }
+
+}
\ No newline at end of file
diff --git a/lib/Drupal/captcha/Tests/CaptchaPersistenceTestCase.php b/lib/Drupal/captcha/Tests/CaptchaPersistenceTestCase.php
new file mode 100644
index 0000000..e0e9aca
--- /dev/null
+++ b/lib/Drupal/captcha/Tests/CaptchaPersistenceTestCase.php
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\captcha\Tests\CaptchaPersistenceTestCase.
+ *
+ * Some tricks to debug:
+ * drupal_debug($data) // from devel module
+ * file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
+ */
+ 
+namespace Drupal\captcha\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+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);
+  }
+
+}
\ No newline at end of file
diff --git a/lib/Drupal/captcha/Tests/CaptchaSessionReuseAttackTestCase.php b/lib/Drupal/captcha/Tests/CaptchaSessionReuseAttackTestCase.php
new file mode 100644
index 0000000..54f97cc
--- /dev/null
+++ b/lib/Drupal/captcha/Tests/CaptchaSessionReuseAttackTestCase.php
@@ -0,0 +1,184 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\captcha\Tests\CaptchaSessionReuseAttackTestCase.
+ *
+ * Some tricks to debug:
+ * drupal_debug($data) // from devel module
+ * file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
+ */
+ 
+namespace Drupal\captcha\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+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);
+
+  }
+
+}
\ No newline at end of file
diff --git a/lib/Drupal/captcha/Tests/CaptchaTestCase.php b/lib/Drupal/captcha/Tests/CaptchaTestCase.php
new file mode 100644
index 0000000..77101d9
--- /dev/null
+++ b/lib/Drupal/captcha/Tests/CaptchaTestCase.php
@@ -0,0 +1,199 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\captcha\Tests\CaptchaTestCase.
+ *
+ * Some tricks to debug:
+ * drupal_debug($data) // from devel module
+ * file_put_contents('tmp.simpletest.html', $this->drupalGetContent());
+ */
+ 
+namespace Drupal\captcha\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+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);
+  }
+
+}
\ No newline at end of file
