diff --git a/mollom.module b/mollom.module
index 9bf12ca..7062fb6 100644
--- a/mollom.module
+++ b/mollom.module
@@ -1690,19 +1690,20 @@ function mollom_process_mollom($element, &$form_state, $complete_form) {
   if (!variable_get('mollom_testing_mode', 0)) {
     $element['captcha']['#attributes']['autocomplete'] = 'off';
   }
-  // For CAPTCHA-only protected forms, retrieve and show an initial CAPTCHA.
+  // For CAPTCHA-only protected forms:
   if (!$form_state['mollom']['require_analysis'] && $form_state['mollom']['require_captcha']) {
-    // Unless the CAPTCHA was solved, display the CAPTCHA.
-    if (!$form_state['mollom']['passed_captcha']) {
+    // Retrieve and show an initial CAPTCHA.
+    if (empty($form_state['process_input'])) {
       // Enable Form API caching, in order to track the state of the CAPTCHA.
       $form_state['cache'] = TRUE;
       // mollom_form_add_captcha() adds the CAPTCHA and disables page caching.
       mollom_form_add_captcha($element, $form_state);
     }
-    // Otherwise, resemble mollom_validate_captcha(). This case is only reached
-    // in case the form 1) is not cached, 2) fully validated, 3) was submitted,
-    // and 4) is getting rebuilt; e.g., "Preview" on comment and node forms.
-    else {
+    // If the CAPTCHA was solved in a previous submission already, resemble
+    // mollom_validate_captcha(). This case is only reached in case the form
+    // 1) is not cached, 2) fully validated, 3) was submitted, and 4) is getting
+    // rebuilt; e.g., "Preview" on comment and node forms.
+    if ($form_state['mollom']['passed_captcha']) {
       $element['captcha']['#solved'] = TRUE;
     }
   }
@@ -1854,6 +1855,7 @@ function mollom_validate_analysis(&$form, &$form_state) {
     switch ($result['spamClassification']) {
       case 'ham':
         $form_state['mollom']['require_captcha'] = FALSE;
+        $form['mollom']['captcha']['#access'] = FALSE;
         mollom_log(array(
           'message' => 'Ham: %teaser',
           'arguments' => array('%teaser' => $teaser),
@@ -1862,6 +1864,7 @@ function mollom_validate_analysis(&$form, &$form_state) {
 
       case 'spam':
         $form_state['mollom']['require_captcha'] = FALSE;
+        $form['mollom']['captcha']['#access'] = FALSE;
         if ($form_state['mollom']['discard']) {
           form_set_error('mollom', t('Your submission has triggered the spam filter and will not be accepted.') . ' ' . _mollom_format_message_falsepositive($form_state, $data));
         }
@@ -1880,6 +1883,7 @@ function mollom_validate_analysis(&$form, &$form_state) {
         }
         else {
           $form_state['mollom']['require_captcha'] = TRUE;
+          $form['mollom']['captcha']['#access'] = TRUE;
         }
         mollom_log(array(
           'message' => 'Unsure: %teaser',
@@ -1896,7 +1900,7 @@ function mollom_validate_analysis(&$form, &$form_state) {
         mollom_log(array(
           'message' => 'Unknown: %teaser',
           'arguments' => array('%teaser' => $teaser),
-        ));
+        ), WATCHDOG_ERROR);
         break;
     }
   }
@@ -2519,8 +2523,8 @@ function mollom_get_captcha(&$form_state) {
       'type' => $form_state['mollom']['captcha_type'],
       'ssl' => (int) (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'),
     );
-    if (!empty($form_state['mollom']['response']['content']['id'])) {
-      $data['contentId'] = $form_state['mollom']['response']['content']['id'];
+    if (!empty($form_state['values']['mollom']['contentId'])) {
+      $data['contentId'] = $form_state['values']['mollom']['contentId'];
     }
     $result = mollom()->createCaptcha($data);
 
@@ -2533,7 +2537,7 @@ function mollom_get_captcha(&$form_state) {
     if (is_array($result) && isset($result['url'])) {
       $url = $result['url'];
       $form_state['mollom']['response'][$key] = $url;
-      $form_state['mollom']['response']['captcha']['id'] = $result['id'];
+      $form_state['mollom']['response']['captcha'] = $result;
     }
     else {
       return '';
diff --git a/mollom.pages.inc b/mollom.pages.inc
index 4933673..87e805a 100644
--- a/mollom.pages.inc
+++ b/mollom.pages.inc
@@ -42,7 +42,7 @@ function mollom_captcha_js($type, $form_build_id, $contentId = NULL) {
   else {
     $form_state['mollom'] = array();
     if (isset($contentId)) {
-      $form_state['mollom']['response']['content']['id'] = $contentId;
+      $form_state['values']['mollom']['contentId'] = $contentId;
     }
   }
   $form_state['mollom']['captcha_type'] = $type;
diff --git a/tests/mollom.test b/tests/mollom.test
index 4240a55..31a4c3e 100644
--- a/tests/mollom.test
+++ b/tests/mollom.test
@@ -585,8 +585,12 @@ class MollomWebTestCase extends DrupalWebTestCase {
    *   An optional message to test does appear after submission.
    */
   protected function postCorrectCaptcha($url, array $edit = array(), $button, $success_message = '') {
+    if (isset($url)) {
+      $this->drupalGet($url);
+    }
+    $this->assertCaptchaField();
     $edit['mollom[captcha]'] = 'correct';
-    $this->drupalPost($url, $edit, $button);
+    $this->drupalPost(NULL, $edit, $button);
     $this->assertNoCaptchaField();
     $this->assertNoText($this->incorrect_message);
     if ($success_message) {
@@ -607,12 +611,14 @@ class MollomWebTestCase extends DrupalWebTestCase {
    *   An optional message to test does not appear after submission.
    */
   protected function postIncorrectCaptcha($url, array $edit = array(), $button, $success_message = '') {
+    if (isset($url)) {
+      $this->drupalGet($url);
+    }
+    $this->assertCaptchaField();
     $edit['mollom[captcha]'] = 'incorrect';
     $before_url = $this->getUrl();
-    $this->drupalPost($url, $edit, $button);
-    if ($this->getUrl() == $before_url) {
-      $this->assertCaptchaField();
-    }
+    $this->drupalPost(NULL, $edit, $button);
+    $this->assertCaptchaField();
     $this->assertText($this->incorrect_message);
     if ($success_message) {
       $this->assertNoText($success_message);
@@ -636,7 +642,7 @@ class MollomWebTestCase extends DrupalWebTestCase {
   protected function assertSpamSubmit($url, array $spam_fields, array $edit = array(), $button, $success_message = '') {
     $edit += array_fill_keys($spam_fields, 'spam');
     $this->drupalPost($url, $edit, $button);
-    $this->assertNoCaptchaField($url);
+    $this->assertNoCaptchaField();
     $this->assertText($this->spam_message);
     if ($success_message) {
       $this->assertNoText($success_message);
@@ -684,11 +690,8 @@ class MollomWebTestCase extends DrupalWebTestCase {
    */
   protected function assertUnsureSubmit($url, array $unsure_fields, array $edit = array(), $button, $success_message = '') {
     $edit += array_fill_keys($unsure_fields, 'unsure');
-    $before_url = $this->getUrl();
     $this->drupalPost($url, $edit, $button);
-    if ($this->getUrl() == $before_url) {
-      $this->assertCaptchaField();
-    }
+    $this->assertCaptchaField();
     $this->assertText($this->unsure_message);
     if ($success_message) {
       $this->assertNoText($success_message);
@@ -3823,6 +3826,10 @@ class MollomDataCRUDTestCase extends MollomWebTestCase {
 class MollomAnalysisTestCase extends MollomWebTestCase {
   protected $disableDefaultSetup = TRUE;
 
+  // Re-route Mollom communication to this testing site.
+  // @todo Remove this when Testing API is fixed.
+  protected $mollomClass = 'MollomDrupalTestLocal';
+
   public static function getInfo() {
     return array(
       'name' => 'Text analysis',
@@ -3840,7 +3847,211 @@ class MollomAnalysisTestCase extends MollomWebTestCase {
       'access administration pages',
       'administer mollom',
     ));
-    $this->web_user = $this->drupalCreateUser(array());
+  }
+
+  function testUnsure() {
+    $methods = get_class_methods($this);
+    foreach ($methods as $method) {
+      if (substr($method, 0, 7) === 'subtest') {
+        //debug($method);
+        $this->$method();
+      }
+    }
+  }
+
+  /**
+   * Tests basic unsure submission flow.
+   */
+  function subtestUnsureCorrect() {
+    $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS);
+
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'title' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId', TRUE);
+    $this->assertResponseIDInForm('captchaId', TRUE);
+    $this->assertCaptchaField();
+
+    $edit = array(
+      'title' => 'unsure unsure',
+      'mollom[captcha]' => 'correct',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoCaptchaField();
+    $this->assertTestSubmitData();
+  }
+
+  /**
+   * Tests unsure post with repetitive incorrect CAPTCHA solution.
+   *
+   * @todo CAPTCHA ID should stay the same.
+   * @see http://drupal.org/node/1959904
+   */
+  function subtestUnsureIncorrect() {
+    $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS);
+
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'title' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId', TRUE);
+    $this->assertResponseIDInForm('captchaId', TRUE);
+    $this->assertCaptchaField();
+
+    $edit = array();
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertCaptchaField();
+    $edit = array(
+      'mollom[captcha]' => 'incorrect',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertCaptchaField();
+
+    $edit = array(
+      'mollom[captcha]' => 'correct',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoCaptchaField();
+    $this->assertTestSubmitData();
+  }
+
+  /**
+   * Tests unsure post with other validation errors.
+   */
+  function subtestUnsureValidation() {
+    $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS);
+
+    // The 'title' field is required. Omit its value to verify that the CAPTCHA
+    // can be solved, repetitive form validations do not show a CAPTCHA again,
+    // and the post can finally be submitted by providing a title.
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'body' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId', TRUE);
+    $this->assertResponseIDInForm('captchaId', TRUE);
+    $this->assertCaptchaField();
+    $edit = array(
+      'body' => 'unsure unsure',
+      'mollom[captcha]' => 'correct',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+    $this->assertResponseIDInForm('captchaId');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'body' => 'unsure unsure unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+    $this->assertResponseIDInForm('captchaId');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'title' => 'unsure',
+      'body' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoCaptchaField();
+    $this->assertTestSubmitData();
+  }
+
+  /**
+   * Tests unsure posts turning into ham.
+   *
+   * @todo CAPTCHA ID should stay the same.
+   * @see http://drupal.org/node/1959904
+   */
+  function subtestUnsureHam() {
+    $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS);
+
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoCaptchaField();
+    // Posting ham as title would submit the form, so post as body instead.
+    $edit = array(
+      'body' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId', TRUE);
+    $this->assertResponseIDInForm('captchaId', TRUE);
+    $this->assertCaptchaField();
+    // Turn the post into ham.
+    $edit = array(
+      'body' => 'ham',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertNoCaptchaField();
+    // Turn the post back into unsure.
+    $edit = array(
+      'body' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertCaptchaField();
+
+    $edit = array(
+      'title' => 'irrelevant',
+      'mollom[captcha]' => 'correct',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoCaptchaField();
+    $this->assertTestSubmitData();
+  }
+
+  /**
+   * Tests unsure posts turning into spam.
+   *
+   * @todo CAPTCHA ID should stay the same.
+   * @see http://drupal.org/node/1959904
+   */
+  function subtestUnsureSpam() {
+    $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS);
+
+    $this->drupalGet('mollom-test/form');
+    $this->assertNoCaptchaField();
+    $edit = array(
+      'title' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId', TRUE);
+    $this->assertResponseIDInForm('captchaId', TRUE);
+    $this->assertCaptchaField();
+    // Turn the post into spam.
+    $edit = array(
+      'title' => 'spam',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertNoCaptchaField();
+    // Turn the post back into unsure.
+    $edit = array(
+      'title' => 'unsure',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertResponseIDInForm('contentId');
+//    $this->assertResponseIDInForm('captchaId');
+    $this->assertCaptchaField();
+
+    $edit = array(
+      'mollom[captcha]' => 'correct',
+    );
+    $this->drupalPost(NULL, $edit, 'Submit');
+    $this->assertNoCaptchaField();
+    $this->assertTestSubmitData();
   }
 
   /**
diff --git a/tests/mollom_test_server.module b/tests/mollom_test_server.module
index 8c54c27..7fd6008 100644
--- a/tests/mollom_test_server.module
+++ b/tests/mollom_test_server.module
@@ -489,14 +489,15 @@ function mollom_test_server_check_content($data) {
     }
     // Lastly, take a forced 'unsure' into account.
     elseif (strpos($post, 'unsure') !== FALSE) {
-      // Differentiate between binary unsure mode.
+      // Enabled unsure mode.
       if (!isset($data['unsure']) || $data['unsure']) {
         $spam = TRUE;
         $ham = TRUE;
       }
+      // Binary mode.
       else {
-        $spam = TRUE;
-        $ham = FALSE;
+        $spam = FALSE;
+        $ham = TRUE;
       }
     }
     // Check blacklist.
@@ -529,9 +530,9 @@ function mollom_test_server_check_content($data) {
     }
     // In case a previous spam check was unsure and a CAPTCHA was solved, the
     // result is supposed to be ham - unless the new content is spam.
-    if ($response['spamClassification'] != 'spam') {
-      $captcha_sessions = variable_get('mollom_test_server_check_captcha_sessions', array());
-      if (!empty($data['captchaId']) && !empty($captcha_sessions[$data['captchaId']])) {
+    if (!empty($data['id']) && $response['spamClassification'] == 'unsure') {
+      $content_captchas = variable_get('mollom_test_server_content_captcha', array());
+      if (!empty($content_captchas[$data['id']])) {
         $response['spamScore'] = 0.0;
         $response['spamClassification'] = 'ham';
       }
@@ -631,7 +632,7 @@ function mollom_test_server_check_content_blacklist($string, $blacklist, $reason
 }
 
 /**
- * API callback for mollom.getImageCaptcha to fetch a CATPCHA image.
+ * API callback for mollom.getImageCaptcha to fetch a CAPTCHA image.
  */
 function mollom_test_server_get_captcha($data) {
   $response = array();
@@ -654,8 +655,6 @@ function mollom_test_server_get_captcha($data) {
 
 /**
  * API callback for mollom.checkCaptcha to validate a CAPTCHA response.
- *
- * @todo Add support for 'redirect' and 'refresh' values.
  */
 function mollom_test_server_check_captcha($data) {
   $response = array();
@@ -673,13 +672,16 @@ function mollom_test_server_check_captcha($data) {
   if (!isset($storage[$captchaId])) {
     return MENU_NOT_FOUND;
   }
-  $storage[$captchaId] = $data;
+  $storage[$captchaId] = array_merge($storage[$captchaId], $data);
   $response['id'] = $captchaId;
   variable_set('mollom_test_server_captcha', $storage);
 
-  $captcha_sessions = variable_get('mollom_test_server_check_captcha_sessions', array());
-  $captcha_sessions[$captchaId] = $response['solved'];
-  variable_set('mollom_test_server_check_captcha_sessions', $captcha_sessions);
+  if (isset($storage[$captchaId]['contentId'])) {
+    $contentId = $storage[$captchaId]['contentId'];
+    $content_captchas = variable_get('mollom_test_server_content_captcha', array());
+    $content_captchas[$contentId] = $response['solved'];
+    variable_set('mollom_test_server_content_captcha', $content_captchas);
+  }
 
   return $response;
 }
