resetSessionID(); $this->messages = array(); // @see DrupalWebTestCase::setUp() $modules = func_get_args(); if (isset($modules[0]) && is_array($modules[0])) { $modules = $modules[0]; } // If not explicitly disabled by a test, setup with Mollom and default admin // user. if (empty($this->disableDefaultSetup)) { // Call parent::setUp() allowing Mollom test cases to pass further modules. $modules[] = 'mollom'; $modules[] = 'dblog'; call_user_func_array(array($this, 'parent::setUp'), $modules); $this->admin_user = $this->drupalCreateUser(array( 'administer mollom', 'access administration pages', 'administer content types', 'administer comments', 'administer permissions', 'administer users', )); } else { $modules[] = 'dblog'; call_user_func_array(array($this, 'parent::setUp'), $modules); } // If not explicitly disabled by a test, setup and validate testing keys. if (empty($this->disableDefaultSetup)) { $this->setKeys(); $this->assertValidKeys(); } } function tearDown() { // Capture any (remaining) watchdog messages. $this->assertMollomWatchdogMessages(); parent::tearDown(); } /** * Assert any watchdog messages based on their severity. * * This function can be (repeatedly) invoked to assert new watchdog messages. * All watchdog messages with a higher severity than WATCHDOG_NOTICE are * considered as "severe". * * @param $max_severity * (optional) A maximum watchdog severity level message constant that log * messages must have to pass the assertion. All messages with a higher * severity will fail. Defaults to WATCHDOG_NOTICE. If a severity level * higher than WATCHDOG_NOTICE is passed, then at least one severe message * is expected. * * @todo Add this to Drupal core. */ protected function assertMollomWatchdogMessages($max_severity = WATCHDOG_NOTICE) { module_load_include('inc', 'dblog', 'dblog.admin'); $this->messages = array(); $result = db_query("SELECT * FROM {watchdog} WHERE type = 'mollom' ORDER BY timestamp ASC"); // The comparison logic applied in this function is a bit confusing, since // the values of the watchdog severity level constants in Drupal core are // negated to their actual "severity level" meaning: // WATCHDOG_EMERG is 0, WATCHDOG_NOTICE is 5, WATCHDOG_DEBUG is 7. $fail_expected = ($max_severity < WATCHDOG_NOTICE); $had_severe_message = FALSE; while ($row = db_fetch_object($result)) { $this->messages[$row->wid] = $row; // Only messages with a maximum severity of $max_severity or less severe // messages must pass. More severe messages need to fail. See note about // severity level constant values above. if ($row->severity >= $max_severity) { $this->pass(_dblog_format_message($row), t('Watchdog')); } else { $this->fail(_dblog_format_message($row), t('Watchdog')); } // In case a severe message is expected, non-severe messages always pass, // since we would trigger a false positive test failure otherwise. // However, in order to actually assert the expectation, there must have // been at least one severe log message. $had_severe_message = ($had_severe_message || $row->severity < WATCHDOG_NOTICE); } // Assert that there was a severe message, in case we expected one. if ($fail_expected && !$had_severe_message) { $this->fail(t('Severe log message not found.'), t('Watchdog')); } // Delete processed watchdog messages. if (!empty($this->messages)) { $seen_ids = array_keys($this->messages); db_query("DELETE FROM {watchdog} WHERE wid IN (" . db_placeholders($seen_ids) . ")", $seen_ids); } } /** * 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->assertSame('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 assertMollomData(), * 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 * The public Mollom API key. * @param $private * The private Mollom API key. * @param $reseller * A boolean that is TRUE if the keys are for a reseller account, or FALSE * otherwise. */ protected function setKeys($public = MOLLOM_TEST_PUBLIC_KEY, $private = MOLLOM_TEST_PRIVATE_KEY, $reseller = MOLLOM_TEST_RESELLER_KEY) { // Save internal properties. $this->public_key = $public; $this->private_key = $private; $this->is_reseller = $reseller; // Set the module key settings. variable_set('mollom_public_key', $public); variable_set('mollom_private_key', $private); // Enable testing mode. variable_set('mollom_testing_mode', 1); // Delete any previously set Mollom servers to make sure we are using // the default ones. variable_del('mollom_servers'); } /** * Call the mollom.verifyKey function directly and check that the current * keys are valid. */ protected function assertValidKeys() { $status = _mollom_status(TRUE); $this->assertMollomWatchdogMessages(); $this->assertIdentical($status, TRUE, t('Mollom servers can be contacted and testing API keys are valid.')); } /** * Configure Mollom protection for a given form. * * @param $form_id * The form id to configure. * @param $mode * The Mollom protection mode for the form. * @param $fields * (optional) A list of form elements to enable for text analysis. If * omitted and the form registers individual elements, all fields are * enabled by default. * @param $edit * (optional) An array of POST data to pass through to drupalPost() when * configuring the form's protection. */ protected function setProtection($form_id, $mode = MOLLOM_MODE_ANALYSIS, $fields = NULL, $edit = array()) { // Always start from overview page, also to make debugging easier. $this->drupalGet('admin/settings/mollom'); // Determine whether the form is already protected. $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $form_id, 0, 1)); // Add a new form. if (!$exists) { $this->clickLink(t('Add form')); $add_form_edit = array( 'mollom[form_id]' => $form_id, ); $this->drupalPost(NULL, $add_form_edit, t('Next')); } // Edit an existing form. else { $this->drupalGet('admin/settings/mollom/manage/' . $form_id); } $edit += array( 'mollom[mode]' => $mode, ); // Process the enabled fields. $form_list = mollom_form_list(); $form_info = mollom_form_info($form_id, $form_list[$form_id]['module']); foreach (array_keys($form_info['elements']) as $field) { if (!isset($fields) || in_array($field, $fields)) { // If the user specified all fields by default or to include this // field, set its checkbox value to TRUE. $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = TRUE; } else { // Otherwise set the field's checkbox value to FALSE. $edit['mollom[enabled_fields][' . rawurlencode($field) . ']'] = FALSE; } } $this->drupalPost(NULL, $edit, t('Save')); if (!$exists) { $this->assertText(t('The form protection has been added.')); } else { $this->assertText(t('The form protection has been updated.')); } } /** * Remove Mollom protection for a given form. * * @param $form_id * The form id to configure. */ protected function delProtection($form_id) { // Determine whether the form is protected. $exists = db_result(db_query_range("SELECT 1 FROM {mollom_form} WHERE form_id = '%s'", $form_id, 0, 1)); if ($exists) { $this->drupalGet('admin/settings/mollom/unprotect/' . $form_id); $this->assertText(t('Mollom will no longer protect this form from spam.'), t('Unprotect confirmation form found.')); $this->drupalPost(NULL, array(), t('Confirm')); } } /** * 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 assertMollomData($entity, $id, $session_id = NULL) { $data = mollom_data_load($entity, $id); $this->assertTrue($data->session_id, t('Mollom session data for %entity @id exists:
@data', array('%entity' => $entity, '@id' => $id, '@data' => var_export($data, TRUE)))); if (isset($session_id)) { $this->assertSame(t('Stored session id'), $data->session_id, $session_id); } return $data; } /** * Assert that no Mollom session data exists for a certain entity. */ protected function assertNoMollomData($entity, $id) { $data = mollom_data_load($entity, $id); $this->assertFalse($data, t('No Mollom session data exists for %entity @id.', array('%entity' => $entity, '@id' => $id))); } /** * Assert that the CAPTCHA field is found on the current page. */ protected function assertCaptchaField() { $this->assertFieldByXPath('//input[@type="text"][@name="mollom[captcha]"]', '', 'CAPTCHA field found.'); $image = $this->xpath('//img[@alt="' . t('Type the characters you see in this picture.') . '"]'); $this->assert(!empty($image), 'CAPTCHA image found.'); } /** * Assert that the CAPTCHA field is not found on the current page. */ protected function assertNoCaptchaField() { $this->assertNoFieldByXPath('//input[@type="text"][@name="mollom[captcha]"]', '', 'CAPTCHA field not found.'); $image = $this->xpath('//img[@alt="' . t('Type the characters you see in this picture.') . '"]'); $this->assert(empty($image), 'CAPTCHA image not found.'); } /** * Assert that the privacy policy link is found on the current page. */ protected function assertPrivacyLink() { $elements = $this->xpath('//div[contains(@class, "mollom-privacy")]'); $this->assertTrue($elements, t('Privacy policy container found.')); } /** * Assert that the privacy policy link is not found on the current page. */ protected function assertNoPrivacyLink() { $elements = $this->xpath('//div[contains(@class, "mollom-privacy")]'); $this->assertFalse($elements, t('Privacy policy container not found.')); } /** * Test submitting a form with a correct CAPTCHA value. * * @param $url * The URL of the form, or NULL to use the current page. * @param $edit * An array of form values used in drupalPost(). * @param $button * The text of the form button to click in drupalPost(). * @param $success_message * An optional message to test does appear after submission. */ protected function postCorrectCaptcha($url, array $edit = array(), $button, $success_message = '') { $edit['mollom[captcha]'] = 'correct'; $this->drupalPost($url, $edit, $button); $this->assertNoCaptchaField(); $this->assertNoText($this->incorrect_message); if ($success_message) { $this->assertText($success_message); } } /** * Test submitting a form with an incorrect CAPTCHA value. * * @param $url * The URL of the form, or NULL to use the current page. * @param $edit * An array of form values used in drupalPost(). * @param $button * The text of the form button to click in drupalPost(). * @param $success_message * An optional message to test does not appear after submission. */ protected function postIncorrectCaptcha($url, array $edit = array(), $button, $success_message = '') { $edit['mollom[captcha]'] = 'incorrect'; $before_url = $this->getUrl(); $this->drupalPost($url, $edit, $button); if ($this->getUrl() == $before_url) { $this->assertCaptchaField(); } $this->assertText($this->incorrect_message); if ($success_message) { $this->assertNoText($success_message); } } /** * Test submitting a form with 'spam' values. * * @param $url * The URL of the form, or NULL to use the current page. * @param $spam_fields * An array of form field names to inject spam content into. * @param $edit * An array of non-spam form values used in drupalPost(). * @param $button * The text of the form button to click in drupalPost(). * @param $success_message * An optional message to test does not appear after submission. */ 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->assertText($this->spam_message); if ($success_message) { $this->assertNoText($success_message); } } /** * Test submitting a form with 'ham' values. * * @param $url * The URL of the form, or NULL to use the current page. * @param $ham_fields * An array of form field names to inject ham content into. * @param $edit * An array of non-spam form values used in drupalPost(). * @param $button * The text of the form button to click in drupalPost(). * @param $success_message * An optional message to test does appear after submission. */ protected function assertHamSubmit($url, array $ham_fields, array $edit = array(), $button, $success_message = '') { $edit += array_fill_keys($ham_fields, 'ham'); $this->drupalPost($url, $edit, $button); $this->assertNoCaptchaField($url); $this->assertNoText($this->spam_message); if ($success_message) { $this->assertText($success_message); } } /** * Test submitting a form with unsure values and resulting CAPTCHA submissions. * * @param $url * The URL of the form, or NULL to use the current page. * @param $unsure_fields * An array of form field names to inject unsure content into. * @param $edit * An array of non-spam form values used in drupalPost(). * @param $button * The text of the form button to click in drupalPost(). * @param $success_message * An optional message to test does appear after sucessful form and CAPTCHA * submission. */ 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->assertText($this->unsure_message); if ($success_message) { $this->assertNoText($success_message); } $this->postIncorrectCaptcha(NULL, $edit, $button, $success_message); $this->postCorrectCaptcha(NULL, $edit, $button, $success_message); } /** * Retrieve a field value by ID. */ 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']; } /** * Retrieve submitted XML-RPC values from testing server implementation. * * @param $method * (optional) The XML-RPC method name to retrieve submitted values from. * Defaults to 'mollom.checkContent'. * * @see MollomWebTestCase::resetServerRecords() * @see mollom_test_xmlrpc() */ protected function getServerRecord($method = 'mollom.checkContent') { // Map the XML-RPC method name to the corresponding function callback name. drupal_load('module', 'mollom_test'); $method_function_map = mollom_test_xmlrpc(); $function = $method_function_map[$method]; // Retrieve last recorded values. $storage = variable_get($function, array()); $return = array_shift($storage); variable_set($function, $storage); return $return; } /** * Resets recorded XML-RPC values. * * @param $method * (optional) The XML-RPC method name to reset records of. Defaults to * 'mollom.checkContent'. * * @see MollomWebTestCase::getServerRecord() * @see mollom_test_xmlrpc() */ protected function resetServerRecords($method = 'mollom.checkContent') { // Map the XML-RPC method name to the corresponding function callback name. drupal_load('module', 'mollom_test'); $method_function_map = mollom_test_xmlrpc(); $function = $method_function_map[$method]; // Delete the variable. variable_del($function); } /** * 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' => WATCHDOG_NOTICE); $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' => WATCHDOG_NOTICE); $this->assertMollomWatchdogMessages($options['watchdog']); return $output; } /** * Asserts that two values belonging to the same variable are equal. * * Checks to see whether two values, which belong to the same variable name or * identifier, are equal and logs a readable assertion message. * * @param $name * A name or identifier to use in the assertion message. * @param $first * The first value to check. * @param $second * The second value to check. * * @return * TRUE if the assertion succeeded, FALSE otherwise. * * @see MollomWebTestCase::assertNotSame() * * @todo D8: Move into core. This improved assertEqual() did not get into D7, * since the function signature differs and it's plenty of work to manually * update all assertEqual() invocations throughout all tests. */ protected function assertSame($name, $first, $second) { $message = t("@name: @first is equal to @second.", array( '@name' => $name, '@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE), )); $this->assertEqual($first, $second, $message); } /** * Asserts that two values belonging to the same variable are not equal. * * Checks to see whether two values, which belong to the same variable name or * identifier, are not equal and logs a readable assertion message. * * @param $name * A name or identifier to use in the assertion message. * @param $first * The first value to check. * @param $second * The second value to check. * * @return * TRUE if the assertion succeeded, FALSE otherwise. * * @see MollomWebTestCase::assertSame() */ protected function assertNotSame($name, $first, $second) { $message = t("@name: '@first' is not equal to '@second'.", array( '@name' => $name, '@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE), )); $this->assertNotEqual($first, $second, $message); } } /** * Tests module installation and global status handling. */ class MollomInstallationTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Installation and key handling', 'description' => 'Tests module installation and key error handling.', 'group' => 'Mollom', ); } function setUp() { // Re-initialize stored session_id and watchdog messages. $this->resetSessionID(); $this->messages = array(); $this->disableDefaultSetup = TRUE; parent::setUp('comment'); $this->admin_user = $this->drupalCreateUser(array( 'access administration pages', 'administer site configuration', 'administer permissions', )); $this->web_user = $this->drupalCreateUser(); } /** * Tests status handling after installation. * * We walk through a regular installation of the Mollom module instead of using * setUp() to ensure that everything works as expected. * * Note: Partial error messages tested here; hence, no t(). */ function testInstallationProcess() { $admin_message = t('Visit the Mollom settings page to configure your keys.', array( '@settings-url' => url('admin/settings/mollom/settings'), )); $this->drupalLogin($this->admin_user); // Ensure there is no requirements error by default. $this->drupalGet('admin/reports/status'); $this->clickLink('run cron manually'); // Install the Mollom module. $this->drupalPost('admin/build/modules', array('status[mollom]' => TRUE), t('Save configuration')); $this->assertRaw(t('The Mollom API keys are not configured yet. !admin-message', array( '!admin-message' => $admin_message, )), t('Post installation warning found.')); // Verify that forms can be submitted without valid Mollom module configuration. $node = $this->drupalCreateNode(array('type' => 'story', 'promoted' => TRUE)); $this->drupalLogin($this->web_user); $this->drupalGet('comment/reply/' . $node->nid); $edit = array( 'comment' => 'spam', ); $this->drupalPost(NULL, $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); $this->assertRaw('
' . $edit['comment'] . '
', t('Comment found.')); // Assign the 'administer mollom' permission and log in a user. $this->drupalLogin($this->admin_user); $edit = array( DRUPAL_AUTHENTICATED_RID . '[administer mollom]' => TRUE, ); $this->drupalPost('admin/user/permissions', $edit, t('Save permissions')); // Verify presence of 'empty keys' error message. $this->drupalGet('admin/settings/mollom'); $this->assertText('The Mollom API keys are not configured yet.'); $this->assertNoText('The configured Mollom API keys are invalid.'); // Verify requirements error about missing API keys. $this->drupalGet('admin/reports/status'); $this->assertRaw(t('The Mollom API keys are not configured yet. !admin-message', array( '!admin-message' => $admin_message, )), t('Requirements error found.')); // Configure invalid keys. $edit = array( 'mollom_public_key' => 'foo', 'mollom_private_key' => 'bar', ); $this->drupalGet('admin/settings/mollom/settings'); $this->drupalPost(NULL, $edit, t('Save configuration'), array('watchdog' => WATCHDOG_EMERG)); $this->assertText(t('The configuration options have been saved.')); $this->assertNoText($this->fallback_message, t('Fallback message not found.')); // Verify presence of 'incorrect keys' error message. $this->assertText('The configured Mollom API keys are invalid.'); $this->assertNoText('The Mollom API keys are not configured yet.'); $this->assertNoText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.')); // Verify requirements error about invalid API keys. $this->drupalGet('admin/reports/status', array('watchdog' => WATCHDOG_EMERG)); $this->assertText('The configured Mollom API keys are invalid.'); // Ensure unreachable servers. variable_set('mollom_servers', array('http://fake-host')); // Verify presence of 'network error' message. $this->drupalGet('admin/settings/mollom/settings', array('watchdog' => WATCHDOG_EMERG)); $this->assertText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.')); // Ensure unreachable servers. variable_set('mollom_servers', array('http://fake-host')); // Verify requirements error about network error. $this->drupalGet('admin/reports/status', array('watchdog' => WATCHDOG_EMERG)); $this->assertText(t('The Mollom servers could not be contacted. Please make sure that your web server can make outgoing HTTP requests.')); $this->assertNoText($this->fallback_message, t('Fallback message not found.')); // Verify that valid keys work. $this->drupalGet('admin/settings/mollom/settings', array('watchdog' => WATCHDOG_EMERG)); $edit = array( 'mollom_public_key' => MOLLOM_TEST_PUBLIC_KEY, 'mollom_private_key' => MOLLOM_TEST_PRIVATE_KEY, 'mollom_testing_mode' => 1, ); $this->drupalPost(NULL, $edit, t('Save configuration')); $this->assertText(t('The configuration options have been saved.')); $this->assertText('The services are operating correctly.'); $this->assertNoText('The Mollom API keys are not configured yet.'); $this->assertNoText('The configured Mollom API keys are invalid.'); // Verify presence of testing mode warning. $this->drupalGet('admin/settings/mollom'); $this->assertText('Mollom testing mode is still enabled.'); } } /** * 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. // By default (i.e., omitting 'checks') we expect spam and quality checking // only. $data['post_body'] = 'ham'; $result = mollom('mollom.checkContent', $data); $this->assertMollomWatchdogMessages(); $this->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_HAM); $this->assertSame('quality', $result['quality'], 1); $this->assertTrue(!isset($result['profanity']), 'profanity not returned.'); $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->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_SPAM); $this->assertSame('quality', $result['quality'], 0); $this->assertTrue(!isset($result['profanity']), 'profanity not returned.'); $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->assertSame('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE); $this->assertSame('quality', $result['quality'], 0.5); $this->assertTrue(!isset($result['profanity']), 'profanity not returned.'); $session_id = $this->assertSessionID($result['session_id']); // Additionally enable profanity checking. $data['post_body'] = 'spam 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_SPAM); $this->assertSame('quality', $result['quality'], 0); $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); $this->assertMollomWatchdogMessages(); $this->assertTrue(!isset($result['spam']), 'spam not returned.'); $this->assertTrue(!isset($result['quality']), 'quality not returned.'); $this->assertSame('profanity', $result['profanity'], 1); $session_id = $this->assertSessionID($result['session_id']); // Pass arbitrary string to profanity checking. $data['post_body'] = $this->randomString(12); $data['session_id'] = $session_id; $result = mollom('mollom.checkContent', $data); $this->assertMollomWatchdogMessages(); $this->assertTrue(!isset($result['spam']), 'spam not returned.'); $this->assertTrue(!isset($result['quality']), 'quality not returned.'); $this->assertSame('profanity', $result['profanity'], 0); $session_id = $this->assertSessionID($result['session_id']); } /** * Tests results of mollom.checkContent() across requests for a single session. */ function testCheckContentSession() { $data = array( 'author_name' => $this->admin_user->name, 'author_mail' => $this->admin_user->mail, 'author_id' => $this->admin_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( 'session_id' => $data['session_id'], 'author_ip' => $data['author_ip'], ); $result = mollom('mollom.getImageCaptcha', $captcha_data); $this->assertMollomWatchdogMessages(); $data['session_id'] = $this->assertSessionID($result['session_id']); $captcha_data = array( 'session_id' => $data['session_id'], 'author_ip' => $data['author_ip'], 'author_id' => $data['author_id'], '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() { // Ensure we get no SSL URL by default. $data = array( 'author_ip' => ip_address(), ); $result = mollom('mollom.getImageCaptcha', $data); $this->assertMollomWatchdogMessages(); $this->assertTrue(strpos($result['url'], 'http://') === 0, t('CAPTCHA URL uses HTTP protocol.')); // Ensure we get a SSL URL when passing the 'ssl' parameter. $data = array( 'author_ip' => ip_address(), 'ssl' => TRUE, ); $result = mollom('mollom.getImageCaptcha', $data); $this->assertMollomWatchdogMessages(); $this->assertTrue(strpos($result['url'], 'https://') === 0, t('CAPTCHA URL uses HTTPS protocol.')); } /** * Tests mollom.checkCaptcha(). */ function testCheckCaptcha() { // Ensure we can send an 'author_id'. // Verifying no severe watchdog messages is sufficient, as unsupported // parameters would trigger a XML-RPC error. $uid = rand(); $data = array( 'author_ip' => ip_address(), 'author_id' => $uid, ); $result = mollom('mollom.getImageCaptcha', $data); $this->assertMollomWatchdogMessages(); $data += array( 'session_id' => $result['session_id'], 'captcha_result' => 'correct', ); $result = mollom('mollom.checkCaptcha', $data); $this->assertMollomWatchdogMessages(); } } class MollomAccessTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Access checking', 'description' => 'Confirm that there is a working key pair and that this status is correctly indicated on the module settings page for appropriate users.', 'group' => 'Mollom', ); } /** * Configure an invalid key pair and ensure error message. */ function testKeyPairs() { // No error message or watchdog messages should be thrown with default // testing keys. $this->drupalLogin($this->admin_user); $this->drupalGet('admin/settings/mollom/settings'); // Set up invalid test keys and check that an error message is shown. $edit = array( 'mollom_public_key' => 'invalid-public-key', 'mollom_private_key' => 'invalid-private-key', ); $this->drupalPost(NULL, $edit, t('Save configuration'), array('watchdog' => WATCHDOG_EMERG)); $this->assertText(t('The configuration options have been saved.')); $this->assertText('The configured Mollom API keys are invalid.'); } /** * Make sure that the Mollom settings page works for users with the * 'administer mollom' permission but not those without * it. */ function testAdminAccessRights() { // Check access for a user that only has access to the 'administer // site configuration' permission. This user should have access to // the Mollom settings page. $this->drupalLogin($this->admin_user); $this->drupalGet('admin/settings/mollom'); $this->assertResponse(200); // Check access for a user that has everything except the 'administer // mollom' permission. This user should not have access to the Mollom // settings page. $this->web_user = $this->drupalCreateUser(array_diff(module_invoke_all('perm'), array('administer mollom'))); $this->drupalLogin($this->web_user); $this->drupalGet('admin/settings/mollom'); $this->assertResponse(403); } /** * Tests 'bypass access' property of registered forms. */ function testBypassAccess() { $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); $this->drupalLogout(); $node = $this->drupalCreateNode(array('body' => 'node body', 'type' => 'story')); // Create a regular user and post a comment. $this->web_user = $this->drupalCreateUser(); $this->drupalLogin($this->web_user); $edit = array( 'comment' => 'ham', ); $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); $this->assertText('node body'); $this->assertText($edit['comment']); // Ensure a user having one of the permissions to bypass access can post // spam without triggering the spam protection. $this->drupalLogin($this->admin_user); $this->drupalGet('node/' . $node->nid); $this->clickLink('edit'); $edit = array( 'subject' => '', 'comment' => 'spam', ); $this->drupalPost(NULL, $edit, t('Preview')); $this->assertNoText($this->spam_message); $this->drupalPost(NULL, array(), t('Save')); $this->assertNoText($this->spam_message); $this->assertText('node body'); // Log in back the regular user and try to edit the comment containing spam. $this->drupalLogin($this->web_user); $this->drupalGet('node/' . $node->nid); $this->clickLink('edit'); $this->drupalPost(NULL, array(), t('Preview')); $this->assertText($this->spam_message); $this->drupalPost(NULL, array(), t('Save')); $this->assertText($this->spam_message); $this->assertNoText('node body'); } } class MollomFallbackTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Fallback behavior', 'description' => 'Check that the module uses the correct fallback mechanism when one or more of the specified Mollom servers are not available.', 'group' => 'Mollom', ); } function setUp() { // Enable testing server implementation. parent::setUp('mollom_test'); } /** * Make sure that "request new password" submissions can be blocked when * the Mollom servers are unreachable. */ function testFallbackMechanismBlock() { // Enable Mollom for the request password form. $this->drupalLogin($this->admin_user); $this->setProtection('user_pass', MOLLOM_MODE_CAPTCHA); // Set the fallback strategy to 'blocking mode'. $this->drupalPost('admin/settings/mollom/settings', array('mollom_fallback' => MOLLOM_FALLBACK_BLOCK), t('Save configuration')); $this->assertText('The configuration options have been saved.'); $this->drupalLogout(); // Configure Mollom to use a non-existent server as that should trigger // the fallback mechanism. variable_set('mollom_servers', array('http://fake-host')); // Check the password request form. // @todo Test mail sending with assertMail() now that it is available. $this->drupalGet('user/password', array('watchdog' => WATCHDOG_EMERG)); $this->assertNoCaptchaField(); $this->assertText($this->fallback_message); } /** * Make sure that "request new password" submissions can be allowed when * the Mollom servers are unreachable. */ function testFallbackMechanismAccept() { // Enable Mollom for the request password form. $this->drupalLogin($this->admin_user); $this->setProtection('user_pass', MOLLOM_MODE_CAPTCHA); // Set the fallback strategy to 'accept mode'. $this->drupalPost('admin/settings/mollom/settings', array('mollom_fallback' => MOLLOM_FALLBACK_ACCEPT), t('Save configuration')); $this->assertText('The configuration options have been saved.'); $this->drupalLogout(); // Configure Mollom to use a non-existent server as that should trigger // the fallback mechanism. variable_set('mollom_servers', array('http://fake-host')); // Check the password request form. $this->drupalGet('user/password', array('watchdog' => WATCHDOG_EMERG)); $this->assertNoCaptchaField(); $this->assertNoText($this->fallback_message); } /** * Make sure that spam protection is still active even when some of the * Mollom servers are unavailable. * * @todo Test mail sending with assertMail() now that it is available. */ function testFailoverMechanism() { $this->drupalLogin($this->admin_user); $this->setProtection('user_pass', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Set the fallback strategy to 'blocking mode', so that if the failover // mechanism does not work, we would expect to get a warning. variable_set('mollom_fallback', MOLLOM_FALLBACK_BLOCK); // Configure Mollom to use a list of servers that have a number of // unknown servers, but one real server. variable_set('mollom_servers', array( 'http://fake-host-1', 'http://fake-host-2', $GLOBALS['base_url'] . '/xmlrpc.php?version=', 'http://xmlrpc1.mollom.com', // The real server. 'http://fake-host-3', )); // Validate that the request password form has a CAPTCHA text field and // that a user is not blocked from submitting it. $this->drupalGet('user/password'); $this->assertCaptchaField(); $this->assertNoText($this->fallback_message); $this->postCorrectCaptcha('user/password', array('name' => $this->admin_user->name), t('E-mail new password')); $this->assertText(t('Further instructions have been sent to your e-mail address.')); } } class MollomServerListRecoveryTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Server list recovery', 'description' => 'Check that the module can recover from an invalid server list.', 'group' => 'Mollom', ); } /** * Make sure the server list is reset when the Mollom servers are unavailable or incorrect. */ function testServerListRecovery() { $list = array( array( 'http://not-a-valid-server-1', 'http://not-a-valid-server-2', ), // The lack of the http://-schema results in different error codes array( 'not-a-valid-server-url-1', 'not-a-valid-server-url-2', ), ); foreach ($list as $servers) { // Call mollom.verifyKey with an invalid server list. The expected behavior // is that the first call fails, but that the second call succeeds because // the server list is automatically reset or recovered by the Mollom module. variable_set('mollom_servers', $servers); $key_is_valid = mollom('mollom.verifyKey'); $this->assertIdentical($key_is_valid, NETWORK_ERROR, t('The Mollom servers could not be contacted.')); $this->assertMollomWatchdogMessages(WATCHDOG_EMERG); $key_is_valid = mollom('mollom.verifyKey'); $this->assertIdentical($key_is_valid, TRUE, t('The Mollom servers could be contacted.')); $this->assertMollomWatchdogMessages(); } } } class MollomLanguageDetectionTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Language detection', 'description' => 'Tests language detection functionality.', 'group' => 'Mollom', ); } /** * Test the language detection functionality at the API level without using a web interface. */ function testLanguageDetectionAPI() { // Note that Mollom supports more languages than those tested. $strings = array( 'en' => "Hi, this is a test of the language detection code to see if it works well.", 'nl' => "Hallo, dit is een test van de taaldetectiecode om te controleren of het werkt.", 'fr' => "Bonjour, ceci est un test du detecteur langue automatique pour voir ci ça marche bien.", 'de' => "Bedecke deinen Himmel, Zeus, Mit Wolkendunst Und übe, dem Knaben gleich, der Disteln köpft, An Eichen dich und Bergeshöhn.", 'ko' => "'엄마야 누나야 강변살자. 뜰에는 반짝이는 금모래 빛. 뒷문 밖에는 갈잎의 노래", 'ru' => "Холуй трясется. Раб хохочет. Палач свою секиру точит. Тиран кромсает каплуна. Сверкает зимняя луна.", 'hu' => "Földszintiek mászófámról pillantva fejjel lefelé ti lógtok bele nézőim az űrbe ki tudja így kölcsönös kíváncsiak a helyes felelet kié", 'el' => "Σαν να 'χουνε την όψη της αιώνες οργωμένη. Κάτι άναρχο κι ατέλειωτο στο πρόσωπό της μένει.", 'ja' => "吹くからに秋の草木のしをるれば", 'th' => "ทั่วประเทศ ประมาณ ๔๐,๐๐๐ แห่ง ชาวไทยนับตั้งแต่ครั้งอดีตมีวิถี ชีวิตผูกพันกับพุทธศาสนาอย่างใกล้ชิด แสดงออกมาเป็น ขนบธรรมเนียมประเพณี", 'zh' => "螽斯羽,诜诜兮。宜尔子孙,振振兮", ); foreach ($strings as $language => $text) { $result = mollom('mollom.detectLanguage', array('text' => $text)); $this->assertEqual($result[0]['language'], $language, t('A language code was specified and they match.')); $this->assertTrue($result[0]['confidence'] > 0, t('A confidence value was specified and it is greater than 0.')); } } } /** * Tests blacklist functionality. * * The blacklists are stored on the server. These tests can fail when * different people run the tests at the same time because all tests share * the same blacklist. You can configure a custom key to avoid this. */ class MollomBlacklistTestCase extends MollomWebTestCase { protected $profile = 'testing'; public static function getInfo() { return array( 'name' => 'Blacklist', 'description' => 'Tests URL and text blacklist functionality.', 'group' => 'Mollom', ); } function setUp() { $this->disableDefaultSetup = TRUE; parent::setUp('mollom'); $this->setKeys(); } /** * Test the URL blacklist functionality at the API level without using a web interface. */ function testUrlBlacklistAPI() { // Remove any stale blacklist entries from test runs that did not finish. $blacklist = mollom('mollom.listBlacklistURL'); foreach ($blacklist as $entry) { if (time() - strtotime($entry['created']) > 86400) { mollom('mollom.removeBlacklistURL', array('url' => $entry['url'])); } } // Blacklist a URL. $domain = $this->randomName() . '.com'; $result = mollom('mollom.addBlacklistURL', array('url' => 'http://' . $domain)); $this->assertTrue($result, t('The URL was blacklisted.')); // Check whether posts containing the blacklisted URL are properly blocked. $result = mollom('mollom.checkContent', array( 'post_body' => "When the exact URL is present, the post should get blocked: http://{$domain}", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Exact URL match was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When the URL is expanded in the back, the post should get blocked: http://{$domain}/oh-my", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Partial URL match was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When the URL is expanded in the front, the post should get blocked: http://www.{$domain}", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('URL with www-prefix was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When the URL has a different schema, the post should get blocked: ftp://www.{$domain}", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('URL with different schema was blocked.')); // @todo Not implemented yet. /* $result = mollom('mollom.checkContent', array( 'post_body' => "When the domain appears on its own, the post should get blocked: www.{$domain}", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Plain domain name with www-prefix was blocked.')); */ $result = mollom('mollom.removeBlacklistURL', array('url' => 'http://' . $domain)); $this->assertTrue($result, t('The blacklisted URL was removed.')); } /** * Test the text blacklist functionality at the API level without using a web interface. */ function testTextBlacklistAPI() { // Remove any stale blacklist entries from test runs that did not finish. $blacklist = mollom('mollom.listBlacklistText'); foreach ($blacklist as $entry) { if (time() - strtotime($entry['created']) > 86400) { mollom('mollom.removeBlacklistText', array( 'text' => $entry['text'], 'context' => $entry['context'], 'reason' => $entry['reason'], )); } } // Blacklist a word. // @todo As of now, only non-numeric, lower-case text seems to be supported. $term = drupal_strtolower(preg_replace('/[^a-zA-Z]/', '', $this->randomName())); $result = mollom('mollom.addBlacklistText', array( 'text' => $term, 'context' => 'everything', 'reason' => 'spam', 'match' => 'contains', )); $this->assertIdentical($result, TRUE, t('The text was blacklisted.')); // Check whether posts containing the blacklisted word are properly blocked. $result = mollom('mollom.checkContent', array( 'post_body' => $term, )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Identical match was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When the term is present, the post should get blocked: " . $term, )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Exact match was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When match is 'contains', the word can be surrounded by other text: abc" . $term . "def", )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Partial match was blocked.')); // Update the blacklist entry to match the term only exactly. $result = mollom('mollom.addBlacklistText', array( 'text' => $term, 'context' => 'everything', 'reason' => 'spam', 'match' => 'exact', )); $this->assertTrue($result, t('The text was blacklisted.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When match is 'exact', it has to be exact: " . $term, )); $this->assertEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Exact match was blocked.')); $result = mollom('mollom.checkContent', array( 'post_body' => "When match is 'exact', it has to be exact: abc{$term}def", )); $this->assertNotEqual($result['spam'], MOLLOM_ANALYSIS_SPAM, t('Partial match was not blocked.')); $result = mollom('mollom.removeBlacklistText', array( 'text' => $term, 'context' => 'everything', 'reason' => 'spam', )); $this->assertTrue($result, t('The blacklisted text was removed.')); // Try to remove a non-existing entry. $result = mollom('mollom.removeBlacklistText', array( 'text' => $term, 'context' => 'everything', 'reason' => 'spam', )); $this->assertMollomWatchdogMessages(WATCHDOG_EMERG); $this->assertNotIdentical($result, TRUE, t('Error response for a non-existing blacklist text found.')); } /** * Test the blacklist administration interface. * * We don't need to check whether the blacklisting actually works * (i.e. blocks posts) because that is tested in testTextBlacklistAPI() and * testURLBlacklistAPI(). */ function testBlacklistUI() { // Log in as an administrator and access the blacklist administration page. $this->admin_user = $this->drupalCreateUser(array( 'administer mollom', 'access administration pages', )); $this->drupalLogin($this->admin_user); // Add a word to the spam blacklist. $this->drupalGet('admin/settings/mollom/blacklist'); $text = $this->randomName(); $edit = array( 'entry[text]' => $text, 'entry[context]' => 'everything', 'entry[match]' => 'contains', ); $this->drupalPost(NULL, $edit, t('Add')); $text = drupal_strtolower($text); $this->assertText(t('The entry was added to the blacklist.')); $this->assertText($text); // Remove the word from the spam blacklist. $links = $this->xpath('//td[contains(., "' . $text . '")]/following-sibling::td/a'); $delete_url = $GLOBALS['base_root'] . (string) $links[0]['href']; $this->drupalGet($delete_url); $this->drupalPost(NULL, array(), t('Delete')); $this->assertEqual($this->getUrl(), url('admin/settings/mollom/blacklist', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertNoText($text, 'Text blacklist removed.'); // Add a word to the profanity blacklist. $this->drupalGet('admin/settings/mollom/blacklist/profanity'); $text = $this->randomName(); $edit = array( 'entry[text]' => $text, 'entry[context]' => 'everything', 'entry[match]' => 'contains', ); $this->drupalPost(NULL, $edit, t('Add')); $this->assertText(t('The entry was added to the blacklist.')); $text = drupal_strtolower($text); $this->assertText($text); // Remove the word from the profanity blacklist. $links = $this->xpath('//td[contains(., "' . $text . '")]/following-sibling::td/a'); $delete_url = $GLOBALS['base_root'] . (string) $links[0]['href']; $this->drupalGet($delete_url); $this->drupalPost(NULL, array(), t('Delete')); $this->assertEqual($this->getUrl(), url('admin/settings/mollom/blacklist/profanity', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertNoText($text, 'Text blacklist removed.'); } } /** * Tests Mollom form configuration functionality. */ class MollomFormConfigurationTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Form administration', 'description' => 'Verify that forms can be properly protected and unprotected.', '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); } /** * Tests configuration of form fields for textual analysis. */ function testFormFieldsConfiguration() { // Protect Mollom test form. $this->drupalGet('admin/settings/mollom/add'); $edit = array( 'mollom[form_id]' => 'mollom_test_form', ); $this->drupalPost(NULL, $edit, t('Next')); $this->assertText('Mollom test form'); $edit = array( 'mollom[mode]' => MOLLOM_MODE_ANALYSIS, 'mollom[enabled_fields][title]' => TRUE, 'mollom[enabled_fields][body]' => TRUE, 'mollom[enabled_fields][exclude]' => FALSE, 'mollom[enabled_fields][' . rawurlencode('parent][child') . ']' => TRUE, 'mollom[enabled_fields][field]' => TRUE, ); $this->drupalPost(NULL, $edit, t('Save')); // Verify that mollom_test_form form was protected. $this->assertText(t('The form protection has been added.')); $this->assertText('Mollom test form'); $mollom_form = mollom_form_load('mollom_test_form'); $this->assertTrue($mollom_form, t('Form configuration exists.')); // Verify that field configuration was properly stored. $this->drupalGet('admin/settings/mollom/manage/mollom_test_form'); foreach ($edit as $name => $value) { // Skip any inputs that are not the fields for analysis checkboxes. if (strpos($name, '[enabled_fields]') === FALSE) { continue; } // assertFieldByName() does not work for checkboxes. // @see assertFieldChecked() $elements = $this->xpath('//input[@name="' . $name . '"]'); if (isset($elements[0])) { if ($value) { $this->assertTrue(!empty($elements[0]['checked']), t('Field @name is checked', array('@name' => $name))); } else { $this->assertTrue(empty($elements[0]['checked']), t('Field @name is not checked', array('@name' => $name))); } } else { $this->fail(t('Field @name not found.', array('@name' => $name))); } } // Add a field to the stored configuration that existed previously. $mollom_form['enabled_fields'][] = 'orphan_field'; mollom_form_save($mollom_form); // Verify that field configuration contains only available elements. $this->drupalGet('admin/settings/mollom/manage/mollom_test_form'); $form_info = mollom_form_info('mollom_test_form', 'mollom_test'); $fields = $this->xpath('//input[starts-with(@name, "mollom[enabled_fields]")]'); $elements = array(); foreach ($fields as $field) { $elements[] = substr(substr(rawurldecode($field['name']), 0, -1), 23); } $this->assertEqual($elements, array_keys($form_info['elements']), t('Field list only contains available form elements.')); // Try a simple submit of the form. $this->drupalLogout(); $edit = array( 'title' => 'unsure', ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $this->assertNoText('Successful form submission.'); $this->assertText($this->unsure_message); $this->postCorrectCaptcha(NULL, array(), 'Submit', 'Successful form submission.'); // Try to submit values for top-level fields. $edit = array( 'title' => 'spam', 'body' => 'spam', ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $this->assertNoText('Successful form submission.'); $this->assertNoText($this->unsure_message); $this->assertText($this->spam_message); // Try to submit values for nested field. $edit = array( 'title' => $this->randomString(), 'parent[child]' => 'spam', ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $this->assertNoText('Successful form submission.'); $this->assertNoText($this->unsure_message); $this->assertText($this->spam_message); // Try to submit values for nested field and multiple value field. // Start with ham values for simple, nested, and first multiple field. $edit = array( 'title' => 'ham', 'parent[child]' => 'ham', 'field[new]' => 'ham', ); $this->drupalPost('mollom-test/form', $edit, 'Add'); // Verify that the form was rebuilt. $this->assertNoText('Successful form submission.'); $this->assertNoText($this->unsure_message); $this->assertNoText($this->spam_message); // Add another value for multiple field. $edit = array( 'field[new]' => 'ham', ); $this->drupalPost(NULL, $edit, 'Add'); // Verify that the form was rebuilt. $this->assertNoText('Successful form submission.'); $this->assertNoText($this->unsure_message); $this->assertNoText($this->spam_message); // Now replace all ham values with random values, add a spam value to the // multiple field and submit the form. $edit = array( 'title' => $this->randomString(), 'parent[child]' => $this->randomString(), 'field[0]' => $this->randomString(), 'field[1]' => $this->randomString(), 'field[new]' => 'spam', ); $this->drupalPost(NULL, $edit, 'Submit'); // Verify that the form was not submitted and cannot be submitted. $this->assertNoText('Successful form submission.'); $this->assertText($this->spam_message); // Verify that we can remove the form protection. $this->drupalLogin($this->admin_user); $this->drupalGet('admin/settings/mollom'); $this->assertText('Mollom test form'); $this->drupalPost('admin/settings/mollom/unprotect/mollom_test_form', array(), t('Confirm')); $this->assertEqual($this->getUrl(), url('admin/settings/mollom', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertNoText('Mollom test form'); $this->assertText(t('The form protection has been removed.')); $mollom_form = mollom_form_load('mollom_test_form'); $this->assertFalse($mollom_form, t('Form protection not found.')); // Verify that the form is no longer protected. $this->drupalLogout(); $edit = array( 'title' => 'unsure', ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $this->assertText('Successful form submission.'); $this->assertNoText($this->unsure_message); $this->assertNoCaptchaField(); } /** * Tests default configuration, protecting, and unprotecting forms. */ function testFormAdministration() { $form_info = mollom_form_list(); foreach ($form_info as $form_id => $info) { $form_info[$form_id] += mollom_form_info($form_id, $info['module']); } // Verify that user registration form is not protected. $this->drupalGet('admin/settings/mollom'); $this->assertNoText($form_info['user_register']['title']); $this->assertFalse(mollom_form_load('user_register'), t('Form configuration does not exist.')); // Re-protect user registration form. $this->drupalGet('admin/settings/mollom/add'); $this->assertNoText(t('All available forms are protected already.')); $edit = array( 'mollom[form_id]' => 'user_register', ); $this->drupalPost(NULL, $edit, t('Next')); $this->assertText($form_info['user_register']['title']); $this->assertNoText(t('Text fields to analyze')); $this->drupalPost(NULL, array(), t('Save')); // Verify that user registration form was protected. $this->assertText(t('The form protection has been added.')); $this->assertText($form_info['user_register']['title']); $this->assertTrue(mollom_form_load('user_register'), t('Form configuration exists.')); // Retrieve a list of all permissions to verify them below. $all_permissions = array(); foreach (module_implements('perm') as $module) { // Transpose permissions into D7 format. $module_permissions = array_flip(module_invoke($module, 'perm')); foreach ($module_permissions as $permission => $key) { $module_permissions[$permission] = array( 'title' => $permission, 'module' => $module, ); } $all_permissions += $module_permissions; } // Iterate over all unconfigured forms and protect them. foreach ($form_info as $form_id => $info) { if (!mollom_form_load($form_id)) { $edit = array( 'mollom[form_id]' => $form_id, ); $this->drupalPost('admin/settings/mollom/add', $edit, t('Next')); $this->assertText($info['title']); // Verify that forms specifying elements have all possible elements // preselected for textual analysis. $edit = array(); if (!empty($info['elements'])) { foreach ($info['elements'] as $field => $label) { $field = rawurlencode($field); $this->assertFieldByName("mollom[enabled_fields][$field]", TRUE); } } // Verify that CAPTCHA-only forms contain no configurable fields. else { $this->assertNoText(t('Analyze text for')); $this->assertNoText(t('Text fields to analyze')); } // Verify that bypass permissions are output. $this->assertText($all_permissions['bypass mollom protection']['title']); foreach ($info['bypass access'] as $permission) { $this->assertText($all_permissions[$permission]['title']); } $this->drupalPost(NULL, $edit, t('Save')); $this->assertText(t('The form protection has been added.')); } } // Verify that trying to add a form redirects to the overview. $this->drupalGet('admin/settings/mollom/add'); $this->assertText(t('All available forms are protected already.')); $this->assertText(t('Operations')); } /** * Tests programmatically, conditionally disabling Mollom. */ function testFormAlter() { // Enable CAPTCHA-only protection for request user password form. $this->drupalLogin($this->admin_user); $this->setProtection('user_pass', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Verify regular form protection. $this->drupalGet('user/password'); $this->assertCaptchaField(); // Conditionally disable protection and verify again. variable_set('mollom_test_disable_mollom', TRUE); $this->drupalGet('user/password'); $this->assertNoCaptchaField(); } } class MollomUserFormsTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'User registration and password protection', 'description' => 'Check that the user registration and password request forms can be protected.', 'group' => 'Mollom', ); } /** * Make sure that the request password form is protected correctly. * * @todo Test mail sending with assertMail() now that it is available. */ function testProtectRequestPassword() { // We first enable Mollom for the request password form. $this->drupalLogin($this->admin_user); $this->setProtection('user_pass', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Create a new user. $this->web_user = $this->drupalCreateUser(); $this->drupalGet('user/password'); // Try to reset the user's password by specifying an invalid CAPTCHA. $edit = array('name' => $this->web_user->name); $this->postIncorrectCaptcha('user/password', $edit, t('E-mail new password')); $this->postCorrectCaptcha(NULL, array(), t('E-mail new password')); // Try to reset the user's password by specifying a valid CAPTCHA. $this->postCorrectCaptcha('user/password', $edit, t('E-mail new password')); $this->assertText(t('Further instructions have been sent to your e-mail address.')); } /** * Make sure that the user registration form is protected correctly. */ function testProtectRegisterUser() { // We first enable Mollom for the user registration form. $this->drupalLogin($this->admin_user); $this->setProtection('user_register', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Validate that the user registration form has a CAPTCHA text field. $this->drupalGet('user/register'); $this->assertCaptchaField(); // Try to register with an invalid CAPTCHA. Make sure the user did not // successfully register. $name = $this->randomName(); $edit = array( 'name' => $name, 'mail' => $name . '@example.com', ); $this->postIncorrectCaptcha('user/register', $edit, t('Create new account')); $this->assertFalse(user_load(array('name' => $name)), t('The user who attempted to register cannot be found in the database when the CAPTCHA is invalid.')); // Try to register with a valid CAPTCHA. Make sure the user was able // to successfully register. $this->postCorrectCaptcha('user/register', $edit, t('Create new account')); $this->assertText(t('Your password and further instructions have been sent to your e-mail address.')); $this->assertTrue(user_load(array('name' => $name)), t('The user who attempted to register appears in the database when the CAPTCHA is valid.')); } } /** * Tests Profile module integration. */ class MollomProfileFormsTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Profile integration', 'description' => 'Tests Profile module integration.', 'group' => 'Mollom', ); } function setUp() { parent::setUp(array('profile')); } /** * Tests Profile module integration with user registration form. */ function testProfileRegistration() { $this->drupalLogin($this->admin_user); // Add the three supported profile field types. $fields = array(); foreach (array('textfield', 'textarea', 'url', 'list') as $type) { $name = 'profile_' . drupal_strtolower($this->randomName()); $title = $this->randomString(); $edit = array( 'category' => 'Registration', 'title' => $title, 'name' => $name, 'register' => 1, ); $fields[$name] = $edit + array( 'type' => $type, ); $this->drupalPost('admin/user/profile/add/' . $type, $edit, t('Save field')); } // Enable text analysis protection for user registration form. $this->setProtection('user_register', MOLLOM_MODE_ANALYSIS); $this->drupalLogout(); // Test each supported field separately. foreach ($fields as $key => $field) { $this->drupalGet('user/register'); $this->assertNoCaptchaField(); $name = $this->randomName(); $edit = array( 'name' => $name, 'mail' => $name . '@example.com', $key => $field['type'] != 'url' ? 'unsure' : 'http://example.com/unsure', ); $this->drupalPost(NULL, $edit, t('Create new account')); $this->assertCaptchaField(); } $this->postCorrectCaptcha(NULL, array(), t('Create new account')); $this->assertText(t('Your password and further instructions have been sent to your e-mail address.')); $this->assertTrue(user_load(array('name' => $name)), t('New user was found in database.')); } } class MollomCommentFormTestCase extends MollomWebTestCase { private $node; public static function getInfo() { return array( 'name' => 'Comment form protection', 'description' => 'Check that the comment submission form can be protected.', 'group' => 'Mollom', ); } function setUp() { parent::setUp('comment'); $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'post comments without approval', 'create story content')); $this->node = $this->drupalCreateNode(array('type' => 'story', 'uid' => $this->web_user->uid)); variable_set('comment_preview_story', COMMENT_PREVIEW_OPTIONAL); } /** * Make sure that the comment submission form can be unprotected. */ function testUnprotectedCommentForm() { // Request the comment reply form. There should be no CAPTCHA. $this->drupalLogin($this->web_user); $this->drupalGet('comment/reply/'. $this->node->nid); $this->assertNoCaptchaField(); $this->assertNoPrivacyLink(); // Preview a comment that is 'spam' and make sure there is still no CAPTCHA. $this->drupalPost(NULL, array('comment' => 'spam'), t('Preview')); $this->assertNoCaptchaField(); $this->assertNoPrivacyLink(); // Save the comment and make sure it appears. $this->drupalPost(NULL, array(), t('Save')); $this->assertRaw('spam
', t('A comment that is known to be spam appears on the screen after it is submitted.')); } /** * Make sure that the comment submission form can be protected by captcha only. */ function testCaptchaProtectedCommentForm() { // Enable Mollom CAPTCHA protection for comments. $this->drupalLogin($this->admin_user); $this->setProtection('comment_form', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Request the comment reply form. There should be a CAPTCHA form. $this->drupalLogin($this->web_user); $this->drupalGet('comment/reply/' . $this->node->nid); $this->assertCaptchaField(); $this->assertSessionIDInForm(); $this->assertNoPrivacyLink(); // 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->assertSessionIDInForm(); $this->assertNoPrivacyLink(); // 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.')); $session_id = $this->assertSessionIDInForm(); $this->assertNoPrivacyLink(); // Finally, we should be able to submit a comment. $this->drupalPost(NULL, array('comment' => 'spam'), t('Save')); $this->assertRaw('spam
', t('Spam comment could be posted with correct CAPTCHA.')); $cid = db_result(db_query("SELECT cid FROM {comments} WHERE comment = '%s' ORDER BY timestamp DESC", array('spam'))); $this->assertMollomData('comment', $cid, $session_id); } /** * Make sure that the comment submission form can be fully protected. */ function testTextAnalysisProtectedCommentForm() { // Enable Mollom text-classification for comments. $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); $this->drupalLogout(); // Request the comment reply form. Initially, there should be no CAPTCHA. $this->drupalLogin($this->web_user); $this->drupalGet('comment/reply/'. $this->node->nid); $this->assertNoCaptchaField(); $this->assertPrivacyLink(); // Try to save a comment that is 'unsure' and make sure there is a CAPTCHA. $edit = array( 'comment' => 'unsure', ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertCaptchaField(); $session_id = $this->assertSessionIDInForm(); $this->assertPrivacyLink(); // Try to submit the form by solving the CAPTCHA incorrectly. At this point, // the submission should be blocked and a new CAPTCHA generated, but only if // the comment is still neither ham or spam. $this->postIncorrectCaptcha(NULL, array(), t('Save')); $this->assertCaptchaField(); $session_id = $this->assertSessionIDInForm(); $this->assertPrivacyLink(); // Correctly solving the CAPTCHA should accept the form submission. $this->postCorrectCaptcha(NULL, array(), t('Save')); $this->assertRaw('' . $edit['comment'] . '
', t('A comment that may contain spam was found.')); $cid = db_result(db_query("SELECT cid FROM {comments} WHERE comment = '%s' ORDER BY timestamp DESC", array($edit['comment']))); $this->assertMollomData('comment', $cid, $session_id); // Try to save a new 'spam' comment; it should be discarded, with no CAPTCHA // appearing on the page. $this->resetSessionID(); $this->drupalGet('comment/reply/' . $this->node->nid); $this->assertPrivacyLink(); $original_number_of_comments = $this->getCommentCount($this->node->nid); $this->assertSpamSubmit(NULL, array('comment'), array(), t('Save')); $session_id = $this->assertSessionIDInForm(); $this->assertCommentCount($this->node->nid, $original_number_of_comments); $this->assertPrivacyLink(); // Try to save again; it should be discarded, with no CAPTCHA. $this->assertSpamSubmit(NULL, array('comment'), array(), t('Save')); $session_id = $this->assertSessionIDInForm(); $this->assertCommentCount($this->node->nid, $original_number_of_comments); $this->assertPrivacyLink(); // Save a new 'ham' comment. $this->resetSessionID(); $this->drupalGet('comment/reply/' . $this->node->nid); $this->assertPrivacyLink(); $original_number_of_comments = $this->getCommentCount($this->node->nid); $this->assertHamSubmit(NULL, array('comment'), array(), t('Save')); $this->assertRaw('ham
', 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_result(db_query("SELECT cid FROM {comments} WHERE comment = '%s' ORDER BY timestamp DESC", array('ham'))); $this->assertMollomData('comment', $cid); } /** * Return the number of comments for a node of the given node ID. We * can't use comment_num_all() here, because that is statically cached * and therefore will not work correctly with the SimpleTest browser. */ private function getCommentCount($nid) { return db_result(db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = %d', $nid)); } /** * Test that the number of comments for a node matches an expected value. * * @param $nid * A node ID * @param $expected * An integer with the expected number of comments for the node. * @param $message * An optional string with the message to be used in the assertion. */ protected function assertCommentCount($nid, $expected, $message = '') { $actual = $this->getCommentCount($nid); if (!$message) { $message = t('Node @nid has @actual comment(s), expected @expected.', array('@nid' => $nid, '@actual' => $actual, '@expected' => $expected)); } $this->assertEqual($actual, $expected, $message); } } class MollomContactFormTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Contact form protection', 'description' => 'Check that the contact form can be protected.', 'group' => 'Mollom', ); } function setUp() { parent::setUp('contact'); $this->web_user = $this->drupalCreateUser(array('access site-wide contact form', 'access user profiles')); } /** * Make sure that the user contact form is protected correctly. * * @todo Test mail sending with assertMail() now that it is available. */ function testProtectContactUserForm() { // Enable Mollom for the contact form. $this->drupalLogin($this->admin_user); $this->setProtection('contact_mail_user'); $this->drupalLogout(); $this->drupalLogin($this->web_user); $url = 'user/' . $this->admin_user->uid . '/contact'; $button = t('Send e-mail'); $success = t('The message has been sent.'); // Submit a 'spam' message. This should be blocked. $this->assertSpamSubmit($url, array('subject', 'message'), array(), $button); $this->assertNoText($success); // Submit a 'ham' message. This should be accepted. $this->assertHamSubmit($url, array('subject', 'message'), array(), $button); $this->assertText($success); // Submit an 'unsure' message. This should be accepted only after the // CAPTCHA has been solved. $this->assertUnsureSubmit($url, array('subject', 'message'), array(), $button, $success); } /** * Make sure that the site-wide contact form is protected correctly. * * @todo Test mail sending with assertMail() now that it is available. */ function testProtectContactSiteForm() { // Enable Mollom for the contact form. $this->drupalLogin($this->admin_user); $this->setProtection('contact_mail_page'); $this->drupalLogout(); // Add some fields to the contact form so that it is active. // Empty 'reply' so as to not have to fiddle with auto-reply messages. $this->drupalLogin($this->web_user); db_query("INSERT INTO {contact} (category, recipients, reply) VALUES ('%s', '%s', '%s')", 'test category', $this->web_user->mail, ''); $url = 'contact'; $button = t('Send e-mail'); $success = t('Your message has been sent.'); // Submit a 'spam' message. This should be blocked. $this->assertSpamSubmit($url, array('subject', 'message'), array(), $button); $this->assertNoText($success); // Submit a 'ham' message. This should be accepted. $this->assertHamSubmit($url, array('subject', 'message'), array(), $button); $this->assertText($success); $report_link = $this->parseMollomMailReportLink(); $this->assertTrue($report_link, t('Report to Mollom link found in e-mail.')); $this->assertEqual($report_link['entity'], 'session', t('Report link in e-mail uses entity type "session".')); $this->assertMollomData($report_link['entity'], $report_link['session_id']); // Submit an 'unsure' message. This should be accepted only after the // CAPTCHA has been solved. $this->assertUnsureSubmit($url, array('subject', 'message'), array(), $button, $success); $report_link = $this->parseMollomMailReportLink(); $this->assertTrue($report_link, t('Report to Mollom link found in e-mail.')); $this->assertEqual($report_link['entity'], 'session', t('Report link in e-mail uses entity type "session".')); $this->assertMollomData($report_link['entity'], $report_link['session_id']); // Report the mail to Mollom. $this->drupalGet($report_link['url']); $edit = array( 'feedback' => 'spam', ); $this->drupalPost(NULL, $edit, t('Delete')); $this->assertText(t('The content was successfully reported as inappropriate.')); } /** * Returns data about the report to Mollom link in the last sent mail. * * Contrary to DrupalWebTestCase::assertMail(), this function removes the last * sent mail from the internally recorded stack. */ function parseMollomMailReportLink() { // Grab the last sent mail. // @see DrupalWebTestCase::assertMail() $captured_emails = variable_get('drupal_test_email_collector', array()); $email = array_pop($captured_emails); variable_set('drupal_test_email_collector', $captured_emails); $found = FALSE; if (preg_match('@http.+?mollom/report/([^/]+)/([^\s]+)@', $email['body'], $matches)) { $found = array( 'url' => $matches[0], 'entity' => $matches[1], 'session_id' => $matches[2], 'mail' => $email, ); } return $found; } } class MollomResellerTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Reseller functionality', 'description' => 'Check that the reseller APIs are working properly.', 'group' => 'Mollom', ); } /** * Make sure that resellers can create a new site. */ function testKeyManagement() { if (!$this->is_reseller) { // If the current test keys are not reseller keys, skip this test. return; } // Create 3 test sites: for ($i = 1; $i <= 3; $i++) { $keys[] = mollom('mollom.createSite', array( 'url' => 'http://example.com/site-'. $i, 'mail' => 'mail@example.com', 'status' => 0, 'testing' => 1, )); } // Assert that there were no XML-RPC errors or watchdog messages. $this->assertMollomWatchdogMessages(); $sites = mollom('mollom.listSites'); foreach ($sites as $site) { // Retrieve the site information: $details = mollom('mollom.getSite', array('client_key' => $site)); $this->assertEqual($details['mail'], 'mail@example.com', t('The original information is correctly retrieved from Mollom.')); $this->assertEqual($details['status'], 0, t('The original information is correctly retrieved from Mollom.')); $this->assertEqual($details['testing'], 1, t('The original information is correctly retrieved from Mollom.')); // Perform a safety check to avoid that the tests would delete // valid sites in case someone messed up their Mollom settings! if ($details['mail'] == 'mail@example.com' || $details['mail'] == 'root@example.com') { // Update the information on the site and verify that it was updated. mollom('mollom.updateSite', array('client_key' => $site, 'mail' => 'root@example.com')); $details = mollom('mollom.getSite', array('client_key' => $site)); $this->assertEqual($details['mail'], 'root@example.com', t('The updated information is correctly retrieved from Mollom.')); // Verify that the existing information did not change (partial updates). $this->assertEqual($details['status'], 0, t('The original information is correctly retrieved from Mollom.')); $this->assertEqual($details['testing'], 1, t('The original information is correctly retrieved from Mollom.')); // Delete the test site: mollom('mollom.deleteSite', array('client_key' => $site)); } else { $this->fail(t('We tried to delete a non-test site.')); } } // Assert that there were no XML-RPC errors or watchdog messages. $this->assertMollomWatchdogMessages(); // Retrieve information about a non-existing site: $details = mollom('mollom.getSite', array('client_key' => 'bogus')); $this->assertEqual(xmlrpc_errno(), TRUE, t('Retrieving information from a non-existing site returned an XML-RPC error.')); $this->assertMollomWatchdogMessages(WATCHDOG_EMERG); // Verify that all sites have been deleted: $sites = mollom('mollom.listSites'); $this->assertEqual(count($sites), 0, t('All Mollom sites have been deleted.')); } } /** * Tests form value processing. */ class MollomDataTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Data processing', 'description' => 'Verify that form registry information is properly transformed into data that is sent to Mollom servers.', 'group' => 'Mollom', ); } function setUp() { // Enable testing server implementation. parent::setUp('mollom_test'); // Re-route Mollom communication to this testing site. variable_set('mollom_servers', array($GLOBALS['base_url'] . '/xmlrpc.php?version=')); } /** * Test mollom_form_get_values(). */ function testFormGetValues() { global $user; // Form registry information. $form_info = array( 'elements' => array( 'subject' => 'Subject', 'message' => 'Message', 'parent][child' => 'Some nested element', ), 'mapping' => array( 'post_title' => 'subject', 'author_name' => 'name', 'author_mail' => 'mail', ), ); // Fields configured via Mollom admin UI based on $form_info['elements']. $fields = array( 'subject', 'message', 'parent][child', ); // Verify submitted form values for an anonymous/arbitrary user. $values = array( 'subject' => 'Foo', 'message' => 'Bar', 'parent' => array( 'child' => 'Beer', ), 'name' => 'Drupaler', 'mail' => 'drupaler@example.com', ); $data = mollom_form_get_values($values, $fields, $form_info['mapping']); $this->assertSame('post_title', $data['post_title'], $values['subject']); $this->assertSame('post_body', $data['post_body'], $values['message'] . "\n" . $values['parent']['child']); $this->assertSame('author_name', $data['author_name'], $values['name']); $this->assertSame('author_mail', $data['author_mail'], $values['mail']); $this->assertFalse(isset($data['author_url']), t('author_url: Undefined.')); $this->assertFalse(isset($data['author_openid']), t('author_openid: Undefined.')); $this->assertFalse(isset($data['author_id']), t('author_id: Undefined.')); $this->assertSame('author_ip', $data['author_ip'], ip_address()); // Verify submitted form values for an registered user. $values = array( 'subject' => 'Foo', 'message' => 'Bar', 'name' => $this->admin_user->name, ); $data = mollom_form_get_values($values, $fields, $form_info['mapping']); $this->assertSame('post_title', $data['post_title'], $values['subject']); $this->assertSame('post_body', $data['post_body'], $values['message']); $this->assertSame('author_name', $data['author_name'], $this->admin_user->name); $this->assertSame('author_mail', $data['author_mail'], $this->admin_user->mail); $this->assertFalse(isset($data['author_url']), t('author_url: Undefined.')); // @todo Test this. $this->assertFalse(isset($data['author_openid']), t('author_openid: Undefined.')); $this->assertSame('author_id', $data['author_id'], $this->admin_user->uid); $this->assertSame('author_ip', $data['author_ip'], ip_address()); } /** * Test submitted post and author information for textual analysis. */ function testAnalysis() { $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); // Make comment preview optional. $edit = array( 'comment_preview' => 0, ); $this->drupalPost('admin/content/node-type/story', $edit, t('Save content type')); // Create a node we can comment on. $node = $this->drupalCreateNode(array('type' => 'story', 'promote' => 1)); $this->drupalGet(''); $this->assertText($node->title); // Log in regular user and post a comment. $this->drupalLogout(); $this->web_user = $this->drupalCreateUser(); $this->drupalLogin($this->web_user); $this->drupalGet(''); $this->clickLink(t('Add new comment')); $edit = array( 'subject' => $this->randomString(), 'comment' => 'unsure', ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText($this->unsure_message); // Verify that submitted data equals post data. $data = $this->getServerRecord(); $this->assertSame('post_title', $data['post_title'], $edit['subject']); $this->assertSame('post_body', $data['post_body'], $edit['comment']); $this->assertSame('author_name', $data['author_name'], $this->web_user->name); $this->assertSame('author_mail', $data['author_mail'], $this->web_user->mail); $this->assertSame('author_id', $data['author_id'], $this->web_user->uid); $this->PostCorrectCaptcha(NULL, array(), t('Save')); $comment = db_fetch_object(db_query("SELECT * FROM {comments} WHERE subject = '%s'", $edit['subject'])); $this->assertTrue($comment, t('Comment exists in database.')); // Verify that submitted data equals post data. $data = $this->getServerRecord('mollom.checkCaptcha'); $this->assertSame('author_id', $data['author_id'], $this->web_user->uid); // Allow anonymous users to post comments without approval. $this->drupalLogin($this->admin_user); $edit = array( DRUPAL_ANONYMOUS_RID . '[access comments]' => TRUE, DRUPAL_ANONYMOUS_RID . '[post comments]' => TRUE, DRUPAL_ANONYMOUS_RID . '[post comments without approval]' => TRUE, ); $this->drupalPost('admin/user/permissions', $edit, t('Save permissions')); // Allow anonymous users to post contact information. $edit = array( 'comment_anonymous' => COMMENT_ANONYMOUS_MAY_CONTACT, ); $this->drupalPost('admin/content/node-type/story', $edit, t('Save content type')); // Log out and post a comment as anonymous user. $this->resetServerRecords(); $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(6) . ' & ' . $this->randomString(8), 'mail' => 'mollom@example.com', 'homepage' => 'http://mollom.com', 'subject' => '"' . $this->randomString() . '"', 'comment' => 'unsure', ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText($this->unsure_message); // Verify that submitted data equals post data. $data = $this->getServerRecord(); $this->assertSame('post_title', $data['post_title'], $edit['subject']); $this->assertSame('post_body', $data['post_body'], $edit['comment']); $this->assertSame('author_name', $data['author_name'], $edit['name']); $this->assertSame('author_mail', $data['author_mail'], $edit['mail']); $this->assertSame('author_url', $data['author_url'], $edit['homepage']); $this->assertFalse(isset($data['author_id']), t('author_id: Undefined.')); $this->PostCorrectCaptcha(NULL, array(), t('Save')); $comment = db_fetch_object(db_query("SELECT * FROM {comments} WHERE subject = '%s'", $edit['subject'])); $this->assertTrue($comment, t('Comment exists in database.')); // Verify that submitted data equals post data. $data = $this->getServerRecord('mollom.checkCaptcha'); $this->assertFalse(isset($data['author_id']), t('author_id: Undefined.')); // Log in admin user and edit comment containing spam. $this->resetServerRecords(); $this->drupalLogin($this->admin_user); $this->drupalGet('comment/edit/' . $comment->cid); // Post without modification. $this->drupalPost(NULL, array(), t('Save')); // Verify that no data was submitted to Mollom. $data = $this->getServerRecord(); $this->assertFalse($data, t('Administrative form submission was not validated by Mollom.')); } /** * Tests that protected forms contain a hidden honeypot field and its value is recorded. */ function testHoneypot() { // Enable protection for mollom_test_form. $this->drupalLogin($this->admin_user); $this->setProtection('mollom_test_form'); $this->drupalLogout(); // Verify that the hidden honeypot field is output. $this->drupalGet('mollom-test/form'); $elements = $this->xpath(strtr('//div[contains(@style, :style)]/descendant::input[@name = :name]', array( ':style' => '"display: none"', ':name' => '"mollom[homepage]"', ))); $this->assertEqual(count($elements), 1, t('Hidden honeypot field found.')); // Verify that a honeypot value is sent to mollom.checkContent. $edit = array( 'title' => 'unsure', 'body' => 'unsure', 'mollom[homepage]' => 'HONEYPOT-VALUE', ); $this->drupalPost(NULL, $edit, 'Submit'); $this->assertCaptchaField(); $data = $this->getServerRecord(); $this->assertSame('honeypot', $data['honeypot'], $edit['mollom[homepage]']); $this->postCorrectCaptcha(NULL, array(), 'Submit', 'Successful form submission.'); $data = $this->getServerRecord(); $this->assertSame('honeypot', $data['honeypot'], $edit['mollom[homepage]']); $data = $this->getServerRecord('mollom.checkCaptcha'); $this->assertSame('honeypot', $data['honeypot'], $edit['mollom[homepage]']); // Change form protection to CAPTCHA only. $this->drupalLogin($this->admin_user); $this->setProtection('mollom_test_form', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); $this->resetServerRecords(); // Verify that the hidden honeypot field is output. $this->drupalGet('mollom-test/form'); $elements = $this->xpath(strtr('//div[contains(@style, :style)]/descendant::input[@name = :name]', array( ':style' => '"display: none"', ':name' => '"mollom[homepage]"', ))); $this->assertEqual(count($elements), 1, t('Hidden honeypot field found.')); // Verify that a honeypot value is sent to mollom.checkContent. // postCorrectCaptcha() cannot be used for mollom_test_form, since the form // is re-displayed again after a successful form submission. $edit = array( 'title' => $this->randomString(), 'mollom[captcha]' => 'correct', 'mollom[homepage]' => 'HONEYPOT-VALUE', ); $this->drupalPost(NULL, $edit, 'Submit'); $this->assertText('Successful form submission.'); $data = $this->getServerRecord('mollom.checkCaptcha'); $this->assertSame('honeypot', $data['honeypot'], $edit['mollom[homepage]']); } /** * Tests automated 'post_id' mapping and session data storage. * * This is an atomic test to verify that a simple 'post_id' mapping defined * via hook_mollom_form_info() is sufficient for basic integration with * Mollom (without reporting). */ function testPostIdMapping() { // Enable protection for mollom_test_form. $this->drupalLogin($this->admin_user); $this->setProtection('mollom_test_form'); $this->drupalLogout(); // Submit a mollom_test thingy. $edit = array( 'title' => 'ham', 'body' => $this->randomString(), ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $this->assertText('Successful form submission.'); $mid = $this->getFieldValueByName('mid'); $this->assertTrue($mid > 0, t('Submission was stored.')); $data = $this->assertMollomData('mollom_test', $mid); // Ensure we were redirected to the form for the stored entry. $this->assertFieldByName('body', $edit['body'], t('Existing body value found.')); $new_mid = $this->getFieldValueByName('mid'); $this->assertEqual($new_mid, $mid, t('Existing entity id found.')); // Verify that session data was stored. $this->assertSame('entity', $data->entity, 'mollom_test'); $this->assertSame('id', $data->id, $mid); $this->assertSame('form_id', $data->form_id, 'mollom_test_form'); $count = db_result(db_query("SELECT COUNT(1) FROM {mollom}")); $this->assertEqual($count, 1, t('Data was stored in {mollom}.')); // Update the stored entry. $edit['title'] = 'unsure'; $this->drupalPost(NULL, $edit, 'Submit'); $this->assertCaptchaField(); $this->postCorrectCaptcha(NULL, array(), 'Submit', 'Successful form submission.'); $new_data = $this->assertMollomData('mollom_test', $mid); // Verify that only session data was updated. $this->assertSame('entity', $data->entity, $new_data->entity); $this->assertSame('id', $data->id, $new_data->id); $this->assertNotSame('session_id', $data->session_id, $new_data->session_id); $this->assertSame('form_id', $data->form_id, $new_data->form_id); $this->assertSame('quality', $data->quality, $new_data->quality); $count = db_result(db_query("SELECT COUNT(1) FROM {mollom}")); $this->assertEqual($count, 1, t('Stored data in {mollom} was updated.')); } /** * Tests data sent for mollom.verifyKey. */ function testVerifyKey() { $this->drupalLogin($this->admin_user); $this->drupalGet('admin/settings/mollom/settings'); // Verify that we additionally sent version data. $data = $this->getServerRecord('mollom.verifyKey'); $info = _mollom_get_version(); $this->assertTrue(!empty($info['platform_name']), t('Version information found.')); $this->assertSame('platform_name', $data['platform_name'], $info['platform_name']); $this->assertSame('platform_version', $data['platform_version'], $info['platform_version']); $this->assertSame('client_name', $data['client_name'], $info['client_name']); $this->assertSame('client_version', $data['client_version'], $info['client_version']); } } /** * Tests text analysis functionality. * * @todo Verify that no button captions appear in the data that is sent for * analyis; i.e., no "Add" string for mollom_test_form. */ class MollomAnalysisTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Text analysis', 'description' => 'Tests text analysis functionality.', 'group' => 'Mollom', ); } function setUp() { // @todo This is the new default setUp() procedure for all new tests, which // should be moved into MollomWebTestCase::setUp() after cleaning up the // tests. $this->disableDefaultSetup = TRUE; parent::setUp(array('mollom', 'mollom_test')); $this->setKeys(); $this->assertValidKeys(); $this->admin_user = $this->drupalCreateUser(array( 'access administration pages', 'administer mollom', )); $this->web_user = $this->drupalCreateUser(array('access content')); } /** * Tests retaining bad posts and moderating them. */ function testRetain() { $this->drupalLogin($this->admin_user); // Verify that mollom_basic_test_form cannot be configured to put posts into // moderation queue. $this->drupalGet('admin/config/content/mollom/manage/mollom_basic_elements_test_form'); $this->assertNoFieldByName('mollom[discard]'); // Configure mollom_test_form to accept bad posts. $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS, NULL, array( 'mollom[discard]' => 0, )); $this->drupalLogout(); // Verify that we are able to post spam and the post is unpublished. $edit = array( 'title' => $this->randomString(), 'body' => 'spam profanity', ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); $mid = $this->assertTestSubmitData(); $data = $this->assertMollomData('mollom_test', $mid); $record = mollom_test_load($mid); $this->assertEqual($record['status'], 0, t('Unpublished test post found.')); $this->assertSame('spam', $data->spam, MOLLOM_ANALYSIS_SPAM); // Verify that editing the post does neither change the session data, nor // the publishing status. $edit = array( 'title' => $this->randomString(), 'body' => 'spam profanity spam profanity', ); $this->drupalPost(NULL, $edit, 'Submit'); $mid = $this->assertTestSubmitData($mid); $data = $this->assertMollomData('mollom_test', $mid); $record = mollom_test_load($mid); $this->assertEqual($record['status'], 0, t('Unpublished test post found.')); $this->assertSame('spam', $data->spam, MOLLOM_ANALYSIS_SPAM); // Verify that publishing the post changes the session data accordingly. $this->drupalLogin($this->admin_user); $edit = array( 'status' => TRUE, ); $this->drupalPost('mollom-test/form/' . $mid, $edit, 'Submit'); $mid = $this->assertTestSubmitData($mid); $data = $this->assertMollomData('mollom_test', $mid); $record = mollom_test_load($mid); $this->assertEqual($record['status'], 1, t('Published test post found.')); $this->assertSame('spam', $data->spam, MOLLOM_ANALYSIS_SPAM); // Verify that neither ham or unsure spam posts, nor non-profane posts are // marked for moderation. $this->drupalLogout(); $expectations = array( 'ham' => array('spam' => MOLLOM_ANALYSIS_HAM, 'profanity' => 0), 'unsure' => array('spam' => MOLLOM_ANALYSIS_UNSURE, 'profanity' => 0), $this->randomString() => array('spam' => MOLLOM_ANALYSIS_UNSURE, 'profanity' => 0), ); foreach ($expectations as $body => $expected) { $edit = array( 'title' => $this->randomString(), 'body' => $body, ); $this->drupalPost('mollom-test/form', $edit, 'Submit'); if ($expected['spam'] == MOLLOM_ANALYSIS_UNSURE) { $this->postCorrectCaptcha(NULL, array(), 'Submit'); } $mid = $this->assertTestSubmitData(); $data = $this->assertMollomData('mollom_test', $mid); $record = mollom_test_load($mid); $this->assertEqual($record['status'], 1, t('Published test post %body found.', array('%body' => $body))); $this->assertSame('spam', $data->spam, $expected['spam']); } } /** * Asserts a successful mollom_test_form submission. * * @param $old_mid * (optional) The existing test record id to assert. */ protected function assertTestSubmitData($old_mid = NULL) { $this->assertText('Successful form submission.'); $mid = $this->getFieldValueByName('mid'); if (isset($old_mid)) { $this->assertSame('Test record id', $mid, $old_mid); } else { $this->assertTrue($mid > 0, t('Test record id @id found.', array('@id' => $mid))); } return $mid; } } /** * Tests report to Mollom functionality. */ class MollomReportTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Reporting functionality', 'description' => 'Verify that session data is properly stored and content can be reported to Mollom.', 'group' => 'Mollom', ); } function setUp() { parent::setUp('comment'); $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'post comments without approval', 'create story content')); } /** * Tests reporting comments. */ function testReportComment() { $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); $this->drupalLogout(); $this->node = $this->drupalCreateNode(array('type' => 'story')); variable_set('comment_preview_story', COMMENT_PREVIEW_OPTIONAL); // Post a comment. $this->drupalLogin($this->web_user); $edit = array( 'comment' => 'ham', ); $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); $this->comment = db_fetch_object(db_query("SELECT * FROM {comments} WHERE comment = '%s' AND nid = %d", array($edit['comment'], $this->node->nid))); $this->assertTrue($this->comment, t('Comment was found in the database.')); $this->assertMollomData('comment', $this->comment->cid); // Log in comment administrator and verify that we can report to Mollom. $this->drupalLogin($this->admin_user); $this->drupalGet('node/' . $this->node->nid); $this->assertText($edit['comment'], t('Comment found.')); $this->clickLink('report to Mollom'); $edit = array( 'feedback' => 'spam', ); $this->drupalPost(NULL, $edit, t('Delete')); $this->assertText(t('The comment has been deleted.')); $this->assertText(t('The content was successfully reported as inappropriate.')); // Verify that the comment and Mollom session data has been deleted. $this->assertFalse(_comment_load($this->comment->cid), t('Comment was deleted.')); $this->assertNoMollomData('comment', $this->comment->cid); } /** * Tests mass-reporting comments. */ function testMassReportComments() { $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); $this->drupalLogout(); $this->node = $this->drupalCreateNode(array('type' => 'story')); variable_set('comment_preview_story', COMMENT_PREVIEW_OPTIONAL); // Post 3 comments. $this->drupalLogin($this->web_user); $this->comments = array(); foreach (range(1, 3) as $num) { $edit = array( 'subject' => $this->randomName(), 'comment' => 'ham', ); $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); $this->comments[$num] = db_fetch_object(db_query("SELECT * FROM {comments} WHERE subject = '%s' AND nid = %d", array($edit['subject'], $this->node->nid))); $this->assertTrue($this->comments[$num], t('Comment was found in the database.')); $this->assertMollomData('comment', $this->comments[$num]->cid); } // Log in comment administrator and verify that we can mass-report all // comments to Mollom. $this->drupalLogin($this->admin_user); $this->drupalGet('admin/content/comment'); $edit = array( 'operation' => 'mollom-unpublish', ); foreach ($this->comments as $comment) { $this->assertText($comment->subject, t('Comment found.')); $edit["comments[{$comment->cid}]"] = TRUE; } $this->drupalPost(NULL, $edit, t('Update')); $this->assertText(t('The selected comments have been reported as inappropriate and are unpublished.')); // Verify that unpublished comments are found in approval queue and // mass-report all comments again to delete them. $this->drupalGet('admin/content/comment/approval'); $edit['operation'] = 'mollom-delete'; foreach ($this->comments as $comment) { $this->assertText($comment->subject, t('Comment found.')); } $this->drupalPost(NULL, $edit, t('Update')); $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. foreach ($this->comments as $comment) { $this->assertFalse(_comment_load($comment->cid), t('Comment was deleted.')); $this->assertNoMollomData('comment', $comment->cid); } } }