Index: mollom.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.admin.inc,v
retrieving revision 1.6
diff -u -p -r1.6 mollom.admin.inc
--- mollom.admin.inc	6 Jan 2010 07:36:59 -0000	1.6
+++ mollom.admin.inc	13 Jan 2010 19:45:57 -0000
@@ -358,9 +358,9 @@ function mollom_comment_admin_overview_s
     list($id, $operation) = explode('-', $form_state['values']['operation']);
     foreach ($form_state['values']['comments'] as $cid => $value) {
       if ($value) {
-        // First, send the proper information to the XML-RPC server:
+        // First, report the comments as spam to Mollom.com.
         if ($data = mollom_data_load('comment', $cid)) {
-          mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => 'spam'));
+          _mollom_send_feedback($data->session);
         }
 
         // Second, perform the proper operation on the comments:
@@ -400,8 +400,9 @@ function mollom_node_admin_overview_subm
     list($id, $operation) = explode('-', $form_state['values']['operation']);
     foreach ($form_state['values']['nodes'] as $nid => $value) {
       if ($value) {
+        // First, report the nodes as spam to Mollom.com.
         if ($data = mollom_data_load('node', $nid)) {
-          mollom('mollom.sendFeedback', array('session_id' => $data->session, 'feedback' => 'spam'));
+           _mollom_send_feedback($data->session);
         }
 
         if ($node = node_load($nid)) {
Index: mollom.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v
retrieving revision 1.13
diff -u -p -r1.13 mollom.module
--- mollom.module	13 Jan 2010 19:37:52 -0000	1.13
+++ mollom.module	13 Jan 2010 22:11:19 -0000
@@ -686,12 +686,12 @@ function mollom_form_get_values($form_va
     $data['author_openid'] = $mapping['author_openid'];
   }
   elseif (!empty($data['author_id'])) {
-    if (!empty($account->uid)) {
-      $data['author_openid'] = _mollom_get_openid($account);
+    if (!empty($account->uid) && ($openid = _mollom_get_openid($account))) {
+      $data['author_openid'] = $openid;
     }
   }
-  elseif (!empty($user->uid)) {
-    $data['author_openid'] = _mollom_get_openid($user);
+  elseif (!empty($user->uid) && ($openid = _mollom_get_openid($user))) {
+    $data['author_openid'] = $openid;
   }
 
   // User IP.
@@ -793,6 +793,7 @@ function mollom_process_mollom($element,
       'passed_captcha' => FALSE,
       'fields' => $element['#mollom_form']['fields'],
       'mapping' => $element['#mollom_form']['mapping'],
+      'response' => NULL,
     );
   }
 
@@ -836,6 +837,7 @@ function mollom_process_mollom($element,
 
       // Assign the session ID returned by Mollom.
       $form_state['mollom']['session_id'] = $result['session_id'];
+      $form_state['mollom']['response']['session_id'] = $result['session_id'];
       // If we received a Mollom session id via textual analysis or a CAPTCHA
       // request, inject it to the form.
       $timestamp = REQUEST_TIME;
@@ -848,7 +850,7 @@ function mollom_process_mollom($element,
       $_SESSION['mollom_sessions'][$result['session_id']] = $timestamp;
 
       // Cache the Mollom form state for mollom_process_mollom_session_id().
-      cache_set($result['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
+      cache_set($form_state['mollom']['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
     }
     // Otherwise, we have a communication or configuration error.
     // @todo Short-cut form processing entirely in this case; see also
@@ -892,7 +894,7 @@ function mollom_process_mollom_session_i
         watchdog('mollom', 'Expired session id %session.', array('%session' => $mollom_session_id));
       }
       else {
-        watchdog('mollom', 'Re-used session id %session. This is not a bug in Mollom. If this happens too often, check your site for attacks.', array('%session' => $mollom_session_id), WATCHDOG_WARNING);
+        watchdog('mollom', 'Unknown session id %session. This is not a bug in Mollom. If this happens too often, check your site for attacks.', array('%session' => $mollom_session_id), WATCHDOG_WARNING);
       }
     }
     elseif ($cache->data['form_id'] !== $form_state['values']['form_id']) {
@@ -931,18 +933,19 @@ function mollom_validate_analysis(&$form
 
   // Assign the session ID returned by Mollom.
   $form_state['mollom']['session_id'] = $result['session_id'];
+  // Store the response for #submit handlers.
+  $form_state['mollom']['response'] = $result;
+  // @todo Only used for Contact module and mail integration in general.
+  $GLOBALS['mollom_response'] = $form_state['mollom']['response'];
+
   // Inject the Mollom session id we received via textual analysis.
-  $timestamp = REQUEST_TIME;
-  $form['mollom']['session_id']['#value'] = $timestamp . '-' . $result['session_id'];
+  $form['mollom']['session_id']['#value'] = REQUEST_TIME . '-' . $result['session_id'];
 
   // Store the Mollom session id in the user session to force a
   // session for anonymous users in Drupal 7 and Drupal 6 Pressflow.
   // @see mollom_exit()
   // @see mollom_form_submit()
-  $_SESSION['mollom_sessions'][$result['session_id']] = $timestamp;
-
-  // Store the response for #submit handlers.
-  $GLOBALS['mollom_response'] = $result;
+  $_SESSION['mollom_sessions'][$result['session_id']] = REQUEST_TIME;
 
   switch ($result['spam']) {
     case MOLLOM_ANALYSIS_HAM:
@@ -967,12 +970,20 @@ function mollom_validate_analysis(&$form
         'author_ip' => $data['author_ip'],
         'session_id' => $result['session_id'],
       );
-      $captcha_result = mollom('mollom.getImageCaptcha', $captcha_data);
+      $result = mollom('mollom.getImageCaptcha', $captcha_data);
 
       // If we get a response, add the image CAPTCHA to the form element.
-      if (isset($captcha_result['url'])) {
+      if (isset($result['session_id']) && isset($result['url'])) {
+        $form_state['mollom']['session_id'] = $result['session_id'];
+        $form_state['mollom']['response']['session_id'] = $result['session_id'];
+        // @todo Only used for Contact module and mail integration in general.
+        $GLOBALS['mollom_response'] = $form_state['mollom']['response'];
+
+        // Inject the Mollom session id we received for the CAPTCHA.
+        $form['mollom']['session_id']['#value'] = REQUEST_TIME . '-' . $result['session_id'];
+
         $captcha = '<a href="http://mollom.com" class="mollom-captcha">';
-        $captcha .= '<img src="' . url($captcha_result['url']) . '" alt="Mollom CAPTCHA" />';
+        $captcha .= '<img src="' . url($result['url']) . '" alt="Mollom CAPTCHA" />';
         // @todo This suffix needs to be injected via JavaScript.
         $captcha .= '</a> (<a href="#" class="mollom-audio-captcha">' . t('play audio CAPTCHA') . '</a>)';
         $form['mollom']['captcha']['#field_prefix'] = $captcha;
@@ -981,15 +992,23 @@ function mollom_validate_analysis(&$form
   }
 
   // Cache the Mollom form state for mollom_process_mollom_session_id().
-  cache_set($result['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
+  cache_set($form_state['mollom']['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
 }
 
 /**
  * Form validation handler for CAPTCHA form element.
  */
 function mollom_validate_captcha(&$form, &$form_state) {
-  if (!$form_state['mollom']['require_captcha'] || $form_state['mollom']['passed_captcha']) {
+  if (!$form_state['mollom']['require_captcha']) {
+    $form['mollom']['captcha']['#access'] = FALSE;
+    return;
+  }
+
+  // When re-validating a form that already passed a CAPTCHA in a previous
+  // request, we need to re-populate our global variable for mollom_data_save().
+  if ($form_state['mollom']['passed_captcha']) {
     $form['mollom']['captcha']['#access'] = FALSE;
+    $GLOBALS['mollom_response'] = $form_state['mollom']['response'];
     return;
   }
 
@@ -1023,6 +1042,12 @@ function mollom_validate_captcha(&$form,
 //    return;
 //  }
 
+  // Store the response for #submit handlers.
+  $form_state['mollom']['response']['session_id'] = $form_state['mollom']['session_id'];
+  $form_state['mollom']['response']['spam'] = (int) $result;
+  // @todo Only used for Contact module and mail integration in general.
+  $GLOBALS['mollom_response'] = $form_state['mollom']['response'];
+
   // Explictly check for TRUE, since mollom.checkCaptcha() can also return an
   // error message (e.g. expired or invalid session_id).
   if ($result === TRUE) {
@@ -1030,9 +1055,6 @@ function mollom_validate_captcha(&$form,
     $form['mollom']['captcha']['#access'] = FALSE;
 
     watchdog('mollom', 'Correct CAPTCHA: <pre>@data<pre>', array('@data' => print_r($form_state['values'], TRUE)));
-
-    // Cache the Mollom form state for mollom_process_mollom_session_id().
-    cache_set($form_state['mollom']['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
   }
   else {
     // Empty the CAPTCHA field value, since the user has to re-enter a new one.
@@ -1041,6 +1063,9 @@ function mollom_validate_captcha(&$form,
     form_set_error('mollom][captcha', t('The CAPTCHA was not completed correctly. Please complete this new CAPTCHA and try again.'));
     watchdog('mollom', 'Incorrect CAPTCHA: <pre>@data<pre>', array('@data' => print_r($form_state['values'], TRUE)));
   }
+
+  // Cache the Mollom form state for mollom_process_mollom_session_id().
+  cache_set($form_state['mollom']['session_id'], $form_state['mollom'], 'cache_mollom', REQUEST_TIME + 21600);
 }
 
 /**
@@ -1051,8 +1076,8 @@ function mollom_validate_captcha(&$form,
  */
 function mollom_form_submit($form_id, &$form_state) {
   // Flush Mollom session information from database cache and user session.
-  if (!empty($form_state['values']['mollom']['session_id'])) {
-    $session_id = $form_state['values']['mollom']['session_id'];
+  if (!empty($form_state['mollom']['session_id'])) {
+    $session_id = $form_state['mollom']['session_id'];
     cache_clear_all($session_id, 'cache_mollom');
     unset($_SESSION['mollom_sessions'][$session_id]);
   }
@@ -1121,33 +1146,29 @@ function mollom($method, $data = array()
 
       if ($result === FALSE && ($error = xmlrpc_error())) {
         if ($error->code == MOLLOM_REFRESH) {
-          // Safety pal to avoid endless loops.
+          // Avoid endless loops.
           if (!$refresh) {
-            // Retrieve a list of valid Mollom servers from mollom.com.
+            $refresh = TRUE;
+
+            // Retrieve a new list of valid Mollom servers.
             $servers = _mollom_retrieve_server_list();
 
-            // Reset the list of servers so we start from the first server in
-            // the list.
+            // Reset the list of servers to restart from the first server.
             reset($servers);
 
-            // Store the updated list of servers in the database.
+            // Update the server list.
             variable_set('mollom_servers', $servers);
 
-            // Log this for debugging purposes.
             $messages[] = array(
               'text' => 'Refreshed servers: %servers',
               'arguments' => array('%servers' => implode(', ', $servers)),
             );
-
-            // Mark that we have refreshed the list.
-            $refresh = TRUE;
           }
         }
         elseif ($error->code == MOLLOM_REDIRECT) {
-          // If this is a network error, we go to the next server in the list.
+          // Try the next server in the list.
           $next = next($servers);
 
-          // Do nothing, we automatically select the next server.
           $messages[] = array(
             'text' => 'Server %server redirected to: %next.',
             'arguments' => array('%server' => $server, '%next' => $next),
@@ -1165,13 +1186,12 @@ function mollom($method, $data = array()
             ),
           );
 
-          // If it is a 'clean' Mollom error we return instantly.
+          // Instantly return upon a 'real' error.
           if ($error->code == MOLLOM_ERROR) {
             _mollom_watchdog($messages, WATCHDOG_ERROR);
             return MOLLOM_ERROR;
           }
-
-          // If this is a network error, we go to the next server in the list.
+          // Otherwise, try the next server.
           next($servers);
         }
       }
@@ -1214,6 +1234,20 @@ function _mollom_watchdog($messages, $se
 }
 
 /**
+ * Send feedback to Mollom.
+ */
+function _mollom_send_feedback($session_id, $feedback = 'spam') {
+  mollom('mollom.sendFeedback', array(
+    'session_id' => $session_id,
+    'feedback' => $feedback,
+  ));
+  watchdog('mollom', 'Reported session id %session as %feedback.', array(
+    '%session' => $session_id,
+    '%feedback' => $feedback,
+  ));
+}
+
+/**
  * Fetch the site's Mollom statistics from the API.
  *
  * @param $refresh
@@ -1594,6 +1628,7 @@ function mollom_mail_alter(&$message) {
   // any mails sent by Drupal since they should never be reported as spam.
   $valid_ids = array('contact_page_mail', 'contact_page_copy', 'contact_user_mail', 'contact_user_copy');
   if (isset($GLOBALS['mollom_response']['session_id']) && in_array($message['id'], $valid_ids)) {
+    mollom_data_save('contact', $message['id']);
     $report_link = t('Report as inappropriate: @link', array(
       '@link' => url('mollom/report/session/' . $GLOBALS['mollom_response']['session_id'], array('absolute' => TRUE)),
     ));
Index: mollom.pages.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.pages.inc,v
retrieving revision 1.3
diff -u -p -r1.3 mollom.pages.inc
--- mollom.pages.inc	21 Dec 2009 20:24:22 -0000	1.3
+++ mollom.pages.inc	13 Jan 2010 20:51:30 -0000
@@ -83,10 +83,7 @@ function mollom_report_form_submit($form
     // Send feedback to Mollom, if we have session data.
     if (isset($data->session) && isset($form_state['values']['feedback']) && $form_state['values']['feedback'] != 'none') {
       // @todo Check the actual reponse.
-      mollom('mollom.sendFeedback', array(
-        'session_id' => $data->session,
-        'feedback' => $form_state['values']['feedback'],
-      ));
+      _mollom_send_feedback($data->session, $form_state['values']['feedback']);
       drupal_set_message(t('The content was successfully reported as inappropriate.'));
     }
 
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v
retrieving revision 1.8
diff -u -p -r1.8 mollom.test
--- tests/mollom.test	13 Jan 2010 19:37:52 -0000	1.8
+++ tests/mollom.test	13 Jan 2010 21:26:09 -0000
@@ -79,9 +79,14 @@ class MollomWebTestCase extends DrupalWe
    * Set up an administrative user account and testing keys.
    */
   function setUp() {
+    // Re-initialize stored session_id and watchdog messages.
+    $this->resetSessionID();
+    $this->messages = array();
+
     // Call parent::setUp() allowing Mollom test cases to pass further modules.
     $modules = func_get_args();
     $modules[] = 'mollom';
+    $modules[] = 'dblog';
     call_user_func_array(array($this, 'parent::setUp'), $modules);
 
     $this->admin_user = $this->drupalCreateUser(array(
@@ -123,20 +128,13 @@ class MollomWebTestCase extends DrupalWe
    * @todo Add this to D7 core.
    */
   protected function assertMollomWatchdogMessages($no_fail_expected = TRUE) {
-    static $seen_ids = array();
-
-    if (!module_exists('dblog')) {
-      return;
-    }
     module_load_include('inc', 'dblog', 'dblog.admin');
 
+    $this->messages = array();
     $query = db_select('watchdog', 'w')
       ->fields('w')
       ->condition('w.type', 'mollom')
       ->orderBy('w.timestamp', 'ASC');
-    if ($seen_ids) {
-      $query->condition('w.wid', $seen_ids, 'NOT IN');
-    }
     foreach ($query->execute() as $row) {
       if ($no_fail_expected ? $row->severity >= WATCHDOG_NOTICE : $row->severity < WATCHDOG_NOTICE) {
         $this->pass(_dblog_format_message($row), t('Watchdog'));
@@ -144,11 +142,78 @@ class MollomWebTestCase extends DrupalWe
       else {
         $this->fail(_dblog_format_message($row), t('Watchdog'));
       }
-      $seen_ids[] = $row->wid;
+      $this->messages[$row->wid] = $row;
+    }
+    // Delete processed watchdog messages.
+    if (!empty($this->messages)) {
+      $seen_ids = array_keys($this->messages);
+      db_delete('watchdog')->condition('wid', $seen_ids)->execute();
     }
   }
 
   /**
+   * Assert that the Mollom session id remains the same.
+   *
+   * The Mollom session id is only known to one server. If we are communicating
+   * with a different Mollom server (due to a refreshed server list or being
+   * redirected), then we will get a new session_id.
+   *
+   * @param $session_id
+   *   A Mollom session_id of the last request, as contained in the XML-RPC
+   *   response.
+   */
+  protected function assertSessionID($session_id) {
+    // Check whether watchdog messages indicate a refresh or redirect.
+    foreach ($this->messages as $message) {
+      if ($message->message == 'Refreshed servers: %servers' || $message->message == 'Server %server redirected to: %next.') {
+        $this->resetSessionID();
+      }
+    }
+
+    if (!isset($this->session_id)) {
+      // Use assertTrue() instead of pass(), to test !empty().
+      $this->assertTrue($session_id, t('New session_id: %session_id', array('%session_id' => $session_id)));
+      $this->session_id = $session_id;
+    }
+    else {
+      $this->_assertEqual('session_id', $session_id, $this->session_id);
+    }
+    return $this->session_id;
+  }
+
+  /**
+   * Reset the statically cached Mollom session id.
+   */
+  protected function resetSessionID() {
+    $this->session_id = NULL;
+  }
+
+  /**
+   * Assert a Mollom session id in a form.
+   *
+   * This is a wrapper around assertSessionID() allows to assert that a proper
+   * Mollom session id is found in the form contained in the internal browser
+   * output. The usual flow is:
+   * - drupalGet() or drupalPost() requests or submits a form.
+   * - drupalGet() and drupalPost() invoke assertMollomWatchdogMessages()
+   *   internally, which records all new watchdog messages.
+   * - This function, assertSessionIDInForm(), is invoked to assert that there
+   *   is a Mollom session id and, depending on the recorded watchdog messages,
+   *   that it either equals the last known session id or the new session id is
+   *   used for future comparisons in case of a server redirect.
+   * - The return value of this function is used to invoke assertData(), to
+   *   verify that the proper session id was stored in the database.
+   */
+  protected function assertSessionIDInForm() {
+    // The session id found in the form element value is prefixed with the UNIX
+    // timestamp denoting the time it was generated/output. The form element
+    // #process callback mollom_process_mollom_session_id() uses this timestamp
+    // to additionally validate its age.
+    list($timestamp, $session_id) = explode('-', $this->getFieldValueByName('mollom[session_id]'));
+    return $this->assertSessionID($session_id);
+  }
+
+  /**
    * Assign the Mollom API keys to internal variables and reset the server list.
    *
    * @param $public
@@ -182,6 +247,7 @@ class MollomWebTestCase extends DrupalWe
     // Call the mollom.verifyKey function directly and check that the key
     // is valid.
     $key_is_valid = mollom('mollom.verifyKey');
+    $this->assertMollomWatchdogMessages();
     $this->assertIdentical($key_is_valid, TRUE, t('The Mollom servers can be contacted and the key pair specified in the mollom.test file is found to be valid.'));
   }
 
@@ -259,10 +325,20 @@ class MollomWebTestCase extends DrupalWe
 
   /**
    * Assert that Mollom session data was stored for a submission.
+   *
+   * @param $entity
+   *   The entity type to search for in {mollom}.
+   * @param $id
+   *   The entity id to search for in {mollom}.
+   * @param $session_id
+   *   (optional) The Mollom session id to assert additionally.
    */
-  protected function assertData($entity, $id) {
+  protected function assertData($entity, $id, $session_id = NULL) {
     $data = mollom_data_load($entity, $id);
-    $this->assertTrue($data->session, t('Mollom session data for %entity @id exists: %session', array('%entity' => $entity, '@id' => $id, '%session' => $data->session)));
+    $this->assertTrue($data->session, t('Mollom session data for %entity @id exists: <pre>@data</pre>', array('%entity' => $entity, '@id' => $id, '@data' => var_export($data, TRUE))));
+    if (isset($session_id)) {
+      $this->_assertEqual(t('Stored session id'), $data->session, $session_id);
+    }
   }
 
   /**
@@ -416,15 +492,17 @@ class MollomWebTestCase extends DrupalWe
   /**
    * Retrieve a field value by ID.
    */
-  protected function getFieldValue($field_id) {
-    $value = '';
-    if ($this->parse()) {
-      $fields = $this->elements->xpath($this->constructFieldXpath('id', $field_id));
-      if (isset($fields[0])) {
-        $value = $fields[0]->value;
-      }
-    }
-    return $value;
+  protected function getFieldValueByID($id) {
+    $fields = $this->xpath($this->constructFieldXpath('id', $id));
+    return (string) $fields[0]['value'];
+  }
+
+  /**
+   * Retrieve a field value by name.
+   */
+  protected function getFieldValueByName($name) {
+    $fields = $this->xpath($this->constructFieldXpath('name', $name));
+    return (string) $fields[0]['value'];
   }
 
   /**
@@ -440,6 +518,46 @@ class MollomWebTestCase extends DrupalWe
   }
 
   /**
+   * Wraps drupalGet() for additional watchdog message assertion.
+   *
+   * @param $options
+   *   In addition to regular $options that are passed to url():
+   *   - watchdog: (optional) Boolean whether to assert that only non-severe
+   *     watchdog messages have been logged. Defaults to TRUE. Use FALSE to
+   *     negate the watchdog message severity assertion.
+   *
+   * @see DrupalWebTestCase->drupalGet()
+   * @see MollomWebTestCase->assertMollomWatchdogMessages()
+   * @see MollomWebTestCase->assertSessionID()
+   */
+  protected function drupalGet($path, array $options = array(), array $headers = array()) {
+    $output = parent::drupalGet($path, $options, $headers);
+    $options += array('watchdog' => TRUE);
+    $this->assertMollomWatchdogMessages($options['watchdog']);
+    return $output;
+  }
+
+  /**
+   * Wraps drupalPost() for additional watchdog message assertion.
+   *
+   * @param $options
+   *   In addition to regular $options that are passed to url():
+   *   - watchdog: (optional) Boolean whether to assert that only non-severe
+   *     watchdog messages have been logged. Defaults to TRUE. Use FALSE to
+   *     negate the watchdog message severity assertion.
+   *
+   * @see MollomWebTestCase->assertMollomWatchdogMessages()
+   * @see MollomWebTestCase->assertSessionID()
+   * @see DrupalWebTestCase->drupalPost()
+   */
+  protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array()) {
+    $output = parent::drupalPost($path, $edit, $submit, $options, $headers);
+    $options += array('watchdog' => TRUE);
+    $this->assertMollomWatchdogMessages($options['watchdog']);
+    return $output;
+  }
+
+  /**
    * Helper function for assertEqual().
    *
    * Check to see if two values are equal. And provide a meaningful response.
@@ -453,7 +571,7 @@ class MollomWebTestCase extends DrupalWe
    * @return
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
-  function _assertEqual($name, $first, $second) {
+  protected function _assertEqual($name, $first, $second) {
     $message = strtr("@name: '@first' is equal to '@second'.", array(
       '@name' => $name,
       '@first' => $first,
@@ -463,6 +581,57 @@ class MollomWebTestCase extends DrupalWe
   }
 }
 
+/**
+ * Tests low-level XML-RPC communication with Mollom servers.
+ */
+class MollomResponseTestCase extends MollomWebTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Server responses',
+      'description' => 'Tests that Mollom server responses match expectations.',
+      'group' => 'Mollom',
+    );
+  }
+
+  /**
+   * Tests mollom.checkContent().
+   */
+  function testCheckContent() {
+    $data = array(
+      'author_name' => $this->admin_user->name,
+      'author_mail' => $this->admin_user->mail,
+      'author_id' => $this->admin_user->uid,
+      'author_ip' => ip_address(),
+    );
+
+    // Ensure proper response for 'ham' submissions.
+    $data['post_body'] = 'ham';
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_HAM);
+    $this->_assertEqual('quality', $result['quality'], 1);
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Ensure proper response for 'spam' submissions, re-using session_id.
+    $data['post_body'] = 'spam';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_SPAM);
+    $this->_assertEqual('quality', $result['quality'], 0);
+    $session_id = $this->assertSessionID($result['session_id']);
+
+    // Ensure proper response for 'unsure' submissions, re-using session_id.
+    $data['post_body'] = 'unsure';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
+    $this->_assertEqual('quality', $result['quality'], 0.5);
+    $session_id = $this->assertSessionID($result['session_id']);
+  }
+}
+
 class MollomAccessTestCase extends MollomWebTestCase {
   public static function getInfo() {
     return array(
@@ -487,9 +656,8 @@ class MollomAccessTestCase extends Mollo
       'mollom_public_key' => 'invalid-public-key',
       'mollom_private_key' => 'invalid-private-key',
     );
-    $this->drupalPost(NULL, $edit, t('Save configuration'));
+    $this->drupalPost(NULL, $edit, t('Save configuration'), array('watchdog' => FALSE));
     $this->assertText(t('The configuration options have been saved.'));
-    $this->assertMollomWatchdogMessages(FALSE);
     $this->assertRaw(t('"messages error"'), t('The Mollom settings page reports that the Mollom keys are invalid.'));
   }
 
@@ -528,10 +696,8 @@ class MollomAccessTestCase extends Mollo
       'comment_body[und][0][value]' => 'ham',
     );
     $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview'));
-    $this->assertMollomWatchdogMessages();
 
     $this->drupalPost(NULL, array(), t('Save'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText('node body');
     $this->assertText($edit['comment_body[und][0][value]']);
 
@@ -542,11 +708,9 @@ class MollomAccessTestCase extends Mollo
     $this->clickLink('edit');
 
     $this->drupalPost(NULL, array('subject' => '', 'comment_body[und][0][value]' => 'spam'), t('Preview'));
-    $this->assertMollomWatchdogMessages();
     $this->assertNoText($this->spam_message);
 
     $this->drupalPost(NULL, array(), t('Save'));
-    $this->assertMollomWatchdogMessages();
     $this->assertNoText($this->spam_message);
     $this->assertText('node body');
 
@@ -556,11 +720,9 @@ class MollomAccessTestCase extends Mollo
     $this->clickLink('edit');
 
     $this->drupalPost(NULL, array(), t('Preview'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText($this->spam_message);
 
     $this->drupalPost(NULL, array(), t('Save'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText($this->spam_message);
     $this->assertNoText('node body');
   }
@@ -592,7 +754,6 @@ class MollomFallbackTestCase extends Mol
     $this->drupalPost('admin/config/content/mollom/settings', array('mollom_fallback' => MOLLOM_FALLBACK_BLOCK), t('Save configuration'));
     $this->assertText('The configuration options have been saved.');
     $this->drupalLogout();
-    $this->assertMollomWatchdogMessages();
 
     // Configure Mollom to use a non-existent server as that should trigger
     // the fallback mechanism.
@@ -600,10 +761,9 @@ class MollomFallbackTestCase extends Mol
 
     // Check the password request form.
     // @todo Test mail sending with assertMail() now that it is available.
-    $this->drupalGet('user/password');
+    $this->drupalGet('user/password', array('watchdog' => FALSE));
     $this->assertNoCaptchaField();
     $this->assertText($this->fallback_message);
-    $this->assertMollomWatchdogMessages(FALSE);
   }
 
   /**
@@ -624,7 +784,7 @@ class MollomFallbackTestCase extends Mol
     variable_set('mollom_servers', array('http://fake-host'));
 
     // Check the password request form.
-    $this->drupalGet('user/password');
+    $this->drupalGet('user/password', array('watchdog' => FALSE));
     $this->assertNoCaptchaField();
     $this->assertNoText($this->fallback_message);
   }
@@ -929,7 +1089,6 @@ class MollomFormConfigurationTestCase ex
     $this->assertNoText('Successful form submission.');
     $this->assertText($this->unsure_message);
     $this->postCorrectCaptcha(NULL, array(), 'Submit', 'Successful form submission.');
-    $this->assertMollomWatchdogMessages();
 
     // Try to submit values for top-level fields.
     $edit = array(
@@ -940,7 +1099,6 @@ class MollomFormConfigurationTestCase ex
     $this->assertNoText('Successful form submission.');
     $this->assertNoText($this->unsure_message);
     $this->assertText($this->spam_message);
-    $this->assertMollomWatchdogMessages();
 
     // Try to submit values for nested field.
     $edit = array(
@@ -951,7 +1109,6 @@ class MollomFormConfigurationTestCase ex
     $this->assertNoText('Successful form submission.');
     $this->assertNoText($this->unsure_message);
     $this->assertText($this->spam_message);
-    $this->assertMollomWatchdogMessages();
 
     // Try to submit values for nested field and multiple value field.
     // Start with ham values for simple, nested, and first multiple field.
@@ -966,7 +1123,6 @@ class MollomFormConfigurationTestCase ex
     $this->assertNoText('Successful form submission.');
     $this->assertNoText($this->unsure_message);
     $this->assertNoText($this->spam_message);
-    $this->assertMollomWatchdogMessages();
 
     // Add another value for multiple field.
     $edit = array(
@@ -978,7 +1134,6 @@ class MollomFormConfigurationTestCase ex
     $this->assertNoText('Successful form submission.');
     $this->assertNoText($this->unsure_message);
     $this->assertNoText($this->spam_message);
-    $this->assertMollomWatchdogMessages();
 
     // Now replace all ham values with random values, add a spam value to the
     // multiple field and submit the form.
@@ -994,7 +1149,6 @@ class MollomFormConfigurationTestCase ex
     // Verify that the form was not submitted and cannot be submitted.
     $this->assertNoText('Successful form submission.');
     $this->assertText($this->spam_message);
-    $this->assertMollomWatchdogMessages();
   }
 
   /**
@@ -1145,7 +1299,7 @@ class MollomCommentFormTestCase extends 
 
   public static function getInfo() {
     return array(
-      'name' => 'Comment submission protection',
+      'name' => 'Comment form protection',
       'description' => 'Check that the comment submission form can be protected.',
       'group' => 'Mollom',
     );
@@ -1193,28 +1347,28 @@ class MollomCommentFormTestCase extends 
 
     // Request the comment reply form. There should be a CAPTCHA form.
     $this->drupalLogin($this->web_user);
-    $this->drupalGet('comment/reply/'. $this->node->nid);
+    $this->drupalGet('comment/reply/' . $this->node->nid);
     $this->assertCaptchaField();
-
-    // Retrieve the Mollom Session ID. After each attempted submission below,
-    // we will verify that this remains the same.
-    $mollom_session_id = $this->getFieldValue("edit-mollom-session-id");
+    $this->assertSessionIDInForm();
 
     // Try to submit an incorrect answer for the CAPTCHA, without value for
     // required field.
     $this->postIncorrectCaptcha(NULL, array(), t('Preview'));
     $this->assertText(t('Comment field is required.'));
-    $this->assertFieldByID('edit-mollom-session-id', $mollom_session_id, t('The Mollom Session ID is present on the comment form after a possible spam comment is previewed.'));
+    $this->assertSessionIDInForm();
 
     // Try to submit a correct answer for the CAPTCHA, still without required
     // field value.
     $this->postCorrectCaptcha(NULL, array(), t('Preview'));
-    $this->assertText(t('Comment field is required.'), t('Missing required field triggers form rebuild.'));
+    $this->assertText(t('Comment field is required.'));
+    $session_id = $this->assertSessionIDInForm();
 
     // Finally, we should be able to submit a comment.
     $this->drupalPost(NULL, array('comment_body[und][0][value]' => 'spam'), t('Save'));
     $this->assertText(t('Your comment has been posted.'));
     $this->assertRaw('<p>spam</p>', t('Spam comment could be posted with correct CAPTCHA.'));
+    $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'spam'))->fetchField();
+    $this->assertData('comment', $cid, $session_id);
   }
 
   /**
@@ -1231,46 +1385,47 @@ class MollomCommentFormTestCase extends 
     $this->drupalGet('comment/reply/'. $this->node->nid);
     $this->assertNoCaptchaField();
 
-    // Try to save a comment that is 'unsure' and make sure there is a CAPTCHA
-    // and a Mollom ID.
+    // Try to save a comment that is 'unsure' and make sure there is a CAPTCHA.
     $this->drupalPost(NULL, array('comment_body[und][0][value]' => 'unsure'), t('Save'));
     $this->assertCaptchaField();
-    $this->assertFieldByID('edit-mollom-session-id', '', t('The Mollom Session ID is present on the comment form after a possible spam comment is previewed.'));
-
-    // Retrieve the Mollom Session ID. After each attempted submission below,
-    // we will verify that this remains the same.
-    $mollom_session_id = $this->getFieldValue("edit-mollom-session-id");
+    $session_id = $this->assertSessionIDInForm();
 
     // Try to submit the form by using an invalid CAPTCHA. At this point,
     // the submission should be rejected and a new CAPTCHA generated (even
     // if the text of the comment is changed to ham).
     $this->postIncorrectCaptcha(NULL, array('comment_body[und][0][value]' => 'ham'), t('Save'));
-    $this->assertFieldByID('edit-mollom-session-id', $mollom_session_id, 'The Mollom Session ID remains unchanged.');
+    $session_id = $this->assertSessionIDInForm();
 
     // Now try using a valid CAPTCHA. The CAPTCHA form should no longer
     // be present.
     $this->postCorrectCaptcha(NULL, array(), t('Save'));
     $this->assertRaw('<p>ham</p>', t('A comment that is known to be ham appears on the screen after it is submitted.'));
+    $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'ham'))->fetchField();
+    $this->assertData('comment', $cid, $session_id);
 
-    // Preview a 'spam' comment and try to save it. It should be
-    // rejected, with no CAPTCHA appearing on the page.
+    // Try to save a new 'spam' comment; it should be rejected, with no CAPTCHA
+    // appearing on the page.
+    $this->resetSessionID();
+    $this->drupalGet('comment/reply/' . $this->node->nid);
     $original_number_of_comments = $this->getCommentCount($this->node->nid);
-    $this->assertSpamSubmit('comment/reply/'. $this->node->nid, array('comment_body[und][0][value]'), array(), t('Save'));
+    $this->assertSpamSubmit(NULL, array('comment_body[und][0][value]'), array(), t('Save'));
+    $session_id = $this->assertSessionIDInForm();
     $this->assertCommentCount($this->node->nid, $original_number_of_comments);
-    $this->assertFieldByID('edit-mollom-session-id', $mollom_session_id, 'The Mollom Session ID remains unchanged.');
 
-    // Try to save again the above 'spam' comment. It should also be
-    // rejected with no CAPTCHA.
+    // Try to save again; it should be rejected, with no CAPTCHA.
     $this->assertSpamSubmit(NULL, array('comment_body[und][0][value]'), array(), t('Save'));
+    $session_id = $this->assertSessionIDInForm();
     $this->assertCommentCount($this->node->nid, $original_number_of_comments);
-    $this->assertFieldByID('edit-mollom-session-id', $mollom_session_id, 'The Mollom Session ID remains unchanged.');
 
-    // Preview and save a comment that is 'ham'. Make sure that the submitted
-    // comment appears on the screen and in the database.
+    // Save a new 'ham' comment.
+    $this->resetSessionID();
+    $this->drupalGet('comment/reply/' . $this->node->nid);
     $original_number_of_comments = $this->getCommentCount($this->node->nid);
-    $this->assertHamSubmit('comment/reply/'. $this->node->nid, array('comment_body[und][0][value]'), array(), t('Save'));
+    $this->assertHamSubmit(NULL, array('comment_body[und][0][value]'), array(), t('Save'));
     $this->assertRaw('<p>ham</p>', t('A comment that is known to be ham appears on the screen after it is submitted.'));
     $this->assertCommentCount($this->node->nid, $original_number_of_comments + 1);
+    $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'ham'))->fetchField();
+    $this->assertData('comment', $cid);
   }
 
   /**
@@ -1597,11 +1752,12 @@ class MollomDataTestCase extends MollomW
     $this->drupalLogout();
     $this->drupalGet('node/' . $node->nid);
     $this->clickLink(t('Add new comment'));
+    // Ensure we have some potentially escaped characters in the values.
     $edit = array(
-      'name' => $this->randomString(),
+      'name' => $this->randomString(6) . ' & ' . $this->randomString(8),
       'mail' => 'mollom@example.com',
       'homepage' => 'http://mollom.com',
-      'subject' => $this->randomString(),
+      'subject' => '"' . $this->randomString() . '"',
       'comment_body[und][0][value]' => 'unsure',
     );
     $this->drupalPost(NULL, $edit, t('Save'));
@@ -1662,8 +1818,7 @@ class MollomReportTestCase extends Mollo
       'comment_body[und][0][value]' => 'ham',
     );
     $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save'));
-    $this->assertMollomWatchdogMessages();
-    $this->comment = db_query('SELECT * FROM {comment} WHERE comment = :comment AND nid = :nid', array(':comment' => $edit['comment_body[und][0][value]'], ':nid' => $this->node->nid))->fetchObject();
+    $this->comment = db_query('SELECT * FROM {comment} WHERE subject = :comment AND nid = :nid', array(':comment' => $edit['comment_body[und][0][value]'], ':nid' => $this->node->nid))->fetchObject();
     $this->assertTrue($this->comment, t('Comment was found in the database.'));
     $this->assertData('comment', $this->comment->cid);
 
@@ -1676,7 +1831,6 @@ class MollomReportTestCase extends Mollo
       'feedback' => 'spam',
     );
     $this->drupalPost(NULL, $edit, t('Delete'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText(t('The content was successfully reported as inappropriate.'));
     $this->assertText(t('The comment and all its replies have been deleted.'));
 
@@ -1689,8 +1843,8 @@ class MollomReportTestCase extends Mollo
    * Tests mass-reporting comments.
    */
   function testMassReportComments() {
-    $this->node = $this->drupalCreateNode(array('type' => 'story'));
-    variable_set('comment_preview_story', COMMENT_PREVIEW_OPTIONAL);
+    $this->node = $this->drupalCreateNode(array('type' => 'article'));
+    variable_set('comment_preview_article', DRUPAL_OPTIONAL);
 
     // Post 3 comments.
     $this->drupalLogin($this->web_user);
@@ -1701,8 +1855,7 @@ class MollomReportTestCase extends Mollo
         'comment_body[und][0][value]' => 'ham',
       );
       $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save'));
-      $this->assertMollomWatchdogMessages();
-      $this->comments[$num] = db_query("SELECT * FROM {comment} WHERE subject = '%s' AND nid = %d", array($edit['subject'], $this->node->nid))->fetchObject();
+      $this->comments[$num] = db_query('SELECT * FROM {comment} WHERE subject = :subject AND nid = :nid', array(':subject' => $edit['subject'], ':nid' => $this->node->nid))->fetchObject();
       $this->assertTrue($this->comments[$num], t('Comment was found in the database.'));
       $this->assertData('comment', $this->comments[$num]->cid);
     }
@@ -1717,7 +1870,6 @@ class MollomReportTestCase extends Mollo
       $edit["comments[{$comment->cid}]"] = TRUE;
     }
     $this->drupalPost(NULL, $edit, t('Update'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText(t('The selected comments have been reported as inappropriate and are unpublished.'));
 
     // Verify that unpublished comments are found in approval queue and
@@ -1728,7 +1880,6 @@ class MollomReportTestCase extends Mollo
       $this->assertText($comment->subject, t('Comment found.'));
     }
     $this->drupalPost(NULL, $edit, t('Update'));
-    $this->assertMollomWatchdogMessages();
     $this->assertText(t('The selected comments have been reported as inappropriate and are deleted.'));
 
     // Verify that the comments and Mollom session data has been deleted.
Index: tests/mollom_test.info
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom_test.info,v
retrieving revision 1.3
diff -u -p -r1.3 mollom_test.info
--- tests/mollom_test.info	21 Dec 2009 20:24:22 -0000	1.3
+++ tests/mollom_test.info	13 Jan 2010 21:03:01 -0000
@@ -2,5 +2,6 @@
 name = Mollom Test
 description = Testing module for Mollom functionality.
 core = 7.x
+package = Testing
 hidden = TRUE
 files[] = mollom_test.module
