resetSessionID(); $this->messages = array(); // 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 = func_get_args(); $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 = func_get_args(); $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 fails. * * @param $no_fail_expected * (optional) Boolean whether a failing watchdog message is expected. * Defaults to TRUE (no fail expected). If FALSE is passed, the logic for * assertion messages is flipped. * * @todo Add this to D7 core. */ protected function assertMollomWatchdogMessages($no_fail_expected = TRUE) { module_load_include('inc', 'dblog', 'dblog.admin'); $this->messages = array(); $query = db_select('watchdog', 'w') ->fields('w') ->condition('w.type', 'mollom') ->orderBy('w.timestamp', 'ASC'); foreach ($query->execute() as $row) { // All watchdog messages with a higher severity than WATCHDOG_NOTICE are // considered as fails, even when no fails are expected. if ($no_fail_expected && $row->severity < WATCHDOG_NOTICE) { $this->fail(theme_dblog_message(array('event' => $row, 'link' => FALSE)), t('Watchdog')); } $this->messages[$row->wid] = $row; } // Delete processed watchdog messages. if (!empty($this->messages)) { $seen_ids = array_keys($this->messages); db_delete('watchdog')->condition('wid', $seen_ids)->execute(); } } /** * Assert that the Mollom session id remains the same. * * The Mollom session id is only known to one server. If we are communicating * with a different Mollom server (due to a refreshed server list or being * redirected), then we will get a new session_id. * * @param $session_id * A Mollom session_id of the last request, as contained in the XML-RPC * response. */ protected function assertSessionID($session_id) { // Check whether watchdog messages indicate a refresh or redirect. foreach ($this->messages as $message) { if ($message->message == 'Refreshed servers: %servers' || $message->message == 'Server %server redirected to: %next.') { $this->resetSessionID(); } } if (!isset($this->session_id)) { // Use assertTrue() instead of pass(), to test !empty(). $this->assertTrue($session_id, t('New session_id: %session_id', array('%session_id' => $session_id))); $this->session_id = $session_id; } else { $this->_assertEqual('session_id', $session_id, $this->session_id); } return $this->session_id; } /** * Reset the statically cached Mollom session id. */ protected function resetSessionID() { $this->session_id = NULL; } /** * Assert a Mollom session id in a form. * * This is a wrapper around assertSessionID() allows to assert that a proper * Mollom session id is found in the form contained in the internal browser * output. The usual flow is: * - drupalGet() or drupalPost() requests or submits a form. * - drupalGet() and drupalPost() invoke assertMollomWatchdogMessages() * internally, which records all new watchdog messages. * - This function, assertSessionIDInForm(), is invoked to assert that there * is a Mollom session id and, depending on the recorded watchdog messages, * that it either equals the last known session id or the new session id is * used for future comparisons in case of a server redirect. * - The return value of this function is used to invoke assertData(), to * verify that the proper session id was stored in the database. */ protected function assertSessionIDInForm() { $session_id = $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); // Set the Mollom keys in developer mode. variable_set('mollom_developer_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. * @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/config/content/mollom'); // Determine whether the form is already protected. $exists = db_query_range('SELECT 1 FROM {mollom_form} WHERE form_id = :form_id', 0, 1, array(':form_id' => $form_id))->fetchField(); // 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->assertLinkByHref('admin/config/content/mollom/manage/' . $form_id); $this->drupalGet('admin/config/content/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_query_range('SELECT 1 FROM {mollom_form} WHERE form_id = :form_id', 0, 1, array(':form_id' => $form_id)); if ($exists) { $this->drupalGet('admin/config/content/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 assertData($entity, $id, $session_id = NULL) { $data = mollom_data_load($entity, $id); $this->assertTrue($data->session, t('Mollom session data for %entity @id exists:
@data
', array('%entity' => $entity, '@id' => $id, '@data' => var_export($data, TRUE)))); if (isset($session_id)) { $this->_assertEqual(t('Stored session id'), $data->session, $session_id); } return $data; } /** * Assert that no Mollom session data exists for a certain entity. */ protected function assertNoData($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="Mollom CAPTCHA"]'); $this->assert(!empty($image), 'CAPTCHA image found.', 'Mollom'); } /** * 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="Mollom CAPTCHA"]'); $this->assert(empty($image), 'CAPTCHA image not found.', 'Mollom'); } /** * 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. * * @see mollom_test.module */ protected function getServerRecord() { $storage = variable_get('mollom_test_check_content', array()); $return = array_shift($storage); variable_set('mollom_test_check_content', $storage); return $return; } /** * Wraps drupalGet() for additional watchdog message assertion. * * @param $options * In addition to regular $options that are passed to url(): * - watchdog: (optional) Boolean whether to assert that only non-severe * watchdog messages have been logged. Defaults to TRUE. Use FALSE to * negate the watchdog message severity assertion. * * @see DrupalWebTestCase->drupalGet() * @see MollomWebTestCase->assertMollomWatchdogMessages() * @see MollomWebTestCase->assertSessionID() */ protected function drupalGet($path, array $options = array(), array $headers = array()) { $output = parent::drupalGet($path, $options, $headers); $options += array('watchdog' => TRUE); $this->assertMollomWatchdogMessages($options['watchdog']); return $output; } /** * Wraps drupalPost() for additional watchdog message assertion. * * @param $options * In addition to regular $options that are passed to url(): * - watchdog: (optional) Boolean whether to assert that only non-severe * watchdog messages have been logged. Defaults to TRUE. Use FALSE to * negate the watchdog message severity assertion. * * @see MollomWebTestCase->assertMollomWatchdogMessages() * @see MollomWebTestCase->assertSessionID() * @see DrupalWebTestCase->drupalPost() */ protected function drupalPost($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL) { $output = parent::drupalPost($path, $edit, $submit, $options, $headers, $form_html_id); $options += array('watchdog' => TRUE); $this->assertMollomWatchdogMessages($options['watchdog']); return $output; } /** * Helper function for assertEqual(). * * Check to see if two values are equal. And provide a meaningful response. * * @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. */ protected function _assertEqual($name, $first, $second) { $message = strtr("@name: '@first' is equal to '@second'.", array( '@name' => $name, '@first' => $first, '@second' => $second, )); $this->assertEqual($first, $second, $message); } /** * Helper function for assertNotEqual(). * * Check to see if two values are equal. And provide a meaningful response. * * @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. */ protected function _assertNotEqual($name, $first, $second) { $message = strtr("@name: '@first' is not equal to '@second'.", array( '@name' => $name, '@first' => $first, '@second' => $second, )); $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 modules', '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. */ function testInstallationProcess() { // Install the Mollom module. $this->drupalLogin($this->admin_user); $this->drupalPost('admin/modules', array('modules[Other][mollom][enable]' => TRUE), t('Save configuration')); // Verify that forms can be submitted without valid Mollom module configuration. $node = $this->drupalCreateNode(array('type' => 'article')); $this->drupalLogin($this->web_user); $this->drupalGet('comment/reply/' . $node->nid); $edit = array( 'comment_body[und][0][value]' => $this->randomName(), ); $this->drupalPost(NULL, $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); $this->assertText(t('Your comment has been posted.')); // 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/people/permissions', $edit, t('Save permissions')); // Verify presence of 'empty keys' error message. $this->drupalGet('admin/config/content/mollom'); $this->assertText(t('The Mollom API keys are not configured yet.')); $this->assertNoText(t('The configured Mollom API keys are invalid.')); // Verify presence of 'incorrect keys' error message. $edit = array( 'mollom_public_key' => 'foo', 'mollom_private_key' => 'bar', ); $this->drupalPost('admin/config/content/mollom/settings', $edit, t('Save configuration'), array('watchdog' => FALSE)); $this->assertText(t('The configuration options have been saved.')); $this->assertText(t('The configured Mollom API keys are invalid.')); $this->assertNoText(t('The Mollom API keys are not configured yet.')); // Verify presence of 'network problem' error message. variable_set('mollom_servers', array('http://fake-host')); $this->drupalGet('admin/config/content/mollom/settings', array('watchdog' => FALSE)); $this->assertText(t('The Mollom servers could not be contacted.')); // Verify that valid keys work. $edit = array( 'mollom_public_key' => MOLLOM_TEST_PUBLIC_KEY, 'mollom_private_key' => MOLLOM_TEST_PRIVATE_KEY, 'mollom_developer_mode' => 1, ); $this->drupalPost('admin/config/content/mollom/settings', $edit, t('Save configuration'), array('watchdog' => FALSE)); $this->assertText(t('The configuration options have been saved.')); $this->assertText(t('We are now blocking spam.')); $this->assertNoText(t('The configured Mollom API keys are invalid.')); $this->assertNoText(t('The Mollom API keys are not configured yet.')); // Verify presence of 'developer mode' warning. $this->assertText(t('Mollom developer 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. $data['post_body'] = 'ham'; $result = mollom('mollom.checkContent', $data); $this->assertMollomWatchdogMessages(); $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_HAM); $this->_assertEqual('quality', $result['quality'], 1); $session_id = $this->assertSessionID($result['session_id']); // Ensure proper response for 'spam' submissions, re-using session_id. $data['post_body'] = 'spam'; $data['session_id'] = $session_id; $result = mollom('mollom.checkContent', $data); $this->assertMollomWatchdogMessages(); $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_SPAM); $this->_assertEqual('quality', $result['quality'], 0); $session_id = $this->assertSessionID($result['session_id']); // Ensure proper response for 'unsure' submissions, re-using session_id. $data['post_body'] = 'unsure'; $data['session_id'] = $session_id; $result = mollom('mollom.checkContent', $data); $this->assertMollomWatchdogMessages(); $this->_assertEqual('spam', $result['spam'], MOLLOM_ANALYSIS_UNSURE); $this->_assertEqual('quality', $result['quality'], 0.5); $session_id = $this->assertSessionID($result['session_id']); } /** * Tests mollom.getImageCaptcha(). * * @todo Add further getImageCaptcha() response assertions. */ 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.')); } } 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/config/content/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' => FALSE)); $this->assertText(t('The configuration options have been saved.')); $this->assertText(t('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/config/content/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/config/content/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' => array(LANGUAGE_NONE => array(array('value' => 'node body'))), 'type' => 'article')); // Create a regular user and post a comment. $this->web_user = $this->drupalCreateUser(array('edit own comments')); $this->drupalLogin($this->web_user); $edit = array( 'comment_body[und][0][value]' => 'ham', ); $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); $this->drupalPost(NULL, array(), t('Save')); $this->assertText('node body'); $this->assertText($edit['comment_body[und][0][value]']); // 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'); $this->drupalPost(NULL, array('subject' => '', 'comment_body[und][0][value]' => 'spam'), 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/config/content/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' => FALSE)); $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/config/content/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' => FALSE)); $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(FALSE); $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 { public static function getInfo() { return array( 'name' => 'Blacklisting', 'description' => 'Tests URL and text blacklist functionality.', 'group' => 'Mollom', ); } /** * 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 (strtotime($entry['created']) - REQUEST_TIME > 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 (strtotime($entry['created']) - REQUEST_TIME > 86400) { mollom('mollom.removeBlacklistText', array('text' => $entry['text'])); } } // 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, 'match' => 'contains', 'reason' => 'spam', )); $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.')); // To update the work (i.e. to change the 'match' property), we simply // overwrite it. $result = mollom('mollom.addBlacklistText', array( 'text' => $term, 'match' => 'exact', 'reason' => 'spam', )); $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)); $this->assertTrue($result, t('The blacklisted text was removed.')); } /** * 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->drupalLogin($this->admin_user); $this->drupalGet('admin/config/content/mollom/blacklist'); // Add a word to the text blacklist. $text = $this->randomName(); $edit = array( 'entry[context]' => 'everything', 'entry[text]' => $text, 'entry[reason]' => 'spam', ); $this->drupalPost(NULL, $edit, t('Add')); $this->assertText(t('The entry was added to the blacklist.')); $this->assertText($text); // Remove the word from the text blacklist. $delete_url = 'admin/config/content/mollom/blacklist/delete/' . base64_encode($text); $this->assertLinkByHref(url($delete_url)); $this->drupalGet($delete_url); $this->drupalPost(NULL, array(), t('Delete')); $this->assertEqual($this->getUrl(), url('admin/config/content/mollom/blacklist', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertNoText($text, 'Text blacklist removed.'); } } /** * Tests Mollom form profanity filtering functionality. */ class MollomProfanityTestCase extends MollomWebTestCase { public static function getInfo() { return array( 'name' => 'Profanity filtering', '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); } /** * Test the different levels of profanity filtering. With our test Mollom * server, the profanity keyword is 'Joomla'. */ function testProfanityFiltering() { // Protect Mollom test form but do not enable the profanity filter. $config_edit = array('mollom[data][profanity]' => 0); $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS, array('title', 'body'), $config_edit); $this->drupalLogout(); // Assert that the profanity filter is disabled. $post_edit = array( 'title' => 'Joomla lover unite!', 'body' => 'This is a post just for Joomla lovers. If you love Joomla, this is the content for you!', ); $this->drupalPost('mollom-test/form', $post_edit, 'Submit'); $this->postCorrectCaptcha(NULL, array(), 'Submit', 'Successful form submission.'); $this->assertNoText($this->profanity_message); // Enable the profanity filter setting for this form. $this->drupalLogin($this->admin_user); $config_edit['mollom[data][profanity]'] = 1; $this->setProtection('mollom_test_form', MOLLOM_MODE_ANALYSIS, array('title', 'body'), $config_edit); $this->drupalLogout(); // Assert that the profanity filter now blocks this content. $this->drupalPost('mollom-test/form', $post_edit, 'Submit'); $this->assertNoText('Successful form submission.'); $this->assertText($this->profanity_message); } } /** * 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/config/content/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/config/content/mollom/manage/mollom_test_form'); foreach ($edit as $name => $value) { if (strpos($name, 'enabled_fields') === FALSE) { // Skip any inputs that are not the fields for analysis checkboxes. continue; } // assertFieldByName() does not work for checkboxes. // @see assertFieldChecked() $elements = $this->xpath('//input[@name="' . $name . '"]'); if ($value) { $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Field @name is checked', array('@name' => $name))); } else { $this->assertFalse(isset($elements[0]) && !empty($elements[0]['checked']), t('Field @name is not checked', 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/config/content/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); } /** * 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_register_form is not protected. $this->drupalGet('admin/config/content/mollom'); $this->assertNoText($form_info['user_register_form']['title']); $this->assertFalse(mollom_form_load('user_register_form'), t('Form configuration does not exist.')); // Re-protect user_register_form form. $this->drupalGet('admin/config/content/mollom/add'); $this->assertNoText(t('All available forms are protected already.')); $edit = array( 'mollom[form_id]' => 'user_register_form', ); $this->drupalPost(NULL, $edit, t('Next')); $this->assertText($form_info['user_register_form']['title']); $this->assertNoText(t('Fields to analyze')); $this->drupalPost(NULL, array(), t('Save')); // Verify that user_register_form form was protected. $this->assertText(t('The form protection has been added.')); $this->assertText($form_info['user_register_form']['title']); $this->assertTrue(mollom_form_load('user_register_form'), t('Form configuration exists.')); // 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/config/content/mollom/add', $edit, t('Next')); $this->assertText($info['title']); if (isset($info['mode'])) { // Verify that CAPTCHA-only forms contain no configurable fields. if ($info['mode'] == MOLLOM_MODE_CAPTCHA) { $this->assertNoText(t('Fields to analyze')); } // Verify that forms specifying textual analysis as default mode have // all possible elements preselected. elseif ($info['mode'] == MOLLOM_MODE_ANALYSIS && !empty($info['elements'])) { foreach ($info['elements'] as $field => $label) { $field = rawurlencode($field); $this->assertFieldByName("mollom[enabled_fields][$field]", TRUE); } } } $this->drupalPost(NULL, array(), 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/config/content/mollom/add'); $this->assertText(t('All available forms are protected already.')); $this->assertText(t('Operations')); } } 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_form', 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_by_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 account is currently pending approval by the site administrator.')); $this->assertTrue(user_load_by_name($name), t('The user who attempted to register appears in the database when the CAPTCHA is valid.')); } } 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('create article content')); $this->node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $this->web_user->uid)); variable_set('comment_preview_article', DRUPAL_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_body[und][0][value]' => '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_body[und][0][value]' => 'spam'), t('Save')); $this->assertText(t('Your comment has been posted.')); $this->assertRaw('

spam

', t('Spam comment could be posted with correct CAPTCHA.')); $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'spam'))->fetchField(); $this->assertData('comment', $cid, $session_id); } /** * 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. $this->drupalPost(NULL, array('comment_body[und][0][value]' => 'unsure'), t('Save')); $this->assertCaptchaField(); $session_id = $this->assertSessionIDInForm(); $this->assertPrivacyLink(); // Try to submit the form by using an invalid CAPTCHA. At this point, // the submission should be rejected and a new CAPTCHA generated (even // if the text of the comment is changed to ham). $this->postIncorrectCaptcha(NULL, array('comment_body[und][0][value]' => 'ham'), t('Save')); $session_id = $this->assertSessionIDInForm(); $this->assertPrivacyLink(); // Now try using a valid CAPTCHA. The CAPTCHA form should no longer // be present. $this->postCorrectCaptcha(NULL, array(), t('Save')); $this->assertRaw('

ham

', t('A comment that is known to be ham appears on the screen after it is submitted.')); $cid = db_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'ham'))->fetchField(); $this->assertData('comment', $cid, $session_id); // Try to save a new 'spam' comment; it should be rejected, with no CAPTCHA // appearing on the page. $this->resetSessionID(); $this->drupalGet('comment/reply/' . $this->node->nid); $this->assertPrivacyLink(); $original_number_of_comments = $this->getCommentCount($this->node->nid); $this->assertSpamSubmit(NULL, array('comment_body[und][0][value]'), array(), t('Save')); $session_id = $this->assertSessionIDInForm(); $this->assertCommentCount($this->node->nid, $original_number_of_comments); $this->assertPrivacyLink(); // Try to save again; it should be rejected, with no CAPTCHA. $this->assertSpamSubmit(NULL, array('comment_body[und][0][value]'), array(), t('Save')); $session_id = $this->assertSessionIDInForm(); $this->assertCommentCount($this->node->nid, $original_number_of_comments); $this->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_body[und][0][value]'), 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_query('SELECT cid FROM {comment} WHERE subject = :subject ORDER BY created DESC', array(':subject' => 'ham'))->fetchField(); $this->assertData('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_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $nid))->fetchField(); } /** * 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', 'access user contact forms')); } /** * 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_personal_form'); $this->drupalLogout(); $this->drupalLogin($this->web_user); $url = 'user/' . $this->admin_user->uid . '/contact'; $button = t('Send message'); $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); // 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_site_form'); $this->drupalLogout(); // Add some fields to the contact form so that it is active. $this->drupalLogin($this->web_user); db_insert('contact') ->fields(array( 'category' => 'test category', 'recipients' => $this->web_user->mail, 'reply' => 'test auto-reply', )) ->execute(); $url = 'contact'; $button = t('Send message'); $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); // Submit an 'unsure' message. This should be accepted only after the // CAPTCHA has been solved. $this->assertUnsureSubmit($url, array('subject', 'message'), array(), $button, $success); } } 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, )); $this->assertEqual(xmlrpc_errno(), FALSE, t('A new site has been registered with Mollom.')); } $sites = mollom('mollom.listSites'); foreach ($sites as $site) { // Retrieve the information from the site: $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: $details['mail'] = 'root@example.com'; mollom('mollom.updateSite', array('client_key' => $site) + $details); $this->assertEqual(xmlrpc_errno(), FALSE, t('The information has been updated on the Mollom server.')); // Retrieve the information from the site and check if it was updated properly: $details = mollom('mollom.getSite', array('client_key' => $site)); $this->assertEqual($details['mail'], 'root@example.com', t('The updated information is correctly retrieved from Mollom.')); // Delete the test site: mollom('mollom.deleteSite', array('client_key' => $site)); $this->assertEqual(xmlrpc_errno(), FALSE, t('The Mollom server deleted a site.')); } else { $this->fail(t('We tried to delete a non-test site.')); } } // 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->_assertEqual('post_title', $data['post_title'], $values['subject']); $this->_assertEqual('post_body', $data['post_body'], $values['message'] . "\n" . $values['parent']['child']); $this->_assertEqual('author_name', $data['author_name'], $values['name']); $this->_assertEqual('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->_assertEqual('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->_assertEqual('post_title', $data['post_title'], $values['subject']); $this->_assertEqual('post_body', $data['post_body'], $values['message']); $this->_assertEqual('author_name', $data['author_name'], $this->admin_user->name); $this->_assertEqual('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->_assertEqual('author_id', $data['author_id'], $this->admin_user->uid); $this->_assertEqual('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/structure/types/manage/article', $edit, t('Save content type')); // Create a node we can comment on. $node = $this->drupalCreateNode(array('type' => 'article', '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_body[und][0][value]' => 'unsure', ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText($this->unsure_message); $this->PostCorrectCaptcha(NULL, array(), t('Save')); $comment = db_query('SELECT * FROM {comment} WHERE subject = :subject', array(':subject' => $edit['subject']))->fetchObject(); $this->assertTrue($comment, t('Comment exists in database.')); // Verify that submitted data equals post data. $data = $this->getServerRecord(); $this->_assertEqual('post_title', $data['post_title'], $edit['subject']); $this->_assertEqual('post_body', $data['post_body'], $edit['comment_body[und][0][value]']); $this->_assertEqual('author_name', $data['author_name'], $this->web_user->name); $this->_assertEqual('author_mail', $data['author_mail'], $this->web_user->mail); $this->_assertEqual('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/people/permissions', $edit, t('Save permissions')); // Allow anonymous users to post contact information. $edit = array( 'comment_anonymous' => COMMENT_ANONYMOUS_MAY_CONTACT, ); $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type')); // Log out and post a comment as anonymous user. $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_body[und][0][value]' => 'unsure', ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText($this->unsure_message); $this->PostCorrectCaptcha(NULL, array(), t('Save')); $comment = db_query('SELECT * FROM {comment} WHERE subject = :subject', array(':subject' => $edit['subject']))->fetchObject(); $this->assertTrue($comment, t('Comment exists in database.')); // Verify that submitted data equals post data. $data = $this->getServerRecord(); $this->_assertEqual('post_title', $data['post_title'], $edit['subject']); $this->_assertEqual('post_body', $data['post_body'], $edit['comment_body[und][0][value]']); $this->_assertEqual('author_name', $data['author_name'], $edit['name']); $this->_assertEqual('author_mail', $data['author_mail'], $edit['mail']); $this->_assertEqual('author_url', $data['author_url'], $edit['homepage']); $this->assertFalse(isset($data['author_id']), t('author_id: Undefined.')); // Log in admin user and edit comment containing spam. $this->drupalLogin($this->admin_user); $this->drupalGet('comment/' . $comment->cid . '/edit'); // 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 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->assertData('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.')); // 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->assertData('mollom_test', $mid); // Verify that only session data was updated. $this->_assertEqual('entity', $data->entity, $new_data->entity); $this->_assertEqual('id', $data->did, $new_data->did); $this->_assertNotEqual('session_id', $data->session, $new_data->session); $this->_assertNotEqual('quality', $data->quality, $new_data->quality); $count = db_query("SELECT COUNT(1) FROM {mollom}")->fetchField(); $this->assertEqual($count, 1, t('Stored data in {mollom} was updated.')); } } /** * 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('create article content')); } /** * Tests reporting comments. */ function testReportComment() { $this->drupalLogin($this->admin_user); $this->setProtection('comment_form'); $this->drupalLogout(); $this->node = $this->drupalCreateNode(array('type' => 'article')); variable_set('comment_preview_article', DRUPAL_OPTIONAL); // Post a comment. $this->drupalLogin($this->web_user); $edit = array( 'comment_body[und][0][value]' => 'ham', ); $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); $this->comment = db_query('SELECT * FROM {comment} WHERE subject = :comment AND nid = :nid', array(':comment' => $edit['comment_body[und][0][value]'], ':nid' => $this->node->nid))->fetchObject(); $this->assertTrue($this->comment, t('Comment was found in the database.')); $this->assertData('comment', $this->comment->cid); // 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_body[und][0][value]'], t('Comment found.')); $this->clickLink('delete'); $edit = array( 'mollom[feedback]' => 'spam', ); $this->drupalPost(NULL, $edit, t('Delete')); $this->assertText(t('The comment and all its replies have 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->assertNoData('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' => 'article')); variable_set('comment_preview_article', DRUPAL_OPTIONAL); // Post 3 comments. $this->drupalLogin($this->web_user); $this->comments = array(); foreach (range(1, 3) as $num) { $edit = array( 'subject' => $this->randomName(), 'comment_body[und][0][value]' => 'ham', ); $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); $this->comments[$num] = db_query('SELECT * FROM {comment} WHERE subject = :subject AND nid = :nid', array(':subject' => $edit['subject'], ':nid' => $this->node->nid))->fetchObject(); $this->assertTrue($this->comments[$num], t('Comment was found in the database.')); $this->assertData('comment', $this->comments[$num]->cid); } // 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' => 'delete', ); foreach ($this->comments as $comment) { $edit["comments[{$comment->cid}]"] = TRUE; } $this->drupalPost(NULL, $edit, t('Update')); foreach ($this->comments as $comment) { $this->assertText($comment->subject, t('Comment found.')); } $edit = array( 'mollom[feedback]' => 'spam', ); $this->drupalPost(NULL, $edit, t('Delete comments')); $this->assertText(t('Deleted @count comments.', array('@count' => count($this->comments)))); $this->assertText(t('The posts were successfully reported as inappropriate.')); // 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->assertNoData('comment', $comment->cid); } } /** * Tests reporting users. */ function testReportUser() { $this->drupalLogin($this->admin_user); $this->setProtection('user_register_form', MOLLOM_MODE_CAPTCHA); $this->drupalLogout(); // Disable e-mail verification. variable_set('user_email_verification', 0); // Register a user. $password = $this->randomName(); $edit = array( 'name' => $this->randomName(), 'pass[pass1]' => $password, 'pass[pass2]' => $password, 'mail' => 'spam@example.com', ); $this->postCorrectCaptcha('user/register', $edit, t('Create new account')); // Determine new uid. $uid = db_query('SELECT uid FROM {users} WHERE name = :name', array(':name' => $edit['name']))->fetchField(); $this->assertData('user', $uid); // Log in administrator and verify that we can report to Mollom. $this->drupalLogin($this->admin_user); $edit = array( 'user_cancel_method' => 'user_cancel_delete', 'mollom[feedback]' => 'spam', ); $this->drupalPost("user/$uid/cancel", $edit, t('Cancel account')); // @todo errrrr, "content"? ;) $this->assertText(t('The content was successfully reported as inappropriate.')); // Verify that Mollom session data has been deleted. $this->assertNoData('user', $uid); } }