/modules/search/search.test
Unknown | 1337 lines | 1152 code | 185 blank | 0 comment | 0 complexity | 3ae6e3d37e0fb4b9c90460fa69e05a6b MD5 | raw file
- <?php
- // $Id: search.test,v 1.69 2010/08/10 01:11:36 dries Exp $
- // The search index can contain different types of content. Typically the type is 'node'.
- // Here we test with _test_ and _test2_ as the type.
- define('SEARCH_TYPE', '_test_');
- define('SEARCH_TYPE_2', '_test2_');
- define('SEARCH_TYPE_JPN', '_test3_');
- class SearchMatchTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Search engine queries',
- 'description' => 'Indexes content and queries it.',
- 'group' => 'Search',
- );
- }
- /**
- * Implementation setUp().
- */
- function setUp() {
- parent::setUp('search');
- }
- /**
- * Test search indexing.
- */
- function testMatching() {
- $this->_setup();
- $this->_testQueries();
- }
- /**
- * Set up a small index of items to test against.
- */
- function _setup() {
- variable_set('minimum_word_size', 3);
- for ($i = 1; $i <= 7; ++$i) {
- search_index($i, SEARCH_TYPE, $this->getText($i));
- }
- for ($i = 1; $i <= 5; ++$i) {
- search_index($i + 7, SEARCH_TYPE_2, $this->getText2($i));
- }
- // No getText builder function for Japanese text; just a simple array.
- foreach (array(
- 13 => '以呂波耳・ほへとち。リヌルヲ。',
- 14 => 'ドルーパルが大好きよ!',
- 15 => 'コーヒーとケーキ',
- ) as $i => $jpn) {
- search_index($i, SEARCH_TYPE_JPN, $jpn);
- }
- search_update_totals();
- }
- /**
- * _test_: Helper method for generating snippets of content.
- *
- * Generated items to test against:
- * 1 ipsum
- * 2 dolore sit
- * 3 sit am ut
- * 4 am ut enim am
- * 5 ut enim am minim veniam
- * 6 enim am minim veniam es cillum
- * 7 am minim veniam es cillum dolore eu
- */
- function getText($n) {
- $words = explode(' ', "Ipsum dolore sit am. Ut enim am minim veniam. Es cillum dolore eu.");
- return implode(' ', array_slice($words, $n - 1, $n));
- }
- /**
- * _test2_: Helper method for generating snippets of content.
- *
- * Generated items to test against:
- * 8 dear
- * 9 king philip
- * 10 philip came over
- * 11 came over from germany
- * 12 over from germany swimming
- */
- function getText2($n) {
- $words = explode(' ', "Dear King Philip came over from Germany swimming.");
- return implode(' ', array_slice($words, $n - 1, $n));
- }
- /**
- * Run predefine queries looking for indexed terms.
- */
- function _testQueries() {
- /*
- Note: OR queries that include short words in OR groups are only accepted
- if the ORed terms are ANDed with at least one long word in the rest of the query.
- e.g. enim dolore OR ut = enim (dolore OR ut) = (enim dolor) OR (enim ut) -> good
- e.g. dolore OR ut = (dolore) OR (ut) -> bad
- This is a design limitation to avoid full table scans.
- */
- $queries = array(
- // Simple AND queries.
- 'ipsum' => array(1),
- 'enim' => array(4, 5, 6),
- 'xxxxx' => array(),
- 'enim minim' => array(5, 6),
- 'enim xxxxx' => array(),
- 'dolore eu' => array(7),
- 'dolore xx' => array(),
- 'ut minim' => array(5),
- 'xx minim' => array(),
- 'enim veniam am minim ut' => array(5),
- // Simple OR queries.
- 'dolore OR ipsum' => array(1, 2, 7),
- 'dolore OR xxxxx' => array(2, 7),
- 'dolore OR ipsum OR enim' => array(1, 2, 4, 5, 6, 7),
- 'ipsum OR dolore sit OR cillum' => array(2, 7),
- 'minim dolore OR ipsum' => array(7),
- 'dolore OR ipsum veniam' => array(7),
- 'minim dolore OR ipsum OR enim' => array(5, 6, 7),
- 'dolore xx OR yy' => array(),
- 'xxxxx dolore OR ipsum' => array(),
- // Negative queries.
- 'dolore -sit' => array(7),
- 'dolore -eu' => array(2),
- 'dolore -xxxxx' => array(2, 7),
- 'dolore -xx' => array(2, 7),
- // Phrase queries.
- '"dolore sit"' => array(2),
- '"sit dolore"' => array(),
- '"am minim veniam es"' => array(6, 7),
- '"minim am veniam es"' => array(),
- // Mixed queries.
- '"am minim veniam es" OR dolore' => array(2, 6, 7),
- '"minim am veniam es" OR "dolore sit"' => array(2),
- '"minim am veniam es" OR "sit dolore"' => array(),
- '"am minim veniam es" -eu' => array(6),
- '"am minim veniam" -"cillum dolore"' => array(5, 6),
- '"am minim veniam" -"dolore cillum"' => array(5, 6, 7),
- 'xxxxx "minim am veniam es" OR dolore' => array(),
- 'xx "minim am veniam es" OR dolore' => array()
- );
- foreach ($queries as $query => $results) {
- $result = db_select('search_index', 'i')
- ->extend('SearchQuery')
- ->searchExpression($query, SEARCH_TYPE)
- ->execute();
- $set = $result ? $result->fetchAll() : array();
- $this->_testQueryMatching($query, $set, $results);
- $this->_testQueryScores($query, $set, $results);
- }
- // These queries are run against the second index type, SEARCH_TYPE_2.
- $queries = array(
- // Simple AND queries.
- 'ipsum' => array(),
- 'enim' => array(),
- 'enim minim' => array(),
- 'dear' => array(8),
- 'germany' => array(11, 12),
- );
- foreach ($queries as $query => $results) {
- $result = db_select('search_index', 'i')
- ->extend('SearchQuery')
- ->searchExpression($query, SEARCH_TYPE_2)
- ->execute();
- $set = $result ? $result->fetchAll() : array();
- $this->_testQueryMatching($query, $set, $results);
- $this->_testQueryScores($query, $set, $results);
- }
- // These queries are run against the third index type, SEARCH_TYPE_JPN.
- $queries = array(
- // Simple AND queries.
- '呂波耳' => array(13),
- '以呂波耳' => array(13),
- 'ほへと ヌルヲ' => array(13),
- 'とちリ' => array(),
- 'ドルーパル' => array(14),
- 'パルが大' => array(14),
- 'コーヒー' => array(15),
- 'ヒーキ' => array(),
- );
- foreach ($queries as $query => $results) {
- $result = db_select('search_index', 'i')
- ->extend('SearchQuery')
- ->searchExpression($query, SEARCH_TYPE_JPN)
- ->execute();
- $set = $result ? $result->fetchAll() : array();
- $this->_testQueryMatching($query, $set, $results);
- $this->_testQueryScores($query, $set, $results);
- }
- }
- /**
- * Test the matching abilities of the engine.
- *
- * Verify if a query produces the correct results.
- */
- function _testQueryMatching($query, $set, $results) {
- // Get result IDs.
- $found = array();
- foreach ($set as $item) {
- $found[] = $item->sid;
- }
- // Compare $results and $found.
- sort($found);
- sort($results);
- $this->assertEqual($found, $results, "Query matching '$query'");
- }
- /**
- * Test the scoring abilities of the engine.
- *
- * Verify if a query produces normalized, monotonous scores.
- */
- function _testQueryScores($query, $set, $results) {
- // Get result scores.
- $scores = array();
- foreach ($set as $item) {
- $scores[] = $item->calculated_score;
- }
- // Check order.
- $sorted = $scores;
- sort($sorted);
- $this->assertEqual($scores, array_reverse($sorted), "Query order '$query'");
- // Check range.
- $this->assertEqual(!count($scores) || (min($scores) > 0.0 && max($scores) <= 1.0001), TRUE, "Query scoring '$query'");
- }
- }
- class SearchBikeShed extends DrupalWebTestCase {
- protected $searching_user;
- public static function getInfo() {
- return array(
- 'name' => 'Bike shed',
- 'description' => 'Tests the bike shed text on the no results page.',
- 'group' => 'Search'
- );
- }
- function setUp() {
- parent::setUp('search');
- // Create user.
- $this->searching_user = $this->drupalCreateUser(array('search content'));
- }
- function testFailedSearch() {
- $this->drupalLogin($this->searching_user);
- $this->drupalGet('search/node');
- $this->assertText(t('Enter your keywords'));
- $edit = array();
- $edit['keys'] = 'bike shed ' . $this->randomName();
- $this->drupalPost('search/node', $edit, t('Search'));
- $this->assertText(t('Consider loosening your query with OR. bike OR shed will often show more results than bike shed.'), t('Help text is displayed when search returns no results.'));
- }
- }
- class SearchAdvancedSearchForm extends DrupalWebTestCase {
- protected $node;
- public static function getInfo() {
- return array(
- 'name' => 'Advanced search form',
- 'description' => 'Indexes content and tests the advanced search form.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- // Create and login user.
- $test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search', 'administer nodes'));
- $this->drupalLogin($test_user);
- // Create initial node.
- $node = $this->drupalCreateNode();
- $this->node = $this->drupalCreateNode();
- // First update the index. This does the initial processing.
- node_update_index();
- // Then, run the shutdown function. Testing is a unique case where indexing
- // and searching has to happen in the same request, so running the shutdown
- // function manually is needed to finish the indexing process.
- search_update_totals();
- }
- /**
- * Test using the search form with GET and POST queries.
- * Test using the advanced search form to limit search to nodes of type "Basic page".
- */
- function testNodeType() {
- $this->assertTrue($this->node->type == 'page', t('Node type is Basic page.'));
- // Assert that the dummy title doesn't equal the real title.
- $dummy_title = 'Lorem ipsum';
- $this->assertNotEqual($dummy_title, $this->node->title, t("Dummy title doens't equal node title"));
- // Search for the dummy title with a GET query.
- $this->drupalGet('search/node/' . $dummy_title);
- $this->assertNoText($this->node->title, t('Basic page node is not found with dummy title.'));
- // Search for the title of the node with a GET query.
- $this->drupalGet('search/node/' . $this->node->title);
- $this->assertText($this->node->title, t('Basic page node is found with GET query.'));
- // Search for the title of the node with a POST query.
- $edit = array('or' => $this->node->title);
- $this->drupalPost('search/node', $edit, t('Advanced search'));
- $this->assertText($this->node->title, t('Basic page node is found with POST query.'));
- // Advanced search type option.
- $this->drupalPost('search/node', array_merge($edit, array('type[page]' => 'page')), t('Advanced search'));
- $this->assertText($this->node->title, t('Basic page node is found with POST query and type:page.'));
- $this->drupalPost('search/node', array_merge($edit, array('type[article]' => 'article')), t('Advanced search'));
- $this->assertText('bike shed', t('Article node is not found with POST query and type:article.'));
- }
- }
- class SearchRankingTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Search engine ranking',
- 'description' => 'Indexes content and tests ranking factors.',
- 'group' => 'Search',
- );
- }
- /**
- * Implementation setUp().
- */
- function setUp() {
- parent::setUp('search', 'statistics', 'comment');
- }
- function testRankings() {
- // Login with sufficient privileges.
- $this->drupalLogin($this->drupalCreateUser(array('post comments without approval', 'create page content')));
- // Build a list of the rankings to test.
- $node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views');
- // Create nodes for testing.
- foreach ($node_ranks as $node_rank) {
- $settings = array('type' => 'page', 'title' => array(LANGUAGE_NONE => array(array('value' => 'Drupal rocks'))), 'body' => array(LANGUAGE_NONE => array(array('value' => "Drupal's search rocks"))));
- foreach (array(0, 1) as $num) {
- if ($num == 1) {
- switch ($node_rank) {
- case 'sticky':
- case 'promote':
- $settings[$node_rank] = 1;
- break;
- case 'relevance':
- $settings['body'][LANGUAGE_NONE][0]['value'] .= " really rocks";
- break;
- case 'recent':
- $settings['created'] = REQUEST_TIME + 3600;
- break;
- case 'comments':
- $settings['comment'] = 2;
- break;
- }
- }
- $nodes[$node_rank][$num] = $this->drupalCreateNode($settings);
- }
- }
- // Update the search index.
- module_invoke_all('update_index');
- search_update_totals();
- // Refresh variables after the treatment.
- $this->refreshVariables();
- // Add a comment to one of the nodes.
- $edit = array();
- $edit['subject'] = 'my comment title';
- $edit['comment_body[' . LANGUAGE_NONE . '][0][value]'] = 'some random comment';
- $this->drupalGet('comment/reply/' . $nodes['comments'][1]->nid);
- $this->drupalPost(NULL, $edit, t('Preview'));
- $this->drupalPost(NULL, $edit, t('Save'));
- // Enable counting of statistics.
- variable_set('statistics_count_content_views', 1);
- // Then View one of the nodes a bunch of times.
- for ($i = 0; $i < 5; $i ++) {
- $this->drupalGet('node/' . $nodes['views'][1]->nid);
- }
- // Test each of the possible rankings.
- foreach ($node_ranks as $node_rank) {
- // Disable all relevancy rankings except the one we are testing.
- foreach ($node_ranks as $var) {
- variable_set('node_rank_' . $var, $var == $node_rank ? 10 : 0);
- }
- // Do the search and assert the results.
- $set = node_search_execute('rocks');
- $this->assertEqual($set[0]['node']->nid, $nodes[$node_rank][1]->nid, 'Search ranking "' . $node_rank . '" order.');
- }
- }
- /**
- * Test rankings of HTML tags.
- */
- function testHTMLRankings() {
- // Login with sufficient privileges.
- $this->drupalLogin($this->drupalCreateUser(array('create page content')));
-
- // Test HTML tags with different weights.
- $sorted_tags = array('h1', 'h2', 'h3', 'h4', 'a', 'h5', 'h6', 'notag');
- $shuffled_tags = $sorted_tags;
- // Shuffle tags to ensure HTML tags are ranked properly.
- shuffle($shuffled_tags);
- $settings = array(
- 'type' => 'page',
- 'title' => array(LANGUAGE_NONE => array(array('value' => 'Simple node'))),
- );
- foreach ($shuffled_tags as $tag) {
- switch ($tag) {
- case 'a':
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => l('Drupal Rocks', 'node'), 'format' => 3)));
- break;
- case 'notag':
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'Drupal Rocks')));
- break;
- default:
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3)));
- break;
- }
- $nodes[$tag] = $this->drupalCreateNode($settings);
- }
- // Update the search index.
- module_invoke_all('update_index');
- search_update_totals();
- // Refresh variables after the treatment.
- $this->refreshVariables();
-
- // Disable all other rankings.
- $node_ranks = array('sticky', 'promote', 'recent', 'comments', 'views');
- foreach ($node_ranks as $node_rank) {
- variable_set('node_rank_' . $node_rank, 0);
- }
- $set = node_search_execute('rocks');
- // Test the ranking of each tag.
- foreach ($sorted_tags as $tag_rank => $tag) {
- // Assert the results.
- if ($tag == 'notag') {
- $this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for plain text order.');
- } else {
- $this->assertEqual($set[$tag_rank]['node']->nid, $nodes[$tag]->nid, 'Search tag ranking for "<' . $sorted_tags[$tag_rank] . '>" order.');
- }
- }
- // Test tags with the same weight against the sorted tags.
- $unsorted_tags = array('u', 'b', 'i', 'strong', 'em');
- foreach ($unsorted_tags as $tag) {
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 3)));
- $node = $this->drupalCreateNode($settings);
- // Update the search index.
- module_invoke_all('update_index');
- search_update_totals();
- // Refresh variables after the treatment.
- $this->refreshVariables();
- $set = node_search_execute('rocks');
- // Ranking should always be second to last.
- $set = array_slice($set, -2, 1);
- // Assert the results.
- $this->assertEqual($set[0]['node']->nid, $node->nid, 'Search tag ranking for "<' . $tag . '>" order.');
-
- // Delete node so it doesn't show up in subsequent search results.
- node_delete($node->nid);
- }
- }
- /**
- * Verifies that if we combine two rankings, search still works.
- *
- * See issue http://drupal.org/node/771596
- */
- function testDoubleRankings() {
- // Login with sufficient privileges.
- $this->drupalLogin($this->drupalCreateUser(array('post comments without approval', 'create page content')));
- // See testRankings() above - build a node that will rank high for sticky.
- $settings = array(
- 'type' => 'page',
- 'title' => array(LANGUAGE_NONE => array(array('value' => 'Drupal rocks'))),
- 'body' => array(LANGUAGE_NONE => array(array('value' => "Drupal's search rocks"))),
- 'sticky' => 1,
- );
- $node = $this->drupalCreateNode($settings);
- // Update the search index.
- module_invoke_all('update_index');
- search_update_totals();
- // Refresh variables after the treatment.
- $this->refreshVariables();
- // Set up for ranking sticky and lots of comments; make sure others are
- // disabled.
- $node_ranks = array('sticky', 'promote', 'relevance', 'recent', 'comments', 'views');
- foreach ($node_ranks as $var) {
- $value = ($var == 'sticky' || $var == 'comments') ? 10 : 0;
- variable_set('node_rank_' . $var, $value);
- }
- // Do the search and assert the results.
- $set = node_search_execute('rocks');
- $this->assertEqual($set[0]['node']->nid, $node->nid, 'Search double ranking order.');
- }
- }
- class SearchBlockTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Block availability',
- 'description' => 'Check if the search form block is available.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- // Create and login user
- $admin_user = $this->drupalCreateUser(array('administer blocks', 'search content'));
- $this->drupalLogin($admin_user);
- }
- function testSearchFormBlock() {
- // Set block title to confirm that the interface is availble.
- $this->drupalPost('admin/structure/block/manage/search/form/configure', array('title' => $this->randomName(8)), t('Save block'));
- $this->assertText(t('The block configuration has been saved.'), t('Block configuration set.'));
- // Set the block to a region to confirm block is availble.
- $edit = array();
- $edit['search_form[region]'] = 'footer';
- $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
- $this->assertText(t('The block settings have been updated.'), t('Block successfully move to footer region.'));
- }
- /**
- * Test that the search block form works correctly.
- */
- function testBlock() {
- // Enable the block, and place it in the 'content' region so that it isn't
- // hidden on 404 pages.
- $edit = array('search_form[region]' => 'content');
- $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
- // Test a normal search via the block form, from the front page.
- $terms = array('search_block_form' => 'test');
- $this->drupalPost('node', $terms, t('Search'));
- $this->assertText('Your search yielded no results');
- // Test a search from the block on a 404 page.
- $this->drupalGet('foo');
- $this->assertResponse(404);
- $this->drupalPost(NULL, $terms, t('Search'));
- $this->assertResponse(200);
- $this->assertText('Your search yielded no results');
- // Test a search from the block when it doesn't appear on the search page.
- $edit = array('pages' => 'search');
- $this->drupalPost('admin/structure/block/manage/search/form/configure', $edit, t('Save block'));
- $this->drupalPost('node', $terms, t('Search'));
- $this->assertText('Your search yielded no results');
- }
- }
- /**
- * Tests that searching for a phrase gets the correct page count.
- */
- class SearchExactTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Search engine phrase queries',
- 'description' => 'Tests that searching for a phrase gets the correct page count.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- }
- /**
- * Tests that the correct number of pager links are found for both keywords and phrases.
- */
- function testExactQuery() {
- // Login with sufficient privileges.
- $this->drupalLogin($this->drupalCreateUser(array('create page content', 'search content')));
- $settings = array(
- 'type' => 'page',
- 'title' => 'Simple Node',
- );
- // Create nodes with exact phrase.
- for ($i = 0; $i <= 17; $i++) {
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love pizza')));
- $this->drupalCreateNode($settings);
- }
- // Create nodes containing keywords.
- for ($i = 0; $i <= 17; $i++) {
- $settings['body'] = array(LANGUAGE_NONE => array(array('value' => 'love cheesy pizza')));
- $this->drupalCreateNode($settings);
- }
- // Update the search index.
- module_invoke_all('update_index');
- search_update_totals();
- // Refresh variables after the treatment.
- $this->refreshVariables();
- // Test that the correct number of pager links are found for keyword search.
- $edit = array('keys' => 'love pizza');
- $this->drupalPost('search/node', $edit, t('Search'));
- $this->assertLinkByHref('page=1', 0, '2nd page link is found for keyword search.');
- $this->assertLinkByHref('page=2', 0, '3rd page link is found for keyword search.');
- $this->assertLinkByHref('page=3', 0, '4th page link is found for keyword search.');
- $this->assertNoLinkByHref('page=4', '5th page link is not found for keyword search.');
- // Test that the correct number of pager links are found for exact phrase search.
- $edit = array('keys' => '"love pizza"');
- $this->drupalPost('search/node', $edit, t('Search'));
- $this->assertLinkByHref('page=1', 0, '2nd page link is found for exact phrase search.');
- $this->assertNoLinkByHref('page=2', '3rd page link is not found for exact phrase search.');
- }
- }
- /**
- * Test integration searching comments.
- */
- class SearchCommentTestCase extends DrupalWebTestCase {
- protected $admin_user;
- public static function getInfo() {
- return array(
- 'name' => 'Comment Search tests',
- 'description' => 'Verify text formats and filters used elsewhere.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('comment', 'search');
- // Create and log in an administrative user having access to the Full HTML
- // text format.
- $full_html_format = db_query_range('SELECT * FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchObject();
- $permissions = array(
- 'administer filters',
- filter_permission_name($full_html_format),
- 'administer permissions',
- 'create page content',
- 'post comments without approval',
- 'access comments',
- );
- $this->admin_user = $this->drupalCreateUser($permissions);
- $this->drupalLogin($this->admin_user);
- }
- /**
- * Verify that comments are rendered using proper format in search results.
- */
- function testSearchResultsComment() {
- $comment_body = 'Test comment body';
- variable_set('comment_preview_article', DRUPAL_OPTIONAL);
- // Enable check_plain() for 'Filtered HTML' text format.
- $filtered_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchField();
- $edit = array(
- 'filters[filter_html_escape][status]' => $filtered_html_format_id,
- );
- $this->drupalPost('admin/config/content/formats/1', $edit, t('Save configuration'));
- // Allow anonymous users to search content.
- $edit = array(
- DRUPAL_ANONYMOUS_RID . '[search content]' => 1,
- DRUPAL_ANONYMOUS_RID . '[access comments]' => 1,
- DRUPAL_ANONYMOUS_RID . '[post comments]' => 1,
- );
- $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
- // Create a node.
- $node = $this->drupalCreateNode(array('type' => 'article'));
- // Post a comment using 'Full HTML' text format.
- $edit_comment = array();
- $edit_comment['subject'] = 'Test comment subject';
- $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = '<h1>' . $comment_body . '</h1>';
- $full_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Full HTML'))->fetchField();
- $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][format]'] = $full_html_format_id;
- $this->drupalPost('comment/reply/' . $node->nid, $edit_comment, t('Save'));
- // Invoke search index update.
- $this->drupalLogout();
- $this->cronRun();
- // Search for the comment subject.
- $edit = array(
- 'search_block_form' => "'" . $edit_comment['subject'] . "'",
- );
- $this->drupalPost('', $edit, t('Search'));
- $this->assertText($node->title, t('Node found in search results.'));
- $this->assertText($edit_comment['subject'], t('Comment subject found in search results.'));
- // Search for the comment body.
- $edit = array(
- 'search_block_form' => "'" . $comment_body . "'",
- );
- $this->drupalPost('', $edit, t('Search'));
- $this->assertText($node->title, t('Node found in search results.'));
- // Verify that comment is rendered using proper format.
- $this->assertText($comment_body, t('Comment body text found in search results.'));
- $this->assertNoRaw(t('n/a'), t('HTML in comment body is not hidden.'));
- $this->assertNoRaw(check_plain($edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]']), t('HTML in comment body is not escaped.'));
- // Hide comments.
- $this->drupalLogin($this->admin_user);
- $node->comment = 0;
- node_save($node);
- // Invoke search index update.
- $this->drupalLogout();
- $this->cronRun();
- // Search for $title.
- $this->drupalPost('', $edit, t('Search'));
- $this->assertNoText($comment_body, t('Comment body text not found in search results.'));
- }
- /**
- * Verify access rules for comment indexing with different permissions.
- */
- function testSearchResultsCommentAccess() {
- $comment_body = 'Test comment body';
- $this->comment_subject = 'Test comment subject';
- $this->admin_role = $this->admin_user->roles;
- unset($this->admin_role[DRUPAL_AUTHENTICATED_RID]);
- $this->admin_role = key($this->admin_role);
- // Create a node.
- variable_set('comment_preview_article', DRUPAL_OPTIONAL);
- $this->node = $this->drupalCreateNode(array('type' => 'article'));
- // Post a comment using 'Full HTML' text format.
- $edit_comment = array();
- $edit_comment['subject'] = $this->comment_subject;
- $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = '<h1>' . $comment_body . '</h1>';
- $this->drupalPost('comment/reply/' . $this->node->nid, $edit_comment, t('Save'));
- $this->drupalLogout();
- $this->setRolePermissions(DRUPAL_ANONYMOUS_RID);
- $this->checkCommentAccess('Anon user has search permission but no access comments permission, comments should not be indexed');
- $this->setRolePermissions(DRUPAL_ANONYMOUS_RID, TRUE);
- $this->checkCommentAccess('Anon user has search permission and access comments permission, comments should be indexed', TRUE);
- $this->drupalLogin($this->admin_user);
- $this->drupalGet('admin/people/permissions');
- // Disable search access for authenticated user to test admin user.
- $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, FALSE, FALSE);
- $this->setRolePermissions($this->admin_role);
- $this->checkCommentAccess('Admin user has search permission but no access comments permission, comments should not be indexed');
- $this->setRolePermissions($this->admin_role, TRUE);
- $this->checkCommentAccess('Admin user has search permission and access comments permission, comments should be indexed', TRUE);
- $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID);
- $this->checkCommentAccess('Authenticated user has search permission but no access comments permission, comments should not be indexed');
- $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE);
- $this->checkCommentAccess('Authenticated user has search permission and access comments permission, comments should be indexed', TRUE);
- // Verify that access comments permission is inherited from the
- // authenticated role.
- $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE, FALSE);
- $this->setRolePermissions($this->admin_role);
- $this->checkCommentAccess('Admin user has search permission and no access comments permission, but comments should be indexed because admin user inherits authenticated user\'s permission to access comments', TRUE);
- // Verify that search content permission is inherited from the authenticated
- // role.
- $this->setRolePermissions(DRUPAL_AUTHENTICATED_RID, TRUE, TRUE);
- $this->setRolePermissions($this->admin_role, TRUE, FALSE);
- $this->checkCommentAccess('Admin user has access comments permission and no search permission, but comments should be indexed because admin user inherits authenticated user\'s permission to search', TRUE);
-
- }
- /**
- * Set permissions for role.
- */
- function setRolePermissions($rid, $access_comments = FALSE, $search_content = TRUE) {
- $permissions = array(
- 'access comments' => $access_comments,
- 'search content' => $search_content,
- );
- user_role_change_permissions($rid, $permissions);
- }
- /**
- * Update search index and search for comment.
- */
- function checkCommentAccess($message, $assume_access = FALSE) {
- // Invoke search index update.
- search_touch_node($this->node->nid);
- $this->cronRun();
- // Search for the comment subject.
- $edit = array(
- 'search_block_form' => "'" . $this->comment_subject . "'",
- );
- $this->drupalPost('', $edit, t('Search'));
- $method = $assume_access ? 'assertText' : 'assertNoText';
- $verb = $assume_access ? 'found' : 'not found';
- $this->{$method}($this->node->title, "Node $verb in search results: " . $message);
- $this->{$method}($this->comment_subject, "Comment subject $verb in search results: " . $message);
- }
- }
- /**
- * Tests that comment count display toggles properly on comment status of node
- *
- * Issue 537278
- *
- * - Nodes with comment status set to Open should always how comment counts
- * - Nodes with comment status set to Closed should show comment counts
- * only when there are comments
- * - Nodes with comment status set to Hidden should never show comment counts
- */
- class SearchCommentCountToggleTestCase extends DrupalWebTestCase {
- protected $searching_user;
- protected $searchable_nodes;
-
- public static function getInfo() {
- return array(
- 'name' => 'Comment count toggle',
- 'description' => 'Verify that comment count display toggles properly on comment status of node.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- // Create searching user.
- $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'post comments without approval'));
- // Create initial nodes.
- $node_params = array('type' => 'article', 'body' => array(LANGUAGE_NONE => array(array('value' => 'SearchCommentToggleTestCase'))));
-
- $this->searchable_nodes['1 comment'] = $this->drupalCreateNode($node_params);
- $this->searchable_nodes['0 comments'] = $this->drupalCreateNode($node_params);
-
- // Login with sufficient privileges.
- $this->drupalLogin($this->searching_user);
-
- // Create a comment array
- $edit_comment = array();
- $edit_comment['subject'] = $this->randomName();
- $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][value]'] = $this->randomName();
- $filtered_html_format_id = db_query_range('SELECT format FROM {filter_format} WHERE name = :name', 0, 1, array(':name' => 'Filtered HTML'))->fetchField();
- $edit_comment['comment_body[' . LANGUAGE_NONE . '][0][format]'] = $filtered_html_format_id;
-
- // Post comment to the test node with comment
- $this->drupalPost('comment/reply/' . $this->searchable_nodes['1 comment']->nid, $edit_comment, t('Save'));
-
- // First update the index. This does the initial processing.
- node_update_index();
- // Then, run the shutdown function. Testing is a unique case where indexing
- // and searching has to happen in the same request, so running the shutdown
- // function manually is needed to finish the indexing process.
- search_update_totals();
- }
- /**
- * Verify that comment count display toggles properly on comment status of node
- */
- function testSearchCommentCountToggle() {
- // Search for the nodes by string in the node body.
- $edit = array(
- 'search_block_form' => "'SearchCommentToggleTestCase'",
- );
- // Test comment count display for nodes with comment status set to Open
- $this->drupalPost('', $edit, t('Search'));
- $this->assertText(t('0 comments'), t('Empty comment count displays for nodes with comment status set to Open'));
- $this->assertText(t('1 comment'), t('Non-empty comment count displays for nodes with comment status set to Open'));
-
- // Test comment count display for nodes with comment status set to Closed
- $this->searchable_nodes['0 comments']->comment = COMMENT_NODE_CLOSED;
- node_save($this->searchable_nodes['0 comments']);
- $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_CLOSED;
- node_save($this->searchable_nodes['1 comment']);
-
- $this->drupalPost('', $edit, t('Search'));
- $this->assertNoText(t('0 comments'), t('Empty comment count does not display for nodes with comment status set to Closed'));
- $this->assertText(t('1 comment'), t('Non-empty comment count displays for nodes with comment status set to Closed'));
- // Test comment count display for nodes with comment status set to Hidden
- $this->searchable_nodes['0 comments']->comment = COMMENT_NODE_HIDDEN;
- node_save($this->searchable_nodes['0 comments']);
- $this->searchable_nodes['1 comment']->comment = COMMENT_NODE_HIDDEN;
- node_save($this->searchable_nodes['1 comment']);
-
- $this->drupalPost('', $edit, t('Search'));
- $this->assertNoText(t('0 comments'), t('Empty comment count does not display for nodes with comment status set to Hidden'));
- $this->assertNoText(t('1 comment'), t('Non-empty comment count does not display for nodes with comment status set to Hidden'));
- }
- }
- /**
- * Test search_simplify() on every Unicode character, and some other cases.
- */
- class SearchSimplifyTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Search simplify',
- 'description' => 'Check that the search_simply() function works as intended.',
- 'group' => 'Search',
- );
- }
- function testSearchSimplifyUnicode() {
- $input = file_get_contents(DRUPAL_ROOT . '/modules/search/tests/UnicodeTest.txt');
- $strings = explode(chr(10), $input);
- foreach ($strings as $key => $string) {
- $simplified = search_simplify($string);
- if ($key % 2) {
- $this->assertIdentical($simplified, ' ', "Line $key is excluded from the index");
- }
- else {
- $this->assertTrue(drupal_strlen($simplified) >= drupal_strlen($string), "Nothing is removed on line $key.");
- }
- }
- $string = '';
- for ($i = 0; $i < 32; $i++) {
- $string .= chr($i);
- }
- // Diff really does not like files starting with \0 so test it separately.
- $this->assertIdentical(' ', search_simplify($string), t('Search simplify works for ASCII control characters.'));
- }
- /**
- * Tests that search_simplify() does the right thing with punctuation.
- */
- function testSearchSimplifyPunctuation() {
- $cases = array(
- array('20.03/94-28,876', '20039428876', 'Punctuation removed from numbers'),
- array('great...drupal--module', 'great drupal module', 'Multiple dot and dashes are word boundaries'),
- array('very_great-drupal.module', 'verygreatdrupalmodule', 'Single dot, dash, underscore are removed'),
- array('regular,punctuation;word', 'regular punctuation word', 'Punctuation is a word boundary'),
- );
- foreach ($cases as $case) {
- $out = trim(search_simplify($case[0]));
- $this->assertEqual($out, $case[1], $case[2]);
- }
- }
- }
- /**
- * Test config page.
- */
- class SearchConfigSettingsForm extends DrupalWebTestCase {
- public $search_user;
- public $search_node;
- public static function getInfo() {
- return array(
- 'name' => 'Config settings form',
- 'description' => 'Verify the search config settings form.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search', 'search_extra_type');
- // Login as a user that can create and search content.
- $this->search_user = $this->drupalCreateUser(array('search content', 'administer search', 'administer nodes', 'bypass node access', 'access user profiles', 'administer users', 'administer blocks'));
- $this->drupalLogin($this->search_user);
- // Add a single piece of content and index it.
- $node = $this->drupalCreateNode();
- $this->search_node = $node;
- // Link the node to itself to test that it's only indexed once. The content
- // also needs the word "pizza" so we can use it as the search keyword.
- $langcode = LANGUAGE_NONE;
- $body_key = "body[$langcode][0][value]";
- $edit[$body_key] = l($node->title, 'node/' . $node->nid) . ' pizza sandwich';
- $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
- node_update_index();
- search_update_totals();
- // Enable the search block.
- $edit = array();
- $edit['search_form[region]'] = 'content';
- $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
- }
- /**
- * Verify the search settings form.
- */
- function testSearchSettingsPage() {
- // Test that the settings form displays the correct count of items left to index.
- $this->drupalGet('admin/config/search/settings');
- $this->assertText(t('There are @count items left to index.', array('@count' => 0)));
- // Test the re-index button.
- $this->drupalPost('admin/config/search/settings', array(), t('Re-index site'));
- $this->assertText(t('Are you sure you want to re-index the site'));
- $this->drupalPost('admin/config/search/settings/reindex', array(), t('Re-index site'));
- $this->assertText(t('The index will be rebuilt'));
- $this->drupalGet('admin/config/search/settings');
- $this->assertText(t('There is 1 item left to index.'));
- }
- /**
- * Verify that you can disable individual search modules.
- */
- function testSearchModuleDisabling() {
- // Array of search types to test: 'path' is the search path, 'title' is
- // the tab title, 'keys' are the keywords to search for, and 'text' is
- // the text to assert is on the results page.
- $module_info = array(
- 'node' => array(
- 'path' => 'node',
- 'title' => 'Content',
- 'keys' => 'pizza',
- 'text' => $this->search_node->title,
- ),
- 'user' => array(
- 'path' => 'user',
- 'title' => 'User',
- 'keys' => $this->search_user->name,
- 'text' => $this->search_user->mail,
- ),
- 'search_extra_type' => array(
- 'path' => 'dummy_path',
- 'title' => 'Dummy search type',
- 'keys' => 'foo',
- 'text' => 'Dummy search snippet to display',
- ),
- );
- $modules = array_keys($module_info);
- // Test each module if it's enabled as the only search module.
- foreach ($modules as $module) {
- // Enable the one module and disable other ones.
- $info = $module_info[$module];
- $edit = array();
- foreach ($modules as $other) {
- $edit['search_active_modules[' . $other . ']'] = (($other == $module) ? $module : FALSE);
- }
- $edit['search_default_module'] = $module;
- $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration'));
- // Run a search from the correct search URL.
- $this->drupalGet('search/' . $info['path'] . '/' . $info['keys']);
- $this->assertNoText('no results', $info['title'] . ' search found results');
- $this->assertText($info['text'], 'Correct search text found');
- // Verify that other module search tab titles are not visible.
- foreach ($modules as $other) {
- if ($other != $module) {
- $title = $module_info[$other]['title'];
- $this->assertNoText($title, $title . ' search tab is not shown');
- }
- }
- // Run a search from the search block on the node page. Verify you get
- // to this module's search results page.
- $terms = array('search_block_form' => $info['keys']);
- $this->drupalPost('node', $terms, t('Search'));
- $this->assertEqual(
- $this->getURL(),
- url('search/' . $info['path'] . '/' . $info['keys'], array('absolute' => TRUE)),
- 'Block redirected to right search page');
- // Try an invalid search path. Should redirect to our active module.
- $this->drupalGet('search/not_a_module_path');
- $this->assertEqual(
- $this->getURL(),
- url('search/' . $info['path'], array('absolute' => TRUE)),
- 'Invalid search path redirected to default search page');
- }
- // Test with all search modules enabled. When you go to the search
- // page or run search, all modules should be shown.
- $edit = array();
- foreach ($modules as $module) {
- $edit['search_active_modules[' . $module . ']'] = $module;
- }
- $edit['search_default_module'] = 'node';
- $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration'));
- foreach (array('search/node/pizza', 'search/node') as $path) {
- $this->drupalGet($path);
- foreach ($modules as $module) {
- $title = $module_info[$module]['title'];
- $this->assertText($title, $title . ' search tab is shown');
- }
- }
- }
- }
- /**
- * Tests the search_excerpt() function.
- */
- class SearchExcerptTestCase extends DrupalUnitTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'Search excerpt extraction',
- 'description' => 'Tests that the search_excerpt() function works.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- }
- /**
- * Tests search_excerpt() with several simulated search keywords.
- *
- * Passes keywords and a sample marked up string, "The quick
- * brown fox jumps over the lazy dog", and compares it to the
- * correctly marked up string. The correctly marked up string
- * contains either highlighted keywords or the original marked
- * up string if no keywords matched the string.
- */
- function testSearchExcerpt() {
- // Make some text with entities and tags.
- $text = 'The <strong>quick</strong> <a href="#">brown</a> fox & jumps <h2>over</h2> the lazy dog';
- // Note: The search_excerpt() function adds some extra spaces -- not
- // important for HTML formatting. Remove these for comparison.
- $expected = 'The quick brown fox & jumps over the lazy dog';
- $result = preg_replace('| +|', ' ', search_excerpt('nothing', $text));
- $this->assertEqual(preg_replace('| +|', ' ', $result), $expected, 'Entire string is returned when keyword is not found in short string');
- $result = preg_replace('| +|', ' ', search_excerpt('fox', $text));
- $this->assertEqual($result, 'The quick brown <strong>fox</strong> & jumps over the lazy dog ...', 'Found keyword is highlighted');
- $longtext = str_repeat($text . ' ', 10);
- $result = preg_replace('| +|', ' ', search_excerpt('nothing', $text));
- $this->assertTrue(strpos($result, $expected) === 0, 'When keyword is not found in long string, return value starts as expected');
- $entities = str_repeat('készítése ', 20);
- $result = preg_replace('| +|', ' ', search_excerpt('nothing', $entities));
- $this->assertFalse(strpos($result, '&'), 'Entities are not present in excerpt');
- $this->assertTrue(strpos($result, 'í') > 0, 'Entities are converted in excerpt');
- }
- }
- /**
- * Test the CJK tokenizer.
- */
- class SearchTokenizerTestCase extends DrupalWebTestCase {
- public static function getInfo() {
- return array(
- 'name' => 'CJK tokenizer',
- 'description' => 'Check that CJK tokenizer works as intended.',
- 'group' => 'Search',
- );
- }
- function setUp() {
- parent::setUp('search');
- }
- /**
- * Verifies that strings of CJK characters are tokenized.
- *
- * The search_simplify() function does special things with numbers, symbols,
- * and punctuation. So we only test that CJK characters that are not in these
- * character classes are tokenized properly. See PREG_CLASS_CKJ for more
- * information.
- */
- function testTokenizer() {
- // Set the minimum word size to 1 (to split all CJK characters) and make
- // sure CJK tokenizing is turned on.
- variable_set('minimum_word_size', 1);
- variable_set('overlap_cjk', TRUE);
- $this->refreshVariables();
- // Create a string of CJK characters from various character ranges in
- // the Unicode tables.
- // Beginnings of the character ranges.
- $starts = array(
- 'CJK unified' => 0x4e00,
- 'CJK Ext A' => 0x3400,
- 'CJK Compat' => 0xf900,
- 'Hangul Jamo' => 0x1100,
- 'Hangul Ext A' => 0xa960,
- 'Hangul Ext B' => 0xd7b0,
- 'Hangul Compat' => 0x3131,
- 'Half non-punct 1' => 0xff21,
- 'Half non-punct 2' => 0xff41,
- 'Half non-punct 3' => 0xff66,
- 'Hangul Syllables' => 0xac00,
- 'Hiragana' => 0x3040,
- 'Katakana' => 0x30a1,
- 'Katakana Ext' => 0x31f0,
- 'CJK Reserve 1' => 0x20000,
- 'CJK Reserve 2' => 0x30000,
- 'Bomofo' => 0x3100,
- 'Bomofo Ext' => 0x31a0,
- 'Lisu' => 0xa4d0,
- 'Yi' => 0xa000,
- );
- // Ends of the character ranges.
- $ends = array(
- 'CJK unified' => 0x9fcf,
- 'CJK Ext A' => 0x4dbf,
- 'CJK Compat' => 0xfaff,
- 'Hangul Jamo' => 0x11ff,
- 'Hangul Ext A' => 0xa97f,
- 'Hangul Ext B' => 0xd7ff,
- 'Hangul Compat' => 0x318e,
- 'Half non-punct 1' => 0xff3a,
- 'Half non-punct 2' => 0xff5a,
- 'Half non-punct 3' => 0xffdc,
- 'Hangul Syllables' => 0xd7af,
- 'Hiragana' => 0x309f,
- 'Katakana' => 0x30ff,
- 'Katakana Ext' => 0x31ff,
- 'CJK Reserve 1' => 0x2fffd,
- 'CJK Reserve 2' => 0x3fffd,
- 'Bomofo' => 0x312f,
- 'Bomofo Ext' => 0x31b7,
- 'Lisu' => 0xa4fd,
- 'Yi' => 0xa48f,
- );
- // Generate characters consisting of starts, midpoints, and ends.
- $chars = array();
- $charcodes = array();
- foreach ($starts as $key => $value) {
- $charcodes[] = $starts[$key];
- $chars[] = $this->code2utf($starts[$key]);
- $mid = round(0.5 * ($starts[$key] + $ends[$key]));
- $charcodes[] = $mid;
- $chars[] = $this->code2utf($mid);
- $charcodes[] = $ends[$key];
- $chars[] = $this->code2utf($ends[$key]);
- }
- // Merge into a string and tokenize.
- $string = implode('', $chars);
- $out = trim(search_simplify($string));
- $expected = drupal_strtolower(implode(' ', $chars));
- // Verify that the output matches what we expect.
- $this->assertEqual($out, $expected, 'CJK tokenizer worked on all supplied CJK characters');
- }
- /**
- * Verifies that strings of non-CJK characters are not tokenized.
- *
- * This is just a sanity check - it verifies that strings of letters are
- * not tokenized.
- */
- function testNoTokenizer() {
- // Set the minimum word size to 1 (to split all CJK characters) and make
- // sure CJK tokenizing is turned on.
- variable_set('minimum_word_size', 1);
- variable_set('overlap_cjk', TRUE);
- $this->refreshVariables();
- $letters = 'abcdefghijklmnopqrstuvwxyz';
- $out = trim(search_simplify($letters));
- $this->assertEqual($letters, $out, 'Letters are not CJK tokenized');
- }
- /**
- * Like PHP chr() function, but for unicode characters.
- *
- * chr() only works for ASCII characters up to character 255. This function
- * converts a number to the corresponding unicode character. Adapted from
- * functions supplied in comments on several functions on php.net.
- */
- function code2utf($num) {
- if ($num < 128) {
- return chr($num);
- }
- if ($num < 2048) {
- return chr(($num >> 6) + 192) . chr(($num & 63) + 128);
- }
- if ($num < 65536) {
- return chr(($num >> 12) + 224) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
- }
- if ($num < 2097152) {
- return chr(($num >> 18) + 240) . chr((($num >> 12) & 63) + 128) . chr((($num >> 6) & 63) + 128) . chr(($num & 63) + 128);
- }
- return '';
- }
- }