Index: mollom.css
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.css,v
retrieving revision 1.3
diff -u -p -r1.3 mollom.css
--- mollom.css	4 Aug 2010 03:58:46 -0000	1.3
+++ mollom.css	10 Sep 2010 19:43:38 -0000
@@ -3,3 +3,7 @@
 #edit-mollom-captcha {
   display: block;
 }
+
+#simpletest-result-form table td {
+  white-space: pre-wrap;
+}
Index: mollom.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.inc,v
retrieving revision 1.6
diff -u -p -r1.6 mollom.inc
--- mollom.inc	9 Sep 2010 13:08:11 -0000	1.6
+++ mollom.inc	11 Sep 2010 00:05:55 -0000
@@ -67,12 +67,13 @@ function _mollom_authentication($public_
  */
 function _mollom_retrieve_server_list() {
   // Start from a hard-coded list of servers.
-  $servers = array('http://xmlrpc2.mollom.com', 'http://xmlrpc3.mollom.com');
+  $servers = array('http://xmlrpc2.mollom.com');
 
   // Use the list of servers to retrieve a list of servers from mollom.com:
   foreach ($servers as $server) {
     $result = xmlrpc($server . '/' . MOLLOM_API_VERSION, array('mollom.getServerList' => array(_mollom_authentication())));
     if (!xmlrpc_error()) {
+      return $servers;
       return $result;
     }
     else {
Index: mollom.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.install,v
retrieving revision 1.24
diff -u -p -r1.24 mollom.install
--- mollom.install	10 Aug 2010 19:33:51 -0000	1.24
+++ mollom.install	10 Sep 2010 22:47:44 -0000
@@ -536,3 +536,5 @@ function mollom_update_7005() {
     'not null' => FALSE,
   ));
 }
+
+// @todo Update 'comment_form' to comment_node_TYPE_form.
Index: mollom.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/mollom.module,v
retrieving revision 1.70
diff -u -p -r1.70 mollom.module
--- mollom.module	9 Sep 2010 15:05:03 -0000	1.70
+++ mollom.module	11 Sep 2010 00:56:04 -0000
@@ -1299,10 +1299,10 @@ function mollom_validate_analysis(&$form
           $form_state['mollom']['response']['session_id'] = $captcha['response']['session_id'];
           $form['mollom']['session_id']['#value'] = $captcha['response']['session_id'];
           $form['mollom']['captcha']['#field_prefix'] = $captcha['markup'];
-          $GLOBALS['mollom_response'] = $form_state['mollom']['response'];
         }
         break;
 
+      case MOLLOM_ANALYSIS_UNKNOWN:
       default:
         // If we end up here, something went totally wrong.
         _mollom_fallback();
@@ -1897,11 +1897,16 @@ function mollom_form_node_multiple_delet
  * Implements hook_mollom_form_list().
  */
 function comment_mollom_form_list() {
-  $forms['comment_form'] = array(
-    'title' => t('Comment form'),
-    'entity' => 'comment',
-    'delete form' => 'comment_confirm_delete',
-  );
+  $forms = array();
+  foreach (node_type_get_types() as $type) {
+    $form_id = "comment_node_{$type->type}_form";
+    $forms[$form_id] = array(
+      'title' => t('@name comment form', array('@name' => $type->name)),
+      'entity' => 'comment',
+      'bundle' => $type->type,
+      'delete form' => 'comment_confirm_delete',
+    );
+  }
   return $forms;
 }
 
@@ -1910,10 +1915,8 @@ function comment_mollom_form_list() {
  */
 function comment_mollom_form_info($form_id) {
   $form_info = array(
-    'title' => t('Comment form'),
     'mode' => MOLLOM_MODE_ANALYSIS,
     'bypass access' => array('administer comments'),
-    'entity' => 'comment',
     'elements' => array(
       'subject' => t('Subject'),
       // @todo Update for Field API.
@@ -2105,3 +2108,10 @@ function contact_mollom_form_info($form_
 /**
  * @} End of "name mollom_contact".
  */
+
+/**
+ * Implements hook_form_FORMID_alter().
+ */
+function mollom_form_simpletest_result_form_alter(&$form) {
+  $form['#attached']['css'][] = drupal_get_path('module', 'mollom') . '/mollom.css';
+}
Index: tests/mollom.test
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom.test,v
retrieving revision 1.54
diff -u -p -r1.54 mollom.test
--- tests/mollom.test	3 Sep 2010 17:34:07 -0000	1.54
+++ tests/mollom.test	11 Sep 2010 00:57:18 -0000
@@ -811,6 +811,8 @@ class MollomInstallationTestCase extends
  * Tests low-level XML-RPC communication with Mollom servers.
  */
 class MollomResponseTestCase extends MollomWebTestCase {
+  protected $profile = 'testing';
+
   public static function getInfo() {
     return array(
       'name' => 'Server responses',
@@ -819,14 +821,22 @@ class MollomResponseTestCase extends Mol
     );
   }
 
+  function setUp() {
+    $this->disableDefaultSetup = TRUE;
+    parent::setUp();
+    $this->setKeys();
+    $this->assertValidKeys();
+  }
+
   /**
    * Tests mollom.checkContent().
    */
   function testCheckContent() {
+    $this->web_user = $this->drupalCreateUser(array());
     $data = array(
-      'author_name' => $this->admin_user->name,
-      'author_mail' => $this->admin_user->mail,
-      'author_id' => $this->admin_user->uid,
+      'author_name' => $this->web_user->name,
+      'author_mail' => $this->web_user->mail,
+      'author_id' => $this->web_user->uid,
       'author_ip' => ip_address(),
     );
 
@@ -872,7 +882,19 @@ class MollomResponseTestCase extends Mol
     $this->assertSame('profanity', $result['profanity'], 1);
     $session_id = $this->assertSessionID($result['session_id']);
 
+    // Change the string to contain profanity only.
+    $data['post_body'] = 'profanity';
+    $data['checks'] = 'spam,quality,profanity';
+    $data['session_id'] = $session_id;
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
+    $this->assertSame('quality', $result['quality'], 0);
+    $this->assertSame('profanity', $result['profanity'], 1);
+    $session_id = $this->assertSessionID($result['session_id']);
+
     // Disable spam checking, only do profanity checking.
+    $data['post_body'] = 'spam profanity';
     $data['checks'] = 'profanity';
     $data['session_id'] = $session_id;
     $result = mollom('mollom.checkContent', $data);
@@ -894,6 +916,46 @@ class MollomResponseTestCase extends Mol
   }
 
   /**
+   * Tests results of mollom.checkContent() across requests for a single session.
+   *
+   * @todo Not sure whether testing this sequence actually makes sense anymore.
+   */
+  function testCheckContentSession() {
+    $this->web_user = $this->drupalCreateUser(array());
+    $data = array(
+      'author_name' => $this->web_user->name,
+      'author_mail' => $this->web_user->mail,
+      'author_id' => $this->web_user->uid,
+      'author_ip' => ip_address(),
+    );
+
+    // Sequence: Post unsure spam, correct CAPTCHA, change post into spam,
+    // expect it to be ham (due to correct CAPTCHA).
+    $data['post_body'] = 'unsure';
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE);
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+
+    $captcha_data = array_intersect_key($data, array_flip(array('session_id', 'author_ip')));
+    $result = mollom('mollom.getImageCaptcha', $captcha_data);
+    $this->assertMollomWatchdogMessages();
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+
+    $captcha_data = array_intersect_key($data, array_flip(array('session_id', 'author_id', 'author_ip')));
+    $captcha_data['captcha_result'] = 'correct';
+    $result = mollom('mollom.checkCaptcha', $captcha_data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertIdentical($result, TRUE, t('CAPTCHA response was correct.'));
+
+    $data['post_body'] = 'spam';
+    $result = mollom('mollom.checkContent', $data);
+    $this->assertMollomWatchdogMessages();
+    $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_HAM);
+    $data['session_id'] = $this->assertSessionID($result['session_id']);
+  }
+
+  /**
    * Tests mollom.getImageCaptcha().
    */
   function testGetImageCaptcha() {
@@ -994,7 +1056,7 @@ class MollomAccessTestCase extends Mollo
    */
   function testBypassAccess() {
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form');
+    $this->setProtection('comment_node_article_form');
     $this->drupalLogout();
 
     $node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NONE => array(array('value' => 'node body'))), 'type' => 'article'));
@@ -1379,26 +1441,24 @@ class MollomBlacklistTestCase extends Mo
 class MollomProfanityTestCase extends MollomWebTestCase {
   public static function getInfo() {
     return array(
-      'name' => 'Profanity filtering',
-      'description' => 'Verify that forms can be properly protected and unprotected.',
+      'name' => 'Profanity checking',
+      'description' => 'Tests form protection with text analysis checking for profanity.',
       'group' => 'Mollom',
     );
   }
 
   function setUp() {
     parent::setUp('mollom_test');
-    // Re-route Mollom communication to this testing site.
-    variable_set('mollom_servers', array($GLOBALS['base_url'] . '/xmlrpc.php?version='));
 
-    $this->drupalLogin($this->admin_user);
+    user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments', 'post comments', 'post comments without approval'));
   }
 
   /**
-   * Test the different levels of profanity filtering. With our test Mollom
-   * server, the profanity keyword is 'Joomla'.
+   * Tests text analysis profanity checking.
    */
   function testProfanityFiltering() {
     // Protect Mollom test form but do not enable the profanity filter.
+    $this->drupalLogin($this->admin_user);
     $edit_config = array(
       'mollom[checks][profanity]' => FALSE,
     );
@@ -1437,6 +1497,68 @@ class MollomProfanityTestCase extends Mo
     $this->assertText('Successful form submission.');
     $this->assertNoText($this->profanity_message);
   }
+
+  function testExaminer() {
+    variable_set('comment_preview_article', DRUPAL_OPTIONAL);
+    $node = $this->drupalCreateNode(array('type' => 'article'));
+    $langcode = LANGUAGE_NONE;
+
+    // Enable spam and profanity checking for the article node comment form.
+    $this->drupalLogin($this->admin_user);
+    $edit_config = array(
+      'mollom[checks][profanity]' => TRUE,
+      'mollom[checks][spam]' => TRUE,
+    );
+    $this->setProtection('comment_node_article_form', MOLLOM_MODE_ANALYSIS, NULL, $edit_config);
+    $this->drupalLogout();
+
+    // Test plan 1: Post profanity (ham), remove profanity, and expect that to
+    // be accepted.
+    $edit = array(
+      'subject' => $this->randomName(),
+    );
+    $this->drupalGet("node/{$node->nid}");
+    $this->assertNoCaptchaField();
+    $this->assertPrivacyLink();
+
+    $edit["comment_body[$langcode][0][value]"] = 'profanity ham';
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertText($this->profanity_message);
+    $this->assertNoText(t('Your comment has been posted.'));
+    $session_id = $this->assertSessionIDInForm();
+
+    $edit["comment_body[$langcode][0][value]"] = 'not profane ham';
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertNoText($this->profanity_message);
+    $this->assertText(t('Your comment has been posted.'));
+    $this->assertRaw('<p>' . $edit["comment_body[$langcode][0][value]"] . '</p>', t('Comment previously containing profanity was found.'));
+    $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => $edit['subject']))->fetchField();
+    $this->assertMollomData('comment', $cid, $session_id);
+
+    // Test plan 2: Post unsure spam, post profanity along with correct CAPTCHA,
+    // and expect that to be rejected.
+    $this->web_user = $this->drupalCreateUser();
+    $this->drupalLogin($this->web_user);
+    $edit = array(
+      'subject' => $this->randomName(),
+    );
+    $this->drupalGet("node/{$node->nid}");
+    $this->assertNoCaptchaField();
+    $this->assertPrivacyLink();
+
+    $edit["comment_body[$langcode][0][value]"] = 'unsure';
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertCaptchaField();
+    $this->assertNoText($this->profanity_message);
+    $this->assertNoText(t('Your comment has been posted.'));
+    $session_id = $this->assertSessionIDInForm();
+
+    $edit["comment_body[$langcode][0][value]"] = 'unsure profanity';
+    $this->postCorrectCaptcha(NULL, $edit, t('Save'));
+    $this->assertNoCaptchaField();
+    $this->assertText($this->profanity_message);
+    $this->assertNoText(t('Your comment has been posted.'));
+  }
 }
 
 /**
@@ -1791,7 +1913,7 @@ class MollomCommentFormTestCase extends 
   function testCaptchaProtectedCommentForm() {
     // Enable Mollom CAPTCHA protection for comments.
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form', MOLLOM_MODE_CAPTCHA);
+    $this->setProtection('comment_node_article_form', MOLLOM_MODE_CAPTCHA);
     $this->drupalLogout();
 
     // Request the comment reply form. There should be a CAPTCHA form.
@@ -1829,7 +1951,7 @@ class MollomCommentFormTestCase extends 
   function testTextAnalysisProtectedCommentForm() {
     // Enable Mollom text-classification for comments.
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form');
+    $this->setProtection('comment_node_article_form');
     $this->drupalLogout();
 
     // Request the comment reply form.  Initially, there should be no CAPTCHA.
@@ -2204,7 +2326,7 @@ class MollomDataTestCase extends MollomW
    */
   function testAnalysis() {
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form');
+    $this->setProtection('comment_node_article_form');
 
     // Make comment preview optional.
     $edit = array(
@@ -2391,7 +2513,7 @@ class MollomReportTestCase extends Mollo
    */
   function testReportComment() {
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form');
+    $this->setProtection('comment_node_article_form');
     $this->drupalLogout();
 
     $this->node = $this->drupalCreateNode(array('type' => 'article'));
@@ -2429,7 +2551,7 @@ class MollomReportTestCase extends Mollo
    */
   function testMassReportComments() {
     $this->drupalLogin($this->admin_user);
-    $this->setProtection('comment_form');
+    $this->setProtection('comment_node_article_form');
     $this->drupalLogout();
 
     $this->node = $this->drupalCreateNode(array('type' => 'article'));
Index: tests/mollom_test.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/mollom/tests/mollom_test.module,v
retrieving revision 1.13
diff -u -p -r1.13 mollom_test.module
--- tests/mollom_test.module	10 Aug 2010 19:33:51 -0000	1.13
+++ tests/mollom_test.module	9 Sep 2010 21:14:07 -0000
@@ -13,6 +13,7 @@ function mollom_test_xmlrpc() {
   return array(
     // $data contains a variable amount of properties, so we cannot specify a
     // signature.
+    'mollom.getServerList' => 'mollom_test_get_server_list',
     'mollom.verifyKey' => 'mollom_test_verify_key',
     'mollom.checkContent' => 'mollom_test_check_content',
     'mollom.getImageCaptcha' => 'mollom_test_get_captcha',
@@ -21,6 +22,17 @@ function mollom_test_xmlrpc() {
 }
 
 /**
+ * XML-RPC callback for mollom.getServerList to retrieve new server list.
+ */
+function mollom_test_get_server_list($data) {
+  $storage = variable_get(__FUNCTION__, array());
+  $storage[] = $data;
+  variable_set(__FUNCTION__, $storage);
+
+  return array($GLOBALS['base_url'] . '/xmlrpc.php?version=');
+}
+
+/**
  * XML-RPC callback for mollom.verifyKey to validate API keys.
  */
 function mollom_test_verify_key($data) {
@@ -85,7 +97,7 @@ function mollom_test_check_content($data
       $quality = 1;
     }
     else {
-      $response['spam'] = MOLLOM_ANALYSIS_UNKNOWN;
+      $response['spam'] = MOLLOM_ANALYSIS_HAM;
       $quality = NULL;
     }
   }
@@ -106,12 +118,8 @@ function mollom_test_check_content($data
   if (isset($data['checks']) && strpos($data['checks'], 'profanity') !== FALSE) {
     $profanity = 0.0;
     foreach (array('post_title', 'post_body') as $key) {
-      if (!isset($data[$key])) {
-        continue;
-      }
-      // @todo Client/server testing value mismatch.
-      if (strpos($data[$key], 'profanity') !== FALSE) {
-        $profanity = 0.8;
+      if (isset($data[$key]) && strpos($data[$key], 'profanity') !== FALSE) {
+        $profanity = 1.0;
       }
     }
     $response['profanity'] = $profanity;
