diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 1eaaac7f4e15022dc9382e2c24d07e786d9f5c44..e64bfca56a95939a7d5098af6e8a7c19b0d00dfa 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -669,8 +669,10 @@ function drupal_settings_initialize() { global $databases, $cookie_domain, $conf, $installed_profile, $update_free_access, $db_url, $db_prefix, $drupal_hash_salt, $is_https, $base_secure_url, $base_insecure_url, $config_directory_name; $conf = array(); - if (file_exists(DRUPAL_ROOT . '/' . conf_path() . '/settings.php')) { - include_once DRUPAL_ROOT . '/' . conf_path() . '/settings.php'; + // Make conf_path() available as local variable in settings.php. + $conf_path = conf_path(); + if (file_exists(DRUPAL_ROOT . '/' . $conf_path . '/settings.php')) { + include_once DRUPAL_ROOT . '/' . $conf_path . '/settings.php'; } $is_https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on'; diff --git a/core/includes/common.inc b/core/includes/common.inc index 890f7388b1311f546a71b72178958bf5ab44fee9..be6cab9dbde28f5e77de0598e46010370603e92e 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -3969,6 +3969,8 @@ function drupal_region_class($region) { * which drupal_add_js() happened earlier in the page request. * - defer: If set to TRUE, the defer attribute is set on the <script> * tag. Defaults to FALSE. + * - async: If set to TRUE, the async attribute is set on the <script> + * tag. Defaults to FALSE. * - cache: If set to FALSE, the JavaScript file is loaded anew on every page * call; in other words, it is not cached. Used only when 'type' references * a JavaScript file. Defaults to TRUE. @@ -4041,6 +4043,7 @@ function drupal_add_js($data = NULL, $options = NULL) { 'preprocess' => TRUE, 'cache' => TRUE, 'defer' => FALSE, + 'async' => FALSE, 'browsers' => array(), ), ); @@ -4088,6 +4091,7 @@ function drupal_js_defaults($data = NULL) { 'scope' => 'header', 'cache' => TRUE, 'defer' => FALSE, + 'async' => FALSE, 'preprocess' => TRUE, 'version' => NULL, 'data' => $data, @@ -4282,9 +4286,16 @@ function drupal_pre_render_scripts($elements) { break; } - // The defer attribute must not be specified if src is not defined. - if (!empty($item['defer']) && isset($element['#attributes']['src'])) { - $element['#attributes']['defer'] = 'defer'; + // The defer and async attributes must not be specified if the src + // attribute is not present. + if (!empty($element['#attributes']['src'])) { + // Both may be specified for legacy browser fallback purposes. + if (!empty($item['async'])) { + $element['#attributes']['async'] = 'async'; + } + if (!empty($item['defer'])) { + $element['#attributes']['defer'] = 'defer'; + } } $elements[] = $element; diff --git a/core/modules/aggregator/aggregator.info b/core/modules/aggregator/aggregator.info index 91357ca6c87396823c9f38ccf1102069f52d7dcd..c45fa82eb8a890f048da943cea9c7f46a8d337f0 100644 --- a/core/modules/aggregator/aggregator.info +++ b/core/modules/aggregator/aggregator.info @@ -3,6 +3,5 @@ description = "Aggregates syndicated content (RSS, RDF, and Atom feeds) from ext package = Core version = VERSION core = 8.x -files[] = aggregator.test configure = admin/config/services/aggregator/settings stylesheets[all][] = aggregator.theme.css diff --git a/core/modules/aggregator/aggregator.test b/core/modules/aggregator/aggregator.test deleted file mode 100644 index 8639389d31d6832d808fb15d7e5e1949fc58213a..0000000000000000000000000000000000000000 --- a/core/modules/aggregator/aggregator.test +++ /dev/null @@ -1,987 +0,0 @@ -<?php - -/** - * @file - * Tests for aggregator.module. - */ - -use Drupal\simpletest\WebTestBase; - -class AggregatorTestCase extends WebTestBase { - function setUp() { - parent::setUp(array('node', 'block', 'aggregator', 'aggregator_test')); - - // Create an Article node type. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - } - - $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content')); - $this->drupalLogin($web_user); - } - - /** - * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed). - * - * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. - * @return $feed - * Full feed object if possible. - * - * @see getFeedEditArray() - */ - function createFeed($feed_url = NULL) { - $edit = $this->getFeedEditArray($feed_url); - $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save')); - $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); - - $feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetch(); - $this->assertTrue(!empty($feed), t('The feed found in database.')); - return $feed; - } - - /** - * Delete an aggregator feed. - * - * @param $feed - * Feed object representing the feed. - */ - function deleteFeed($feed) { - $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, array(), t('Delete')); - $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), t('Feed deleted successfully.')); - } - - /** - * Return a randomly generated feed edit array. - * - * @param $feed_url - * If given, feed will be created with this URL, otherwise /rss.xml will be used. - * @return - * A feed array. - */ - function getFeedEditArray($feed_url = NULL) { - $feed_name = $this->randomName(10); - if (!$feed_url) { - $feed_url = url('rss.xml', array( - 'query' => array('feed' => $feed_name), - 'absolute' => TRUE, - )); - } - $edit = array( - 'title' => $feed_name, - 'url' => $feed_url, - 'refresh' => '900', - ); - return $edit; - } - - /** - * Return the count of the randomly created feed array. - * - * @return - * Number of feed items on default feed created by createFeed(). - */ - function getDefaultFeedItemCount() { - // Our tests are based off of rss.xml, so let's find out how many elements should be related. - $feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, config('system.rss-publishing')->get('feed_default_items'))->fetchField(); - return $feed_count > 10 ? 10 : $feed_count; - } - - /** - * Update feed items (simulate click to admin/config/services/aggregator/update/$fid). - * - * @param $feed - * Feed object representing the feed. - * @param $expected_count - * Expected number of feed items. - */ - function updateFeedItems(&$feed, $expected_count) { - // First, let's ensure we can get to the rss xml. - $this->drupalGet($feed->url); - $this->assertResponse(200, t('!url is reachable.', array('!url' => $feed->url))); - - // Attempt to access the update link directly without an access token. - $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); - $this->assertResponse(403); - - // Refresh the feed (simulated link click). - $this->drupalGet('admin/config/services/aggregator'); - $this->clickLink('update items'); - - // Ensure we have the right number of items. - $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid)); - $items = array(); - $feed->items = array(); - foreach ($result as $item) { - $feed->items[] = $item->iid; - } - $feed->item_count = count($feed->items); - $this->assertEqual($expected_count, $feed->item_count, t('Total items in feed equal to the total items in database (!val1 != !val2)', array('!val1' => $expected_count, '!val2' => $feed->item_count))); - } - - /** - * Confirm item removal from a feed. - * - * @param $feed - * Feed object representing the feed. - */ - function removeFeedItems($feed) { - $this->drupalPost('admin/config/services/aggregator/remove/' . $feed->fid, array(), t('Remove items')); - $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), t('Feed items removed.')); - } - - /** - * Add and remove feed items and ensure that the count is zero. - * - * @param $feed - * Feed object representing the feed. - * @param $expected_count - * Expected number of feed items. - */ - function updateAndRemove($feed, $expected_count) { - $this->updateFeedItems($feed, $expected_count); - $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); - $this->assertTrue($count); - $this->removeFeedItems($feed); - $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); - $this->assertTrue($count == 0); - } - - /** - * Pull feed categories from aggregator_category_feed table. - * - * @param $feed - * Feed object representing the feed. - */ - function getFeedCategories($feed) { - // add the categories to the feed so we can use them - $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->fid)); - foreach ($result as $category) { - $feed->categories[] = $category->cid; - } - } - - /** - * Pull categories from aggregator_category table. - */ - function getCategories() { - $categories = array(); - $result = db_query('SELECT * FROM {aggregator_category}'); - foreach ($result as $category) { - $categories[$category->cid] = $category; - } - return $categories; - } - - - /** - * Check if the feed name and url is unique. - * - * @param $feed_name - * String containing the feed name to check. - * @param $feed_url - * String containing the feed url to check. - * @return - * TRUE if feed is unique. - */ - function uniqueFeed($feed_name, $feed_url) { - $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField(); - return (1 == $result); - } - - /** - * Create a valid OPML file from an array of feeds. - * - * @param $feeds - * An array of feeds. - * @return - * Path to valid OPML file. - */ - function getValidOpml($feeds) { - // Properly escape URLs so that XML parsers don't choke on them. - foreach ($feeds as &$feed) { - $feed['url'] = htmlspecialchars($feed['url']); - } - /** - * Does not have an XML declaration, must pass the parser. - */ - $opml = <<<EOF -<opml version="1.0"> - <head></head> - <body> - <!-- First feed to be imported. --> - <outline text="{$feeds[0]['title']}" xmlurl="{$feeds[0]['url']}" /> - - <!-- Second feed. Test string delimitation and attribute order. --> - <outline xmlurl='{$feeds[1]['url']}' text='{$feeds[1]['title']}'/> - - <!-- Test for duplicate URL and title. --> - <outline xmlurl="{$feeds[0]['url']}" text="Duplicate URL"/> - <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title']}"/> - - <!-- Test that feeds are only added with required attributes. --> - <outline text="{$feeds[2]['title']}" /> - <outline xmlurl="{$feeds[2]['url']}" /> - </body> -</opml> -EOF; - - $path = 'public://valid-opml.xml'; - return file_unmanaged_save_data($opml, $path); - } - - /** - * Create an invalid OPML file. - * - * @return - * Path to invalid OPML file. - */ - function getInvalidOpml() { - $opml = <<<EOF -<opml> - <invalid> -</opml> -EOF; - - $path = 'public://invalid-opml.xml'; - return file_unmanaged_save_data($opml, $path); - } - - /** - * Create a valid but empty OPML file. - * - * @return - * Path to empty OPML file. - */ - function getEmptyOpml() { - $opml = <<<EOF -<?xml version="1.0" encoding="utf-8"?> -<opml version="1.0"> - <head></head> - <body> - <outline text="Sample text" /> - <outline text="Sample text" url="Sample URL" /> - </body> -</opml> -EOF; - - $path = 'public://empty-opml.xml'; - return file_unmanaged_save_data($opml, $path); - } - - function getRSS091Sample() { - return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_rss091.xml'; - } - - function getAtomSample() { - // The content of this sample ATOM feed is based directly off of the - // example provided in RFC 4287. - return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_atom.xml'; - } - - function getHtmlEntitiesSample() { - return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_title_entities.xml'; - } - - /** - * Creates sample article nodes. - * - * @param $count - * (optional) The number of nodes to generate. - */ - function createSampleNodes($count = 5) { - $langcode = LANGUAGE_NOT_SPECIFIED; - // Post $count article nodes. - for ($i = 0; $i < $count; $i++) { - $edit = array(); - $edit['title'] = $this->randomName(); - $edit["body[$langcode][0][value]"] = $this->randomName(); - $this->drupalPost('node/add/article', $edit, t('Save')); - } - } -} - -/** - * Tests aggregator configuration settings. - */ -class AggregatorConfigurationTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Aggregator configuration', - 'description' => 'Test aggregator settings page.', - 'group' => 'Aggregator', - ); - } - - /** - * Tests the settings form to ensure the correct default values are used. - */ - function testSettingsPage() { - $edit = array( - 'aggregator_allowed_html_tags' => '<a>', - 'aggregator_summary_items' => 10, - 'aggregator_clear' => 3600, - 'aggregator_category_selector' => 'select', - 'aggregator_teaser_length' => 200, - ); - $this->drupalPost('admin/config/services/aggregator/settings', $edit, t('Save configuration')); - $this->assertText(t('The configuration options have been saved.')); - - foreach ($edit as $name => $value) { - $this->assertFieldByName($name, $value, t('"@name" has correct default value.', array('@name' => $name))); - } - } -} - -class AddFeedTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Add feed functionality', - 'description' => 'Add feed test.', - 'group' => 'Aggregator' - ); - } - - /** - * Create a feed, ensure that it is unique, check the source, and delete the feed. - */ - function testAddFeed() { - $feed = $this->createFeed(); - - // Check feed data. - $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/add/feed', array('absolute' => TRUE)), t('Directed to correct url.')); - $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), t('The feed is unique.')); - - // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, t('Feed source exists.')); - $this->assertText($feed->title, t('Page title')); - $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); - $this->assertResponse(200, t('Feed categorization page exists.')); - - // Delete feed. - $this->deleteFeed($feed); - } - - /** - * Tests feeds with very long URLs. - */ - function testAddLongFeed() { - // Create a feed with a URL of > 255 characters. - $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb"; - $feed = $this->createFeed($long_url); - - // Create a second feed of > 255 characters, where the only difference is - // after the 255th character. - $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889"; - $feed_2 = $this->createFeed($long_url_2); - - // Check feed data. - $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The first long URL feed is unique.'); - $this->assertTrue($this->uniqueFeed($feed_2->title, $feed_2->url), 'The second long URL feed is unique.'); - - // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, 'Long URL feed source exists.'); - $this->assertText($feed->title, 'Page title'); - $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); - $this->assertResponse(200, 'Long URL feed categorization page exists.'); - - // Delete feeds. - $this->deleteFeed($feed); - $this->deleteFeed($feed_2); - } -} - -class CategorizeFeedTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Categorize feed functionality', - 'description' => 'Categorize feed test.', - 'group' => 'Aggregator' - ); - } - - /** - * Create a feed and make sure you can add more than one category to it. - */ - function testCategorizeFeed() { - - // Create 2 categories. - $category_1 = array('title' => $this->randomName(10), 'description' => ''); - $this->drupalPost('admin/config/services/aggregator/add/category', $category_1, t('Save')); - $this->assertRaw(t('The category %title has been added.', array('%title' => $category_1['title'])), t('The category %title has been added.', array('%title' => $category_1['title']))); - - $category_2 = array('title' => $this->randomName(10), 'description' => ''); - $this->drupalPost('admin/config/services/aggregator/add/category', $category_2, t('Save')); - $this->assertRaw(t('The category %title has been added.', array('%title' => $category_2['title'])), t('The category %title has been added.', array('%title' => $category_2['title']))); - - // Get categories from database. - $categories = $this->getCategories(); - - // Create a feed and assign 2 categories to it. - $feed = $this->getFeedEditArray(); - $feed['block'] = 5; - foreach ($categories as $cid => $category) { - $feed['category'][$cid] = $cid; - } - - // Use aggregator_save_feed() function to save the feed. - aggregator_save_feed($feed); - $db_feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed['title'], ':url' => $feed['url']))->fetch(); - - // Assert the feed has two categories. - $this->getFeedCategories($db_feed); - $this->assertEqual(count($db_feed->categories), 2, t('Feed has 2 categories')); - } -} - -class UpdateFeedTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Update feed functionality', - 'description' => 'Update feed test.', - 'group' => 'Aggregator' - ); - } - - /** - * Create a feed and attempt to update it. - */ - function testUpdateFeed() { - $remamining_fields = array('title', 'url', ''); - foreach ($remamining_fields as $same_field) { - $feed = $this->createFeed(); - - // Get new feed data array and modify newly created feed. - $edit = $this->getFeedEditArray(); - $edit['refresh'] = 1800; // Change refresh value. - if (isset($feed->{$same_field})) { - $edit[$same_field] = $feed->{$same_field}; - } - $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, $edit, t('Save')); - $this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), t('The feed %name has been updated.', array('%name' => $edit['title']))); - - // Check feed data. - $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/', array('absolute' => TRUE))); - $this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), t('The feed is unique.')); - - // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, t('Feed source exists.')); - $this->assertText($edit['title'], t('Page title')); - - // Delete feed. - $feed->title = $edit['title']; // Set correct title so deleteFeed() will work. - $this->deleteFeed($feed); - } - } -} - -class RemoveFeedTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Remove feed functionality', - 'description' => 'Remove feed test.', - 'group' => 'Aggregator' - ); - } - - /** - * Remove a feed and ensure that all it services are removed. - */ - function testRemoveFeed() { - $feed = $this->createFeed(); - - // Delete feed. - $this->deleteFeed($feed); - - // Check feed source. - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(404, t('Deleted feed source does not exists.')); - - // Check database for feed. - $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField(); - $this->assertFalse($result, t('Feed not found in database')); - } -} - -class UpdateFeedItemTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Update feed item functionality', - 'description' => 'Update feed items from a feed.', - 'group' => 'Aggregator' - ); - } - - /** - * Test running "update items" from the 'admin/config/services/aggregator' page. - */ - function testUpdateFeedItem() { - $this->createSampleNodes(); - - // Create a feed and test updating feed items if possible. - $feed = $this->createFeed(); - if (!empty($feed)) { - $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); - $this->removeFeedItems($feed); - } - - // Delete feed. - $this->deleteFeed($feed); - - // Test updating feed items without valid timestamp information. - $edit = array( - 'title' => "Feed without publish timestamp", - 'url' => $this->getRSS091Sample(), - ); - - $this->drupalGet($edit['url']); - $this->assertResponse(array(200), t('URL !url is accessible', array('!url' => $edit['url']))); - - $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save')); - $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); - - $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject(); - - aggregator_refresh($feed); - $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); - - // Sleep for 3 second. - sleep(3); - db_update('aggregator_feed') - ->condition('fid', $feed->fid) - ->fields(array( - 'checked' => 0, - 'hash' => '', - 'etag' => '', - 'modified' => 0, - )) - ->execute(); - aggregator_refresh($feed); - - $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); - $this->assertTrue($before === $after, t('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); - } -} - -class RemoveFeedItemTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Remove feed item functionality', - 'description' => 'Remove feed items from a feed.', - 'group' => 'Aggregator' - ); - } - - /** - * Test running "remove items" from the 'admin/config/services/aggregator' page. - */ - function testRemoveFeedItem() { - // Create a bunch of test feeds. - $feed_urls = array(); - // No last-modified, no etag. - $feed_urls[] = url('aggregator/test-feed', array('absolute' => TRUE)); - // Last-modified, but no etag. - $feed_urls[] = url('aggregator/test-feed/1', array('absolute' => TRUE)); - // No Last-modified, but etag. - $feed_urls[] = url('aggregator/test-feed/0/1', array('absolute' => TRUE)); - // Last-modified and etag. - $feed_urls[] = url('aggregator/test-feed/1/1', array('absolute' => TRUE)); - - foreach ($feed_urls as $feed_url) { - $feed = $this->createFeed($feed_url); - // Update and remove items two times in a row to make sure that removal - // resets all 'modified' information (modified, etag, hash) and allows for - // immediate update. - $this->updateAndRemove($feed, 2); - $this->updateAndRemove($feed, 2); - $this->updateAndRemove($feed, 2); - // Delete feed. - $this->deleteFeed($feed); - } - } -} - -class CategorizeFeedItemTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Categorize feed item functionality', - 'description' => 'Test feed item categorization.', - 'group' => 'Aggregator' - ); - } - - /** - * If a feed has a category, make sure that the children inherit that - * categorization. - */ - function testCategorizeFeedItem() { - $this->createSampleNodes(); - - // Simulate form submission on "admin/config/services/aggregator/add/category". - $edit = array('title' => $this->randomName(10), 'description' => ''); - $this->drupalPost('admin/config/services/aggregator/add/category', $edit, t('Save')); - $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), t('The category %title has been added.', array('%title' => $edit['title']))); - - $category = db_query("SELECT * FROM {aggregator_category} WHERE title = :title", array(':title' => $edit['title']))->fetch(); - $this->assertTrue(!empty($category), t('The category found in database.')); - - $link_path = 'aggregator/categories/' . $category->cid; - $menu_link = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetch(); - $this->assertTrue(!empty($menu_link), t('The menu link associated with the category found in database.')); - - $feed = $this->createFeed(); - db_insert('aggregator_category_feed') - ->fields(array( - 'cid' => $category->cid, - 'fid' => $feed->fid, - )) - ->execute(); - $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); - $this->getFeedCategories($feed); - $this->assertTrue(!empty($feed->categories), t('The category found in the feed.')); - - // For each category of a feed, ensure feed items have that category, too. - if (!empty($feed->categories) && !empty($feed->items)) { - foreach ($feed->categories as $category) { - $categorized_count = db_select('aggregator_category_item') - ->condition('iid', $feed->items, 'IN') - ->countQuery() - ->execute() - ->fetchField(); - - $this->assertEqual($feed->item_count, $categorized_count, t('Total items in feed equal to the total categorized feed items in database')); - } - } - - // Delete feed. - $this->deleteFeed($feed); - } -} - -class ImportOPMLTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Import feeds from OPML functionality', - 'description' => 'Test OPML import.', - 'group' => 'Aggregator', - ); - } - - /** - * Open OPML import form. - */ - function openImportForm() { - db_delete('aggregator_category')->execute(); - - $category = $this->randomName(10); - $cid = db_insert('aggregator_category') - ->fields(array( - 'title' => $category, - 'description' => '', - )) - ->execute(); - - $this->drupalGet('admin/config/services/aggregator/add/opml'); - $this->assertText('A single OPML document may contain a collection of many feeds.', t('Found OPML help text.')); - $this->assertField('files[upload]', t('Found file upload field.')); - $this->assertField('remote', t('Found Remote URL field.')); - $this->assertField('refresh', '', t('Found Refresh field.')); - $this->assertFieldByName("category[$cid]", $cid, t('Found category field.')); - } - - /** - * Submit form filled with invalid fields. - */ - function validateImportFormFields() { - $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - - $edit = array(); - $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); - $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), t('Error if no fields are filled.')); - - $path = $this->getEmptyOpml(); - $edit = array( - 'files[upload]' => $path, - 'remote' => file_create_url($path), - ); - $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); - $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), t('Error if both fields are filled.')); - - $edit = array('remote' => 'invalidUrl://empty'); - $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); - $this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.'); - - $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - $this->assertEqual($before, $after, t('No feeds were added during the three last form submissions.')); - } - - /** - * Submit form with invalid, empty and valid OPML files. - */ - function submitImportForm() { - $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - - $form['files[upload]'] = $this->getInvalidOpml(); - $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); - $this->assertText(t('No new feed has been added.'), t('Attempting to upload invalid XML.')); - - $edit = array('remote' => file_create_url($this->getEmptyOpml())); - $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); - $this->assertText(t('No new feed has been added.'), t('Attempting to load empty OPML from remote URL.')); - - $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - $this->assertEqual($before, $after, t('No feeds were added during the two last form submissions.')); - - db_delete('aggregator_feed')->execute(); - db_delete('aggregator_category')->execute(); - db_delete('aggregator_category_feed')->execute(); - - $category = $this->randomName(10); - db_insert('aggregator_category') - ->fields(array( - 'cid' => 1, - 'title' => $category, - 'description' => '', - )) - ->execute(); - - $feeds[0] = $this->getFeedEditArray(); - $feeds[1] = $this->getFeedEditArray(); - $feeds[2] = $this->getFeedEditArray(); - $edit = array( - 'files[upload]' => $this->getValidOpml($feeds), - 'refresh' => '900', - 'category[1]' => $category, - ); - $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); - $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), t('Verifying that a duplicate URL was identified')); - $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), t('Verifying that a duplicate title was identified')); - - $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - $this->assertEqual($after, 2, t('Verifying that two distinct feeds were added.')); - - $feeds_from_db = db_query("SELECT f.title, f.url, f.refresh, cf.cid FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} cf ON f.fid = cf.fid"); - $refresh = $category = TRUE; - foreach ($feeds_from_db as $feed) { - $title[$feed->url] = $feed->title; - $url[$feed->title] = $feed->url; - $category = $category && $feed->cid == 1; - $refresh = $refresh && $feed->refresh == 900; - } - - $this->assertEqual($title[$feeds[0]['url']], $feeds[0]['title'], t('First feed was added correctly.')); - $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['url'], t('Second feed was added correctly.')); - $this->assertTrue($refresh, t('Refresh times are correct.')); - $this->assertTrue($category, t('Categories are correct.')); - } - - function testOPMLImport() { - $this->openImportForm(); - $this->validateImportFormFields(); - $this->submitImportForm(); - } -} - -class AggregatorCronTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Update on cron functionality', - 'description' => 'Update feeds on cron.', - 'group' => 'Aggregator' - ); - } - - /** - * Add feeds update them on cron. - */ - public function testCron() { - // Create feed and test basic updating on cron. - global $base_url; - $key = config('system.cron')->get('cron_key'); - $this->createSampleNodes(); - $feed = $this->createFeed(); - $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); - $this->removeFeedItems($feed); - $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); - $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); - - // Test feed locking when queued for update. - $this->removeFeedItems($feed); - db_update('aggregator_feed') - ->condition('fid', $feed->fid) - ->fields(array( - 'queued' => REQUEST_TIME, - )) - ->execute(); - $this->cronRun(); - $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); - db_update('aggregator_feed') - ->condition('fid', $feed->fid) - ->fields(array( - 'queued' => 0, - )) - ->execute(); - $this->cronRun(); - $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); - } -} - -class AggregatorRenderingTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Checks display of aggregator items', - 'description' => 'Checks display of aggregator items on the page.', - 'group' => 'Aggregator' - ); - } - - /** - * Add a feed block to the page and checks its links. - * - * TODO: Test the category block as well. - */ - public function testBlockLinks() { - // Create feed. - $this->createSampleNodes(); - $feed = $this->createFeed(); - $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); - - // Place block on page (@see block.test:moveBlockToRegion()) - // Need admin user to be able to access block admin. - $this->admin_user = $this->drupalCreateUser(array( - 'administer blocks', - 'access administration pages', - 'administer news feeds', - 'access news feeds', - )); - $this->drupalLogin($this->admin_user); - - // Prepare to use the block admin form. - $block = array( - 'module' => 'aggregator', - 'delta' => 'feed-' . $feed->fid, - 'title' => $feed->title, - ); - $region = 'footer'; - $edit = array(); - $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region; - // Check the feed block is available in the block list form. - $this->drupalGet('admin/structure/block'); - $this->assertFieldByName('blocks[' . $block['module'] . '_' . $block['delta'] . '][region]', '', 'Aggregator feed block is available for positioning.'); - // Position it. - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region))); - // Confirm that the block is now being displayed on pages. - $this->drupalGet('node'); - $this->assertText(t($block['title']), t('Feed block is displayed on the page.')); - - // Find the expected read_more link. - $href = 'aggregator/sources/' . $feed->fid; - $links = $this->xpath('//a[@href = :href]', array(':href' => url($href))); - $this->assert(isset($links[0]), t('Link to href %href found.', array('%href' => $href))); - - // Visit that page. - $this->drupalGet($href); - $correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->title)); - $this->assertFalse(empty($correct_titles), t('Aggregator feed page is available and has the correct title.')); - - // Set the number of news items to 0 to test that the block does not show - // up. - $feed->block = 0; - aggregator_save_feed((array) $feed); - // It is nescessary to flush the cache after saving the number of items. - $this->resetAll(); - // Check that the block is no longer displayed. - $this->drupalGet('node'); - $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.'); - } - - /** - * Create a feed and check that feed's page. - */ - public function testFeedPage() { - // Increase the number of items published in the rss.xml feed so we have - // enough articles to test paging. - $config = config('system.rss-publishing'); - $config->set('feed_default_items', 30); - $config->save(); - - // Create a feed with 30 items. - $this->createSampleNodes(30); - $feed = $this->createFeed(); - $this->updateFeedItems($feed, 30); - - // Check for the presence of a pager. - $this->drupalGet('aggregator/sources/' . $feed->fid); - $elements = $this->xpath("//ul[@class=:class]", array(':class' => 'pager')); - $this->assertTrue(!empty($elements), t('Individual source page contains a pager.')); - - // Reset the number of items in rss.xml to the default value. - $config->set('feed_default_items', 10); - $config->save(); - } -} - -/** - * Tests for feed parsing. - */ -class FeedParserTestCase extends AggregatorTestCase { - public static function getInfo() { - return array( - 'name' => 'Feed parser functionality', - 'description' => 'Test the built-in feed parser with valid feed samples.', - 'group' => 'Aggregator', - ); - } - - function setUp() { - parent::setUp(); - // Do not remove old aggregator items during these tests, since our sample - // feeds have hardcoded dates in them (which may be expired when this test - // is run). - variable_set('aggregator_clear', AGGREGATOR_CLEAR_NEVER); - } - - /** - * Test a feed that uses the RSS 0.91 format. - */ - function testRSS091Sample() { - $feed = $this->createFeed($this->getRSS091Sample()); - aggregator_refresh($feed); - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); - $this->assertText('First example feed item title'); - $this->assertLinkByHref('http://example.com/example-turns-one'); - $this->assertText('First example feed item description.'); - } - - /** - * Test a feed that uses the Atom format. - */ - function testAtomSample() { - $feed = $this->createFeed($this->getAtomSample()); - aggregator_refresh($feed); - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); - $this->assertText('Atom-Powered Robots Run Amok'); - $this->assertLinkByHref('http://example.org/2003/12/13/atom03'); - $this->assertText('Some text.'); - $this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.'); - } - - /** - * Tests a feed that uses HTML entities in item titles. - */ - function testHtmlEntitiesSample() { - $feed = $this->createFeed($this->getHtmlEntitiesSample()); - aggregator_refresh($feed); - $this->drupalGet('aggregator/sources/' . $feed->fid); - $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); - $this->assertRaw("Quote" Amp&"); - } -} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..aec086cbd7c0fb669c9b0e9e5890fc97ca744eaf --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AddFeedTest.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\AddFeedTest. + */ + +namespace Drupal\aggregator\Tests; + +/** + * Tests aggregator feed adding. + */ +class AddFeedTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Add feed functionality', + 'description' => 'Add feed test.', + 'group' => 'Aggregator' + ); + } + + /** + * Create a feed, ensure that it is unique, check the source, and delete the feed. + */ + function testAddFeed() { + $feed = $this->createFeed(); + + // Check feed data. + $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/add/feed', array('absolute' => TRUE)), t('Directed to correct url.')); + $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), t('The feed is unique.')); + + // Check feed source. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, t('Feed source exists.')); + $this->assertText($feed->title, t('Page title')); + $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); + $this->assertResponse(200, t('Feed categorization page exists.')); + + // Delete feed. + $this->deleteFeed($feed); + } + + /** + * Tests feeds with very long URLs. + */ + function testAddLongFeed() { + // Create a feed with a URL of > 255 characters. + $long_url = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889&ix=heb"; + $feed = $this->createFeed($long_url); + + // Create a second feed of > 255 characters, where the only difference is + // after the 255th character. + $long_url_2 = "https://www.google.com/search?ix=heb&sourceid=chrome&ie=UTF-8&q=angie+byron#sclient=psy-ab&hl=en&safe=off&source=hp&q=angie+byron&pbx=1&oq=angie+byron&aq=f&aqi=&aql=&gs_sm=3&gs_upl=0l0l0l10534l0l0l0l0l0l0l0l0ll0l0&bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&fp=a70b6b1f0abe28d8&biw=1629&bih=889"; + $feed_2 = $this->createFeed($long_url_2); + + // Check feed data. + $this->assertTrue($this->uniqueFeed($feed->title, $feed->url), 'The first long URL feed is unique.'); + $this->assertTrue($this->uniqueFeed($feed_2->title, $feed_2->url), 'The second long URL feed is unique.'); + + // Check feed source. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, 'Long URL feed source exists.'); + $this->assertText($feed->title, 'Page title'); + $this->drupalGet('aggregator/sources/' . $feed->fid . '/categorize'); + $this->assertResponse(200, 'Long URL feed categorization page exists.'); + + // Delete feeds. + $this->deleteFeed($feed); + $this->deleteFeed($feed_2); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorConfigurationTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorConfigurationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..65eb6ab11398ec7eed31f7afcd94250b01e95449 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorConfigurationTest.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\AggregatorConfigurationTest. + */ + +namespace Drupal\aggregator\Tests; + +/** + * Tests aggregator configuration settings. + */ +class AggregatorConfigurationTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Aggregator configuration', + 'description' => 'Test aggregator settings page.', + 'group' => 'Aggregator', + ); + } + + /** + * Tests the settings form to ensure the correct default values are used. + */ + function testSettingsPage() { + $edit = array( + 'aggregator_allowed_html_tags' => '<a>', + 'aggregator_summary_items' => 10, + 'aggregator_clear' => 3600, + 'aggregator_category_selector' => 'select', + 'aggregator_teaser_length' => 200, + ); + $this->drupalPost('admin/config/services/aggregator/settings', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.')); + + foreach ($edit as $name => $value) { + $this->assertFieldByName($name, $value, t('"@name" has correct default value.', array('@name' => $name))); + } + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a6c29687b3056330c8d069cd5e815eb9212e29cb --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorCronTest.php @@ -0,0 +1,54 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\AggregatorCronTest. + */ + +namespace Drupal\aggregator\Tests; + +class AggregatorCronTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Update on cron functionality', + 'description' => 'Update feeds on cron.', + 'group' => 'Aggregator' + ); + } + + /** + * Add feeds update them on cron. + */ + public function testCron() { + // Create feed and test basic updating on cron. + global $base_url; + $key = config('system.cron')->get('cron_key'); + $this->createSampleNodes(); + $feed = $this->createFeed(); + $this->cronRun(); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); + $this->removeFeedItems($feed); + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); + $this->cronRun(); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); + + // Test feed locking when queued for update. + $this->removeFeedItems($feed); + db_update('aggregator_feed') + ->condition('fid', $feed->fid) + ->fields(array( + 'queued' => REQUEST_TIME, + )) + ->execute(); + $this->cronRun(); + $this->assertEqual(0, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); + db_update('aggregator_feed') + ->condition('fid', $feed->fid) + ->fields(array( + 'queued' => 0, + )) + ->execute(); + $this->cronRun(); + $this->assertEqual(5, db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(), 'Expected number of items in database.'); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7a5b58ea56d618b00f054105926d8d6bbad037e7 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php @@ -0,0 +1,104 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\AggregatorRenderingTest. + */ + +namespace Drupal\aggregator\Tests; + +class AggregatorRenderingTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Checks display of aggregator items', + 'description' => 'Checks display of aggregator items on the page.', + 'group' => 'Aggregator' + ); + } + + /** + * Add a feed block to the page and checks its links. + * + * TODO: Test the category block as well. + */ + public function testBlockLinks() { + // Create feed. + $this->createSampleNodes(); + $feed = $this->createFeed(); + $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); + + // Place block on page (@see block.test:moveBlockToRegion()) + // Need admin user to be able to access block admin. + $this->admin_user = $this->drupalCreateUser(array( + 'administer blocks', + 'access administration pages', + 'administer news feeds', + 'access news feeds', + )); + $this->drupalLogin($this->admin_user); + + // Prepare to use the block admin form. + $block = array( + 'module' => 'aggregator', + 'delta' => 'feed-' . $feed->fid, + 'title' => $feed->title, + ); + $region = 'footer'; + $edit = array(); + $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region; + // Check the feed block is available in the block list form. + $this->drupalGet('admin/structure/block'); + $this->assertFieldByName('blocks[' . $block['module'] . '_' . $block['delta'] . '][region]', '', 'Aggregator feed block is available for positioning.'); + // Position it. + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region))); + // Confirm that the block is now being displayed on pages. + $this->drupalGet('node'); + $this->assertText(t($block['title']), t('Feed block is displayed on the page.')); + + // Find the expected read_more link. + $href = 'aggregator/sources/' . $feed->fid; + $links = $this->xpath('//a[@href = :href]', array(':href' => url($href))); + $this->assert(isset($links[0]), t('Link to href %href found.', array('%href' => $href))); + + // Visit that page. + $this->drupalGet($href); + $correct_titles = $this->xpath('//h1[normalize-space(text())=:title]', array(':title' => $feed->title)); + $this->assertFalse(empty($correct_titles), t('Aggregator feed page is available and has the correct title.')); + + // Set the number of news items to 0 to test that the block does not show + // up. + $feed->block = 0; + aggregator_save_feed((array) $feed); + // It is nescessary to flush the cache after saving the number of items. + $this->resetAll(); + // Check that the block is no longer displayed. + $this->drupalGet('node'); + $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.'); + } + + /** + * Create a feed and check that feed's page. + */ + public function testFeedPage() { + // Increase the number of items published in the rss.xml feed so we have + // enough articles to test paging. + $config = config('system.rss-publishing'); + $config->set('feed_default_items', 30); + $config->save(); + + // Create a feed with 30 items. + $this->createSampleNodes(30); + $feed = $this->createFeed(); + $this->updateFeedItems($feed, 30); + + // Check for the presence of a pager. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $elements = $this->xpath("//ul[@class=:class]", array(':class' => 'pager')); + $this->assertTrue(!empty($elements), t('Individual source page contains a pager.')); + + // Reset the number of items in rss.xml to the default value. + $config->set('feed_default_items', 10); + $config->save(); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..94831706eae29aee7b18a362414e22f57b1c11d3 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorTestBase.php @@ -0,0 +1,307 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\AggregatorTestBase. + */ + +namespace Drupal\aggregator\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Defines a base class for testing aggregator.module. + */ +class AggregatorTestBase extends WebTestBase { + function setUp() { + parent::setUp(array('node', 'block', 'aggregator', 'aggregator_test')); + + // Create an Article node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + + $web_user = $this->drupalCreateUser(array('administer news feeds', 'access news feeds', 'create article content')); + $this->drupalLogin($web_user); + } + + /** + * Create an aggregator feed (simulate form submission on admin/config/services/aggregator/add/feed). + * + * @param $feed_url + * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * @return $feed + * Full feed object if possible. + * + * @see getFeedEditArray() + */ + function createFeed($feed_url = NULL) { + $edit = $this->getFeedEditArray($feed_url); + $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save')); + $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); + + $feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title'], ':url' => $edit['url']))->fetch(); + $this->assertTrue(!empty($feed), t('The feed found in database.')); + return $feed; + } + + /** + * Delete an aggregator feed. + * + * @param $feed + * Feed object representing the feed. + */ + function deleteFeed($feed) { + $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, array(), t('Delete')); + $this->assertRaw(t('The feed %title has been deleted.', array('%title' => $feed->title)), t('Feed deleted successfully.')); + } + + /** + * Return a randomly generated feed edit array. + * + * @param $feed_url + * If given, feed will be created with this URL, otherwise /rss.xml will be used. + * @return + * A feed array. + */ + function getFeedEditArray($feed_url = NULL) { + $feed_name = $this->randomName(10); + if (!$feed_url) { + $feed_url = url('rss.xml', array( + 'query' => array('feed' => $feed_name), + 'absolute' => TRUE, + )); + } + $edit = array( + 'title' => $feed_name, + 'url' => $feed_url, + 'refresh' => '900', + ); + return $edit; + } + + /** + * Return the count of the randomly created feed array. + * + * @return + * Number of feed items on default feed created by createFeed(). + */ + function getDefaultFeedItemCount() { + // Our tests are based off of rss.xml, so let's find out how many elements should be related. + $feed_count = db_query_range('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1', 0, config('system.rss-publishing')->get('feed_default_items'))->fetchField(); + return $feed_count > 10 ? 10 : $feed_count; + } + + /** + * Update feed items (simulate click to admin/config/services/aggregator/update/$fid). + * + * @param $feed + * Feed object representing the feed. + * @param $expected_count + * Expected number of feed items. + */ + function updateFeedItems(&$feed, $expected_count) { + // First, let's ensure we can get to the rss xml. + $this->drupalGet($feed->url); + $this->assertResponse(200, t('!url is reachable.', array('!url' => $feed->url))); + + // Attempt to access the update link directly without an access token. + $this->drupalGet('admin/config/services/aggregator/update/' . $feed->fid); + $this->assertResponse(403); + + // Refresh the feed (simulated link click). + $this->drupalGet('admin/config/services/aggregator'); + $this->clickLink('update items'); + + // Ensure we have the right number of items. + $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid)); + $items = array(); + $feed->items = array(); + foreach ($result as $item) { + $feed->items[] = $item->iid; + } + $feed->item_count = count($feed->items); + $this->assertEqual($expected_count, $feed->item_count, t('Total items in feed equal to the total items in database (!val1 != !val2)', array('!val1' => $expected_count, '!val2' => $feed->item_count))); + } + + /** + * Confirm item removal from a feed. + * + * @param $feed + * Feed object representing the feed. + */ + function removeFeedItems($feed) { + $this->drupalPost('admin/config/services/aggregator/remove/' . $feed->fid, array(), t('Remove items')); + $this->assertRaw(t('The news items from %title have been removed.', array('%title' => $feed->title)), t('Feed items removed.')); + } + + /** + * Add and remove feed items and ensure that the count is zero. + * + * @param $feed + * Feed object representing the feed. + * @param $expected_count + * Expected number of feed items. + */ + function updateAndRemove($feed, $expected_count) { + $this->updateFeedItems($feed, $expected_count); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $this->assertTrue($count); + $this->removeFeedItems($feed); + $count = db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $this->assertTrue($count == 0); + } + + /** + * Pull feed categories from aggregator_category_feed table. + * + * @param $feed + * Feed object representing the feed. + */ + function getFeedCategories($feed) { + // add the categories to the feed so we can use them + $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $feed->fid)); + foreach ($result as $category) { + $feed->categories[] = $category->cid; + } + } + + /** + * Pull categories from aggregator_category table. + */ + function getCategories() { + $categories = array(); + $result = db_query('SELECT * FROM {aggregator_category}'); + foreach ($result as $category) { + $categories[$category->cid] = $category; + } + return $categories; + } + + + /** + * Check if the feed name and url is unique. + * + * @param $feed_name + * String containing the feed name to check. + * @param $feed_url + * String containing the feed url to check. + * @return + * TRUE if feed is unique. + */ + function uniqueFeed($feed_name, $feed_url) { + $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed_name, ':url' => $feed_url))->fetchField(); + return (1 == $result); + } + + /** + * Create a valid OPML file from an array of feeds. + * + * @param $feeds + * An array of feeds. + * @return + * Path to valid OPML file. + */ + function getValidOpml($feeds) { + // Properly escape URLs so that XML parsers don't choke on them. + foreach ($feeds as &$feed) { + $feed['url'] = htmlspecialchars($feed['url']); + } + /** + * Does not have an XML declaration, must pass the parser. + */ + $opml = <<<EOF +<opml version="1.0"> + <head></head> + <body> + <!-- First feed to be imported. --> + <outline text="{$feeds[0]['title']}" xmlurl="{$feeds[0]['url']}" /> + + <!-- Second feed. Test string delimitation and attribute order. --> + <outline xmlurl='{$feeds[1]['url']}' text='{$feeds[1]['title']}'/> + + <!-- Test for duplicate URL and title. --> + <outline xmlurl="{$feeds[0]['url']}" text="Duplicate URL"/> + <outline xmlurl="http://duplicate.title" text="{$feeds[1]['title']}"/> + + <!-- Test that feeds are only added with required attributes. --> + <outline text="{$feeds[2]['title']}" /> + <outline xmlurl="{$feeds[2]['url']}" /> + </body> +</opml> +EOF; + + $path = 'public://valid-opml.xml'; + return file_unmanaged_save_data($opml, $path); + } + + /** + * Create an invalid OPML file. + * + * @return + * Path to invalid OPML file. + */ + function getInvalidOpml() { + $opml = <<<EOF +<opml> + <invalid> +</opml> +EOF; + + $path = 'public://invalid-opml.xml'; + return file_unmanaged_save_data($opml, $path); + } + + /** + * Create a valid but empty OPML file. + * + * @return + * Path to empty OPML file. + */ + function getEmptyOpml() { + $opml = <<<EOF +<?xml version="1.0" encoding="utf-8"?> +<opml version="1.0"> + <head></head> + <body> + <outline text="Sample text" /> + <outline text="Sample text" url="Sample URL" /> + </body> +</opml> +EOF; + + $path = 'public://empty-opml.xml'; + return file_unmanaged_save_data($opml, $path); + } + + function getRSS091Sample() { + return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_rss091.xml'; + } + + function getAtomSample() { + // The content of this sample ATOM feed is based directly off of the + // example provided in RFC 4287. + return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_atom.xml'; + } + + function getHtmlEntitiesSample() { + return $GLOBALS['base_url'] . '/' . drupal_get_path('module', 'aggregator') . '/tests/aggregator_test_title_entities.xml'; + } + + /** + * Creates sample article nodes. + * + * @param $count + * (optional) The number of nodes to generate. + */ + function createSampleNodes($count = 5) { + $langcode = LANGUAGE_NOT_SPECIFIED; + // Post $count article nodes. + for ($i = 0; $i < $count; $i++) { + $edit = array(); + $edit['title'] = $this->randomName(); + $edit["body[$langcode][0][value]"] = $this->randomName(); + $this->drupalPost('node/add/article', $edit, t('Save')); + } + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5866358628b2b09bb2b13b95d73bed6fe035d79b --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedItemTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\CategorizeFeedItemTest. + */ + +namespace Drupal\aggregator\Tests; + +class CategorizeFeedItemTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Categorize feed item functionality', + 'description' => 'Test feed item categorization.', + 'group' => 'Aggregator' + ); + } + + /** + * If a feed has a category, make sure that the children inherit that + * categorization. + */ + function testCategorizeFeedItem() { + $this->createSampleNodes(); + + // Simulate form submission on "admin/config/services/aggregator/add/category". + $edit = array('title' => $this->randomName(10), 'description' => ''); + $this->drupalPost('admin/config/services/aggregator/add/category', $edit, t('Save')); + $this->assertRaw(t('The category %title has been added.', array('%title' => $edit['title'])), t('The category %title has been added.', array('%title' => $edit['title']))); + + $category = db_query("SELECT * FROM {aggregator_category} WHERE title = :title", array(':title' => $edit['title']))->fetch(); + $this->assertTrue(!empty($category), t('The category found in database.')); + + $link_path = 'aggregator/categories/' . $category->cid; + $menu_link = db_query("SELECT * FROM {menu_links} WHERE link_path = :link_path", array(':link_path' => $link_path))->fetch(); + $this->assertTrue(!empty($menu_link), t('The menu link associated with the category found in database.')); + + $feed = $this->createFeed(); + db_insert('aggregator_category_feed') + ->fields(array( + 'cid' => $category->cid, + 'fid' => $feed->fid, + )) + ->execute(); + $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); + $this->getFeedCategories($feed); + $this->assertTrue(!empty($feed->categories), t('The category found in the feed.')); + + // For each category of a feed, ensure feed items have that category, too. + if (!empty($feed->categories) && !empty($feed->items)) { + foreach ($feed->categories as $category) { + $categorized_count = db_select('aggregator_category_item') + ->condition('iid', $feed->items, 'IN') + ->countQuery() + ->execute() + ->fetchField(); + + $this->assertEqual($feed->item_count, $categorized_count, t('Total items in feed equal to the total categorized feed items in database')); + } + } + + // Delete feed. + $this->deleteFeed($feed); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2ff6316b8b41b49287ce315d1f568f53386c69ad --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/CategorizeFeedTest.php @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\CategorizeFeedTest. + */ + +namespace Drupal\aggregator\Tests; + +class CategorizeFeedTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Categorize feed functionality', + 'description' => 'Categorize feed test.', + 'group' => 'Aggregator' + ); + } + + /** + * Create a feed and make sure you can add more than one category to it. + */ + function testCategorizeFeed() { + + // Create 2 categories. + $category_1 = array('title' => $this->randomName(10), 'description' => ''); + $this->drupalPost('admin/config/services/aggregator/add/category', $category_1, t('Save')); + $this->assertRaw(t('The category %title has been added.', array('%title' => $category_1['title'])), t('The category %title has been added.', array('%title' => $category_1['title']))); + + $category_2 = array('title' => $this->randomName(10), 'description' => ''); + $this->drupalPost('admin/config/services/aggregator/add/category', $category_2, t('Save')); + $this->assertRaw(t('The category %title has been added.', array('%title' => $category_2['title'])), t('The category %title has been added.', array('%title' => $category_2['title']))); + + // Get categories from database. + $categories = $this->getCategories(); + + // Create a feed and assign 2 categories to it. + $feed = $this->getFeedEditArray(); + $feed['block'] = 5; + foreach ($categories as $cid => $category) { + $feed['category'][$cid] = $cid; + } + + // Use aggregator_save_feed() function to save the feed. + aggregator_save_feed($feed); + $db_feed = db_query("SELECT * FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed['title'], ':url' => $feed['url']))->fetch(); + + // Assert the feed has two categories. + $this->getFeedCategories($db_feed); + $this->assertEqual(count($db_feed->categories), 2, t('Feed has 2 categories')); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..97f4392b456fd84b58699a5efc39b1a6fe09d016 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php @@ -0,0 +1,67 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\FeedParserTest. + */ + +namespace Drupal\aggregator\Tests; + +/** + * Tests for feed parsing. + */ +class FeedParserTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Feed parser functionality', + 'description' => 'Test the built-in feed parser with valid feed samples.', + 'group' => 'Aggregator', + ); + } + + function setUp() { + parent::setUp(); + // Do not remove old aggregator items during these tests, since our sample + // feeds have hardcoded dates in them (which may be expired when this test + // is run). + variable_set('aggregator_clear', AGGREGATOR_CLEAR_NEVER); + } + + /** + * Test a feed that uses the RSS 0.91 format. + */ + function testRSS091Sample() { + $feed = $this->createFeed($this->getRSS091Sample()); + aggregator_refresh($feed); + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); + $this->assertText('First example feed item title'); + $this->assertLinkByHref('http://example.com/example-turns-one'); + $this->assertText('First example feed item description.'); + } + + /** + * Test a feed that uses the Atom format. + */ + function testAtomSample() { + $feed = $this->createFeed($this->getAtomSample()); + aggregator_refresh($feed); + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); + $this->assertText('Atom-Powered Robots Run Amok'); + $this->assertLinkByHref('http://example.org/2003/12/13/atom03'); + $this->assertText('Some text.'); + $this->assertEqual('urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a', db_query('SELECT guid FROM {aggregator_item} WHERE link = :link', array(':link' => 'http://example.org/2003/12/13/atom03'))->fetchField(), 'Atom entry id element is parsed correctly.'); + } + + /** + * Tests a feed that uses HTML entities in item titles. + */ + function testHtmlEntitiesSample() { + $feed = $this->createFeed($this->getHtmlEntitiesSample()); + aggregator_refresh($feed); + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, t('Feed %name exists.', array('%name' => $feed->title))); + $this->assertRaw("Quote" Amp&"); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa79f1c1865b93576f0a60b5577c5288ae9bdb85 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php @@ -0,0 +1,132 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\ImportOpmlTest. + */ + +namespace Drupal\aggregator\Tests; + +class ImportOpmlTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Import feeds from OPML functionality', + 'description' => 'Test OPML import.', + 'group' => 'Aggregator', + ); + } + + /** + * Open OPML import form. + */ + function openImportForm() { + db_delete('aggregator_category')->execute(); + + $category = $this->randomName(10); + $cid = db_insert('aggregator_category') + ->fields(array( + 'title' => $category, + 'description' => '', + )) + ->execute(); + + $this->drupalGet('admin/config/services/aggregator/add/opml'); + $this->assertText('A single OPML document may contain a collection of many feeds.', t('Found OPML help text.')); + $this->assertField('files[upload]', t('Found file upload field.')); + $this->assertField('remote', t('Found Remote URL field.')); + $this->assertField('refresh', '', t('Found Refresh field.')); + $this->assertFieldByName("category[$cid]", $cid, t('Found category field.')); + } + + /** + * Submit form filled with invalid fields. + */ + function validateImportFormFields() { + $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); + + $edit = array(); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); + $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), t('Error if no fields are filled.')); + + $path = $this->getEmptyOpml(); + $edit = array( + 'files[upload]' => $path, + 'remote' => file_create_url($path), + ); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); + $this->assertRaw(t('You must <em>either</em> upload a file or enter a URL.'), t('Error if both fields are filled.')); + + $edit = array('remote' => 'invalidUrl://empty'); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); + $this->assertText(t('The URL invalidUrl://empty is not valid.'), 'Error if the URL is invalid.'); + + $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); + $this->assertEqual($before, $after, t('No feeds were added during the three last form submissions.')); + } + + /** + * Submit form with invalid, empty and valid OPML files. + */ + function submitImportForm() { + $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); + + $form['files[upload]'] = $this->getInvalidOpml(); + $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); + $this->assertText(t('No new feed has been added.'), t('Attempting to upload invalid XML.')); + + $edit = array('remote' => file_create_url($this->getEmptyOpml())); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); + $this->assertText(t('No new feed has been added.'), t('Attempting to load empty OPML from remote URL.')); + + $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); + $this->assertEqual($before, $after, t('No feeds were added during the two last form submissions.')); + + db_delete('aggregator_feed')->execute(); + db_delete('aggregator_category')->execute(); + db_delete('aggregator_category_feed')->execute(); + + $category = $this->randomName(10); + db_insert('aggregator_category') + ->fields(array( + 'cid' => 1, + 'title' => $category, + 'description' => '', + )) + ->execute(); + + $feeds[0] = $this->getFeedEditArray(); + $feeds[1] = $this->getFeedEditArray(); + $feeds[2] = $this->getFeedEditArray(); + $edit = array( + 'files[upload]' => $this->getValidOpml($feeds), + 'refresh' => '900', + 'category[1]' => $category, + ); + $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); + $this->assertRaw(t('A feed with the URL %url already exists.', array('%url' => $feeds[0]['url'])), t('Verifying that a duplicate URL was identified')); + $this->assertRaw(t('A feed named %title already exists.', array('%title' => $feeds[1]['title'])), t('Verifying that a duplicate title was identified')); + + $after = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); + $this->assertEqual($after, 2, t('Verifying that two distinct feeds were added.')); + + $feeds_from_db = db_query("SELECT f.title, f.url, f.refresh, cf.cid FROM {aggregator_feed} f LEFT JOIN {aggregator_category_feed} cf ON f.fid = cf.fid"); + $refresh = $category = TRUE; + foreach ($feeds_from_db as $feed) { + $title[$feed->url] = $feed->title; + $url[$feed->title] = $feed->url; + $category = $category && $feed->cid == 1; + $refresh = $refresh && $feed->refresh == 900; + } + + $this->assertEqual($title[$feeds[0]['url']], $feeds[0]['title'], t('First feed was added correctly.')); + $this->assertEqual($url[$feeds[1]['title']], $feeds[1]['url'], t('Second feed was added correctly.')); + $this->assertTrue($refresh, t('Refresh times are correct.')); + $this->assertTrue($category, t('Categories are correct.')); + } + + function testOpmlImport() { + $this->openImportForm(); + $this->validateImportFormFields(); + $this->submitImportForm(); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedItemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c35345f732d3bc80fb6d5da28158199dcf4b8f0b --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedItemTest.php @@ -0,0 +1,46 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\RemoveFeedItemTest. + */ + +namespace Drupal\aggregator\Tests; + +class RemoveFeedItemTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Remove feed item functionality', + 'description' => 'Remove feed items from a feed.', + 'group' => 'Aggregator' + ); + } + + /** + * Test running "remove items" from the 'admin/config/services/aggregator' page. + */ + function testRemoveFeedItem() { + // Create a bunch of test feeds. + $feed_urls = array(); + // No last-modified, no etag. + $feed_urls[] = url('aggregator/test-feed', array('absolute' => TRUE)); + // Last-modified, but no etag. + $feed_urls[] = url('aggregator/test-feed/1', array('absolute' => TRUE)); + // No Last-modified, but etag. + $feed_urls[] = url('aggregator/test-feed/0/1', array('absolute' => TRUE)); + // Last-modified and etag. + $feed_urls[] = url('aggregator/test-feed/1/1', array('absolute' => TRUE)); + + foreach ($feed_urls as $feed_url) { + $feed = $this->createFeed($feed_url); + // Update and remove items two times in a row to make sure that removal + // resets all 'modified' information (modified, etag, hash) and allows for + // immediate update. + $this->updateAndRemove($feed, 2); + $this->updateAndRemove($feed, 2); + $this->updateAndRemove($feed, 2); + // Delete feed. + $this->deleteFeed($feed); + } + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3dae7e90168bfada1e1038632bb88b60ef0378e1 --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/RemoveFeedTest.php @@ -0,0 +1,36 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\RemoveFeedTest. + */ + +namespace Drupal\aggregator\Tests; + +class RemoveFeedTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Remove feed functionality', + 'description' => 'Remove feed test.', + 'group' => 'Aggregator' + ); + } + + /** + * Remove a feed and ensure that all it services are removed. + */ + function testRemoveFeed() { + $feed = $this->createFeed(); + + // Delete feed. + $this->deleteFeed($feed); + + // Check feed source. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(404, t('Deleted feed source does not exists.')); + + // Check database for feed. + $result = db_query("SELECT COUNT(*) FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $feed->title, ':url' => $feed->url))->fetchField(); + $this->assertFalse($result, t('Feed not found in database')); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9a8aeecabad4c2f18508b496db1cf79fbc3940dc --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedItemTest.php @@ -0,0 +1,68 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\UpdateFeedItemTest. + */ + +namespace Drupal\aggregator\Tests; + +class UpdateFeedItemTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Update feed item functionality', + 'description' => 'Update feed items from a feed.', + 'group' => 'Aggregator' + ); + } + + /** + * Test running "update items" from the 'admin/config/services/aggregator' page. + */ + function testUpdateFeedItem() { + $this->createSampleNodes(); + + // Create a feed and test updating feed items if possible. + $feed = $this->createFeed(); + if (!empty($feed)) { + $this->updateFeedItems($feed, $this->getDefaultFeedItemCount()); + $this->removeFeedItems($feed); + } + + // Delete feed. + $this->deleteFeed($feed); + + // Test updating feed items without valid timestamp information. + $edit = array( + 'title' => "Feed without publish timestamp", + 'url' => $this->getRSS091Sample(), + ); + + $this->drupalGet($edit['url']); + $this->assertResponse(array(200), t('URL !url is accessible', array('!url' => $edit['url']))); + + $this->drupalPost('admin/config/services/aggregator/add/feed', $edit, t('Save')); + $this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title'])), t('The feed !name has been added.', array('!name' => $edit['title']))); + + $feed = db_query("SELECT * FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url']))->fetchObject(); + + aggregator_refresh($feed); + $before = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + + // Sleep for 3 second. + sleep(3); + db_update('aggregator_feed') + ->condition('fid', $feed->fid) + ->fields(array( + 'checked' => 0, + 'hash' => '', + 'etag' => '', + 'modified' => 0, + )) + ->execute(); + aggregator_refresh($feed); + + $after = db_query('SELECT timestamp FROM {aggregator_item} WHERE fid = :fid', array(':fid' => $feed->fid))->fetchField(); + $this->assertTrue($before === $after, t('Publish timestamp of feed item was not updated (!before === !after)', array('!before' => $before, '!after' => $after))); + } +} diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1cf4089b75cbeea889b9dd16a5578eea4ef32cad --- /dev/null +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/UpdateFeedTest.php @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Definition of Drupal\aggregator\Tests\UpdateFeedTest. + */ + +namespace Drupal\aggregator\Tests; + +class UpdateFeedTest extends AggregatorTestBase { + public static function getInfo() { + return array( + 'name' => 'Update feed functionality', + 'description' => 'Update feed test.', + 'group' => 'Aggregator' + ); + } + + /** + * Create a feed and attempt to update it. + */ + function testUpdateFeed() { + $remamining_fields = array('title', 'url', ''); + foreach ($remamining_fields as $same_field) { + $feed = $this->createFeed(); + + // Get new feed data array and modify newly created feed. + $edit = $this->getFeedEditArray(); + $edit['refresh'] = 1800; // Change refresh value. + if (isset($feed->{$same_field})) { + $edit[$same_field] = $feed->{$same_field}; + } + $this->drupalPost('admin/config/services/aggregator/edit/feed/' . $feed->fid, $edit, t('Save')); + $this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title'])), t('The feed %name has been updated.', array('%name' => $edit['title']))); + + // Check feed data. + $this->assertEqual($this->getUrl(), url('admin/config/services/aggregator/', array('absolute' => TRUE))); + $this->assertTrue($this->uniqueFeed($edit['title'], $edit['url']), t('The feed is unique.')); + + // Check feed source. + $this->drupalGet('aggregator/sources/' . $feed->fid); + $this->assertResponse(200, t('Feed source exists.')); + $this->assertText($edit['title'], t('Page title')); + + // Delete feed. + $feed->title = $edit['title']; // Set correct title so deleteFeed() will work. + $this->deleteFeed($feed); + } + } +} diff --git a/core/modules/block/block.info b/core/modules/block/block.info index 6ad58ad661b272762d19dc3b8d5b301dc1f8a7fe..e22e79742522c2e384fda9b84514ba84c8a83fce 100644 --- a/core/modules/block/block.info +++ b/core/modules/block/block.info @@ -3,5 +3,4 @@ description = Controls the visual building blocks a page is constructed with. Bl package = Core version = VERSION core = 8.x -files[] = block.test configure = admin/structure/block diff --git a/core/modules/block/block.test b/core/modules/block/block.test deleted file mode 100644 index 0e17a7cf2753d236ff54e7d90011eb25503a86aa..0000000000000000000000000000000000000000 --- a/core/modules/block/block.test +++ /dev/null @@ -1,1129 +0,0 @@ -<?php - -/** - * @file - * Tests for block.module. - */ - -use Drupal\simpletest\WebTestBase; -use Drupal\simpletest\UnitTestBase; - -class BlockTestCase extends WebTestBase { - protected $regions; - protected $admin_user; - - public static function getInfo() { - return array( - 'name' => 'Block functionality', - 'description' => 'Add, edit and delete custom block. Configure and move a module-defined block.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block')); - - // Create Full HTML text format. - $full_html_format = array( - 'format' => 'full_html', - 'name' => 'Full HTML', - ); - $full_html_format = (object) $full_html_format; - filter_format_save($full_html_format); - $this->checkPermissions(array(), TRUE); - - // Create and log in an administrative user having access to the Full HTML - // text format. - $this->admin_user = $this->drupalCreateUser(array( - 'administer blocks', - filter_permission_name($full_html_format), - 'access administration pages', - )); - $this->drupalLogin($this->admin_user); - - // Define the existing regions - $this->regions = array(); - $this->regions[] = 'header'; - $this->regions[] = 'sidebar_first'; - $this->regions[] = 'content'; - $this->regions[] = 'sidebar_second'; - $this->regions[] = 'footer'; - } - - /** - * Test creating custom block, moving it to a specific region and then deleting it. - */ - function testCustomBlock() { - // Enable a second theme. - theme_enable(array('seven')); - - // Confirm that the add block link appears on block overview pages. - $this->drupalGet('admin/structure/block'); - $this->assertRaw(l('Add block', 'admin/structure/block/add'), t('Add block link is present on block overview page for default theme.')); - $this->drupalGet('admin/structure/block/list/seven'); - $this->assertRaw(l('Add block', 'admin/structure/block/list/seven/add'), t('Add block link is present on block overview page for non-default theme.')); - - // Confirm that hidden regions are not shown as options for block placement - // when adding a new block. - theme_enable(array('bartik')); - $themes = list_themes(); - $this->drupalGet('admin/structure/block/add'); - foreach ($themes as $key => $theme) { - if ($theme->status) { - foreach ($theme->info['regions_hidden'] as $hidden_region) { - $elements = $this->xpath('//select[@id=:id]//option[@value=:value]', array(':id' => 'edit-regions-' . $key, ':value' => $hidden_region)); - $this->assertFalse(isset($elements[0]), t('The hidden region @region is not available for @theme.', array('@region' => $hidden_region, '@theme' => $key))); - } - } - } - - // Add a new custom block by filling out the input form on the admin/structure/block/add page. - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $this->randomName(8); - $custom_block['body[value]'] = $this->randomName(32); - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - // Confirm that the custom block has been created, and then query the created bid. - $this->assertText(t('The block has been created.'), t('Custom block successfully created.')); - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - - // Check to see if the custom block was created by checking that it's in the database. - $this->assertNotNull($bid, t('Custom block found in database')); - - // Check that block_block_view() returns the correct title and content. - $data = block_block_view($bid); - $format = db_query("SELECT format FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchField(); - $this->assertTrue(array_key_exists('subject', $data) && empty($data['subject']), t('block_block_view() provides an empty block subject, since custom blocks do not have default titles.')); - $this->assertEqual(check_markup($custom_block['body[value]'], $format), $data['content'], t('block_block_view() provides correct block content.')); - - // Check whether the block can be moved to all available regions. - $custom_block['module'] = 'block'; - $custom_block['delta'] = $bid; - foreach ($this->regions as $region) { - $this->moveBlockToRegion($custom_block, $region); - } - - // Verify presence of configure and delete links for custom block. - $this->drupalGet('admin/structure/block'); - $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/configure', 0, t('Custom block configure link found.')); - $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/delete', 0, t('Custom block delete link found.')); - - // Set visibility only for authenticated users, to verify delete functionality. - $edit = array(); - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; - $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', $edit, t('Save block')); - - // Delete the created custom block & verify that it's been deleted and no longer appearing on the page. - $this->clickLink(t('delete')); - $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete')); - $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['info'])), t('Custom block successfully deleted.')); - $this->assertNoText(t($custom_block['title']), t('Custom block no longer appears on page.')); - $count = db_query("SELECT 1 FROM {block_role} WHERE module = :module AND delta = :delta", array(':module' => $custom_block['module'], ':delta' => $custom_block['delta']))->fetchField(); - $this->assertFalse($count, t('Table block_role being cleaned.')); - } - - /** - * Test creating custom block using Full HTML. - */ - function testCustomBlockFormat() { - // Add a new custom block by filling out the input form on the admin/structure/block/add page. - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $this->randomName(8); - $custom_block['body[value]'] = '<h1>Full HTML</h1>'; - $full_html_format = filter_format_load('full_html'); - $custom_block['body[format]'] = $full_html_format->format; - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - // Set the created custom block to a specific region. - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $edit = array(); - $edit['blocks[block_' . $bid . '][region]'] = $this->regions[1]; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - - // Confirm that the custom block is being displayed using configured text format. - $this->drupalGet('node'); - $this->assertRaw('<h1>Full HTML</h1>', t('Custom block successfully being displayed using Full HTML.')); - - // Confirm that a user without access to Full HTML can not see the body field, - // but can still submit the form without errors. - $block_admin = $this->drupalCreateUser(array('administer blocks')); - $this->drupalLogin($block_admin); - $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure'); - $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Body field contains denied message')); - $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', array(), t('Save block')); - $this->assertNoText(t('Ensure that each block description is unique.')); - - // Confirm that the custom block is still being displayed using configured text format. - $this->drupalGet('node'); - $this->assertRaw('<h1>Full HTML</h1>', t('Custom block successfully being displayed using Full HTML.')); - } - - /** - * Test block visibility. - */ - function testBlockVisibility() { - // Enable Node module and change the front page path to 'node'. - module_enable(array('node')); - variable_set('site_frontpage', 'node'); - - $block = array(); - - // Create a random title for the block - $title = $this->randomName(8); - - // Create the custom block - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $title; - $custom_block['body[value]'] = $this->randomName(32); - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $block['module'] = 'block'; - $block['delta'] = $bid; - $block['title'] = $title; - - // Set the block to be hidden on any user path, and to be shown only to - // authenticated users. - $edit = array(); - $edit['pages'] = 'user*'; - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); - - // Move block to the first sidebar. - $this->moveBlockToRegion($block, $this->regions[1]); - - $this->drupalGet(''); - $this->assertText($title, t('Block was displayed on the front page.')); - - $this->drupalGet('user'); - $this->assertNoText($title, t('Block was not displayed according to block visibility rules.')); - - $this->drupalGet('USER/' . $this->admin_user->uid); - $this->assertNoText($title, t('Block was not displayed according to block visibility rules regardless of path case.')); - - // Confirm that the block is not displayed to anonymous users. - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertNoText($title, t('Block was not displayed to anonymous users.')); - - // Confirm that an empty block is not displayed. - $this->assertNoRaw('block-system-help', t('Empty block not displayed.')); - } - - /** - * Test block visibility when using "pages" restriction but leaving - * "pages" textarea empty - */ - function testBlockVisibilityListedEmpty() { - $block = array(); - - // Create a random title for the block - $title = $this->randomName(8); - - // Create the custom block - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $title; - $custom_block['body[value]'] = $this->randomName(32); - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $block['module'] = 'block'; - $block['delta'] = $bid; - $block['title'] = $title; - - // Move block to the first sidebar. - $this->moveBlockToRegion($block, $this->regions[1]); - - // Set the block to be hidden on any user path, and to be shown only to - // authenticated users. - $edit = array(); - $edit['visibility'] = BLOCK_VISIBILITY_LISTED; - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); - - $this->drupalGet(''); - $this->assertNoText($title, t('Block was not displayed according to block visibility rules.')); - - $this->drupalGet('user'); - $this->assertNoText($title, t('Block was not displayed according to block visibility rules regardless of path case.')); - - // Confirm that the block is not displayed to anonymous users. - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertNoText($title, t('Block was not displayed to anonymous users.')); - } - - /** - * Test user customization of block visibility. - */ - function testBlockVisibilityPerUser() { - $block = array(); - - // Create a random title for the block. - $title = $this->randomName(8); - - // Create our custom test block. - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $title; - $custom_block['body[value]'] = $this->randomName(32); - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $block['module'] = 'block'; - $block['delta'] = $bid; - $block['title'] = $title; - - // Move block to the first sidebar. - $this->moveBlockToRegion($block, $this->regions[1]); - - // Set the block to be customizable per user, visible by default. - $edit = array(); - $edit['custom'] = BLOCK_CUSTOM_ENABLED; - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); - - // Disable block visibility for the admin user. - $edit = array(); - $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = FALSE; - $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save')); - - $this->drupalGet(''); - $this->assertNoText($block['title'], t('Block was not displayed according to per user block visibility setting.')); - - // Set the block to be customizable per user, hidden by default. - $edit = array(); - $edit['custom'] = BLOCK_CUSTOM_DISABLED; - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); - - // Enable block visibility for the admin user. - $edit = array(); - $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = TRUE; - $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save')); - - $this->drupalGet(''); - $this->assertText($block['title'], t('Block was displayed according to per user block visibility setting.')); - } - - /** - * Test configuring and moving a module-define block to specific regions. - */ - function testBlock() { - // Select the Navigation block to be configured and moved. - $block = array(); - $block['module'] = 'system'; - $block['delta'] = 'management'; - $block['title'] = $this->randomName(8); - - // Set block title to confirm that interface works and override any custom titles. - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => $block['title']), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); - $bid = db_query("SELECT bid FROM {block} WHERE module = :module AND delta = :delta", array( - ':module' => $block['module'], - ':delta' => $block['delta'], - ))->fetchField(); - - // Check to see if the block was created by checking that it's in the database. - $this->assertNotNull($bid, t('Block found in database')); - - // Check whether the block can be moved to all available regions. - foreach ($this->regions as $region) { - $this->moveBlockToRegion($block, $region); - } - - // Set the block to the disabled region. - $edit = array(); - $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = '-1'; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - - // Confirm that the block was moved to the proper region. - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to disabled region.')); - $this->assertNoText(t($block['title']), t('Block no longer appears on page.')); - - // Confirm that the region's xpath is not available. - $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-block-' . $bid)); - $this->assertNoFieldByXPath($xpath, FALSE, t('Custom block found in no regions.')); - - // For convenience of developers, put the navigation block back. - $edit = array(); - $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $this->regions[1]; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block successfully move to first sidebar region.')); - - $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => 'Navigation'), t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); - } - - function moveBlockToRegion($block, $region) { - // Set the created block to a specific region. - $edit = array(); - $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - - // Confirm that the block was moved to the proper region. - $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region))); - - // Confirm that the block is being displayed. - $this->drupalGet('node'); - $this->assertText(t($block['title']), t('Block successfully being displayed on the page.')); - - // Confirm that the custom block was found at the proper region. - $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array( - ':region-class' => 'region region-' . str_replace('_', '-', $region), - ':block-id' => 'block-' . $block['module'] . '-' . $block['delta'], - )); - $this->assertFieldByXPath($xpath, NULL, t('Custom block found in %region_name region.', array('%region_name' => $region))); - } - - /** - * Test _block_rehash(). - */ - function testBlockRehash() { - module_enable(array('block_test')); - $this->assertTrue(module_exists('block_test'), t('Test block module enabled.')); - - // Our new block should be inserted in the database when we visit the - // block management page. - $this->drupalGet('admin/structure/block'); - // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE. - $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); - $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.')); - - // Disable caching for this block. - variable_set('block_test_caching', DRUPAL_NO_CACHE); - // Flushing all caches should call _block_rehash(). - $this->resetAll(); - // Verify that the database is updated with the new caching mode. - $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); - $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE.")); - } -} - -class NonDefaultBlockAdmin extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Non default theme admin', - 'description' => 'Check the administer page for non default theme.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block')); - } - - /** - * Test non-default theme admin. - */ - function testNonDefaultBlockAdmin() { - $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); - $this->drupalLogin($admin_user); - $new_theme = 'bartik'; - theme_enable(array($new_theme)); - $this->drupalGet('admin/structure/block/list/' . $new_theme); - } -} - -/** - * Test blocks correctly initialized when picking a new default theme. - */ -class NewDefaultThemeBlocks extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'New default theme blocks', - 'description' => 'Checks that the new default theme gets blocks.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block')); - } - - /** - * Check the enabled Bartik blocks are correctly copied over. - */ - function testNewDefaultThemeBlocks() { - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer themes')); - $this->drupalLogin($admin_user); - - // Ensure no other theme's blocks are in the block table yet. - $themes = array(); - $themes['default'] = variable_get('theme_default', 'stark'); - if ($admin_theme = variable_get('admin_theme')) { - $themes['admin'] = $admin_theme; - } - $count = db_query_range('SELECT 1 FROM {block} WHERE theme NOT IN (:themes)', 0, 1, array(':themes' => $themes))->fetchField(); - $this->assertFalse($count, t('Only the default theme and the admin theme have blocks.')); - - // Populate list of all blocks for matching against new theme. - $blocks = array(); - $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => $themes['default'])); - foreach ($result as $block) { - // $block->theme and $block->bid will not match, so remove them. - unset($block->theme, $block->bid); - $blocks[$block->module][$block->delta] = $block; - } - - // Turn on a new theme and ensure that it contains all of the blocks - // the default theme had. - $new_theme = 'bartik'; - theme_enable(array($new_theme)); - variable_set('theme_default', $new_theme); - $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => $new_theme)); - foreach ($result as $block) { - unset($block->theme, $block->bid); - $this->assertEqual($blocks[$block->module][$block->delta], $block, t('Block %name matched', array('%name' => $block->module . '-' . $block->delta))); - } - } -} - -/** - * Test the block system with admin themes. - */ -class BlockAdminThemeTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Admin theme block admin accessibility', - 'description' => "Check whether the block administer page for a disabled theme accessible if and only if it's the admin theme.", - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block')); - } - - /** - * Check for the accessibility of the admin theme on the block admin page. - */ - function testAdminTheme() { - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); - $this->drupalLogin($admin_user); - - // Ensure that access to block admin page is denied when theme is disabled. - $this->drupalGet('admin/structure/block/list/bartik'); - $this->assertResponse(403, t('The block admin page for a disabled theme can not be accessed')); - - // Enable admin theme and confirm that tab is accessible. - $edit['admin_theme'] = 'bartik'; - $this->drupalPost('admin/appearance', $edit, t('Save configuration')); - $this->drupalGet('admin/structure/block/list/bartik'); - $this->assertResponse(200, t('The block admin page for the admin theme can be accessed')); - } -} - -/** - * Test block caching. - */ -class BlockCacheTestCase extends WebTestBase { - protected $admin_user; - protected $normal_user; - protected $normal_user_alt; - - public static function getInfo() { - return array( - 'name' => 'Block caching', - 'description' => 'Test block caching.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block', 'block_test')); - - // Create an admin user, log in and enable test blocks. - $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages')); - $this->drupalLogin($this->admin_user); - - // Create additional users to test caching modes. - $this->normal_user = $this->drupalCreateUser(); - $this->normal_user_alt = $this->drupalCreateUser(); - // Sync the roles, since drupalCreateUser() creates separate roles for - // the same permission sets. - $this->normal_user_alt->roles = $this->normal_user->roles; - $this->normal_user_alt->save(); - - // Enable our test block. - $edit['blocks[block_test_test_cache][region]'] = 'sidebar_first'; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - } - - /** - * Test DRUPAL_CACHE_PER_ROLE. - */ - function testCachePerRole() { - $this->setCacheMode(DRUPAL_CACHE_PER_ROLE); - - // Enable our test block. Set some content for it to display. - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - $this->drupalLogin($this->normal_user); - $this->drupalGet(''); - $this->assertText($current_content, t('Block content displays.')); - - // Change the content, but the cached copy should still be served. - $old_content = $current_content; - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - $this->drupalGet(''); - $this->assertText($old_content, t('Block is served from the cache.')); - - // Clear the cache and verify that the stale data is no longer there. - cache_clear_all(); - $this->drupalGet(''); - $this->assertNoText($old_content, t('Block cache clear removes stale cache data.')); - $this->assertText($current_content, t('Fresh block content is displayed after clearing the cache.')); - - // Test whether the cached data is served for the correct users. - $old_content = $current_content; - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertNoText($old_content, t('Anonymous user does not see content cached per-role for normal user.')); - - $this->drupalLogin($this->normal_user_alt); - $this->drupalGet(''); - $this->assertText($old_content, t('User with the same roles sees per-role cached content.')); - - $this->drupalLogin($this->admin_user); - $this->drupalGet(''); - $this->assertNoText($old_content, t('Admin user does not see content cached per-role for normal user.')); - - $this->drupalLogin($this->normal_user); - $this->drupalGet(''); - $this->assertText($old_content, t('Block is served from the per-role cache.')); - } - - /** - * Test DRUPAL_CACHE_GLOBAL. - */ - function testCacheGlobal() { - $this->setCacheMode(DRUPAL_CACHE_GLOBAL); - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - $this->drupalGet(''); - $this->assertText($current_content, t('Block content displays.')); - - $old_content = $current_content; - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - $this->drupalLogout(); - $this->drupalGet('user'); - $this->assertText($old_content, t('Block content served from global cache.')); - } - - /** - * Test DRUPAL_NO_CACHE. - */ - function testNoCache() { - $this->setCacheMode(DRUPAL_NO_CACHE); - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - // If DRUPAL_NO_CACHE has no effect, the next request would be cached. - $this->drupalGet(''); - $this->assertText($current_content, t('Block content displays.')); - - // A cached copy should not be served. - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - $this->drupalGet(''); - $this->assertText($current_content, t('DRUPAL_NO_CACHE prevents blocks from being cached.')); - } - - /** - * Test DRUPAL_CACHE_PER_USER. - */ - function testCachePerUser() { - $this->setCacheMode(DRUPAL_CACHE_PER_USER); - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - $this->drupalLogin($this->normal_user); - - $this->drupalGet(''); - $this->assertText($current_content, t('Block content displays.')); - - $old_content = $current_content; - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - $this->drupalGet(''); - $this->assertText($old_content, t('Block is served from per-user cache.')); - - $this->drupalLogin($this->normal_user_alt); - $this->drupalGet(''); - $this->assertText($current_content, t('Per-user block cache is not served for other users.')); - - $this->drupalLogin($this->normal_user); - $this->drupalGet(''); - $this->assertText($old_content, t('Per-user block cache is persistent.')); - } - - /** - * Test DRUPAL_CACHE_PER_PAGE. - */ - function testCachePerPage() { - $this->setCacheMode(DRUPAL_CACHE_PER_PAGE); - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - $this->drupalGet('node'); - $this->assertText($current_content, t('Block content displays on the node page.')); - - $old_content = $current_content; - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - - $this->drupalGet('user'); - $this->assertNoText($old_content, t('Block content cached for the node page does not show up for the user page.')); - $this->drupalGet('node'); - $this->assertText($old_content, t('Block content cached for the node page.')); - } - - /** - * Private helper method to set the test block's cache mode. - */ - private function setCacheMode($cache_mode) { - db_update('block') - ->fields(array('cache' => $cache_mode)) - ->condition('module', 'block_test') - ->execute(); - - $current_mode = db_query("SELECT cache FROM {block} WHERE module = 'block_test'")->fetchField(); - if ($current_mode != $cache_mode) { - $this->fail(t('Unable to set cache mode to %mode. Current mode: %current_mode', array('%mode' => $cache_mode, '%current_mode' => $current_mode))); - } - } -} - -/** - * Test block HTML id validity. - */ -class BlockHTMLIdTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'Block HTML id', - 'description' => 'Test block HTML id validity.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block', 'block_test')); - - // Create an admin user, log in and enable test blocks. - $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages')); - $this->drupalLogin($this->admin_user); - - // Enable our test block. - $edit['blocks[block_test_test_html_id][region]'] = 'sidebar_first'; - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - - // Make sure the block has some content so it will appear - $current_content = $this->randomName(); - variable_set('block_test_content', $current_content); - } - - /** - * Test valid HTML id. - */ - function testHTMLId() { - $this->drupalGet(''); - $this->assertRaw('block-block-test-test-html-id', t('HTML id for test block is valid.')); - } -} - - -/** - * Unit tests for template_preprocess_block(). - */ -class BlockTemplateSuggestionsUnitTest extends UnitTestBase { - public static function getInfo() { - return array( - 'name' => 'Block template suggestions', - 'description' => 'Test the template_preprocess_block() function.', - 'group' => 'Block', - ); - } - - /** - * Test if template_preprocess_block() handles the suggestions right. - */ - function testBlockThemeHookSuggestions() { - // Define block delta with underscore to be preprocessed - $block1 = new stdClass(); - $block1->module = 'block'; - $block1->delta = 'underscore_test'; - $block1->region = 'footer'; - $variables1 = array(); - $variables1['elements']['#block'] = $block1; - $variables1['elements']['#children'] = ''; - template_preprocess_block($variables1); - $this->assertEqual($variables1['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__underscore_test'), t('Found expected block suggestions for delta with underscore')); - - // Define block delta with hyphens to be preprocessed. Hyphens should be - // replaced with underscores. - $block2 = new stdClass(); - $block2->module = 'block'; - $block2->delta = 'hyphen-test'; - $block2->region = 'footer'; - $variables2 = array(); - $variables2['elements']['#block'] = $block2; - $variables2['elements']['#children'] = ''; - // Test adding a class to the block content. - $variables2['content_attributes_array']['class'][] = 'test-class'; - template_preprocess_block($variables2); - $this->assertEqual($variables2['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__hyphen_test'), t('Hyphens (-) in block delta were replaced by underscore (_)')); - // Test that the default class and added class are available. - $this->assertEqual($variables2['content_attributes_array']['class'], array('test-class', 'content'), t('Default .content class added to block content_attributes_array')); - } -} - -/** - * Tests that hidden regions do not inherit blocks when a theme is enabled. - */ -class BlockHiddenRegionTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Blocks not in hidden region', - 'description' => 'Checks that a newly enabled theme does not inherit blocks to its hidden regions.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block', 'block_test', 'search')); - - // Enable Search block in default theme. - db_merge('block') - ->key(array( - 'module' => 'search', - 'delta' => 'form', - 'theme' => variable_get('theme_default', 'stark'), - )) - ->fields(array( - 'status' => 1, - 'weight' => -1, - 'region' => 'sidebar_first', - 'pages' => '', - 'cache' => -1, - )) - ->execute(); - } - - /** - * Tests that hidden regions do not inherit blocks when a theme is enabled. - */ - function testBlockNotInHiddenRegion() { - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes', 'search content')); - $this->drupalLogin($admin_user); - - // Ensure that the search form block is displayed. - $this->drupalGet(''); - $this->assertText('Search', t('Block was displayed on the front page.')); - - // Enable "block_test_theme" and set it as the default theme. - $theme = 'block_test_theme'; - theme_enable(array($theme)); - variable_set('theme_default', $theme); - menu_router_rebuild(); - - // Ensure that "block_test_theme" is set as the default theme. - $this->drupalGet('admin/structure/block'); - $this->assertText('Block test theme(' . t('active tab') . ')', t('Default local task on blocks admin page is the block test theme.')); - - // Ensure that the search form block is displayed. - $this->drupalGet(''); - $this->assertText('Search', t('Block was displayed on the front page.')); - } -} - - -/** - * Tests personalized block settings for user accounts. - */ -class BlockUserAccountSettingsTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Personalized block settings', - 'description' => 'Tests the block settings in user accounts.', - 'group' => 'Block', - ); - } - - public function setUp() { - parent::setUp(array('block', 'field_ui')); - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - } - - /** - * Tests that the personalized block is shown. - */ - function testAccountSettingsPage() { - $this->drupalGet('admin/config/people/accounts/fields'); - $this->assertText(t('Personalize blocks'), 'Personalized block is present.'); - } -} - -/** - * Functional tests for the language list configuration forms. - */ -class BlockLanguageTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Language block visibility', - 'description' => 'Tests if a block can be configure to be only visibile on a particular language.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp('language', 'block'); - } - - /** - * Tests the visibility settings for the blocks based on language. - */ - public function testLanguageBlockVisibility() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Check if the visibility setting is available. - $this->drupalGet('admin/structure/block/add'); - $this->assertField('langcodes[en]', t('Language visibility field is visible.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[en]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Change the default language. - $edit = array( - 'site_default' => 'fr', - ); - $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); - - // Reset the static cache of the language list. - drupal_static_reset('language_list'); - - // Check that a page has a block - $this->drupalGet('', array('language' => language_load('en'))); - $this->assertText($body, t('The body of the custom block appears on the page.')); - - // Check that a page doesn't has a block for the current language anymore - $this->drupalGet('', array('language' => language_load('fr'))); - $this->assertNoText($body, t('The body of the custom block does not appear on the page.')); - } - - /** - * Tests if the visibility settings are removed if the language is deleted. - */ - public function testLanguageBlockVisibilityLanguageDelete() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[fr]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Check that we have an entry in the database after saving the setting. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); - - // Delete the language. - $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); - - // Check that the setting related to this language has been deleted. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); - } - - /** - * Tests if the visibility settings are removed if the block is deleted. - */ - public function testLanguageBlockVisibilityBlockDelete() { - // Create a new user, allow him to manage the blocks and the languages. - $admin_user = $this->drupalCreateUser(array( - 'administer languages', 'administer blocks', - )); - $this->drupalLogin($admin_user); - - // Add predefined language. - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Create a new block. - $info_name = $this->randomString(10); - $body = ''; - for ($i = 0; $i <= 100; $i++) { - $body .= chr(rand(97, 122)); - } - $edit = array( - 'regions[stark]' => 'sidebar_first', - 'info' => $info_name, - 'title' => 'test', - 'body[value]' => $body, - ); - $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); - - // Set visibility setting for one language. - $edit = array( - 'langcodes[fr]' => TRUE, - ); - $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); - - // Check that we have an entry in the database after saving the setting. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); - - // Delete the custom block. - $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete')); - - // Check that the setting related to this block has been deleted. - $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( - ':module' => 'block', - ':delta' => '1' - ))->fetchField(); - $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); - } -} - -/** - * Tests that a block assigned to an invalid region triggers the warning. - */ -class BlockInvalidRegionTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Blocks in invalid regions', - 'description' => 'Checks that an active block assigned to a non-existing region triggers the warning message and is disabled.', - 'group' => 'Block', - ); - } - - function setUp() { - parent::setUp(array('block', 'block_test')); - // Create an admin user. - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages')); - $this->drupalLogin($admin_user); - } - - /** - * Tests that blocks assigned to invalid regions work correctly. - */ - function testBlockInInvalidRegion() { - // Enable a test block in the default theme and place it in an invalid region. - db_merge('block') - ->key(array( - 'module' => 'block_test', - 'delta' => 'test_html_id', - 'theme' => variable_get('theme_default', 'stark'), - )) - ->fields(array( - 'status' => 1, - 'region' => 'invalid_region', - 'cache' => DRUPAL_NO_CACHE, - )) - ->execute(); - - $warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => t('Test block html id'), '%region' => 'invalid_region')); - - // Clearing the cache should disable the test block placed in the invalid region. - $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); - $this->assertRaw($warning_message, 'Enabled block was in the invalid region and has been disabled.'); - - // Clear the cache to check if the warning message is not triggered. - $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); - $this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.'); - - // Place disabled test block in the invalid region of the default theme. - db_merge('block') - ->key(array( - 'module' => 'block_test', - 'delta' => 'test_html_id', - 'theme' => variable_get('theme_default', 'stark'), - )) - ->fields(array( - 'region' => 'invalid_region', - 'cache' => DRUPAL_NO_CACHE, - )) - ->execute(); - - // Clear the cache to check if the warning message is not triggered. - $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); - $this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.'); - } -} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0397802259ce9c63321c587f841e68734db3b15a --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockAdminThemeTest.php @@ -0,0 +1,46 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockAdminThemeTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test the block system with admin themes. + */ +class BlockAdminThemeTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Admin theme block admin accessibility', + 'description' => "Check whether the block administer page for a disabled theme accessible if and only if it's the admin theme.", + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block')); + } + + /** + * Check for the accessibility of the admin theme on the block admin page. + */ + function testAdminTheme() { + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); + $this->drupalLogin($admin_user); + + // Ensure that access to block admin page is denied when theme is disabled. + $this->drupalGet('admin/structure/block/list/bartik'); + $this->assertResponse(403, t('The block admin page for a disabled theme can not be accessed')); + + // Enable admin theme and confirm that tab is accessible. + $edit['admin_theme'] = 'bartik'; + $this->drupalPost('admin/appearance', $edit, t('Save configuration')); + $this->drupalGet('admin/structure/block/list/bartik'); + $this->assertResponse(200, t('The block admin page for the admin theme can be accessed')); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php new file mode 100644 index 0000000000000000000000000000000000000000..868fa24f9dd5f1657ea0aaa4cee85ff61616d959 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php @@ -0,0 +1,197 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockCacheTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test block caching. + */ +class BlockCacheTest extends WebTestBase { + protected $admin_user; + protected $normal_user; + protected $normal_user_alt; + + public static function getInfo() { + return array( + 'name' => 'Block caching', + 'description' => 'Test block caching.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block', 'block_test')); + + // Create an admin user, log in and enable test blocks. + $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + // Create additional users to test caching modes. + $this->normal_user = $this->drupalCreateUser(); + $this->normal_user_alt = $this->drupalCreateUser(); + // Sync the roles, since drupalCreateUser() creates separate roles for + // the same permission sets. + $this->normal_user_alt->roles = $this->normal_user->roles; + $this->normal_user_alt->save(); + + // Enable our test block. + $edit['blocks[block_test_test_cache][region]'] = 'sidebar_first'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + } + + /** + * Test DRUPAL_CACHE_PER_ROLE. + */ + function testCachePerRole() { + $this->setCacheMode(DRUPAL_CACHE_PER_ROLE); + + // Enable our test block. Set some content for it to display. + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + $this->drupalLogin($this->normal_user); + $this->drupalGet(''); + $this->assertText($current_content, t('Block content displays.')); + + // Change the content, but the cached copy should still be served. + $old_content = $current_content; + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + $this->drupalGet(''); + $this->assertText($old_content, t('Block is served from the cache.')); + + // Clear the cache and verify that the stale data is no longer there. + cache_clear_all(); + $this->drupalGet(''); + $this->assertNoText($old_content, t('Block cache clear removes stale cache data.')); + $this->assertText($current_content, t('Fresh block content is displayed after clearing the cache.')); + + // Test whether the cached data is served for the correct users. + $old_content = $current_content; + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($old_content, t('Anonymous user does not see content cached per-role for normal user.')); + + $this->drupalLogin($this->normal_user_alt); + $this->drupalGet(''); + $this->assertText($old_content, t('User with the same roles sees per-role cached content.')); + + $this->drupalLogin($this->admin_user); + $this->drupalGet(''); + $this->assertNoText($old_content, t('Admin user does not see content cached per-role for normal user.')); + + $this->drupalLogin($this->normal_user); + $this->drupalGet(''); + $this->assertText($old_content, t('Block is served from the per-role cache.')); + } + + /** + * Test DRUPAL_CACHE_GLOBAL. + */ + function testCacheGlobal() { + $this->setCacheMode(DRUPAL_CACHE_GLOBAL); + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + $this->drupalGet(''); + $this->assertText($current_content, t('Block content displays.')); + + $old_content = $current_content; + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + $this->drupalLogout(); + $this->drupalGet('user'); + $this->assertText($old_content, t('Block content served from global cache.')); + } + + /** + * Test DRUPAL_NO_CACHE. + */ + function testNoCache() { + $this->setCacheMode(DRUPAL_NO_CACHE); + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + // If DRUPAL_NO_CACHE has no effect, the next request would be cached. + $this->drupalGet(''); + $this->assertText($current_content, t('Block content displays.')); + + // A cached copy should not be served. + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + $this->drupalGet(''); + $this->assertText($current_content, t('DRUPAL_NO_CACHE prevents blocks from being cached.')); + } + + /** + * Test DRUPAL_CACHE_PER_USER. + */ + function testCachePerUser() { + $this->setCacheMode(DRUPAL_CACHE_PER_USER); + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + $this->drupalLogin($this->normal_user); + + $this->drupalGet(''); + $this->assertText($current_content, t('Block content displays.')); + + $old_content = $current_content; + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + $this->drupalGet(''); + $this->assertText($old_content, t('Block is served from per-user cache.')); + + $this->drupalLogin($this->normal_user_alt); + $this->drupalGet(''); + $this->assertText($current_content, t('Per-user block cache is not served for other users.')); + + $this->drupalLogin($this->normal_user); + $this->drupalGet(''); + $this->assertText($old_content, t('Per-user block cache is persistent.')); + } + + /** + * Test DRUPAL_CACHE_PER_PAGE. + */ + function testCachePerPage() { + $this->setCacheMode(DRUPAL_CACHE_PER_PAGE); + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + $this->drupalGet('node'); + $this->assertText($current_content, t('Block content displays on the node page.')); + + $old_content = $current_content; + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + + $this->drupalGet('user'); + $this->assertNoText($old_content, t('Block content cached for the node page does not show up for the user page.')); + $this->drupalGet('node'); + $this->assertText($old_content, t('Block content cached for the node page.')); + } + + /** + * Private helper method to set the test block's cache mode. + */ + private function setCacheMode($cache_mode) { + db_update('block') + ->fields(array('cache' => $cache_mode)) + ->condition('module', 'block_test') + ->execute(); + + $current_mode = db_query("SELECT cache FROM {block} WHERE module = 'block_test'")->fetchField(); + if ($current_mode != $cache_mode) { + $this->fail(t('Unable to set cache mode to %mode. Current mode: %current_mode', array('%mode' => $cache_mode, '%current_mode' => $current_mode))); + } + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0c3186c08dc78104648ebd798042d96a9ab7430c --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockHiddenRegionTest.php @@ -0,0 +1,70 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockHiddenRegionTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests that hidden regions do not inherit blocks when a theme is enabled. + */ +class BlockHiddenRegionTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Blocks not in hidden region', + 'description' => 'Checks that a newly enabled theme does not inherit blocks to its hidden regions.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block', 'block_test', 'search')); + + // Enable Search block in default theme. + db_merge('block') + ->key(array( + 'module' => 'search', + 'delta' => 'form', + 'theme' => variable_get('theme_default', 'stark'), + )) + ->fields(array( + 'status' => 1, + 'weight' => -1, + 'region' => 'sidebar_first', + 'pages' => '', + 'cache' => -1, + )) + ->execute(); + } + + /** + * Tests that hidden regions do not inherit blocks when a theme is enabled. + */ + function testBlockNotInHiddenRegion() { + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes', 'search content')); + $this->drupalLogin($admin_user); + + // Ensure that the search form block is displayed. + $this->drupalGet(''); + $this->assertText('Search', t('Block was displayed on the front page.')); + + // Enable "block_test_theme" and set it as the default theme. + $theme = 'block_test_theme'; + theme_enable(array($theme)); + variable_set('theme_default', $theme); + menu_router_rebuild(); + + // Ensure that "block_test_theme" is set as the default theme. + $this->drupalGet('admin/structure/block'); + $this->assertText('Block test theme(' . t('active tab') . ')', t('Default local task on blocks admin page is the block test theme.')); + + // Ensure that the search form block is displayed. + $this->drupalGet(''); + $this->assertText('Search', t('Block was displayed on the front page.')); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c0891acc8a089f6b4a9489d4bc4936f4bcd2e0e4 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockHtmlIdTest.php @@ -0,0 +1,48 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockHtmlIdTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test block HTML id validity. + */ +class BlockHtmlIdTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'Block HTML id', + 'description' => 'Test block HTML id validity.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block', 'block_test')); + + // Create an admin user, log in and enable test blocks. + $this->admin_user = $this->drupalCreateUser(array('administer blocks', 'access administration pages')); + $this->drupalLogin($this->admin_user); + + // Enable our test block. + $edit['blocks[block_test_test_html_id][region]'] = 'sidebar_first'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Make sure the block has some content so it will appear + $current_content = $this->randomName(); + variable_set('block_test_content', $current_content); + } + + /** + * Test valid HTML id. + */ + function testHtmlId() { + $this->drupalGet(''); + $this->assertRaw('block-block-test-test-html-id', t('HTML id for test block is valid.')); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8882b642189ab07035c79d71548527d6b042a8b0 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php @@ -0,0 +1,76 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockInvalidRegionTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests that a block assigned to an invalid region triggers the warning. + */ +class BlockInvalidRegionTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Blocks in invalid regions', + 'description' => 'Checks that an active block assigned to a non-existing region triggers the warning message and is disabled.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block', 'block_test')); + // Create an admin user. + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages')); + $this->drupalLogin($admin_user); + } + + /** + * Tests that blocks assigned to invalid regions work correctly. + */ + function testBlockInInvalidRegion() { + // Enable a test block in the default theme and place it in an invalid region. + db_merge('block') + ->key(array( + 'module' => 'block_test', + 'delta' => 'test_html_id', + 'theme' => variable_get('theme_default', 'stark'), + )) + ->fields(array( + 'status' => 1, + 'region' => 'invalid_region', + 'cache' => DRUPAL_NO_CACHE, + )) + ->execute(); + + $warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => t('Test block html id'), '%region' => 'invalid_region')); + + // Clearing the cache should disable the test block placed in the invalid region. + $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); + $this->assertRaw($warning_message, 'Enabled block was in the invalid region and has been disabled.'); + + // Clear the cache to check if the warning message is not triggered. + $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); + $this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.'); + + // Place disabled test block in the invalid region of the default theme. + db_merge('block') + ->key(array( + 'module' => 'block_test', + 'delta' => 'test_html_id', + 'theme' => variable_get('theme_default', 'stark'), + )) + ->fields(array( + 'region' => 'invalid_region', + 'cache' => DRUPAL_NO_CACHE, + )) + ->execute(); + + // Clear the cache to check if the warning message is not triggered. + $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches'); + $this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.'); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..04aef2d36e6f99c7cc0bbf4fe836bd13bc090200 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php @@ -0,0 +1,196 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockLanguageTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Functional tests for the language list configuration forms. + */ +class BlockLanguageTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Language block visibility', + 'description' => 'Tests if a block can be configure to be only visibile on a particular language.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp('language', 'block'); + } + + /** + * Tests the visibility settings for the blocks based on language. + */ + public function testLanguageBlockVisibility() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Check if the visibility setting is available. + $this->drupalGet('admin/structure/block/add'); + $this->assertField('langcodes[en]', t('Language visibility field is visible.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[en]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Change the default language. + $edit = array( + 'site_default' => 'fr', + ); + $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration')); + + // Reset the static cache of the language list. + drupal_static_reset('language_list'); + + // Check that a page has a block + $this->drupalGet('', array('language' => language_load('en'))); + $this->assertText($body, t('The body of the custom block appears on the page.')); + + // Check that a page doesn't has a block for the current language anymore + $this->drupalGet('', array('language' => language_load('fr'))); + $this->assertNoText($body, t('The body of the custom block does not appear on the page.')); + } + + /** + * Tests if the visibility settings are removed if the language is deleted. + */ + public function testLanguageBlockVisibilityLanguageDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the language. + $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete')); + + // Check that the setting related to this language has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } + + /** + * Tests if the visibility settings are removed if the block is deleted. + */ + public function testLanguageBlockVisibilityBlockDelete() { + // Create a new user, allow him to manage the blocks and the languages. + $admin_user = $this->drupalCreateUser(array( + 'administer languages', 'administer blocks', + )); + $this->drupalLogin($admin_user); + + // Add predefined language. + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Create a new block. + $info_name = $this->randomString(10); + $body = ''; + for ($i = 0; $i <= 100; $i++) { + $body .= chr(rand(97, 122)); + } + $edit = array( + 'regions[stark]' => 'sidebar_first', + 'info' => $info_name, + 'title' => 'test', + 'body[value]' => $body, + ); + $this->drupalPost('admin/structure/block/add', $edit, t('Save block')); + + // Set visibility setting for one language. + $edit = array( + 'langcodes[fr]' => TRUE, + ); + $this->drupalPost('admin/structure/block/manage/block/1/configure', $edit, t('Save block')); + + // Check that we have an entry in the database after saving the setting. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 1, t('The block language visibility has an entry in the database.')); + + // Delete the custom block. + $this->drupalPost('admin/structure/block/manage/block/1/delete', array(), t('Delete')); + + // Check that the setting related to this block has been deleted. + $count = db_query('SELECT COUNT(langcode) FROM {block_language} WHERE module = :module AND delta = :delta', array( + ':module' => 'block', + ':delta' => '1' + ))->fetchField(); + $this->assertTrue($count == 0, t('The block language visibility do not have an entry in the database.')); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..32b1e09d6aea7fb3e5ca41fb4fcabff0ac1e88e5 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php @@ -0,0 +1,56 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockTemplateSuggestionsUnitTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\UnitTestBase; +use stdClass; + +/** + * Unit tests for template_preprocess_block(). + */ +class BlockTemplateSuggestionsUnitTest extends UnitTestBase { + public static function getInfo() { + return array( + 'name' => 'Block template suggestions', + 'description' => 'Test the template_preprocess_block() function.', + 'group' => 'Block', + ); + } + + /** + * Test if template_preprocess_block() handles the suggestions right. + */ + function testBlockThemeHookSuggestions() { + // Define block delta with underscore to be preprocessed + $block1 = new stdClass(); + $block1->module = 'block'; + $block1->delta = 'underscore_test'; + $block1->region = 'footer'; + $variables1 = array(); + $variables1['elements']['#block'] = $block1; + $variables1['elements']['#children'] = ''; + template_preprocess_block($variables1); + $this->assertEqual($variables1['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__underscore_test'), t('Found expected block suggestions for delta with underscore')); + + // Define block delta with hyphens to be preprocessed. Hyphens should be + // replaced with underscores. + $block2 = new stdClass(); + $block2->module = 'block'; + $block2->delta = 'hyphen-test'; + $block2->region = 'footer'; + $variables2 = array(); + $variables2['elements']['#block'] = $block2; + $variables2['elements']['#children'] = ''; + // Test adding a class to the block content. + $variables2['content_attributes_array']['class'][] = 'test-class'; + template_preprocess_block($variables2); + $this->assertEqual($variables2['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__hyphen_test'), t('Hyphens (-) in block delta were replaced by underscore (_)')); + // Test that the default class and added class are available. + $this->assertEqual($variables2['content_attributes_array']['class'], array('test-class', 'content'), t('Default .content class added to block content_attributes_array')); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..38e65c40a4d052f35bd924e8c7404ecd8ded8f0a --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php @@ -0,0 +1,403 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +class BlockTest extends WebTestBase { + protected $regions; + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Block functionality', + 'description' => 'Add, edit and delete custom block. Configure and move a module-defined block.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Create Full HTML text format. + $full_html_format = array( + 'format' => 'full_html', + 'name' => 'Full HTML', + ); + $full_html_format = (object) $full_html_format; + filter_format_save($full_html_format); + $this->checkPermissions(array(), TRUE); + + // Create and log in an administrative user having access to the Full HTML + // text format. + $this->admin_user = $this->drupalCreateUser(array( + 'administer blocks', + filter_permission_name($full_html_format), + 'access administration pages', + )); + $this->drupalLogin($this->admin_user); + + // Define the existing regions + $this->regions = array(); + $this->regions[] = 'header'; + $this->regions[] = 'sidebar_first'; + $this->regions[] = 'content'; + $this->regions[] = 'sidebar_second'; + $this->regions[] = 'footer'; + } + + /** + * Test creating custom block, moving it to a specific region and then deleting it. + */ + function testCustomBlock() { + // Enable a second theme. + theme_enable(array('seven')); + + // Confirm that the add block link appears on block overview pages. + $this->drupalGet('admin/structure/block'); + $this->assertRaw(l('Add block', 'admin/structure/block/add'), t('Add block link is present on block overview page for default theme.')); + $this->drupalGet('admin/structure/block/list/seven'); + $this->assertRaw(l('Add block', 'admin/structure/block/list/seven/add'), t('Add block link is present on block overview page for non-default theme.')); + + // Confirm that hidden regions are not shown as options for block placement + // when adding a new block. + theme_enable(array('bartik')); + $themes = list_themes(); + $this->drupalGet('admin/structure/block/add'); + foreach ($themes as $key => $theme) { + if ($theme->status) { + foreach ($theme->info['regions_hidden'] as $hidden_region) { + $elements = $this->xpath('//select[@id=:id]//option[@value=:value]', array(':id' => 'edit-regions-' . $key, ':value' => $hidden_region)); + $this->assertFalse(isset($elements[0]), t('The hidden region @region is not available for @theme.', array('@region' => $hidden_region, '@theme' => $key))); + } + } + } + + // Add a new custom block by filling out the input form on the admin/structure/block/add page. + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $this->randomName(8); + $custom_block['body[value]'] = $this->randomName(32); + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + // Confirm that the custom block has been created, and then query the created bid. + $this->assertText(t('The block has been created.'), t('Custom block successfully created.')); + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + + // Check to see if the custom block was created by checking that it's in the database. + $this->assertNotNull($bid, t('Custom block found in database')); + + // Check that block_block_view() returns the correct title and content. + $data = block_block_view($bid); + $format = db_query("SELECT format FROM {block_custom} WHERE bid = :bid", array(':bid' => $bid))->fetchField(); + $this->assertTrue(array_key_exists('subject', $data) && empty($data['subject']), t('block_block_view() provides an empty block subject, since custom blocks do not have default titles.')); + $this->assertEqual(check_markup($custom_block['body[value]'], $format), $data['content'], t('block_block_view() provides correct block content.')); + + // Check whether the block can be moved to all available regions. + $custom_block['module'] = 'block'; + $custom_block['delta'] = $bid; + foreach ($this->regions as $region) { + $this->moveBlockToRegion($custom_block, $region); + } + + // Verify presence of configure and delete links for custom block. + $this->drupalGet('admin/structure/block'); + $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/configure', 0, t('Custom block configure link found.')); + $this->assertLinkByHref('admin/structure/block/manage/block/' . $bid . '/delete', 0, t('Custom block delete link found.')); + + // Set visibility only for authenticated users, to verify delete functionality. + $edit = array(); + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', $edit, t('Save block')); + + // Delete the created custom block & verify that it's been deleted and no longer appearing on the page. + $this->clickLink(t('delete')); + $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete')); + $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['info'])), t('Custom block successfully deleted.')); + $this->assertNoText(t($custom_block['title']), t('Custom block no longer appears on page.')); + $count = db_query("SELECT 1 FROM {block_role} WHERE module = :module AND delta = :delta", array(':module' => $custom_block['module'], ':delta' => $custom_block['delta']))->fetchField(); + $this->assertFalse($count, t('Table block_role being cleaned.')); + } + + /** + * Test creating custom block using Full HTML. + */ + function testCustomBlockFormat() { + // Add a new custom block by filling out the input form on the admin/structure/block/add page. + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $this->randomName(8); + $custom_block['body[value]'] = '<h1>Full HTML</h1>'; + $full_html_format = filter_format_load('full_html'); + $custom_block['body[format]'] = $full_html_format->format; + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + // Set the created custom block to a specific region. + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $edit = array(); + $edit['blocks[block_' . $bid . '][region]'] = $this->regions[1]; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Confirm that the custom block is being displayed using configured text format. + $this->drupalGet('node'); + $this->assertRaw('<h1>Full HTML</h1>', t('Custom block successfully being displayed using Full HTML.')); + + // Confirm that a user without access to Full HTML can not see the body field, + // but can still submit the form without errors. + $block_admin = $this->drupalCreateUser(array('administer blocks')); + $this->drupalLogin($block_admin); + $this->drupalGet('admin/structure/block/manage/block/' . $bid . '/configure'); + $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Body field contains denied message')); + $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/configure', array(), t('Save block')); + $this->assertNoText(t('Ensure that each block description is unique.')); + + // Confirm that the custom block is still being displayed using configured text format. + $this->drupalGet('node'); + $this->assertRaw('<h1>Full HTML</h1>', t('Custom block successfully being displayed using Full HTML.')); + } + + /** + * Test block visibility. + */ + function testBlockVisibility() { + // Enable Node module and change the front page path to 'node'. + module_enable(array('node')); + variable_set('site_frontpage', 'node'); + + $block = array(); + + // Create a random title for the block + $title = $this->randomName(8); + + // Create the custom block + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $title; + $custom_block['body[value]'] = $this->randomName(32); + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $block['module'] = 'block'; + $block['delta'] = $bid; + $block['title'] = $title; + + // Set the block to be hidden on any user path, and to be shown only to + // authenticated users. + $edit = array(); + $edit['pages'] = 'user*'; + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE; + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); + + // Move block to the first sidebar. + $this->moveBlockToRegion($block, $this->regions[1]); + + $this->drupalGet(''); + $this->assertText($title, t('Block was displayed on the front page.')); + + $this->drupalGet('user'); + $this->assertNoText($title, t('Block was not displayed according to block visibility rules.')); + + $this->drupalGet('USER/' . $this->admin_user->uid); + $this->assertNoText($title, t('Block was not displayed according to block visibility rules regardless of path case.')); + + // Confirm that the block is not displayed to anonymous users. + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($title, t('Block was not displayed to anonymous users.')); + + // Confirm that an empty block is not displayed. + $this->assertNoRaw('block-system-help', t('Empty block not displayed.')); + } + + /** + * Test block visibility when using "pages" restriction but leaving + * "pages" textarea empty + */ + function testBlockVisibilityListedEmpty() { + $block = array(); + + // Create a random title for the block + $title = $this->randomName(8); + + // Create the custom block + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $title; + $custom_block['body[value]'] = $this->randomName(32); + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $block['module'] = 'block'; + $block['delta'] = $bid; + $block['title'] = $title; + + // Move block to the first sidebar. + $this->moveBlockToRegion($block, $this->regions[1]); + + // Set the block to be hidden on any user path, and to be shown only to + // authenticated users. + $edit = array(); + $edit['visibility'] = BLOCK_VISIBILITY_LISTED; + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); + + $this->drupalGet(''); + $this->assertNoText($title, t('Block was not displayed according to block visibility rules.')); + + $this->drupalGet('user'); + $this->assertNoText($title, t('Block was not displayed according to block visibility rules regardless of path case.')); + + // Confirm that the block is not displayed to anonymous users. + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($title, t('Block was not displayed to anonymous users.')); + } + + /** + * Test user customization of block visibility. + */ + function testBlockVisibilityPerUser() { + $block = array(); + + // Create a random title for the block. + $title = $this->randomName(8); + + // Create our custom test block. + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $title; + $custom_block['body[value]'] = $this->randomName(32); + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $block['module'] = 'block'; + $block['delta'] = $bid; + $block['title'] = $title; + + // Move block to the first sidebar. + $this->moveBlockToRegion($block, $this->regions[1]); + + // Set the block to be customizable per user, visible by default. + $edit = array(); + $edit['custom'] = BLOCK_CUSTOM_ENABLED; + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); + + // Disable block visibility for the admin user. + $edit = array(); + $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = FALSE; + $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save')); + + $this->drupalGet(''); + $this->assertNoText($block['title'], t('Block was not displayed according to per user block visibility setting.')); + + // Set the block to be customizable per user, hidden by default. + $edit = array(); + $edit['custom'] = BLOCK_CUSTOM_DISABLED; + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', $edit, t('Save block')); + + // Enable block visibility for the admin user. + $edit = array(); + $edit['block[' . $block['module'] . '][' . $block['delta'] . ']'] = TRUE; + $this->drupalPost('user/' . $this->admin_user->uid . '/edit', $edit, t('Save')); + + $this->drupalGet(''); + $this->assertText($block['title'], t('Block was displayed according to per user block visibility setting.')); + } + + /** + * Test configuring and moving a module-define block to specific regions. + */ + function testBlock() { + // Select the Navigation block to be configured and moved. + $block = array(); + $block['module'] = 'system'; + $block['delta'] = 'management'; + $block['title'] = $this->randomName(8); + + // Set block title to confirm that interface works and override any custom titles. + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => $block['title']), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); + $bid = db_query("SELECT bid FROM {block} WHERE module = :module AND delta = :delta", array( + ':module' => $block['module'], + ':delta' => $block['delta'], + ))->fetchField(); + + // Check to see if the block was created by checking that it's in the database. + $this->assertNotNull($bid, t('Block found in database')); + + // Check whether the block can be moved to all available regions. + foreach ($this->regions as $region) { + $this->moveBlockToRegion($block, $region); + } + + // Set the block to the disabled region. + $edit = array(); + $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = '-1'; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Confirm that the block was moved to the proper region. + $this->assertText(t('The block settings have been updated.'), t('Block successfully move to disabled region.')); + $this->assertNoText(t($block['title']), t('Block no longer appears on page.')); + + // Confirm that the region's xpath is not available. + $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-block-' . $bid)); + $this->assertNoFieldByXPath($xpath, FALSE, t('Custom block found in no regions.')); + + // For convenience of developers, put the navigation block back. + $edit = array(); + $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $this->regions[1]; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block successfully move to first sidebar region.')); + + $this->drupalPost('admin/structure/block/manage/' . $block['module'] . '/' . $block['delta'] . '/configure', array('title' => 'Navigation'), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); + } + + function moveBlockToRegion($block, $region) { + // Set the created block to a specific region. + $edit = array(); + $edit['blocks[' . $block['module'] . '_' . $block['delta'] . '][region]'] = $region; + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + + // Confirm that the block was moved to the proper region. + $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to %region_name region.', array( '%region_name' => $region))); + + // Confirm that the block is being displayed. + $this->drupalGet('node'); + $this->assertText(t($block['title']), t('Block successfully being displayed on the page.')); + + // Confirm that the custom block was found at the proper region. + $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array( + ':region-class' => 'region region-' . str_replace('_', '-', $region), + ':block-id' => 'block-' . $block['module'] . '-' . $block['delta'], + )); + $this->assertFieldByXPath($xpath, NULL, t('Custom block found in %region_name region.', array('%region_name' => $region))); + } + + /** + * Test _block_rehash(). + */ + function testBlockRehash() { + module_enable(array('block_test')); + $this->assertTrue(module_exists('block_test'), t('Test block module enabled.')); + + // Our new block should be inserted in the database when we visit the + // block management page. + $this->drupalGet('admin/structure/block'); + // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE. + $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); + $this->assertEqual($current_caching, DRUPAL_CACHE_PER_ROLE, t('Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.')); + + // Disable caching for this block. + variable_set('block_test_caching', DRUPAL_NO_CACHE); + // Flushing all caches should call _block_rehash(). + $this->resetAll(); + // Verify that the database is updated with the new caching mode. + $current_caching = db_query("SELECT cache FROM {block} WHERE module = 'block_test' AND delta = 'test_cache'")->fetchField(); + $this->assertEqual($current_caching, DRUPAL_NO_CACHE, t("Test block's database entry updated to DRUPAL_NO_CACHE.")); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockUserAccountSettingsTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockUserAccountSettingsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4878b51c4e60d9868f214e40e5c630853c69809b --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/BlockUserAccountSettingsTest.php @@ -0,0 +1,37 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\BlockUserAccountSettingsTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests personalized block settings for user accounts. + */ +class BlockUserAccountSettingsTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Personalized block settings', + 'description' => 'Tests the block settings in user accounts.', + 'group' => 'Block', + ); + } + + public function setUp() { + parent::setUp(array('block', 'field_ui')); + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + } + + /** + * Tests that the personalized block is shown. + */ + function testAccountSettingsPage() { + $this->drupalGet('admin/config/people/accounts/fields'); + $this->assertText(t('Personalize blocks'), 'Personalized block is present.'); + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/NewDefaultThemeBlocksTest.php b/core/modules/block/lib/Drupal/block/Tests/NewDefaultThemeBlocksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b17e6012d91964b1b18de1d5e141d91be5b41e11 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/NewDefaultThemeBlocksTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\NewDefaultThemeBlocksTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test blocks correctly initialized when picking a new default theme. + */ +class NewDefaultThemeBlocksTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'New default theme blocks', + 'description' => 'Checks that the new default theme gets blocks.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block')); + } + + /** + * Check the enabled Bartik blocks are correctly copied over. + */ + function testNewDefaultThemeBlocks() { + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer themes')); + $this->drupalLogin($admin_user); + + // Ensure no other theme's blocks are in the block table yet. + $themes = array(); + $themes['default'] = variable_get('theme_default', 'stark'); + if ($admin_theme = variable_get('admin_theme')) { + $themes['admin'] = $admin_theme; + } + $count = db_query_range('SELECT 1 FROM {block} WHERE theme NOT IN (:themes)', 0, 1, array(':themes' => $themes))->fetchField(); + $this->assertFalse($count, t('Only the default theme and the admin theme have blocks.')); + + // Populate list of all blocks for matching against new theme. + $blocks = array(); + $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => $themes['default'])); + foreach ($result as $block) { + // $block->theme and $block->bid will not match, so remove them. + unset($block->theme, $block->bid); + $blocks[$block->module][$block->delta] = $block; + } + + // Turn on a new theme and ensure that it contains all of the blocks + // the default theme had. + $new_theme = 'bartik'; + theme_enable(array($new_theme)); + variable_set('theme_default', $new_theme); + $result = db_query('SELECT * FROM {block} WHERE theme = :theme', array(':theme' => $new_theme)); + foreach ($result as $block) { + unset($block->theme, $block->bid); + $this->assertEqual($blocks[$block->module][$block->delta], $block, t('Block %name matched', array('%name' => $block->module . '-' . $block->delta))); + } + } +} diff --git a/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php b/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..376ba0984a508459c935c5923504f02140230561 --- /dev/null +++ b/core/modules/block/lib/Drupal/block/Tests/NonDefaultBlockAdminTest.php @@ -0,0 +1,35 @@ +<?php + +/** + * @file + * Definition of Drupal\block\Tests\NonDefaultBlockAdminTest. + */ + +namespace Drupal\block\Tests; + +use Drupal\simpletest\WebTestBase; + +class NonDefaultBlockAdminTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Non default theme admin', + 'description' => 'Check the administer page for non default theme.', + 'group' => 'Block', + ); + } + + function setUp() { + parent::setUp(array('block')); + } + + /** + * Test non-default theme admin. + */ + function testNonDefaultBlockAdmin() { + $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer themes')); + $this->drupalLogin($admin_user); + $new_theme = 'bartik'; + theme_enable(array($new_theme)); + $this->drupalGet('admin/structure/block/list/' . $new_theme); + } +} diff --git a/core/modules/comment/comment.info b/core/modules/comment/comment.info index 037d548fce8a5c3286ad2aed5df80b3fefa3bbd1..39b1dc69d8724a239aed218f518cbf23c6c6d527 100644 --- a/core/modules/comment/comment.info +++ b/core/modules/comment/comment.info @@ -6,6 +6,5 @@ core = 8.x dependencies[] = node dependencies[] = text dependencies[] = entity -files[] = comment.test configure = admin/content/comment stylesheets[all][] = comment.theme.css diff --git a/core/modules/comment/comment.test b/core/modules/comment/comment.test deleted file mode 100644 index 1ede06328f65a1654e1cdc9db3e9b7c5e550f78b..0000000000000000000000000000000000000000 --- a/core/modules/comment/comment.test +++ /dev/null @@ -1,1973 +0,0 @@ -<?php - -/** - * @file - * Tests for the Comment module. - */ - -use Drupal\comment\Comment; -use Drupal\comment\Tests\CommentTestBase; -use Drupal\simpletest\WebTestBase; - -class CommentInterfaceTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment interface', - 'description' => 'Test comment user interfaces.', - 'group' => 'Comment', - ); - } - - /** - * Tests the comment interface. - */ - function testCommentInterface() { - $langcode = LANGUAGE_NOT_SPECIFIED; - // Set comments to have subject and preview disabled. - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_DISABLED); - $this->setCommentForm(TRUE); - $this->setCommentSubject(FALSE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - $this->drupalLogout(); - - // Post comment #1 without subject or preview. - $this->drupalLogin($this->web_user); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), t('Comment found.')); - - // Set comments to have subject and preview to required. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_REQUIRED); - $this->drupalLogout(); - - // Create comment #2 that allows subject and requires preview. - $this->drupalLogin($this->web_user); - $subject_text = $this->randomName(); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), t('Comment found.')); - - // Check comment display. - $this->drupalGet('node/' . $this->node->nid . '/' . $comment->id); - $this->assertText($subject_text, t('Individual comment subject found.')); - $this->assertText($comment_text, t('Individual comment body found.')); - - // Set comments to have subject and preview to optional. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_OPTIONAL); - - // Test changing the comment author to "Anonymous". - $this->drupalGet('comment/' . $comment->id . '/edit'); - $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => '')); - $comment_loaded = comment_load($comment->id); - $this->assertTrue(empty($comment_loaded->name) && $comment_loaded->uid == 0, t('Comment author successfully changed to anonymous.')); - - // Test changing the comment author to an unverified user. - $random_name = $this->randomName(); - $this->drupalGet('comment/' . $comment->id . '/edit'); - $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => $random_name)); - $this->drupalGet('node/' . $this->node->nid); - $this->assertText($random_name . ' (' . t('not verified') . ')', t('Comment author successfully changed to an unverified user.')); - - // Test changing the comment author to a verified user. - $this->drupalGet('comment/' . $comment->id . '/edit'); - $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => $this->web_user->name)); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($comment_loaded->name == $this->web_user->name && $comment_loaded->uid == $this->web_user->uid, t('Comment author successfully changed to a registered user.')); - - $this->drupalLogout(); - - // Reply to comment #2 creating comment #3 with optional preview and no - // subject though field enabled. - $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $this->assertText($subject_text, t('Individual comment-reply subject found.')); - $this->assertText($comment_text, t('Individual comment-reply body found.')); - $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), t('Reply found.')); - $this->assertEqual($comment->id, $reply_loaded->pid, t('Pid of a reply to a comment is set correctly.')); - $this->assertEqual(rtrim($comment_loaded->thread, '/') . '.00/', $reply_loaded->thread, t('Thread of reply grows correctly.')); - - // Second reply to comment #3 creating comment #4. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $this->assertText($subject_text, t('Individual comment-reply subject found.')); - $this->assertText($comment_text, t('Individual comment-reply body found.')); - $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), t('Second reply found.')); - $this->assertEqual(rtrim($comment_loaded->thread, '/') . '.01/', $reply_loaded->thread, t('Thread of second reply grows correctly.')); - - // Edit reply. - $this->drupalGet('comment/' . $reply->id . '/edit'); - $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $this->assertTrue($this->commentExists($reply, TRUE), t('Modified reply found.')); - - // Correct link count - $this->drupalGet('node'); - $this->assertRaw('4 comments', t('Link to the 4 comments exist.')); - - // Confirm a new comment is posted to the correct page. - $this->setCommentsPerPage(2); - $comment_new_page = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); - $this->assertTrue($this->commentExists($comment_new_page), t('Page one exists. %s')); - $this->drupalGet('node/' . $this->node->nid, array('query' => array('page' => 1))); - $this->assertTrue($this->commentExists($reply, TRUE), t('Page two exists. %s')); - $this->setCommentsPerPage(50); - - // Attempt to reply to an unpublished comment. - $reply_loaded->status = COMMENT_NOT_PUBLISHED; - $reply_loaded->save(); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply_loaded->cid); - $this->assertText(t('The comment you are replying to does not exist.'), 'Replying to an unpublished comment'); - - // Attempt to post to node with comments disabled. - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_HIDDEN)); - $this->assertTrue($this->node, t('Article node created.')); - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertText('This discussion is closed', t('Posting to node with comments disabled')); - $this->assertNoField('edit-comment', t('Comment body field found.')); - - // Attempt to post to node with read-only comments. - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_CLOSED)); - $this->assertTrue($this->node, t('Article node created.')); - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertText('This discussion is closed', t('Posting to node with comments read-only')); - $this->assertNoField('edit-comment', t('Comment body field found.')); - - // Attempt to post to node with comments enabled (check field names etc). - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); - $this->assertTrue($this->node, t('Article node created.')); - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertNoText('This discussion is closed', t('Posting to node with comments enabled')); - $this->assertField('edit-comment-body-' . $langcode . '-0-value', t('Comment body field found.')); - - // Delete comment and make sure that reply is also removed. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $this->deleteComment($comment); - $this->deleteComment($comment_new_page); - - $this->drupalGet('node/' . $this->node->nid); - $this->assertFalse($this->commentExists($comment), t('Comment not found.')); - $this->assertFalse($this->commentExists($reply, TRUE), t('Reply not found.')); - - // Enabled comment form on node page. - $this->drupalLogin($this->admin_user); - $this->setCommentForm(TRUE); - $this->drupalLogout(); - - // Submit comment through node form. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/' . $this->node->nid); - $form_comment = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $this->assertTrue($this->commentExists($form_comment), t('Form comment found.')); - - // Disable comment form on node page. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $this->setCommentForm(FALSE); - } - - /** - * Tests new comment marker. - */ - public function testCommentNewCommentsIndicator() { - // Test if the right links are displayed when no comment is present for the - // node. - $this->drupalLogin($this->admin_user); - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); - $this->drupalGet('node'); - $this->assertNoLink(t('@count comments', array('@count' => 0))); - $this->assertNoLink(t('@count new comments', array('@count' => 0))); - $this->assertLink(t('Read more')); - $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper')); - $this->assertTrue(count($count) == 1, t('One child found')); - - // Create a new comment. This helper function may be run with different - // comment settings so use comment_save() to avoid complex setup. - $comment = entity_create('comment', array( - 'cid' => NULL, - 'nid' => $this->node->nid, - 'node_type' => $this->node->type, - 'pid' => 0, - 'uid' => $this->loggedInUser->uid, - 'status' => COMMENT_PUBLISHED, - 'subject' => $this->randomName(), - 'hostname' => ip_address(), - 'langcode' => LANGUAGE_NOT_SPECIFIED, - 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), - )); - comment_save($comment); - $this->drupalLogout(); - - // Log in with 'web user' and check comment links. - $this->drupalLogin($this->web_user); - $this->drupalGet('node'); - $this->assertLink(t('1 new comment')); - $this->clickLink(t('1 new comment')); - $this->assertRaw('<a id="new"></a>', t('Found "new" marker.')); - $this->assertTrue($this->xpath('//a[@id=:new]/following-sibling::a[1][@id=:comment_id]', array(':new' => 'new', ':comment_id' => 'comment-1')), t('The "new" anchor is positioned at the right comment.')); - - // Test if "new comment" link is correctly removed. - $this->drupalGet('node'); - $this->assertLink(t('1 comment')); - $this->assertLink(t('Read more')); - $this->assertNoLink(t('1 new comment')); - $this->assertNoLink(t('@count new comments', array('@count' => 0))); - $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper')); - $this->assertTrue(count($count) == 2, print_r($count, TRUE)); - } - - /** - * Tests CSS classes on comments. - */ - function testCommentClasses() { - // Create all permutations for comments, users, and nodes. - $parameters = array( - 'node_uid' => array(0, $this->web_user->uid), - 'comment_uid' => array(0, $this->web_user->uid, $this->admin_user->uid), - 'comment_status' => array(COMMENT_PUBLISHED, COMMENT_NOT_PUBLISHED), - 'user' => array('anonymous', 'authenticated', 'admin'), - ); - $permutations = $this->generatePermutations($parameters); - - foreach ($permutations as $case) { - // Create a new node. - $node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid'])); - - // Add a comment. - $comment = entity_create('comment', array( - 'nid' => $node->nid, - 'uid' => $case['comment_uid'], - 'status' => $case['comment_status'], - 'subject' => $this->randomName(), - 'language' => LANGUAGE_NOT_SPECIFIED, - 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), - )); - comment_save($comment); - - // Adjust the current/viewing user. - switch ($case['user']) { - case 'anonymous': - if ($this->loggedInUser) { - $this->drupalLogout(); - } - $case['user_uid'] = 0; - break; - - case 'authenticated': - $this->drupalLogin($this->web_user); - $case['user_uid'] = $this->web_user->uid; - break; - - case 'admin': - $this->drupalLogin($this->admin_user); - $case['user_uid'] = $this->admin_user->uid; - break; - } - // Request the node with the comment. - $this->drupalGet('node/' . $node->nid); - - // Verify classes if the comment is visible for the current user. - if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') { - // Verify the by-anonymous class. - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-anonymous")]'); - if ($case['comment_uid'] == 0) { - $this->assertTrue(count($comments) == 1, 'by-anonymous class found.'); - } - else { - $this->assertFalse(count($comments), 'by-anonymous class not found.'); - } - - // Verify the by-node-author class. - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-node-author")]'); - if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['node_uid']) { - $this->assertTrue(count($comments) == 1, 'by-node-author class found.'); - } - else { - $this->assertFalse(count($comments), 'by-node-author class not found.'); - } - - // Verify the by-viewer class. - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-viewer")]'); - if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['user_uid']) { - $this->assertTrue(count($comments) == 1, 'by-viewer class found.'); - } - else { - $this->assertFalse(count($comments), 'by-viewer class not found.'); - } - } - - // Verify the unpublished class. - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "unpublished")]'); - if ($case['comment_status'] == COMMENT_NOT_PUBLISHED && $case['user'] == 'admin') { - $this->assertTrue(count($comments) == 1, 'unpublished class found.'); - } - else { - $this->assertFalse(count($comments), 'unpublished class not found.'); - } - - // Verify the new class. - if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') { - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "new")]'); - if ($case['user'] != 'anonymous') { - $this->assertTrue(count($comments) == 1, 'new class found.'); - - // Request the node again. The new class should disappear. - $this->drupalGet('node/' . $node->nid); - $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "new")]'); - $this->assertFalse(count($comments), 'new class not found.'); - } - else { - $this->assertFalse(count($comments), 'new class not found.'); - } - } - } - } - - /** - * Tests the node comment statistics. - */ - function testCommentNodeCommentStatistics() { - $langcode = LANGUAGE_NOT_SPECIFIED; - // Set comments to have subject and preview disabled. - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_DISABLED); - $this->setCommentForm(TRUE); - $this->setCommentSubject(FALSE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - $this->drupalLogout(); - - // Creates a second user to post comments. - $this->web_user2 = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments')); - - // Checks the initial values of node comment statistics with no comment. - $node = node_load($this->node->nid); - $this->assertEqual($node->last_comment_timestamp, $this->node->created, t('The initial value of node last_comment_timestamp is the node created date.')); - $this->assertEqual($node->last_comment_name, NULL, t('The initial value of node last_comment_name is NULL.')); - $this->assertEqual($node->last_comment_uid, $this->web_user->uid, t('The initial value of node last_comment_uid is the node uid.')); - $this->assertEqual($node->comment_count, 0, t('The initial value of node comment_count is zero.')); - - // Post comment #1 as web_user2. - $this->drupalLogin($this->web_user2); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text); - $comment_loaded = comment_load($comment->id); - - // Checks the new values of node comment statistics with comment #1. - // The node needs to be reloaded with a node_load_multiple cache reset. - $node = node_load($this->node->nid, NULL, TRUE); - $this->assertEqual($node->last_comment_name, NULL, t('The value of node last_comment_name is NULL.')); - $this->assertEqual($node->last_comment_uid, $this->web_user2->uid, t('The value of node last_comment_uid is the comment #1 uid.')); - $this->assertEqual($node->comment_count, 1, t('The value of node comment_count is 1.')); - - // Prepare for anonymous comment submission (comment approval enabled). - variable_set('user_register', USER_REGISTER_VISITORS); - $this->drupalLogin($this->admin_user); - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'skip comment approval' => FALSE, - )); - // Ensure that the poster can leave some contact info. - $this->setCommentAnonymous('1'); - $this->drupalLogout(); - - // Post comment #2 as anonymous (comment approval enabled). - $this->drupalGet('comment/reply/' . $this->node->nid); - $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', TRUE); - $comment_unpublished_loaded = comment_load($anonymous_comment->id); - - // Checks the new values of node comment statistics with comment #2 and - // ensure they haven't changed since the comment has not been moderated. - // The node needs to be reloaded with a node_load_multiple cache reset. - $node = node_load($this->node->nid, NULL, TRUE); - $this->assertEqual($node->last_comment_name, NULL, t('The value of node last_comment_name is still NULL.')); - $this->assertEqual($node->last_comment_uid, $this->web_user2->uid, t('The value of node last_comment_uid is still the comment #1 uid.')); - $this->assertEqual($node->comment_count, 1, t('The value of node comment_count is still 1.')); - - // Prepare for anonymous comment submission (no approval required). - $this->drupalLogin($this->admin_user); - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'skip comment approval' => TRUE, - )); - $this->drupalLogout(); - - // Post comment #3 as anonymous. - $this->drupalGet('comment/reply/' . $this->node->nid); - $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', array('name' => $this->randomName())); - $comment_loaded = comment_load($anonymous_comment->id); - - // Checks the new values of node comment statistics with comment #3. - // The node needs to be reloaded with a node_load_multiple cache reset. - $node = node_load($this->node->nid, NULL, TRUE); - $this->assertEqual($node->last_comment_name, $comment_loaded->name, t('The value of node last_comment_name is the name of the anonymous user.')); - $this->assertEqual($node->last_comment_uid, 0, t('The value of node last_comment_uid is zero.')); - $this->assertEqual($node->comment_count, 2, t('The value of node comment_count is 2.')); - } - - /** - * Tests comment links. - * - * The output of comment links depends on various environment conditions: - * - Various Comment module configuration settings, user registration - * settings, and user access permissions. - * - Whether the user is authenticated or not, and whether any comments exist. - * - * To account for all possible cases, this test creates permutations of all - * possible conditions and tests the expected appearance of comment links in - * each environment. - */ - function testCommentLinks() { - // Bartik theme alters comment links, so use a different theme. - theme_enable(array('stark')); - variable_set('theme_default', 'stark'); - - // Remove additional user permissions from $this->web_user added by setUp(), - // since this test is limited to anonymous and authenticated roles only. - user_role_delete(key($this->web_user->roles)); - - // Matrix of possible environmental conditions and configuration settings. - // See setEnvironment() for details. - $conditions = array( - 'authenticated' => array(FALSE, TRUE), - 'comment count' => array(FALSE, TRUE), - 'access comments' => array(0, 1), - 'post comments' => array(0, 1), - 'form' => array(COMMENT_FORM_BELOW, COMMENT_FORM_SEPARATE_PAGE), - // USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL is irrelevant for this - // test; there is only a difference between open and closed registration. - 'user_register' => array(USER_REGISTER_VISITORS, USER_REGISTER_ADMINISTRATORS_ONLY), - // @todo Complete test coverage for: - //'comments' => array(COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, COMMENT_NODE_HIDDEN), - //// COMMENT_ANONYMOUS_MUST_CONTACT is irrelevant for this test. - //'contact ' => array(COMMENT_ANONYMOUS_MAY_CONTACT, COMMENT_ANONYMOUS_MAYNOT_CONTACT), - ); - - $environments = $this->generatePermutations($conditions); - foreach ($environments as $info) { - $this->assertCommentLinks($info); - } - } - - /** - * Re-configures the environment, module settings, and user permissions. - * - * @param $info - * An associative array describing the environment to setup: - * - Environment conditions: - * - authenticated: Boolean whether to test with $this->web_user or - * anonymous. - * - comment count: Boolean whether to test with a new/unread comment on - * $this->node or no comments. - * - Configuration settings: - * - form: COMMENT_FORM_BELOW or COMMENT_FORM_SEPARATE_PAGE. - * - user_register: USER_REGISTER_ADMINISTRATORS_ONLY or - * USER_REGISTER_VISITORS. - * - contact: COMMENT_ANONYMOUS_MAY_CONTACT or - * COMMENT_ANONYMOUS_MAYNOT_CONTACT. - * - comments: COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, or - * COMMENT_NODE_HIDDEN. - * - User permissions: - * These are granted or revoked for the user, according to the - * 'authenticated' flag above. Pass 0 or 1 as parameter values. See - * user_role_change_permissions(). - * - access comments - * - post comments - * - skip comment approval - * - edit own comments - */ - function setEnvironment(array $info) { - static $current; - - // Apply defaults to initial environment. - if (!isset($current)) { - $current = array( - 'authenticated' => FALSE, - 'comment count' => FALSE, - 'form' => COMMENT_FORM_BELOW, - 'user_register' => USER_REGISTER_VISITORS, - 'contact' => COMMENT_ANONYMOUS_MAY_CONTACT, - 'comments' => COMMENT_NODE_OPEN, - 'access comments' => 0, - 'post comments' => 0, - // Enabled by default, because it's irrelevant for this test. - 'skip comment approval' => 1, - 'edit own comments' => 0, - ); - } - // Complete new environment with current environment. - $info = array_merge($current, $info); - - // Change environment conditions. - if ($current['authenticated'] != $info['authenticated']) { - if ($this->loggedInUser) { - $this->drupalLogout(); - } - else { - $this->drupalLogin($this->web_user); - } - } - if ($current['comment count'] != $info['comment count']) { - if ($info['comment count']) { - // Create a comment via CRUD API functionality, since - // $this->postComment() relies on actual user permissions. - $comment = entity_create('comment', array( - 'cid' => NULL, - 'nid' => $this->node->nid, - 'node_type' => $this->node->type, - 'pid' => 0, - 'uid' => 0, - 'status' => COMMENT_PUBLISHED, - 'subject' => $this->randomName(), - 'hostname' => ip_address(), - 'langcode' => LANGUAGE_NOT_SPECIFIED, - 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), - )); - comment_save($comment); - $this->comment = $comment; - - // comment_num_new() relies on node_last_viewed(), so ensure that no one - // has seen the node of this comment. - db_delete('history')->condition('nid', $this->node->nid)->execute(); - } - else { - $cids = db_query("SELECT cid FROM {comment}")->fetchCol(); - comment_delete_multiple($cids); - unset($this->comment); - } - } - - // Change comment settings. - variable_set('comment_form_location_' . $this->node->type, $info['form']); - variable_set('comment_anonymous_' . $this->node->type, $info['contact']); - if ($this->node->comment != $info['comments']) { - $this->node->comment = $info['comments']; - node_save($this->node); - } - - // Change user settings. - variable_set('user_register', $info['user_register']); - - // Change user permissions. - $rid = ($this->loggedInUser ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID); - $perms = array_intersect_key($info, array('access comments' => 1, 'post comments' => 1, 'skip comment approval' => 1, 'edit own comments' => 1)); - user_role_change_permissions($rid, $perms); - - // Output verbose debugging information. - // @see Drupal\simpletest\TestBase::error() - $t_form = array( - COMMENT_FORM_BELOW => 'below', - COMMENT_FORM_SEPARATE_PAGE => 'separate page', - ); - $t_contact = array( - COMMENT_ANONYMOUS_MAY_CONTACT => 'optional', - COMMENT_ANONYMOUS_MAYNOT_CONTACT => 'disabled', - COMMENT_ANONYMOUS_MUST_CONTACT => 'required', - ); - $t_comments = array( - COMMENT_NODE_OPEN => 'open', - COMMENT_NODE_CLOSED => 'closed', - COMMENT_NODE_HIDDEN => 'hidden', - ); - $verbose = $info; - $verbose['form'] = $t_form[$info['form']]; - $verbose['contact'] = $t_contact[$info['contact']]; - $verbose['comments'] = $t_comments[$info['comments']]; - $message = t('Changed environment:<pre>@verbose</pre>', array( - '@verbose' => var_export($verbose, TRUE), - )); - $this->assert('debug', $message, 'Debug'); - - // Update current environment. - $current = $info; - - return $info; - } - - /** - * Asserts that comment links appear according to the passed environment. - * - * @param $info - * An associative array describing the environment to pass to - * setEnvironment(). - */ - function assertCommentLinks(array $info) { - $info = $this->setEnvironment($info); - - $nid = $this->node->nid; - - foreach (array('', "node/$nid") as $path) { - $this->drupalGet($path); - - // User is allowed to view comments. - if ($info['access comments']) { - if ($path == '') { - // In teaser view, a link containing the comment count is always - // expected. - if ($info['comment count']) { - $this->assertLink(t('1 comment')); - - // For logged in users, a link containing the amount of new/unread - // comments is expected. - // See important note about comment_num_new() below. - if ($this->loggedInUser && isset($this->comment) && !isset($this->comment->seen)) { - $this->assertLink(t('1 new comment')); - $this->comment->seen = TRUE; - } - } - } - } - else { - $this->assertNoLink(t('1 comment')); - $this->assertNoLink(t('1 new comment')); - } - // comment_num_new() is based on node views, so comments are marked as - // read when a node is viewed, regardless of whether we have access to - // comments. - if ($path == "node/$nid" && $this->loggedInUser && isset($this->comment)) { - $this->comment->seen = TRUE; - } - - // User is not allowed to post comments. - if (!$info['post comments']) { - $this->assertNoLink('Add new comment'); - - // Anonymous users should see a note to log in or register in case - // authenticated users are allowed to post comments. - // @see theme_comment_post_forbidden() - if (!$this->loggedInUser) { - if (user_access('post comments', $this->web_user)) { - // The note depends on whether users are actually able to register. - if ($info['user_register']) { - $this->assertText('Log in or register to post comments'); - } - else { - $this->assertText('Log in to post comments'); - } - } - else { - $this->assertNoText('Log in or register to post comments'); - $this->assertNoText('Log in to post comments'); - } - } - } - // User is allowed to post comments. - else { - $this->assertNoText('Log in or register to post comments'); - - // "Add new comment" is always expected, except when there are no - // comments or if the user cannot see them. - if ($path == "node/$nid" && $info['form'] == COMMENT_FORM_BELOW && (!$info['comment count'] || !$info['access comments'])) { - $this->assertNoLink('Add new comment'); - } - else { - $this->assertLink('Add new comment'); - - // Verify that the "Add new comment" link points to the correct URL - // based on the comment form location configuration. - if ($info['form'] == COMMENT_FORM_SEPARATE_PAGE) { - $this->assertLinkByHref("comment/reply/$nid#comment-form", 0, 'Comment form link destination is on a separate page.'); - $this->assertNoLinkByHref("node/$nid#comment-form"); - } - else { - $this->assertLinkByHref("node/$nid#comment-form", 0, 'Comment form link destination is on node.'); - $this->assertNoLinkByHref("comment/reply/$nid#comment-form"); - } - } - - // Also verify that the comment form appears according to the configured - // location. - if ($path == "node/$nid") { - $elements = $this->xpath('//form[@id=:id]', array(':id' => 'comment-form')); - if ($info['form'] == COMMENT_FORM_BELOW) { - $this->assertTrue(count($elements), t('Comment form found below.')); - } - else { - $this->assertFalse(count($elements), t('Comment form not found below.')); - } - } - } - } - } -} - -/** - * Tests previewing comments. - */ -class CommentPreviewTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment preview', - 'description' => 'Test comment preview.', - 'group' => 'Comment', - ); - } - - /** - * Tests comment preview. - */ - function testCommentPreview() { - $langcode = LANGUAGE_NOT_SPECIFIED; - - // As admin user, configure comment settings. - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_OPTIONAL); - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - $this->drupalLogout(); - - // Login as web user and add a signature and a user picture. - $this->drupalLogin($this->web_user); - variable_set('user_signatures', 1); - variable_set('user_pictures', 1); - $test_signature = $this->randomName(); - $edit['signature[value]'] = '<a href="http://example.com/">' . $test_signature. '</a>'; - $edit['signature[format]'] = 'filtered_html'; - $image = current($this->drupalGetTestFiles('image')); - $edit['files[picture_upload]'] = drupal_realpath($image->uri); - $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); - - // As the web user, fill in the comment form and preview the comment. - $edit = array(); - $edit['subject'] = $this->randomName(8); - $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('node/' . $this->node->nid, $edit, t('Preview')); - - // Check that the preview is displaying the title and body. - $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); - $this->assertText($edit['subject'], t('Subject displayed.')); - $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], t('Comment displayed.')); - - // Check that the title and body fields are displayed with the correct values. - $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); - $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); - - // Check that the signature is displaying with the correct text format. - $this->assertLink($test_signature); - - // Check that the user picture is displayed. - $this->assertFieldByXPath("//div[contains(@class, 'preview')]//div[contains(@class, 'user-picture')]//img", NULL, 'User picture displayed.'); - } - - /** - * Tests comment edit, preview, and save. - */ - function testCommentEditPreviewSave() { - $langcode = LANGUAGE_NOT_SPECIFIED; - $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval')); - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_OPTIONAL); - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - - $edit = array(); - $edit['subject'] = $this->randomName(8); - $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $edit['name'] = $web_user->name; - $edit['date'] = '2008-03-02 17:23 +0300'; - $raw_date = strtotime($edit['date']); - $expected_text_date = format_date($raw_date); - $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O'); - $comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE); - $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview')); - - // Check that the preview is displaying the subject, comment, author and date correctly. - $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); - $this->assertText($edit['subject'], t('Subject displayed.')); - $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], t('Comment displayed.')); - $this->assertText($edit['name'], t('Author displayed.')); - $this->assertText($expected_text_date, t('Date displayed.')); - - // Check that the subject, comment, author and date fields are displayed with the correct values. - $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); - $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); - $this->assertFieldByName('name', $edit['name'], t('Author field displayed.')); - $this->assertFieldByName('date', $edit['date'], t('Date field displayed.')); - - // Check that saving a comment produces a success message. - $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Save')); - $this->assertText(t('Your comment has been posted.'), t('Comment posted.')); - - // Check that the comment fields are correct after loading the saved comment. - $this->drupalGet('comment/' . $comment->id . '/edit'); - $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); - $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); - $this->assertFieldByName('name', $edit['name'], t('Author field displayed.')); - $this->assertFieldByName('date', $expected_form_date, t('Date field displayed.')); - - // Submit the form using the displayed values. - $displayed = array(); - $displayed['subject'] = (string) current($this->xpath("//input[@id='edit-subject']/@value")); - $displayed['comment_body[' . $langcode . '][0][value]'] = (string) current($this->xpath("//textarea[@id='edit-comment-body-" . $langcode . "-0-value']")); - $displayed['name'] = (string) current($this->xpath("//input[@id='edit-name']/@value")); - $displayed['date'] = (string) current($this->xpath("//input[@id='edit-date']/@value")); - $this->drupalPost('comment/' . $comment->id . '/edit', $displayed, t('Save')); - - // Check that the saved comment is still correct. - $comment_loaded = comment_load($comment->id); - $this->assertEqual($comment_loaded->subject, $edit['subject'], t('Subject loaded.')); - $this->assertEqual($comment_loaded->comment_body[$langcode][0]['value'], $edit['comment_body[' . $langcode . '][0][value]'], t('Comment body loaded.')); - $this->assertEqual($comment_loaded->name, $edit['name'], t('Name loaded.')); - $this->assertEqual($comment_loaded->created, $raw_date, t('Date loaded.')); - - } - -} -/** - * Tests anonymous commenting. - */ -class CommentAnonymous extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Anonymous comments', - 'description' => 'Test anonymous comments.', - 'group' => 'Comment', - ); - } - - function setUp() { - parent::setUp(); - variable_set('user_register', USER_REGISTER_VISITORS); - } - - /** - * Tests anonymous comment functionality. - */ - function testAnonymous() { - $this->drupalLogin($this->admin_user); - // Enabled anonymous user comments. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'skip comment approval' => TRUE, - )); - $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. - $this->drupalLogout(); - - // Post anonymous comment without contact info. - $anonymous_comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName()); - $this->assertTrue($this->commentExists($anonymous_comment1), t('Anonymous comment without contact info found.')); - - // Allow contact info. - $this->drupalLogin($this->admin_user); - $this->setCommentAnonymous('1'); - - // Attempt to edit anonymous comment. - $this->drupalGet('comment/' . $anonymous_comment1->id . '/edit'); - $edited_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); - $this->assertTrue($this->commentExists($edited_comment, FALSE), t('Modified reply found.')); - $this->drupalLogout(); - - // Post anonymous comment with contact info (optional). - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertTrue($this->commentContactInfoAvailable(), t('Contact information available.')); - - $anonymous_comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); - $this->assertTrue($this->commentExists($anonymous_comment2), t('Anonymous comment with contact info (optional) found.')); - - // Ensure anonymous users cannot post in the name of registered users. - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit = array( - 'name' => $this->admin_user->name, - 'mail' => $this->randomName() . '@example.com', - 'subject' => $this->randomName(), - "comment_body[$langcode][0][value]" => $this->randomName(), - ); - $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); - $this->assertText(t('The name you used belongs to a registered user.')); - - // Require contact info. - $this->drupalLogin($this->admin_user); - $this->setCommentAnonymous('2'); - $this->drupalLogout(); - - // Try to post comment with contact info (required). - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertTrue($this->commentContactInfoAvailable(), t('Contact information available.')); - - $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); - $this->assertText(t('E-mail field is required.'), t('E-mail required.')); // Name should have 'Anonymous' for value by default. - $this->assertFalse($this->commentExists($anonymous_comment3), t('Anonymous comment with contact info (required) not found.')); - - // Post comment with contact info (required). - $author_name = $this->randomName(); - $author_mail = $this->randomName() . '@example.com'; - $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), array('name' => $author_name, 'mail' => $author_mail)); - $this->assertTrue($this->commentExists($anonymous_comment3), t('Anonymous comment with contact info (required) found.')); - - // Make sure the user data appears correctly when editing the comment. - $this->drupalLogin($this->admin_user); - $this->drupalGet('comment/' . $anonymous_comment3->id . '/edit'); - $this->assertRaw($author_name, t("The anonymous user's name is correct when editing the comment.")); - $this->assertRaw($author_mail, t("The anonymous user's e-mail address is correct when editing the comment.")); - - // Unpublish comment. - $this->performCommentOperation($anonymous_comment3, 'unpublish'); - - $this->drupalGet('admin/content/comment/approval'); - $this->assertRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was unpublished.')); - - // Publish comment. - $this->performCommentOperation($anonymous_comment3, 'publish', TRUE); - - $this->drupalGet('admin/content/comment'); - $this->assertRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was published.')); - - // Delete comment. - $this->performCommentOperation($anonymous_comment3, 'delete'); - - $this->drupalGet('admin/content/comment'); - $this->assertNoRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was deleted.')); - $this->drupalLogout(); - - // Reset. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => FALSE, - 'post comments' => FALSE, - 'skip comment approval' => FALSE, - )); - - // Attempt to view comments while disallowed. - // NOTE: if authenticated user has permission to post comments, then a - // "Login or register to post comments" type link may be shown. - $this->drupalGet('node/' . $this->node->nid); - $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); - $this->assertNoLink('Add new comment', t('Link to add comment was found.')); - - // Attempt to view node-comment form while disallowed. - $this->drupalGet('comment/reply/' . $this->node->nid); - $this->assertText('You are not authorized to post comments', t('Error attempting to post comment.')); - $this->assertNoFieldByName('subject', '', t('Subject field not found.')); - $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', t('Comment field not found.')); - - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => FALSE, - 'skip comment approval' => FALSE, - )); - $this->drupalGet('node/' . $this->node->nid); - $this->assertPattern('@<h2[^>]*>Comments</h2>@', t('Comments were displayed.')); - $this->assertLink('Log in', 1, t('Link to log in was found.')); - $this->assertLink('register', 1, t('Link to register was found.')); - - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => FALSE, - 'post comments' => TRUE, - 'skip comment approval' => TRUE, - )); - $this->drupalGet('node/' . $this->node->nid); - $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); - $this->assertFieldByName('subject', '', t('Subject field found.')); - $this->assertFieldByName("comment_body[$langcode][0][value]", '', t('Comment field found.')); - - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $anonymous_comment3->id); - $this->assertText('You are not authorized to view comments', t('Error attempting to post reply.')); - $this->assertNoText($author_name, t('Comment not displayed.')); - } -} - -/** - * Verifies pagination of comments. - */ -class CommentPagerTest extends CommentTestBase { - - public static function getInfo() { - return array( - 'name' => 'Comment paging settings', - 'description' => 'Test paging of comments and their settings.', - 'group' => 'Comment', - ); - } - - /** - * Confirms comment paging works correctly with flat and threaded comments. - */ - function testCommentPaging() { - $this->drupalLogin($this->admin_user); - - // Set comment variables. - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_DISABLED); - - // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $comments = array(); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); - - // Set comments to one per page so that we are able to test paging without - // needing to insert large numbers of comments. - $this->setCommentsPerPage(1); - - // Check the first page of the node, and confirm the correct comments are - // shown. - $this->drupalGet('node/' . $node->nid); - $this->assertRaw(t('next'), t('Paging links found.')); - $this->assertTrue($this->commentExists($comments[0]), t('Comment 1 appears on page 1.')); - $this->assertFalse($this->commentExists($comments[1]), t('Comment 2 does not appear on page 1.')); - $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 1.')); - - // Check the second page. - $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 1))); - $this->assertTrue($this->commentExists($comments[1]), t('Comment 2 appears on page 2.')); - $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 2.')); - $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 2.')); - - // Check the third page. - $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 2))); - $this->assertTrue($this->commentExists($comments[2]), t('Comment 3 appears on page 3.')); - $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 3.')); - $this->assertFalse($this->commentExists($comments[1]), t('Comment 2 does not appear on page 3.')); - - // Post a reply to the oldest comment and test again. - $replies = array(); - $oldest_comment = reset($comments); - $this->drupalGet('comment/reply/' . $node->nid . '/' . $oldest_comment->id); - $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - $this->setCommentsPerPage(2); - // We are still in flat view - the replies should not be on the first page, - // even though they are replies to the oldest comment. - $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); - $this->assertFalse($this->commentExists($reply, TRUE), t('In flat mode, reply does not appear on page 1.')); - - // If we switch to threaded mode, the replies on the oldest comment - // should be bumped to the first page and comment 6 should be bumped - // to the second page. - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); - $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); - $this->assertTrue($this->commentExists($reply, TRUE), t('In threaded mode, reply appears on page 1.')); - $this->assertFalse($this->commentExists($comments[1]), t('In threaded mode, comment 2 has been bumped off of page 1.')); - - // If (# replies > # comments per page) in threaded expanded view, - // the overage should be bumped. - $reply2 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); - $this->assertFalse($this->commentExists($reply2, TRUE), t('In threaded mode where # replies > # comments per page, the newest reply does not appear on page 1.')); - - $this->drupalLogout(); - } - - /** - * Tests comment ordering and threading. - */ - function testCommentOrderingThreading() { - $this->drupalLogin($this->admin_user); - - // Set comment variables. - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_DISABLED); - - // Display all the comments on the same page. - $this->setCommentsPerPage(1000); - - // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $comments = array(); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the first comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[0]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the last comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[2]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[3]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // At this point, the comment tree is: - // - 0 - // - 4 - // - 1 - // - 3 - // - 6 - // - 2 - // - 5 - - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); - - $expected_order = array( - 0, - 1, - 2, - 3, - 4, - 5, - 6, - ); - $this->drupalGet('node/' . $node->nid); - $this->assertCommentOrder($comments, $expected_order); - - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); - - $expected_order = array( - 0, - 4, - 1, - 3, - 6, - 2, - 5, - ); - $this->drupalGet('node/' . $node->nid); - $this->assertCommentOrder($comments, $expected_order); - } - - /** - * Asserts that the comments are displayed in the correct order. - * - * @param $comments - * And array of comments. - * @param $expected_order - * An array of keys from $comments describing the expected order. - */ - function assertCommentOrder(array $comments, array $expected_order) { - $expected_cids = array(); - - // First, rekey the expected order by cid. - foreach ($expected_order as $key) { - $expected_cids[] = $comments[$key]->id; - } - - $comment_anchors = $this->xpath('//a[starts-with(@id,"comment-")]'); - $result_order = array(); - foreach ($comment_anchors as $anchor) { - $result_order[] = substr($anchor['id'], 8); - } - - return $this->assertIdentical($expected_cids, $result_order, t('Comment order: expected @expected, returned @returned.', array('@expected' => implode(',', $expected_cids), '@returned' => implode(',', $result_order)))); - } - - /** - * Tests comment_new_page_count(). - */ - function testCommentNewPageIndicator() { - $this->drupalLogin($this->admin_user); - - // Set comment variables. - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_DISABLED); - - // Set comments to one per page so that we are able to test paging without - // needing to insert large numbers of comments. - $this->setCommentsPerPage(1); - - // Create a node and three comments. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $comments = array(); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the second comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the first comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[0]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the last comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[2]->id); - $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - - // At this point, the comment tree is: - // - 0 - // - 4 - // - 1 - // - 3 - // - 2 - // - 5 - - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); - - $expected_pages = array( - 1 => 5, // Page of comment 5 - 2 => 4, // Page of comment 4 - 3 => 3, // Page of comment 3 - 4 => 2, // Page of comment 2 - 5 => 1, // Page of comment 1 - 6 => 0, // Page of comment 0 - ); - - $node = node_load($node->nid); - foreach ($expected_pages as $new_replies => $expected_page) { - $returned = comment_new_page_count($node->comment_count, $new_replies, $node); - $returned_page = is_array($returned) ? $returned['page'] : 0; - $this->assertIdentical($expected_page, $returned_page, t('Flat mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); - } - - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); - - $expected_pages = array( - 1 => 5, // Page of comment 5 - 2 => 1, // Page of comment 4 - 3 => 1, // Page of comment 4 - 4 => 1, // Page of comment 4 - 5 => 1, // Page of comment 4 - 6 => 0, // Page of comment 0 - ); - - $node = node_load($node->nid); - foreach ($expected_pages as $new_replies => $expected_page) { - $returned = comment_new_page_count($node->comment_count, $new_replies, $node); - $returned_page = is_array($returned) ? $returned['page'] : 0; - $this->assertEqual($expected_page, $returned_page, t('Threaded mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); - } - } -} - -/** - * Tests comments with node access. - * - * See http://drupal.org/node/886752 -- verify there is no PostgreSQL error when - * viewing a node with threaded comments (a comment and a reply), if a node - * access module is in use. - */ -class CommentNodeAccessTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment node access', - 'description' => 'Test comment viewing with node access.', - 'group' => 'Comment', - ); - } - - function setUp() { - WebTestBase::setUp('comment', 'search', 'node_access_test'); - node_access_rebuild(); - - // Create users and test node. - $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks')); - $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments', 'node test view')); - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); - } - - /** - * Test that threaded comments can be viewed. - */ - function testThreadedCommentView() { - $langcode = LANGUAGE_NOT_SPECIFIED; - // Set comments to have subject required and preview disabled. - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_DISABLED); - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - $this->drupalLogout(); - - // Post comment. - $this->drupalLogin($this->web_user); - $comment_text = $this->randomName(); - $comment_subject = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $comment_subject); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), t('Comment found.')); - - // Check comment display. - $this->drupalGet('node/' . $this->node->nid . '/' . $comment->id); - $this->assertText($comment_subject, t('Individual comment subject found.')); - $this->assertText($comment_text, t('Individual comment body found.')); - - // Reply to comment, creating second comment. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $reply_text = $this->randomName(); - $reply_subject = $this->randomName(); - $reply = $this->postComment(NULL, $reply_text, $reply_subject, TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), t('Reply found.')); - - // Go to the node page and verify comment and reply are visible. - $this->drupalGet('node/' . $this->node->nid); - $this->assertText($comment_text); - $this->assertText($comment_subject); - $this->assertText($reply_text); - $this->assertText($reply_subject); - } -} -/** - * Tests comment approval functionality. - */ -class CommentApprovalTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment approval', - 'description' => 'Test comment approval functionality.', - 'group' => 'Comment', - ); - } - - /** - * Test comment approval functionality through admin/content/comment. - */ - function testApprovalAdminInterface() { - // Set anonymous comments to require approval. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'skip comment approval' => FALSE, - )); - $this->drupalLogin($this->admin_user); - $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. - - // Test that the comments page loads correctly when there are no comments - $this->drupalGet('admin/content/comment'); - $this->assertText(t('No comments available.')); - - $this->drupalLogout(); - - // Post anonymous comment without contact info. - $subject = $this->randomName(); - $body = $this->randomName(); - $this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message. - $this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), t('Comment requires approval.')); - - // Get unapproved comment id. - $this->drupalLogin($this->admin_user); - $anonymous_comment4 = $this->getUnapprovedComment($subject); - $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body)); - $this->drupalLogout(); - - $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.')); - - // Approve comment. - $this->drupalLogin($this->admin_user); - $this->performCommentOperation($anonymous_comment4, 'publish', TRUE); - $this->drupalLogout(); - - $this->drupalGet('node/' . $this->node->nid); - $this->assertTrue($this->commentExists($anonymous_comment4), t('Anonymous comment visible.')); - - // Post 2 anonymous comments without contact info. - $comments[] = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); - $comments[] = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); - - // Publish multiple comments in one operation. - $this->drupalLogin($this->admin_user); - $this->drupalGet('admin/content/comment/approval'); - $this->assertText(t('Unapproved comments (@count)', array('@count' => 2)), t('Two unapproved comments waiting for approval.')); - $edit = array( - "comments[{$comments[0]->id}]" => 1, - "comments[{$comments[1]->id}]" => 1, - ); - $this->drupalPost(NULL, $edit, t('Update')); - $this->assertText(t('Unapproved comments (@count)', array('@count' => 0)), t('All comments were approved.')); - - // Delete multiple comments in one operation. - $edit = array( - 'operation' => 'delete', - "comments[{$comments[0]->id}]" => 1, - "comments[{$comments[1]->id}]" => 1, - "comments[{$anonymous_comment4->id}]" => 1, - ); - $this->drupalPost(NULL, $edit, t('Update')); - $this->assertText(t('Are you sure you want to delete these comments and all their children?'), t('Confirmation required.')); - $this->drupalPost(NULL, $edit, t('Delete comments')); - $this->assertText(t('No comments available.'), t('All comments were deleted.')); - } - - /** - * Tests comment approval functionality through the node interface. - */ - function testApprovalNodeInterface() { - // Set anonymous comments to require approval. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access comments' => TRUE, - 'post comments' => TRUE, - 'skip comment approval' => FALSE, - )); - $this->drupalLogin($this->admin_user); - $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. - $this->drupalLogout(); - - // Post anonymous comment without contact info. - $subject = $this->randomName(); - $body = $this->randomName(); - $this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message. - $this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), t('Comment requires approval.')); - - // Get unapproved comment id. - $this->drupalLogin($this->admin_user); - $anonymous_comment4 = $this->getUnapprovedComment($subject); - $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body)); - $this->drupalLogout(); - - $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.')); - - // Approve comment. - $this->drupalLogin($this->admin_user); - $this->drupalGet('comment/1/approve'); - $this->assertResponse(403, t('Forged comment approval was denied.')); - $this->drupalGet('comment/1/approve', array('query' => array('token' => 'forged'))); - $this->assertResponse(403, t('Forged comment approval was denied.')); - $this->drupalGet('node/' . $this->node->nid); - $this->clickLink(t('approve')); - $this->drupalLogout(); - - $this->drupalGet('node/' . $this->node->nid); - $this->assertTrue($this->commentExists($anonymous_comment4), t('Anonymous comment visible.')); - } -} - -/** - * Tests the Comment module blocks. - */ -class CommentBlockFunctionalTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment blocks', - 'description' => 'Test comment block functionality.', - 'group' => 'Comment', - ); - } - - /** - * Tests the recent comments block. - */ - function testRecentCommentBlock() { - $this->drupalLogin($this->admin_user); - - // Set the block to a region to confirm block is available. - $edit = array( - 'blocks[comment_recent][region]' => 'sidebar_first', - ); - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); - - // Set block title and variables. - $block = array( - 'title' => $this->randomName(), - 'comment_block_count' => 2, - ); - $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); - - // Add some test comments, one without a subject. - $comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName()); - $comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); - $comment3 = $this->postComment($this->node, $this->randomName()); - - // Test that a user without the 'access comments' permission cannot see the - // block. - $this->drupalLogout(); - user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access comments')); - // drupalCreateNode() does not automatically flush content caches unlike - // posting a node from a node form. - cache_clear_all(); - $this->drupalGet(''); - $this->assertNoText($block['title'], t('Block was not found.')); - user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments')); - - // Test that a user with the 'access comments' permission can see the - // block. - $this->drupalLogin($this->web_user); - $this->drupalGet(''); - $this->assertText($block['title'], t('Block was found.')); - - // Test the only the 2 latest comments are shown and in the proper order. - $this->assertNoText($comment1->subject, t('Comment not found in block.')); - $this->assertText($comment2->subject, t('Comment found in block.')); - $this->assertText($comment3->comment, t('Comment found in block.')); - $this->assertTrue(strpos($this->drupalGetContent(), $comment3->comment) < strpos($this->drupalGetContent(), $comment2->subject), t('Comments were ordered correctly in block.')); - - // Set the number of recent comments to show to 10. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $block = array( - 'comment_block_count' => 10, - ); - $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); - - // Post an additional comment. - $comment4 = $this->postComment($this->node, $this->randomName(), $this->randomName()); - - // Test that all four comments are shown. - $this->assertText($comment1->subject, t('Comment found in block.')); - $this->assertText($comment2->subject, t('Comment found in block.')); - $this->assertText($comment3->comment, t('Comment found in block.')); - $this->assertText($comment4->subject, t('Comment found in block.')); - - // Test that links to comments work when comments are across pages. - $this->setCommentsPerPage(1); - $this->drupalGet(''); - $this->clickLink($comment1->subject); - $this->assertText($comment1->subject, t('Comment link goes to correct page.')); - $this->drupalGet(''); - $this->clickLink($comment2->subject); - $this->assertText($comment2->subject, t('Comment link goes to correct page.')); - $this->clickLink($comment4->subject); - $this->assertText($comment4->subject, t('Comment link goes to correct page.')); - // Check that when viewing a comment page from a link to the comment, that - // rel="canonical" is added to the head of the document. - $this->assertRaw('<link rel="canonical"', t('Canonical URL was found in the HTML head')); - } -} - -/** - * Unit tests for Comment module integration with RSS feeds. - */ -class CommentRSSUnitTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment RSS', - 'description' => 'Test comments as part of an RSS feed.', - 'group' => 'Comment', - ); - } - - /** - * Tests comments as part of an RSS feed. - */ - function testCommentRSS() { - // Find comment in RSS feed. - $this->drupalLogin($this->web_user); - $comment = $this->postComment($this->node, $this->randomName(), $this->randomName()); - $this->drupalGet('rss.xml'); - $raw = '<comments>' . url('node/' . $this->node->nid, array('fragment' => 'comments', 'absolute' => TRUE)) . '</comments>'; - $this->assertRaw($raw, t('Comments as part of RSS feed.')); - - // Hide comments from RSS feed and check presence. - $this->node->comment = COMMENT_NODE_HIDDEN; - node_save($this->node); - $this->drupalGet('rss.xml'); - $this->assertNoRaw($raw, t('Hidden comments is not a part of RSS feed.')); - } -} - - -/** - * Tests comment content rebuilding. - */ -class CommentContentRebuild extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment Rebuild', - 'description' => 'Test to make sure the comment content is rebuilt.', - 'group' => 'Comment', - ); - } - - /** - * Tests the rebuilding of comment's content arrays on calling comment_view(). - */ - function testCommentRebuild() { - // Update the comment settings so preview isn't required. - $this->drupalLogin($this->admin_user); - $this->setCommentSubject(TRUE); - $this->setCommentPreview(DRUPAL_OPTIONAL); - $this->drupalLogout(); - - // Log in as the web user and add the comment. - $this->drupalLogin($this->web_user); - $subject_text = $this->randomName(); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), t('Comment found.')); - - // Add the property to the content array and then see if it still exists on build. - $comment_loaded->content['test_property'] = array('#value' => $this->randomString()); - $built_content = comment_view($comment_loaded, $this->node); - - // This means that the content was rebuilt as the added test property no longer exists. - $this->assertFalse(isset($built_content['test_property']), t('Comment content was emptied before being built.')); - } -} - -/** - * Tests comment token replacement in strings. - */ -class CommentTokenReplaceTestCase extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment token replacement', - 'description' => 'Generates text using placeholders for dummy content to check comment token replacement.', - 'group' => 'Comment', - ); - } - - /** - * Creates a comment, then tests the tokens generated from it. - */ - function testCommentTokenReplacement() { - global $language_interface; - $url_options = array( - 'absolute' => TRUE, - 'language' => $language_interface, - ); - - $this->drupalLogin($this->admin_user); - - // Set comment variables. - $this->setCommentSubject(TRUE); - - // Create a node and a comment. - $node = $this->drupalCreateNode(array('type' => 'article')); - $parent_comment = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); - - // Post a reply to the comment. - $this->drupalGet('comment/reply/' . $node->nid . '/' . $parent_comment->id); - $child_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); - $comment = comment_load($child_comment->id); - $comment->homepage = 'http://example.org/'; - - // Add HTML to ensure that sanitation of some fields tested directly. - $comment->subject = '<blink>Blinking Comment</blink>'; - $instance = field_info_instance('comment', 'body', 'comment_body'); - - // Generate and test sanitized tokens. - $tests = array(); - $tests['[comment:cid]'] = $comment->cid; - $tests['[comment:hostname]'] = check_plain($comment->hostname); - $tests['[comment:name]'] = filter_xss($comment->name); - $tests['[comment:mail]'] = check_plain($this->admin_user->mail); - $tests['[comment:homepage]'] = check_url($comment->homepage); - $tests['[comment:title]'] = filter_xss($comment->subject); - $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NOT_SPECIFIED, $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0], 'value'); - $tests['[comment:url]'] = url('comment/' . $comment->cid, $url_options + array('fragment' => 'comment-' . $comment->cid)); - $tests['[comment:edit-url]'] = url('comment/' . $comment->cid . '/edit', $url_options); - $tests['[comment:created:since]'] = format_interval(REQUEST_TIME - $comment->created, 2, $language_interface->langcode); - $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language_interface->langcode); - $tests['[comment:parent:cid]'] = $comment->pid; - $tests['[comment:parent:title]'] = check_plain($parent_comment->subject); - $tests['[comment:node:nid]'] = $comment->nid; - $tests['[comment:node:title]'] = check_plain($node->title); - $tests['[comment:author:uid]'] = $comment->uid; - $tests['[comment:author:name]'] = check_plain($this->admin_user->name); - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('comment' => $comment), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Sanitized comment token %token replaced.', array('%token' => $input))); - } - - // Generate and test unsanitized tokens. - $tests['[comment:hostname]'] = $comment->hostname; - $tests['[comment:name]'] = $comment->name; - $tests['[comment:mail]'] = $this->admin_user->mail; - $tests['[comment:homepage]'] = $comment->homepage; - $tests['[comment:title]'] = $comment->subject; - $tests['[comment:body]'] = $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0]['value']; - $tests['[comment:parent:title]'] = $parent_comment->subject; - $tests['[comment:node:title]'] = $node->title; - $tests['[comment:author:name]'] = $this->admin_user->name; - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('comment' => $comment), array('language' => $language_interface, 'sanitize' => FALSE)); - $this->assertEqual($output, $expected, t('Unsanitized comment token %token replaced.', array('%token' => $input))); - } - - // Load node so comment_count gets computed. - $node = node_load($node->nid); - - // Generate comment tokens for the node (it has 2 comments, both new). - $tests = array(); - $tests['[node:comment-count]'] = 2; - $tests['[node:comment-count-new]'] = 2; - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Node comment token %token replaced.', array('%token' => $input))); - } - } -} - -/** - * Tests actions provided by the Comment module. - */ -class CommentActionsTestCase extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment actions', - 'description' => 'Test actions provided by the comment module.', - 'group' => 'Comment', - ); - } - - /** - * Tests comment publish and unpublish actions. - */ - function testCommentPublishUnpublishActions() { - $this->drupalLogin($this->web_user); - $comment_text = $this->randomName(); - $subject = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $subject); - $comment = comment_load($comment->id); - - // Unpublish a comment (direct form: doesn't actually save the comment). - comment_unpublish_action($comment); - $this->assertEqual($comment->status, COMMENT_NOT_PUBLISHED, t('Comment was unpublished')); - $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), t('Found watchdog message')); - $this->clearWatchdog(); - - // Unpublish a comment (indirect form: modify the comment in the database). - comment_unpublish_action(NULL, array('cid' => $comment->cid)); - $this->assertEqual(comment_load($comment->cid)->status, COMMENT_NOT_PUBLISHED, t('Comment was unpublished')); - $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), t('Found watchdog message')); - - // Publish a comment (direct form: doesn't actually save the comment). - comment_publish_action($comment); - $this->assertEqual($comment->status, COMMENT_PUBLISHED, t('Comment was published')); - $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), t('Found watchdog message')); - $this->clearWatchdog(); - - // Publish a comment (indirect form: modify the comment in the database). - comment_publish_action(NULL, array('cid' => $comment->cid)); - $this->assertEqual(comment_load($comment->cid)->status, COMMENT_PUBLISHED, t('Comment was published')); - $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), t('Found watchdog message')); - $this->clearWatchdog(); - } - - /** - * Verifies that a watchdog message has been entered. - * - * @param $watchdog_message - * The watchdog message. - * @param $variables - * The array of variables passed to watchdog(). - * @param $message - * The assertion message. - */ - function assertWatchdogMessage($watchdog_message, $variables, $message) { - $status = (bool) db_query_range("SELECT 1 FROM {watchdog} WHERE message = :message AND variables = :variables", 0, 1, array(':message' => $watchdog_message, ':variables' => serialize($variables)))->fetchField(); - return $this->assert($status, $message); - } - - /** - * Clears watchdog. - */ - function clearWatchdog() { - db_truncate('watchdog')->execute(); - } -} - -/** - * Tests fields on comments. - */ -class CommentFieldsTest extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment fields', - 'description' => 'Tests fields on comments.', - 'group' => 'Comment', - ); - } - - /** - * Tests that the default 'comment_body' field is correctly added. - */ - function testCommentDefaultFields() { - // Do not make assumptions on default node types created by the test - // install profile, and create our own. - $this->drupalCreateContentType(array('type' => 'test_node_type')); - - // Check that the 'comment_body' field is present on all comment bundles. - $instances = field_info_instances('comment'); - foreach (node_type_get_types() as $type_name => $info) { - $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), t('The comment_body field is present for comments on type @type', array('@type' => $type_name))); - - // Delete the instance along the way. - field_delete_instance($instances['comment_node_' . $type_name]['comment_body']); - } - - // Check that the 'comment_body' field is deleted. - $field = field_info_field('comment_body'); - $this->assertTrue(empty($field), t('The comment_body field was deleted')); - - // Create a new content type. - $type_name = 'test_node_type_2'; - $this->drupalCreateContentType(array('type' => $type_name)); - - // Check that the 'comment_body' field exists and has an instance on the - // new comment bundle. - $field = field_info_field('comment_body'); - $this->assertTrue($field, t('The comment_body field exists')); - $instances = field_info_instances('comment'); - $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), t('The comment_body field is present for comments on type @type', array('@type' => $type_name))); - } - - /** - * Tests that comment module works when enabled after a content module. - */ - function testCommentEnable() { - // Create a user to do module administration. - $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules')); - $this->drupalLogin($this->admin_user); - - // Disable the comment module. - $edit = array(); - $edit['modules[Core][comment][enable]'] = FALSE; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->resetAll(); - $this->assertFalse(module_exists('comment'), t('Comment module disabled.')); - - // Enable core content type modules (book, and poll). - $edit = array(); - $edit['modules[Core][book][enable]'] = 'book'; - $edit['modules[Core][poll][enable]'] = 'poll'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - - // Now enable the comment module. - $edit = array(); - $edit['modules[Core][comment][enable]'] = 'comment'; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->resetAll(); - $this->assertTrue(module_exists('comment'), t('Comment module enabled.')); - - // Create nodes of each type. - $book_node = $this->drupalCreateNode(array('type' => 'book')); - $poll_node = $this->drupalCreateNode(array('type' => 'poll', 'active' => 1, 'runtime' => 0, 'choice' => array(array('chtext' => '')))); - - $this->drupalLogout(); - - // Try to post a comment on each node. A failure will be triggered if the - // comment body is missing on one of these forms, due to postComment() - // asserting that the body is actually posted correctly. - $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'post comments', 'skip comment approval')); - $this->drupalLogin($this->web_user); - $this->postComment($book_node, $this->randomName(), $this->randomName()); - $this->postComment($poll_node, $this->randomName(), $this->randomName()); - } - - /** - * Tests that comment module works correctly with plain text format. - */ - function testCommentFormat() { - // Disable text processing for comments. - $this->drupalLogin($this->admin_user); - $edit = array('instance[settings][text_processing]' => 0); - $this->drupalPost('admin/structure/types/manage/article/comment/fields/comment_body', $edit, t('Save settings')); - - // Post a comment without an explicit subject. - $this->drupalLogin($this->web_user); - $edit = array('comment_body[und][0][value]' => $this->randomName(8)); - $this->drupalPost('node/' . $this->node->nid, $edit, t('Save')); - } -} - -/** - * Tests comment threading. - */ -class CommentThreadingTestCase extends CommentTestBase { - public static function getInfo() { - return array( - 'name' => 'Comment Threading', - 'description' => 'Test to make sure the comment number increments properly.', - 'group' => 'Comment', - ); - } - - /** - * Tests the comment threading. - */ - function testCommentThreading() { - $langcode = LANGUAGE_NOT_SPECIFIED; - // Set comments to have a subject with preview disabled. - $this->drupalLogin($this->admin_user); - $this->setCommentPreview(DRUPAL_DISABLED); - $this->setCommentForm(TRUE); - $this->setCommentSubject(TRUE); - $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); - $this->drupalLogout(); - - // Create a node. - $this->drupalLogin($this->web_user); - $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); - - // Post comment #1. - $this->drupalLogin($this->web_user); - $subject_text = $this->randomName(); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), 'Comment #1. Comment found.'); - $this->assertEqual($comment_loaded->thread, '01/'); - - // Reply to comment #1 creating comment #2. - $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #2. Reply found.'); - $this->assertEqual($reply_loaded->thread, '01.00/'); - - // Reply to comment #2 creating comment #3. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply->id); - $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #3. Second reply found.'); - $this->assertEqual($reply_loaded->thread, '01.00.00/'); - - // Reply to comment #1 creating comment #4. - $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($comment), 'Comment #4. Third reply found.'); - $this->assertEqual($reply_loaded->thread, '01.01/'); - - // Post comment #2 overall comment #5. - $this->drupalLogin($this->web_user); - $subject_text = $this->randomName(); - $comment_text = $this->randomName(); - $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); - $comment_loaded = comment_load($comment->id); - $this->assertTrue($this->commentExists($comment), 'Comment #5. Second comment found.'); - $this->assertEqual($comment_loaded->thread, '02/'); - - // Reply to comment #5 creating comment #6. - $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #6. Reply found.'); - $this->assertEqual($reply_loaded->thread, '02.00/'); - - // Reply to comment #6 creating comment #7. - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply->id); - $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #7. Second reply found.'); - $this->assertEqual($reply_loaded->thread, '02.00.00/'); - - // Reply to comment #5 creating comment #8. - $this->drupalLogin($this->web_user); - $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); - $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); - $reply_loaded = comment_load($reply->id); - $this->assertTrue($this->commentExists($comment), 'Comment #8. Third reply found.'); - $this->assertEqual($reply_loaded->thread, '02.01/'); - } -} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8f802c1d1e4c09c2974ab5a4ca1acd1d2196c65c --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentActionsTest.php @@ -0,0 +1,77 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentActionsTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests actions provided by the Comment module. + */ +class CommentActionsTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment actions', + 'description' => 'Test actions provided by the comment module.', + 'group' => 'Comment', + ); + } + + /** + * Tests comment publish and unpublish actions. + */ + function testCommentPublishUnpublishActions() { + $this->drupalLogin($this->web_user); + $comment_text = $this->randomName(); + $subject = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject); + $comment = comment_load($comment->id); + + // Unpublish a comment (direct form: doesn't actually save the comment). + comment_unpublish_action($comment); + $this->assertEqual($comment->status, COMMENT_NOT_PUBLISHED, t('Comment was unpublished')); + $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), t('Found watchdog message')); + $this->clearWatchdog(); + + // Unpublish a comment (indirect form: modify the comment in the database). + comment_unpublish_action(NULL, array('cid' => $comment->cid)); + $this->assertEqual(comment_load($comment->cid)->status, COMMENT_NOT_PUBLISHED, t('Comment was unpublished')); + $this->assertWatchdogMessage('Unpublished comment %subject.', array('%subject' => $subject), t('Found watchdog message')); + + // Publish a comment (direct form: doesn't actually save the comment). + comment_publish_action($comment); + $this->assertEqual($comment->status, COMMENT_PUBLISHED, t('Comment was published')); + $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), t('Found watchdog message')); + $this->clearWatchdog(); + + // Publish a comment (indirect form: modify the comment in the database). + comment_publish_action(NULL, array('cid' => $comment->cid)); + $this->assertEqual(comment_load($comment->cid)->status, COMMENT_PUBLISHED, t('Comment was published')); + $this->assertWatchdogMessage('Published comment %subject.', array('%subject' => $subject), t('Found watchdog message')); + $this->clearWatchdog(); + } + + /** + * Verifies that a watchdog message has been entered. + * + * @param $watchdog_message + * The watchdog message. + * @param $variables + * The array of variables passed to watchdog(). + * @param $message + * The assertion message. + */ + function assertWatchdogMessage($watchdog_message, $variables, $message) { + $status = (bool) db_query_range("SELECT 1 FROM {watchdog} WHERE message = :message AND variables = :variables", 0, 1, array(':message' => $watchdog_message, ':variables' => serialize($variables)))->fetchField(); + return $this->assert($status, $message); + } + + /** + * Clears watchdog. + */ + function clearWatchdog() { + db_truncate('watchdog')->execute(); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5181e329cb8933d972c4a68a6836ec2753c8be97 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentAnonymousTest.php @@ -0,0 +1,161 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentAnonymousTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests anonymous commenting. + */ +class CommentAnonymousTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Anonymous comments', + 'description' => 'Test anonymous comments.', + 'group' => 'Comment', + ); + } + + function setUp() { + parent::setUp(); + variable_set('user_register', USER_REGISTER_VISITORS); + } + + /** + * Tests anonymous comment functionality. + */ + function testAnonymous() { + $this->drupalLogin($this->admin_user); + // Enabled anonymous user comments. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + )); + $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. + $this->drupalLogout(); + + // Post anonymous comment without contact info. + $anonymous_comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($anonymous_comment1), t('Anonymous comment without contact info found.')); + + // Allow contact info. + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('1'); + + // Attempt to edit anonymous comment. + $this->drupalGet('comment/' . $anonymous_comment1->id . '/edit'); + $edited_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($edited_comment, FALSE), t('Modified reply found.')); + $this->drupalLogout(); + + // Post anonymous comment with contact info (optional). + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertTrue($this->commentContactInfoAvailable(), t('Contact information available.')); + + $anonymous_comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($anonymous_comment2), t('Anonymous comment with contact info (optional) found.')); + + // Ensure anonymous users cannot post in the name of registered users. + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit = array( + 'name' => $this->admin_user->name, + 'mail' => $this->randomName() . '@example.com', + 'subject' => $this->randomName(), + "comment_body[$langcode][0][value]" => $this->randomName(), + ); + $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); + $this->assertText(t('The name you used belongs to a registered user.')); + + // Require contact info. + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('2'); + $this->drupalLogout(); + + // Try to post comment with contact info (required). + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertTrue($this->commentContactInfoAvailable(), t('Contact information available.')); + + $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); + $this->assertText(t('E-mail field is required.'), t('E-mail required.')); // Name should have 'Anonymous' for value by default. + $this->assertFalse($this->commentExists($anonymous_comment3), t('Anonymous comment with contact info (required) not found.')); + + // Post comment with contact info (required). + $author_name = $this->randomName(); + $author_mail = $this->randomName() . '@example.com'; + $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), array('name' => $author_name, 'mail' => $author_mail)); + $this->assertTrue($this->commentExists($anonymous_comment3), t('Anonymous comment with contact info (required) found.')); + + // Make sure the user data appears correctly when editing the comment. + $this->drupalLogin($this->admin_user); + $this->drupalGet('comment/' . $anonymous_comment3->id . '/edit'); + $this->assertRaw($author_name, t("The anonymous user's name is correct when editing the comment.")); + $this->assertRaw($author_mail, t("The anonymous user's e-mail address is correct when editing the comment.")); + + // Unpublish comment. + $this->performCommentOperation($anonymous_comment3, 'unpublish'); + + $this->drupalGet('admin/content/comment/approval'); + $this->assertRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was unpublished.')); + + // Publish comment. + $this->performCommentOperation($anonymous_comment3, 'publish', TRUE); + + $this->drupalGet('admin/content/comment'); + $this->assertRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was published.')); + + // Delete comment. + $this->performCommentOperation($anonymous_comment3, 'delete'); + + $this->drupalGet('admin/content/comment'); + $this->assertNoRaw('comments[' . $anonymous_comment3->id . ']', t('Comment was deleted.')); + $this->drupalLogout(); + + // Reset. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => FALSE, + 'skip comment approval' => FALSE, + )); + + // Attempt to view comments while disallowed. + // NOTE: if authenticated user has permission to post comments, then a + // "Login or register to post comments" type link may be shown. + $this->drupalGet('node/' . $this->node->nid); + $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); + $this->assertNoLink('Add new comment', t('Link to add comment was found.')); + + // Attempt to view node-comment form while disallowed. + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertText('You are not authorized to post comments', t('Error attempting to post comment.')); + $this->assertNoFieldByName('subject', '', t('Subject field not found.')); + $this->assertNoFieldByName("comment_body[$langcode][0][value]", '', t('Comment field not found.')); + + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => FALSE, + 'skip comment approval' => FALSE, + )); + $this->drupalGet('node/' . $this->node->nid); + $this->assertPattern('@<h2[^>]*>Comments</h2>@', t('Comments were displayed.')); + $this->assertLink('Log in', 1, t('Link to log in was found.')); + $this->assertLink('register', 1, t('Link to register was found.')); + + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + )); + $this->drupalGet('node/' . $this->node->nid); + $this->assertNoPattern('@<h2[^>]*>Comments</h2>@', t('Comments were not displayed.')); + $this->assertFieldByName('subject', '', t('Subject field found.')); + $this->assertFieldByName("comment_body[$langcode][0][value]", '', t('Comment field found.')); + + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $anonymous_comment3->id); + $this->assertText('You are not authorized to view comments', t('Error attempting to post reply.')); + $this->assertNoText($author_name, t('Comment not displayed.')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..02b3be1ada6b176ce9582132e253644c41b5cd07 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentApprovalTest.php @@ -0,0 +1,132 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentApprovalTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests comment approval functionality. + */ +class CommentApprovalTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment approval', + 'description' => 'Test comment approval functionality.', + 'group' => 'Comment', + ); + } + + /** + * Test comment approval functionality through admin/content/comment. + */ + function testApprovalAdminInterface() { + // Set anonymous comments to require approval. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => FALSE, + )); + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. + + // Test that the comments page loads correctly when there are no comments + $this->drupalGet('admin/content/comment'); + $this->assertText(t('No comments available.')); + + $this->drupalLogout(); + + // Post anonymous comment without contact info. + $subject = $this->randomName(); + $body = $this->randomName(); + $this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message. + $this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), t('Comment requires approval.')); + + // Get unapproved comment id. + $this->drupalLogin($this->admin_user); + $anonymous_comment4 = $this->getUnapprovedComment($subject); + $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body)); + $this->drupalLogout(); + + $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.')); + + // Approve comment. + $this->drupalLogin($this->admin_user); + $this->performCommentOperation($anonymous_comment4, 'publish', TRUE); + $this->drupalLogout(); + + $this->drupalGet('node/' . $this->node->nid); + $this->assertTrue($this->commentExists($anonymous_comment4), t('Anonymous comment visible.')); + + // Post 2 anonymous comments without contact info. + $comments[] = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); + + // Publish multiple comments in one operation. + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/content/comment/approval'); + $this->assertText(t('Unapproved comments (@count)', array('@count' => 2)), t('Two unapproved comments waiting for approval.')); + $edit = array( + "comments[{$comments[0]->id}]" => 1, + "comments[{$comments[1]->id}]" => 1, + ); + $this->drupalPost(NULL, $edit, t('Update')); + $this->assertText(t('Unapproved comments (@count)', array('@count' => 0)), t('All comments were approved.')); + + // Delete multiple comments in one operation. + $edit = array( + 'operation' => 'delete', + "comments[{$comments[0]->id}]" => 1, + "comments[{$comments[1]->id}]" => 1, + "comments[{$anonymous_comment4->id}]" => 1, + ); + $this->drupalPost(NULL, $edit, t('Update')); + $this->assertText(t('Are you sure you want to delete these comments and all their children?'), t('Confirmation required.')); + $this->drupalPost(NULL, $edit, t('Delete comments')); + $this->assertText(t('No comments available.'), t('All comments were deleted.')); + } + + /** + * Tests comment approval functionality through the node interface. + */ + function testApprovalNodeInterface() { + // Set anonymous comments to require approval. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => FALSE, + )); + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. + $this->drupalLogout(); + + // Post anonymous comment without contact info. + $subject = $this->randomName(); + $body = $this->randomName(); + $this->postComment($this->node, $body, $subject, TRUE); // Set $contact to true so that it won't check for id and message. + $this->assertText(t('Your comment has been queued for review by site administrators and will be published after approval.'), t('Comment requires approval.')); + + // Get unapproved comment id. + $this->drupalLogin($this->admin_user); + $anonymous_comment4 = $this->getUnapprovedComment($subject); + $anonymous_comment4 = entity_create('comment', array('id' => $anonymous_comment4, 'subject' => $subject, 'comment' => $body)); + $this->drupalLogout(); + + $this->assertFalse($this->commentExists($anonymous_comment4), t('Anonymous comment was not published.')); + + // Approve comment. + $this->drupalLogin($this->admin_user); + $this->drupalGet('comment/1/approve'); + $this->assertResponse(403, t('Forged comment approval was denied.')); + $this->drupalGet('comment/1/approve', array('query' => array('token' => 'forged'))); + $this->assertResponse(403, t('Forged comment approval was denied.')); + $this->drupalGet('node/' . $this->node->nid); + $this->clickLink(t('approve')); + $this->drupalLogout(); + + $this->drupalGet('node/' . $this->node->nid); + $this->assertTrue($this->commentExists($anonymous_comment4), t('Anonymous comment visible.')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1190c20eaa534afc03fd4a35950c36b48ec6ced2 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php @@ -0,0 +1,103 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentBlockTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests the Comment module blocks. + */ +class CommentBlockTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment blocks', + 'description' => 'Test comment block functionality.', + 'group' => 'Comment', + ); + } + + /** + * Tests the recent comments block. + */ + function testRecentCommentBlock() { + $this->drupalLogin($this->admin_user); + + // Set the block to a region to confirm block is available. + $edit = array( + 'blocks[comment_recent][region]' => 'sidebar_first', + ); + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); + + // Set block title and variables. + $block = array( + 'title' => $this->randomName(), + 'comment_block_count' => 2, + ); + $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Add some test comments, one without a subject. + $comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $comment3 = $this->postComment($this->node, $this->randomName()); + + // Test that a user without the 'access comments' permission cannot see the + // block. + $this->drupalLogout(); + user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access comments')); + // drupalCreateNode() does not automatically flush content caches unlike + // posting a node from a node form. + cache_clear_all(); + $this->drupalGet(''); + $this->assertNoText($block['title'], t('Block was not found.')); + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments')); + + // Test that a user with the 'access comments' permission can see the + // block. + $this->drupalLogin($this->web_user); + $this->drupalGet(''); + $this->assertText($block['title'], t('Block was found.')); + + // Test the only the 2 latest comments are shown and in the proper order. + $this->assertNoText($comment1->subject, t('Comment not found in block.')); + $this->assertText($comment2->subject, t('Comment found in block.')); + $this->assertText($comment3->comment, t('Comment found in block.')); + $this->assertTrue(strpos($this->drupalGetContent(), $comment3->comment) < strpos($this->drupalGetContent(), $comment2->subject), t('Comments were ordered correctly in block.')); + + // Set the number of recent comments to show to 10. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $block = array( + 'comment_block_count' => 10, + ); + $this->drupalPost('admin/structure/block/manage/comment/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Post an additional comment. + $comment4 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + + // Test that all four comments are shown. + $this->assertText($comment1->subject, t('Comment found in block.')); + $this->assertText($comment2->subject, t('Comment found in block.')); + $this->assertText($comment3->comment, t('Comment found in block.')); + $this->assertText($comment4->subject, t('Comment found in block.')); + + // Test that links to comments work when comments are across pages. + $this->setCommentsPerPage(1); + $this->drupalGet(''); + $this->clickLink($comment1->subject); + $this->assertText($comment1->subject, t('Comment link goes to correct page.')); + $this->drupalGet(''); + $this->clickLink($comment2->subject); + $this->assertText($comment2->subject, t('Comment link goes to correct page.')); + $this->clickLink($comment4->subject); + $this->assertText($comment4->subject, t('Comment link goes to correct page.')); + // Check that when viewing a comment page from a link to the comment, that + // rel="canonical" is added to the head of the document. + $this->assertRaw('<link rel="canonical"', t('Canonical URL was found in the HTML head')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2119fe0759f59c30ed0a7c87b4d56e362bdcf75f --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentContentRebuildTest.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentContentRebuildTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests comment content rebuilding. + */ +class CommentContentRebuildTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment Rebuild', + 'description' => 'Test to make sure the comment content is rebuilt.', + 'group' => 'Comment', + ); + } + + /** + * Tests the rebuilding of comment's content arrays on calling comment_view(). + */ + function testCommentRebuild() { + // Update the comment settings so preview isn't required. + $this->drupalLogin($this->admin_user); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->drupalLogout(); + + // Log in as the web user and add the comment. + $this->drupalLogin($this->web_user); + $subject_text = $this->randomName(); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), t('Comment found.')); + + // Add the property to the content array and then see if it still exists on build. + $comment_loaded->content['test_property'] = array('#value' => $this->randomString()); + $built_content = comment_view($comment_loaded, $this->node); + + // This means that the content was rebuilt as the added test property no longer exists. + $this->assertFalse(isset($built_content['test_property']), t('Comment content was emptied before being built.')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..44c79b207cdff78a4beb218e3c8a1ff8a10ca7ee --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentFieldsTest.php @@ -0,0 +1,112 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentFieldsTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests fields on comments. + */ +class CommentFieldsTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment fields', + 'description' => 'Tests fields on comments.', + 'group' => 'Comment', + ); + } + + /** + * Tests that the default 'comment_body' field is correctly added. + */ + function testCommentDefaultFields() { + // Do not make assumptions on default node types created by the test + // install profile, and create our own. + $this->drupalCreateContentType(array('type' => 'test_node_type')); + + // Check that the 'comment_body' field is present on all comment bundles. + $instances = field_info_instances('comment'); + foreach (node_type_get_types() as $type_name => $info) { + $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), t('The comment_body field is present for comments on type @type', array('@type' => $type_name))); + + // Delete the instance along the way. + field_delete_instance($instances['comment_node_' . $type_name]['comment_body']); + } + + // Check that the 'comment_body' field is deleted. + $field = field_info_field('comment_body'); + $this->assertTrue(empty($field), t('The comment_body field was deleted')); + + // Create a new content type. + $type_name = 'test_node_type_2'; + $this->drupalCreateContentType(array('type' => $type_name)); + + // Check that the 'comment_body' field exists and has an instance on the + // new comment bundle. + $field = field_info_field('comment_body'); + $this->assertTrue($field, t('The comment_body field exists')); + $instances = field_info_instances('comment'); + $this->assertTrue(isset($instances['comment_node_' . $type_name]['comment_body']), t('The comment_body field is present for comments on type @type', array('@type' => $type_name))); + } + + /** + * Tests that comment module works when enabled after a content module. + */ + function testCommentEnable() { + // Create a user to do module administration. + $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'administer modules')); + $this->drupalLogin($this->admin_user); + + // Disable the comment module. + $edit = array(); + $edit['modules[Core][comment][enable]'] = FALSE; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->resetAll(); + $this->assertFalse(module_exists('comment'), t('Comment module disabled.')); + + // Enable core content type modules (book, and poll). + $edit = array(); + $edit['modules[Core][book][enable]'] = 'book'; + $edit['modules[Core][poll][enable]'] = 'poll'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + + // Now enable the comment module. + $edit = array(); + $edit['modules[Core][comment][enable]'] = 'comment'; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->resetAll(); + $this->assertTrue(module_exists('comment'), t('Comment module enabled.')); + + // Create nodes of each type. + $book_node = $this->drupalCreateNode(array('type' => 'book')); + $poll_node = $this->drupalCreateNode(array('type' => 'poll', 'active' => 1, 'runtime' => 0, 'choice' => array(array('chtext' => '')))); + + $this->drupalLogout(); + + // Try to post a comment on each node. A failure will be triggered if the + // comment body is missing on one of these forms, due to postComment() + // asserting that the body is actually posted correctly. + $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'post comments', 'skip comment approval')); + $this->drupalLogin($this->web_user); + $this->postComment($book_node, $this->randomName(), $this->randomName()); + $this->postComment($poll_node, $this->randomName(), $this->randomName()); + } + + /** + * Tests that comment module works correctly with plain text format. + */ + function testCommentFormat() { + // Disable text processing for comments. + $this->drupalLogin($this->admin_user); + $edit = array('instance[settings][text_processing]' => 0); + $this->drupalPost('admin/structure/types/manage/article/comment/fields/comment_body', $edit, t('Save settings')); + + // Post a comment without an explicit subject. + $this->drupalLogin($this->web_user); + $edit = array('comment_body[und][0][value]' => $this->randomName(8)); + $this->drupalPost('node/' . $this->node->nid, $edit, t('Save')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b7f42ae010019e4ede203e8d608fb87c5cb23da4 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentInterfaceTest.php @@ -0,0 +1,702 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentInterfaceTest. + */ + +namespace Drupal\comment\Tests; + +class CommentInterfaceTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment interface', + 'description' => 'Test comment user interfaces.', + 'group' => 'Comment', + ); + } + + /** + * Tests the comment interface. + */ + function testCommentInterface() { + $langcode = LANGUAGE_NOT_SPECIFIED; + // Set comments to have subject and preview disabled. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_DISABLED); + $this->setCommentForm(TRUE); + $this->setCommentSubject(FALSE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + $this->drupalLogout(); + + // Post comment #1 without subject or preview. + $this->drupalLogin($this->web_user); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), t('Comment found.')); + + // Set comments to have subject and preview to required. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_REQUIRED); + $this->drupalLogout(); + + // Create comment #2 that allows subject and requires preview. + $this->drupalLogin($this->web_user); + $subject_text = $this->randomName(); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), t('Comment found.')); + + // Check comment display. + $this->drupalGet('node/' . $this->node->nid . '/' . $comment->id); + $this->assertText($subject_text, t('Individual comment subject found.')); + $this->assertText($comment_text, t('Individual comment body found.')); + + // Set comments to have subject and preview to optional. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_OPTIONAL); + + // Test changing the comment author to "Anonymous". + $this->drupalGet('comment/' . $comment->id . '/edit'); + $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => '')); + $comment_loaded = comment_load($comment->id); + $this->assertTrue(empty($comment_loaded->name) && $comment_loaded->uid == 0, t('Comment author successfully changed to anonymous.')); + + // Test changing the comment author to an unverified user. + $random_name = $this->randomName(); + $this->drupalGet('comment/' . $comment->id . '/edit'); + $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => $random_name)); + $this->drupalGet('node/' . $this->node->nid); + $this->assertText($random_name . ' (' . t('not verified') . ')', t('Comment author successfully changed to an unverified user.')); + + // Test changing the comment author to a verified user. + $this->drupalGet('comment/' . $comment->id . '/edit'); + $comment = $this->postComment(NULL, $comment->comment, $comment->subject, array('name' => $this->web_user->name)); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($comment_loaded->name == $this->web_user->name && $comment_loaded->uid == $this->web_user->uid, t('Comment author successfully changed to a registered user.')); + + $this->drupalLogout(); + + // Reply to comment #2 creating comment #3 with optional preview and no + // subject though field enabled. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $this->assertText($subject_text, t('Individual comment-reply subject found.')); + $this->assertText($comment_text, t('Individual comment-reply body found.')); + $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), t('Reply found.')); + $this->assertEqual($comment->id, $reply_loaded->pid, t('Pid of a reply to a comment is set correctly.')); + $this->assertEqual(rtrim($comment_loaded->thread, '/') . '.00/', $reply_loaded->thread, t('Thread of reply grows correctly.')); + + // Second reply to comment #3 creating comment #4. + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $this->assertText($subject_text, t('Individual comment-reply subject found.')); + $this->assertText($comment_text, t('Individual comment-reply body found.')); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), t('Second reply found.')); + $this->assertEqual(rtrim($comment_loaded->thread, '/') . '.01/', $reply_loaded->thread, t('Thread of second reply grows correctly.')); + + // Edit reply. + $this->drupalGet('comment/' . $reply->id . '/edit'); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $this->assertTrue($this->commentExists($reply, TRUE), t('Modified reply found.')); + + // Correct link count + $this->drupalGet('node'); + $this->assertRaw('4 comments', t('Link to the 4 comments exist.')); + + // Confirm a new comment is posted to the correct page. + $this->setCommentsPerPage(2); + $comment_new_page = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); + $this->assertTrue($this->commentExists($comment_new_page), t('Page one exists. %s')); + $this->drupalGet('node/' . $this->node->nid, array('query' => array('page' => 1))); + $this->assertTrue($this->commentExists($reply, TRUE), t('Page two exists. %s')); + $this->setCommentsPerPage(50); + + // Attempt to reply to an unpublished comment. + $reply_loaded->status = COMMENT_NOT_PUBLISHED; + $reply_loaded->save(); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply_loaded->cid); + $this->assertText(t('The comment you are replying to does not exist.'), 'Replying to an unpublished comment'); + + // Attempt to post to node with comments disabled. + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_HIDDEN)); + $this->assertTrue($this->node, t('Article node created.')); + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertText('This discussion is closed', t('Posting to node with comments disabled')); + $this->assertNoField('edit-comment', t('Comment body field found.')); + + // Attempt to post to node with read-only comments. + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_CLOSED)); + $this->assertTrue($this->node, t('Article node created.')); + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertText('This discussion is closed', t('Posting to node with comments read-only')); + $this->assertNoField('edit-comment', t('Comment body field found.')); + + // Attempt to post to node with comments enabled (check field names etc). + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); + $this->assertTrue($this->node, t('Article node created.')); + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertNoText('This discussion is closed', t('Posting to node with comments enabled')); + $this->assertField('edit-comment-body-' . $langcode . '-0-value', t('Comment body field found.')); + + // Delete comment and make sure that reply is also removed. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->deleteComment($comment); + $this->deleteComment($comment_new_page); + + $this->drupalGet('node/' . $this->node->nid); + $this->assertFalse($this->commentExists($comment), t('Comment not found.')); + $this->assertFalse($this->commentExists($reply, TRUE), t('Reply not found.')); + + // Enabled comment form on node page. + $this->drupalLogin($this->admin_user); + $this->setCommentForm(TRUE); + $this->drupalLogout(); + + // Submit comment through node form. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/' . $this->node->nid); + $form_comment = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $this->assertTrue($this->commentExists($form_comment), t('Form comment found.')); + + // Disable comment form on node page. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->setCommentForm(FALSE); + } + + /** + * Tests new comment marker. + */ + public function testCommentNewCommentsIndicator() { + // Test if the right links are displayed when no comment is present for the + // node. + $this->drupalLogin($this->admin_user); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'comment' => COMMENT_NODE_OPEN)); + $this->drupalGet('node'); + $this->assertNoLink(t('@count comments', array('@count' => 0))); + $this->assertNoLink(t('@count new comments', array('@count' => 0))); + $this->assertLink(t('Read more')); + $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper')); + $this->assertTrue(count($count) == 1, t('One child found')); + + // Create a new comment. This helper function may be run with different + // comment settings so use comment_save() to avoid complex setup. + $comment = entity_create('comment', array( + 'cid' => NULL, + 'nid' => $this->node->nid, + 'node_type' => $this->node->type, + 'pid' => 0, + 'uid' => $this->loggedInUser->uid, + 'status' => COMMENT_PUBLISHED, + 'subject' => $this->randomName(), + 'hostname' => ip_address(), + 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), + )); + comment_save($comment); + $this->drupalLogout(); + + // Log in with 'web user' and check comment links. + $this->drupalLogin($this->web_user); + $this->drupalGet('node'); + $this->assertLink(t('1 new comment')); + $this->clickLink(t('1 new comment')); + $this->assertRaw('<a id="new"></a>', t('Found "new" marker.')); + $this->assertTrue($this->xpath('//a[@id=:new]/following-sibling::a[1][@id=:comment_id]', array(':new' => 'new', ':comment_id' => 'comment-1')), t('The "new" anchor is positioned at the right comment.')); + + // Test if "new comment" link is correctly removed. + $this->drupalGet('node'); + $this->assertLink(t('1 comment')); + $this->assertLink(t('Read more')); + $this->assertNoLink(t('1 new comment')); + $this->assertNoLink(t('@count new comments', array('@count' => 0))); + $count = $this->xpath('//div[@id=:id]/div[@class=:class]/ul/li', array(':id' => 'node-' . $this->node->nid, ':class' => 'link-wrapper')); + $this->assertTrue(count($count) == 2, print_r($count, TRUE)); + } + + /** + * Tests CSS classes on comments. + */ + function testCommentClasses() { + // Create all permutations for comments, users, and nodes. + $parameters = array( + 'node_uid' => array(0, $this->web_user->uid), + 'comment_uid' => array(0, $this->web_user->uid, $this->admin_user->uid), + 'comment_status' => array(COMMENT_PUBLISHED, COMMENT_NOT_PUBLISHED), + 'user' => array('anonymous', 'authenticated', 'admin'), + ); + $permutations = $this->generatePermutations($parameters); + + foreach ($permutations as $case) { + // Create a new node. + $node = $this->drupalCreateNode(array('type' => 'article', 'uid' => $case['node_uid'])); + + // Add a comment. + $comment = entity_create('comment', array( + 'nid' => $node->nid, + 'uid' => $case['comment_uid'], + 'status' => $case['comment_status'], + 'subject' => $this->randomName(), + 'language' => LANGUAGE_NOT_SPECIFIED, + 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), + )); + comment_save($comment); + + // Adjust the current/viewing user. + switch ($case['user']) { + case 'anonymous': + if ($this->loggedInUser) { + $this->drupalLogout(); + } + $case['user_uid'] = 0; + break; + + case 'authenticated': + $this->drupalLogin($this->web_user); + $case['user_uid'] = $this->web_user->uid; + break; + + case 'admin': + $this->drupalLogin($this->admin_user); + $case['user_uid'] = $this->admin_user->uid; + break; + } + // Request the node with the comment. + $this->drupalGet('node/' . $node->nid); + + // Verify classes if the comment is visible for the current user. + if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') { + // Verify the by-anonymous class. + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-anonymous")]'); + if ($case['comment_uid'] == 0) { + $this->assertTrue(count($comments) == 1, 'by-anonymous class found.'); + } + else { + $this->assertFalse(count($comments), 'by-anonymous class not found.'); + } + + // Verify the by-node-author class. + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-node-author")]'); + if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['node_uid']) { + $this->assertTrue(count($comments) == 1, 'by-node-author class found.'); + } + else { + $this->assertFalse(count($comments), 'by-node-author class not found.'); + } + + // Verify the by-viewer class. + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "by-viewer")]'); + if ($case['comment_uid'] > 0 && $case['comment_uid'] == $case['user_uid']) { + $this->assertTrue(count($comments) == 1, 'by-viewer class found.'); + } + else { + $this->assertFalse(count($comments), 'by-viewer class not found.'); + } + } + + // Verify the unpublished class. + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "unpublished")]'); + if ($case['comment_status'] == COMMENT_NOT_PUBLISHED && $case['user'] == 'admin') { + $this->assertTrue(count($comments) == 1, 'unpublished class found.'); + } + else { + $this->assertFalse(count($comments), 'unpublished class not found.'); + } + + // Verify the new class. + if ($case['comment_status'] == COMMENT_PUBLISHED || $case['user'] == 'admin') { + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "new")]'); + if ($case['user'] != 'anonymous') { + $this->assertTrue(count($comments) == 1, 'new class found.'); + + // Request the node again. The new class should disappear. + $this->drupalGet('node/' . $node->nid); + $comments = $this->xpath('//*[contains(@class, "comment") and contains(@class, "new")]'); + $this->assertFalse(count($comments), 'new class not found.'); + } + else { + $this->assertFalse(count($comments), 'new class not found.'); + } + } + } + } + + /** + * Tests the node comment statistics. + */ + function testCommentNodeCommentStatistics() { + $langcode = LANGUAGE_NOT_SPECIFIED; + // Set comments to have subject and preview disabled. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_DISABLED); + $this->setCommentForm(TRUE); + $this->setCommentSubject(FALSE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + $this->drupalLogout(); + + // Creates a second user to post comments. + $this->web_user2 = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments')); + + // Checks the initial values of node comment statistics with no comment. + $node = node_load($this->node->nid); + $this->assertEqual($node->last_comment_timestamp, $this->node->created, t('The initial value of node last_comment_timestamp is the node created date.')); + $this->assertEqual($node->last_comment_name, NULL, t('The initial value of node last_comment_name is NULL.')); + $this->assertEqual($node->last_comment_uid, $this->web_user->uid, t('The initial value of node last_comment_uid is the node uid.')); + $this->assertEqual($node->comment_count, 0, t('The initial value of node comment_count is zero.')); + + // Post comment #1 as web_user2. + $this->drupalLogin($this->web_user2); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text); + $comment_loaded = comment_load($comment->id); + + // Checks the new values of node comment statistics with comment #1. + // The node needs to be reloaded with a node_load_multiple cache reset. + $node = node_load($this->node->nid, NULL, TRUE); + $this->assertEqual($node->last_comment_name, NULL, t('The value of node last_comment_name is NULL.')); + $this->assertEqual($node->last_comment_uid, $this->web_user2->uid, t('The value of node last_comment_uid is the comment #1 uid.')); + $this->assertEqual($node->comment_count, 1, t('The value of node comment_count is 1.')); + + // Prepare for anonymous comment submission (comment approval enabled). + variable_set('user_register', USER_REGISTER_VISITORS); + $this->drupalLogin($this->admin_user); + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => FALSE, + )); + // Ensure that the poster can leave some contact info. + $this->setCommentAnonymous('1'); + $this->drupalLogout(); + + // Post comment #2 as anonymous (comment approval enabled). + $this->drupalGet('comment/reply/' . $this->node->nid); + $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', TRUE); + $comment_unpublished_loaded = comment_load($anonymous_comment->id); + + // Checks the new values of node comment statistics with comment #2 and + // ensure they haven't changed since the comment has not been moderated. + // The node needs to be reloaded with a node_load_multiple cache reset. + $node = node_load($this->node->nid, NULL, TRUE); + $this->assertEqual($node->last_comment_name, NULL, t('The value of node last_comment_name is still NULL.')); + $this->assertEqual($node->last_comment_uid, $this->web_user2->uid, t('The value of node last_comment_uid is still the comment #1 uid.')); + $this->assertEqual($node->comment_count, 1, t('The value of node comment_count is still 1.')); + + // Prepare for anonymous comment submission (no approval required). + $this->drupalLogin($this->admin_user); + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + )); + $this->drupalLogout(); + + // Post comment #3 as anonymous. + $this->drupalGet('comment/reply/' . $this->node->nid); + $anonymous_comment = $this->postComment($this->node, $this->randomName(), '', array('name' => $this->randomName())); + $comment_loaded = comment_load($anonymous_comment->id); + + // Checks the new values of node comment statistics with comment #3. + // The node needs to be reloaded with a node_load_multiple cache reset. + $node = node_load($this->node->nid, NULL, TRUE); + $this->assertEqual($node->last_comment_name, $comment_loaded->name, t('The value of node last_comment_name is the name of the anonymous user.')); + $this->assertEqual($node->last_comment_uid, 0, t('The value of node last_comment_uid is zero.')); + $this->assertEqual($node->comment_count, 2, t('The value of node comment_count is 2.')); + } + + /** + * Tests comment links. + * + * The output of comment links depends on various environment conditions: + * - Various Comment module configuration settings, user registration + * settings, and user access permissions. + * - Whether the user is authenticated or not, and whether any comments exist. + * + * To account for all possible cases, this test creates permutations of all + * possible conditions and tests the expected appearance of comment links in + * each environment. + */ + function testCommentLinks() { + // Bartik theme alters comment links, so use a different theme. + theme_enable(array('stark')); + variable_set('theme_default', 'stark'); + + // Remove additional user permissions from $this->web_user added by setUp(), + // since this test is limited to anonymous and authenticated roles only. + user_role_delete(key($this->web_user->roles)); + + // Matrix of possible environmental conditions and configuration settings. + // See setEnvironment() for details. + $conditions = array( + 'authenticated' => array(FALSE, TRUE), + 'comment count' => array(FALSE, TRUE), + 'access comments' => array(0, 1), + 'post comments' => array(0, 1), + 'form' => array(COMMENT_FORM_BELOW, COMMENT_FORM_SEPARATE_PAGE), + // USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL is irrelevant for this + // test; there is only a difference between open and closed registration. + 'user_register' => array(USER_REGISTER_VISITORS, USER_REGISTER_ADMINISTRATORS_ONLY), + // @todo Complete test coverage for: + //'comments' => array(COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, COMMENT_NODE_HIDDEN), + //// COMMENT_ANONYMOUS_MUST_CONTACT is irrelevant for this test. + //'contact ' => array(COMMENT_ANONYMOUS_MAY_CONTACT, COMMENT_ANONYMOUS_MAYNOT_CONTACT), + ); + + $environments = $this->generatePermutations($conditions); + foreach ($environments as $info) { + $this->assertCommentLinks($info); + } + } + + /** + * Re-configures the environment, module settings, and user permissions. + * + * @param $info + * An associative array describing the environment to setup: + * - Environment conditions: + * - authenticated: Boolean whether to test with $this->web_user or + * anonymous. + * - comment count: Boolean whether to test with a new/unread comment on + * $this->node or no comments. + * - Configuration settings: + * - form: COMMENT_FORM_BELOW or COMMENT_FORM_SEPARATE_PAGE. + * - user_register: USER_REGISTER_ADMINISTRATORS_ONLY or + * USER_REGISTER_VISITORS. + * - contact: COMMENT_ANONYMOUS_MAY_CONTACT or + * COMMENT_ANONYMOUS_MAYNOT_CONTACT. + * - comments: COMMENT_NODE_OPEN, COMMENT_NODE_CLOSED, or + * COMMENT_NODE_HIDDEN. + * - User permissions: + * These are granted or revoked for the user, according to the + * 'authenticated' flag above. Pass 0 or 1 as parameter values. See + * user_role_change_permissions(). + * - access comments + * - post comments + * - skip comment approval + * - edit own comments + */ + function setEnvironment(array $info) { + static $current; + + // Apply defaults to initial environment. + if (!isset($current)) { + $current = array( + 'authenticated' => FALSE, + 'comment count' => FALSE, + 'form' => COMMENT_FORM_BELOW, + 'user_register' => USER_REGISTER_VISITORS, + 'contact' => COMMENT_ANONYMOUS_MAY_CONTACT, + 'comments' => COMMENT_NODE_OPEN, + 'access comments' => 0, + 'post comments' => 0, + // Enabled by default, because it's irrelevant for this test. + 'skip comment approval' => 1, + 'edit own comments' => 0, + ); + } + // Complete new environment with current environment. + $info = array_merge($current, $info); + + // Change environment conditions. + if ($current['authenticated'] != $info['authenticated']) { + if ($this->loggedInUser) { + $this->drupalLogout(); + } + else { + $this->drupalLogin($this->web_user); + } + } + if ($current['comment count'] != $info['comment count']) { + if ($info['comment count']) { + // Create a comment via CRUD API functionality, since + // $this->postComment() relies on actual user permissions. + $comment = entity_create('comment', array( + 'cid' => NULL, + 'nid' => $this->node->nid, + 'node_type' => $this->node->type, + 'pid' => 0, + 'uid' => 0, + 'status' => COMMENT_PUBLISHED, + 'subject' => $this->randomName(), + 'hostname' => ip_address(), + 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'comment_body' => array(LANGUAGE_NOT_SPECIFIED => array($this->randomName())), + )); + comment_save($comment); + $this->comment = $comment; + + // comment_num_new() relies on node_last_viewed(), so ensure that no one + // has seen the node of this comment. + db_delete('history')->condition('nid', $this->node->nid)->execute(); + } + else { + $cids = db_query("SELECT cid FROM {comment}")->fetchCol(); + comment_delete_multiple($cids); + unset($this->comment); + } + } + + // Change comment settings. + variable_set('comment_form_location_' . $this->node->type, $info['form']); + variable_set('comment_anonymous_' . $this->node->type, $info['contact']); + if ($this->node->comment != $info['comments']) { + $this->node->comment = $info['comments']; + node_save($this->node); + } + + // Change user settings. + variable_set('user_register', $info['user_register']); + + // Change user permissions. + $rid = ($this->loggedInUser ? DRUPAL_AUTHENTICATED_RID : DRUPAL_ANONYMOUS_RID); + $perms = array_intersect_key($info, array('access comments' => 1, 'post comments' => 1, 'skip comment approval' => 1, 'edit own comments' => 1)); + user_role_change_permissions($rid, $perms); + + // Output verbose debugging information. + // @see Drupal\simpletest\TestBase::error() + $t_form = array( + COMMENT_FORM_BELOW => 'below', + COMMENT_FORM_SEPARATE_PAGE => 'separate page', + ); + $t_contact = array( + COMMENT_ANONYMOUS_MAY_CONTACT => 'optional', + COMMENT_ANONYMOUS_MAYNOT_CONTACT => 'disabled', + COMMENT_ANONYMOUS_MUST_CONTACT => 'required', + ); + $t_comments = array( + COMMENT_NODE_OPEN => 'open', + COMMENT_NODE_CLOSED => 'closed', + COMMENT_NODE_HIDDEN => 'hidden', + ); + $verbose = $info; + $verbose['form'] = $t_form[$info['form']]; + $verbose['contact'] = $t_contact[$info['contact']]; + $verbose['comments'] = $t_comments[$info['comments']]; + $message = t('Changed environment:<pre>@verbose</pre>', array( + '@verbose' => var_export($verbose, TRUE), + )); + $this->assert('debug', $message, 'Debug'); + + // Update current environment. + $current = $info; + + return $info; + } + + /** + * Asserts that comment links appear according to the passed environment. + * + * @param $info + * An associative array describing the environment to pass to + * setEnvironment(). + */ + function assertCommentLinks(array $info) { + $info = $this->setEnvironment($info); + + $nid = $this->node->nid; + + foreach (array('', "node/$nid") as $path) { + $this->drupalGet($path); + + // User is allowed to view comments. + if ($info['access comments']) { + if ($path == '') { + // In teaser view, a link containing the comment count is always + // expected. + if ($info['comment count']) { + $this->assertLink(t('1 comment')); + + // For logged in users, a link containing the amount of new/unread + // comments is expected. + // See important note about comment_num_new() below. + if ($this->loggedInUser && isset($this->comment) && !isset($this->comment->seen)) { + $this->assertLink(t('1 new comment')); + $this->comment->seen = TRUE; + } + } + } + } + else { + $this->assertNoLink(t('1 comment')); + $this->assertNoLink(t('1 new comment')); + } + // comment_num_new() is based on node views, so comments are marked as + // read when a node is viewed, regardless of whether we have access to + // comments. + if ($path == "node/$nid" && $this->loggedInUser && isset($this->comment)) { + $this->comment->seen = TRUE; + } + + // User is not allowed to post comments. + if (!$info['post comments']) { + $this->assertNoLink('Add new comment'); + + // Anonymous users should see a note to log in or register in case + // authenticated users are allowed to post comments. + // @see theme_comment_post_forbidden() + if (!$this->loggedInUser) { + if (user_access('post comments', $this->web_user)) { + // The note depends on whether users are actually able to register. + if ($info['user_register']) { + $this->assertText('Log in or register to post comments'); + } + else { + $this->assertText('Log in to post comments'); + } + } + else { + $this->assertNoText('Log in or register to post comments'); + $this->assertNoText('Log in to post comments'); + } + } + } + // User is allowed to post comments. + else { + $this->assertNoText('Log in or register to post comments'); + + // "Add new comment" is always expected, except when there are no + // comments or if the user cannot see them. + if ($path == "node/$nid" && $info['form'] == COMMENT_FORM_BELOW && (!$info['comment count'] || !$info['access comments'])) { + $this->assertNoLink('Add new comment'); + } + else { + $this->assertLink('Add new comment'); + + // Verify that the "Add new comment" link points to the correct URL + // based on the comment form location configuration. + if ($info['form'] == COMMENT_FORM_SEPARATE_PAGE) { + $this->assertLinkByHref("comment/reply/$nid#comment-form", 0, 'Comment form link destination is on a separate page.'); + $this->assertNoLinkByHref("node/$nid#comment-form"); + } + else { + $this->assertLinkByHref("node/$nid#comment-form", 0, 'Comment form link destination is on node.'); + $this->assertNoLinkByHref("comment/reply/$nid#comment-form"); + } + } + + // Also verify that the comment form appears according to the configured + // location. + if ($path == "node/$nid") { + $elements = $this->xpath('//form[@id=:id]', array(':id' => 'comment-form')); + if ($info['form'] == COMMENT_FORM_BELOW) { + $this->assertTrue(count($elements), t('Comment form found below.')); + } + else { + $this->assertFalse(count($elements), t('Comment form not found below.')); + } + } + } + } + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c6d48a55d91470d1332fafea5a8da24f104646a2 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentNodeAccessTest.php @@ -0,0 +1,79 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentNodeAccessTest. + */ + +namespace Drupal\comment\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests comments with node access. + * + * See http://drupal.org/node/886752 -- verify there is no PostgreSQL error when + * viewing a node with threaded comments (a comment and a reply), if a node + * access module is in use. + */ +class CommentNodeAccessTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment node access', + 'description' => 'Test comment viewing with node access.', + 'group' => 'Comment', + ); + } + + function setUp() { + WebTestBase::setUp('comment', 'search', 'node_access_test'); + node_access_rebuild(); + + // Create users and test node. + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks')); + $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments', 'node test view')); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); + } + + /** + * Test that threaded comments can be viewed. + */ + function testThreadedCommentView() { + $langcode = LANGUAGE_NOT_SPECIFIED; + // Set comments to have subject required and preview disabled. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_DISABLED); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + $this->drupalLogout(); + + // Post comment. + $this->drupalLogin($this->web_user); + $comment_text = $this->randomName(); + $comment_subject = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $comment_subject); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), t('Comment found.')); + + // Check comment display. + $this->drupalGet('node/' . $this->node->nid . '/' . $comment->id); + $this->assertText($comment_subject, t('Individual comment subject found.')); + $this->assertText($comment_text, t('Individual comment body found.')); + + // Reply to comment, creating second comment. + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $reply_text = $this->randomName(); + $reply_subject = $this->randomName(); + $reply = $this->postComment(NULL, $reply_text, $reply_subject, TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), t('Reply found.')); + + // Go to the node page and verify comment and reply are visible. + $this->drupalGet('node/' . $this->node->nid); + $this->assertText($comment_text); + $this->assertText($comment_subject); + $this->assertText($reply_text); + $this->assertText($reply_subject); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..df7c8f1d7f639430bef73786a61a7bc054153b42 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPagerTest.php @@ -0,0 +1,273 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentPagerTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Verifies pagination of comments. + */ +class CommentPagerTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment paging settings', + 'description' => 'Test paging of comments and their settings.', + 'group' => 'Comment', + ); + } + + /** + * Confirms comment paging works correctly with flat and threaded comments. + */ + function testCommentPaging() { + $this->drupalLogin($this->admin_user); + + // Set comment variables. + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_DISABLED); + + // Create a node and three comments. + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $comments = array(); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); + + // Set comments to one per page so that we are able to test paging without + // needing to insert large numbers of comments. + $this->setCommentsPerPage(1); + + // Check the first page of the node, and confirm the correct comments are + // shown. + $this->drupalGet('node/' . $node->nid); + $this->assertRaw(t('next'), t('Paging links found.')); + $this->assertTrue($this->commentExists($comments[0]), t('Comment 1 appears on page 1.')); + $this->assertFalse($this->commentExists($comments[1]), t('Comment 2 does not appear on page 1.')); + $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 1.')); + + // Check the second page. + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 1))); + $this->assertTrue($this->commentExists($comments[1]), t('Comment 2 appears on page 2.')); + $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 2.')); + $this->assertFalse($this->commentExists($comments[2]), t('Comment 3 does not appear on page 2.')); + + // Check the third page. + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 2))); + $this->assertTrue($this->commentExists($comments[2]), t('Comment 3 appears on page 3.')); + $this->assertFalse($this->commentExists($comments[0]), t('Comment 1 does not appear on page 3.')); + $this->assertFalse($this->commentExists($comments[1]), t('Comment 2 does not appear on page 3.')); + + // Post a reply to the oldest comment and test again. + $replies = array(); + $oldest_comment = reset($comments); + $this->drupalGet('comment/reply/' . $node->nid . '/' . $oldest_comment->id); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + $this->setCommentsPerPage(2); + // We are still in flat view - the replies should not be on the first page, + // even though they are replies to the oldest comment. + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); + $this->assertFalse($this->commentExists($reply, TRUE), t('In flat mode, reply does not appear on page 1.')); + + // If we switch to threaded mode, the replies on the oldest comment + // should be bumped to the first page and comment 6 should be bumped + // to the second page. + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); + $this->assertTrue($this->commentExists($reply, TRUE), t('In threaded mode, reply appears on page 1.')); + $this->assertFalse($this->commentExists($comments[1]), t('In threaded mode, comment 2 has been bumped off of page 1.')); + + // If (# replies > # comments per page) in threaded expanded view, + // the overage should be bumped. + $reply2 = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $this->drupalGet('node/' . $node->nid, array('query' => array('page' => 0))); + $this->assertFalse($this->commentExists($reply2, TRUE), t('In threaded mode where # replies > # comments per page, the newest reply does not appear on page 1.')); + + $this->drupalLogout(); + } + + /** + * Tests comment ordering and threading. + */ + function testCommentOrderingThreading() { + $this->drupalLogin($this->admin_user); + + // Set comment variables. + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_DISABLED); + + // Display all the comments on the same page. + $this->setCommentsPerPage(1000); + + // Create a node and three comments. + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $comments = array(); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the second comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the first comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[0]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the last comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[2]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the second comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[3]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // At this point, the comment tree is: + // - 0 + // - 4 + // - 1 + // - 3 + // - 6 + // - 2 + // - 5 + + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); + + $expected_order = array( + 0, + 1, + 2, + 3, + 4, + 5, + 6, + ); + $this->drupalGet('node/' . $node->nid); + $this->assertCommentOrder($comments, $expected_order); + + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); + + $expected_order = array( + 0, + 4, + 1, + 3, + 6, + 2, + 5, + ); + $this->drupalGet('node/' . $node->nid); + $this->assertCommentOrder($comments, $expected_order); + } + + /** + * Asserts that the comments are displayed in the correct order. + * + * @param $comments + * And array of comments. + * @param $expected_order + * An array of keys from $comments describing the expected order. + */ + function assertCommentOrder(array $comments, array $expected_order) { + $expected_cids = array(); + + // First, rekey the expected order by cid. + foreach ($expected_order as $key) { + $expected_cids[] = $comments[$key]->id; + } + + $comment_anchors = $this->xpath('//a[starts-with(@id,"comment-")]'); + $result_order = array(); + foreach ($comment_anchors as $anchor) { + $result_order[] = substr($anchor['id'], 8); + } + + return $this->assertIdentical($expected_cids, $result_order, t('Comment order: expected @expected, returned @returned.', array('@expected' => implode(',', $expected_cids), '@returned' => implode(',', $result_order)))); + } + + /** + * Tests comment_new_page_count(). + */ + function testCommentNewPageIndicator() { + $this->drupalLogin($this->admin_user); + + // Set comment variables. + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(DRUPAL_DISABLED); + + // Set comments to one per page so that we are able to test paging without + // needing to insert large numbers of comments. + $this->setCommentsPerPage(1); + + // Create a node and three comments. + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $comments = array(); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + $comments[] = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the second comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[1]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the first comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[0]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the last comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $comments[2]->id); + $comments[] = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + + // At this point, the comment tree is: + // - 0 + // - 4 + // - 1 + // - 3 + // - 2 + // - 5 + + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_FLAT, t('Comment paging changed.')); + + $expected_pages = array( + 1 => 5, // Page of comment 5 + 2 => 4, // Page of comment 4 + 3 => 3, // Page of comment 3 + 4 => 2, // Page of comment 2 + 5 => 1, // Page of comment 1 + 6 => 0, // Page of comment 0 + ); + + $node = node_load($node->nid); + foreach ($expected_pages as $new_replies => $expected_page) { + $returned = comment_new_page_count($node->comment_count, $new_replies, $node); + $returned_page = is_array($returned) ? $returned['page'] : 0; + $this->assertIdentical($expected_page, $returned_page, t('Flat mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); + } + + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Switched to threaded mode.')); + + $expected_pages = array( + 1 => 5, // Page of comment 5 + 2 => 1, // Page of comment 4 + 3 => 1, // Page of comment 4 + 4 => 1, // Page of comment 4 + 5 => 1, // Page of comment 4 + 6 => 0, // Page of comment 0 + ); + + $node = node_load($node->nid); + foreach ($expected_pages as $new_replies => $expected_page) { + $returned = comment_new_page_count($node->comment_count, $new_replies, $node); + $returned_page = is_array($returned) ? $returned['page'] : 0; + $this->assertEqual($expected_page, $returned_page, t('Threaded mode, @new replies: expected page @expected, returned page @returned.', array('@new' => $new_replies, '@expected' => $expected_page, '@returned' => $returned_page))); + } + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e6eef561e6942dcee16685b08b0f0b860ee855ac --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php @@ -0,0 +1,133 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentPreviewTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests previewing comments. + */ +class CommentPreviewTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment preview', + 'description' => 'Test comment preview.', + 'group' => 'Comment', + ); + } + + /** + * Tests comment preview. + */ + function testCommentPreview() { + $langcode = LANGUAGE_NOT_SPECIFIED; + + // As admin user, configure comment settings. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + $this->drupalLogout(); + + // Login as web user and add a signature and a user picture. + $this->drupalLogin($this->web_user); + variable_set('user_signatures', 1); + variable_set('user_pictures', 1); + $test_signature = $this->randomName(); + $edit['signature[value]'] = '<a href="http://example.com/">' . $test_signature. '</a>'; + $edit['signature[format]'] = 'filtered_html'; + $image = current($this->drupalGetTestFiles('image')); + $edit['files[picture_upload]'] = drupal_realpath($image->uri); + $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); + + // As the web user, fill in the comment form and preview the comment. + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $this->drupalPost('node/' . $this->node->nid, $edit, t('Preview')); + + // Check that the preview is displaying the title and body. + $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); + $this->assertText($edit['subject'], t('Subject displayed.')); + $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], t('Comment displayed.')); + + // Check that the title and body fields are displayed with the correct values. + $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); + + // Check that the signature is displaying with the correct text format. + $this->assertLink($test_signature); + + // Check that the user picture is displayed. + $this->assertFieldByXPath("//div[contains(@class, 'preview')]//div[contains(@class, 'user-picture')]//img", NULL, 'User picture displayed.'); + } + + /** + * Tests comment edit, preview, and save. + */ + function testCommentEditPreviewSave() { + $langcode = LANGUAGE_NOT_SPECIFIED; + $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval')); + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $edit['name'] = $web_user->name; + $edit['date'] = '2008-03-02 17:23 +0300'; + $raw_date = strtotime($edit['date']); + $expected_text_date = format_date($raw_date); + $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O'); + $comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE); + $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview')); + + // Check that the preview is displaying the subject, comment, author and date correctly. + $this->assertTitle(t('Preview comment | Drupal'), t('Page title is "Preview comment".')); + $this->assertText($edit['subject'], t('Subject displayed.')); + $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], t('Comment displayed.')); + $this->assertText($edit['name'], t('Author displayed.')); + $this->assertText($expected_text_date, t('Date displayed.')); + + // Check that the subject, comment, author and date fields are displayed with the correct values. + $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); + $this->assertFieldByName('name', $edit['name'], t('Author field displayed.')); + $this->assertFieldByName('date', $edit['date'], t('Date field displayed.')); + + // Check that saving a comment produces a success message. + $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Save')); + $this->assertText(t('Your comment has been posted.'), t('Comment posted.')); + + // Check that the comment fields are correct after loading the saved comment. + $this->drupalGet('comment/' . $comment->id . '/edit'); + $this->assertFieldByName('subject', $edit['subject'], t('Subject field displayed.')); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], t('Comment field displayed.')); + $this->assertFieldByName('name', $edit['name'], t('Author field displayed.')); + $this->assertFieldByName('date', $expected_form_date, t('Date field displayed.')); + + // Submit the form using the displayed values. + $displayed = array(); + $displayed['subject'] = (string) current($this->xpath("//input[@id='edit-subject']/@value")); + $displayed['comment_body[' . $langcode . '][0][value]'] = (string) current($this->xpath("//textarea[@id='edit-comment-body-" . $langcode . "-0-value']")); + $displayed['name'] = (string) current($this->xpath("//input[@id='edit-name']/@value")); + $displayed['date'] = (string) current($this->xpath("//input[@id='edit-date']/@value")); + $this->drupalPost('comment/' . $comment->id . '/edit', $displayed, t('Save')); + + // Check that the saved comment is still correct. + $comment_loaded = comment_load($comment->id); + $this->assertEqual($comment_loaded->subject, $edit['subject'], t('Subject loaded.')); + $this->assertEqual($comment_loaded->comment_body[$langcode][0]['value'], $edit['comment_body[' . $langcode . '][0][value]'], t('Comment body loaded.')); + $this->assertEqual($comment_loaded->name, $edit['name'], t('Name loaded.')); + $this->assertEqual($comment_loaded->created, $raw_date, t('Date loaded.')); + + } + +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3c9559fc2e3d06953701ae22b4af20fe902377a1 --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentRssTest.php @@ -0,0 +1,39 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentRssTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests for Comment module integration with RSS feeds. + */ +class CommentRssTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment RSS', + 'description' => 'Test comments as part of an RSS feed.', + 'group' => 'Comment', + ); + } + + /** + * Tests comments as part of an RSS feed. + */ + function testCommentRss() { + // Find comment in RSS feed. + $this->drupalLogin($this->web_user); + $comment = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->drupalGet('rss.xml'); + $raw = '<comments>' . url('node/' . $this->node->nid, array('fragment' => 'comments', 'absolute' => TRUE)) . '</comments>'; + $this->assertRaw($raw, t('Comments as part of RSS feed.')); + + // Hide comments from RSS feed and check presence. + $this->node->comment = COMMENT_NODE_HIDDEN; + node_save($this->node); + $this->drupalGet('rss.xml'); + $this->assertNoRaw($raw, t('Hidden comments is not a part of RSS feed.')); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1c57146cf80c9d6e1ce723037c35ec2c258e687b --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentThreadingTest.php @@ -0,0 +1,103 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentThreadingTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests comment threading. + */ +class CommentThreadingTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment Threading', + 'description' => 'Test to make sure the comment number increments properly.', + 'group' => 'Comment', + ); + } + + /** + * Tests the comment threading. + */ + function testCommentThreading() { + $langcode = LANGUAGE_NOT_SPECIFIED; + // Set comments to have a subject with preview disabled. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_DISABLED); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, t('Comment paging changed.')); + $this->drupalLogout(); + + // Create a node. + $this->drupalLogin($this->web_user); + $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid)); + + // Post comment #1. + $this->drupalLogin($this->web_user); + $subject_text = $this->randomName(); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), 'Comment #1. Comment found.'); + $this->assertEqual($comment_loaded->thread, '01/'); + + // Reply to comment #1 creating comment #2. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #2. Reply found.'); + $this->assertEqual($reply_loaded->thread, '01.00/'); + + // Reply to comment #2 creating comment #3. + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply->id); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #3. Second reply found.'); + $this->assertEqual($reply_loaded->thread, '01.00.00/'); + + // Reply to comment #1 creating comment #4. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($comment), 'Comment #4. Third reply found.'); + $this->assertEqual($reply_loaded->thread, '01.01/'); + + // Post comment #2 overall comment #5. + $this->drupalLogin($this->web_user); + $subject_text = $this->randomName(); + $comment_text = $this->randomName(); + $comment = $this->postComment($this->node, $comment_text, $subject_text, TRUE); + $comment_loaded = comment_load($comment->id); + $this->assertTrue($this->commentExists($comment), 'Comment #5. Second comment found.'); + $this->assertEqual($comment_loaded->thread, '02/'); + + // Reply to comment #5 creating comment #6. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #6. Reply found.'); + $this->assertEqual($reply_loaded->thread, '02.00/'); + + // Reply to comment #6 creating comment #7. + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $reply->id); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName(), TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($reply, TRUE), 'Comment #7. Second reply found.'); + $this->assertEqual($reply_loaded->thread, '02.00.00/'); + + // Reply to comment #5 creating comment #8. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/' . $this->node->nid . '/' . $comment->id); + $reply = $this->postComment(NULL, $this->randomName(), '', TRUE); + $reply_loaded = comment_load($reply->id); + $this->assertTrue($this->commentExists($comment), 'Comment #8. Third reply found.'); + $this->assertEqual($reply_loaded->thread, '02.01/'); + } +} diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a658cfb0b1e807bda0b9d2cfaf7bb4f51ec6223c --- /dev/null +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTokenReplaceTest.php @@ -0,0 +1,108 @@ +<?php + +/** + * @file + * Definition of Drupal\comment\Tests\CommentTokenReplaceTest. + */ + +namespace Drupal\comment\Tests; + +/** + * Tests comment token replacement in strings. + */ +class CommentTokenReplaceTest extends CommentTestBase { + public static function getInfo() { + return array( + 'name' => 'Comment token replacement', + 'description' => 'Generates text using placeholders for dummy content to check comment token replacement.', + 'group' => 'Comment', + ); + } + + /** + * Creates a comment, then tests the tokens generated from it. + */ + function testCommentTokenReplacement() { + global $language_interface; + $url_options = array( + 'absolute' => TRUE, + 'language' => $language_interface, + ); + + $this->drupalLogin($this->admin_user); + + // Set comment variables. + $this->setCommentSubject(TRUE); + + // Create a node and a comment. + $node = $this->drupalCreateNode(array('type' => 'article')); + $parent_comment = $this->postComment($node, $this->randomName(), $this->randomName(), TRUE); + + // Post a reply to the comment. + $this->drupalGet('comment/reply/' . $node->nid . '/' . $parent_comment->id); + $child_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); + $comment = comment_load($child_comment->id); + $comment->homepage = 'http://example.org/'; + + // Add HTML to ensure that sanitation of some fields tested directly. + $comment->subject = '<blink>Blinking Comment</blink>'; + $instance = field_info_instance('comment', 'body', 'comment_body'); + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[comment:cid]'] = $comment->cid; + $tests['[comment:hostname]'] = check_plain($comment->hostname); + $tests['[comment:name]'] = filter_xss($comment->name); + $tests['[comment:mail]'] = check_plain($this->admin_user->mail); + $tests['[comment:homepage]'] = check_url($comment->homepage); + $tests['[comment:title]'] = filter_xss($comment->subject); + $tests['[comment:body]'] = _text_sanitize($instance, LANGUAGE_NOT_SPECIFIED, $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0], 'value'); + $tests['[comment:url]'] = url('comment/' . $comment->cid, $url_options + array('fragment' => 'comment-' . $comment->cid)); + $tests['[comment:edit-url]'] = url('comment/' . $comment->cid . '/edit', $url_options); + $tests['[comment:created:since]'] = format_interval(REQUEST_TIME - $comment->created, 2, $language_interface->langcode); + $tests['[comment:changed:since]'] = format_interval(REQUEST_TIME - $comment->changed, 2, $language_interface->langcode); + $tests['[comment:parent:cid]'] = $comment->pid; + $tests['[comment:parent:title]'] = check_plain($parent_comment->subject); + $tests['[comment:node:nid]'] = $comment->nid; + $tests['[comment:node:title]'] = check_plain($node->title); + $tests['[comment:author:uid]'] = $comment->uid; + $tests['[comment:author:name]'] = check_plain($this->admin_user->name); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('comment' => $comment), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Sanitized comment token %token replaced.', array('%token' => $input))); + } + + // Generate and test unsanitized tokens. + $tests['[comment:hostname]'] = $comment->hostname; + $tests['[comment:name]'] = $comment->name; + $tests['[comment:mail]'] = $this->admin_user->mail; + $tests['[comment:homepage]'] = $comment->homepage; + $tests['[comment:title]'] = $comment->subject; + $tests['[comment:body]'] = $comment->comment_body[LANGUAGE_NOT_SPECIFIED][0]['value']; + $tests['[comment:parent:title]'] = $parent_comment->subject; + $tests['[comment:node:title]'] = $node->title; + $tests['[comment:author:name]'] = $this->admin_user->name; + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('comment' => $comment), array('language' => $language_interface, 'sanitize' => FALSE)); + $this->assertEqual($output, $expected, t('Unsanitized comment token %token replaced.', array('%token' => $input))); + } + + // Load node so comment_count gets computed. + $node = node_load($node->nid); + + // Generate comment tokens for the node (it has 2 comments, both new). + $tests = array(); + $tests['[node:comment-count]'] = 2; + $tests['[node:comment-count-new]'] = 2; + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Node comment token %token replaced.', array('%token' => $input))); + } + } +} diff --git a/core/modules/contact/contact.info b/core/modules/contact/contact.info index dd2ac0f528e16fc4427e9021bdc61f194a058068..eff6d335e4afa4ee53e8829e2593671ff78b1385 100644 --- a/core/modules/contact/contact.info +++ b/core/modules/contact/contact.info @@ -3,5 +3,4 @@ description = Enables the use of both personal and site-wide contact forms. package = Core version = VERSION core = 8.x -files[] = contact.test configure = admin/structure/contact diff --git a/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8b68d6fa5b8fbc9d7cd495f7d3f6ce6a5cde80aa --- /dev/null +++ b/core/modules/contact/lib/Drupal/contact/Tests/ContactPersonalTest.php @@ -0,0 +1,149 @@ +<?php + +/** + * @file + * Definition of Drupal\contact\Tests\ContactPersonalTest. + */ + +namespace Drupal\contact\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests the personal contact form. + */ +class ContactPersonalTest extends WebTestBase { + private $admin_user; + private $web_user; + private $contact_user; + + public static function getInfo() { + return array( + 'name' => 'Personal contact form', + 'description' => 'Tests personal contact form functionality.', + 'group' => 'Contact', + ); + } + + function setUp() { + parent::setUp('contact'); + + // Create an admin user. + $this->admin_user = $this->drupalCreateUser(array('administer contact forms', 'administer users')); + + // Create some normal users with their contact forms enabled by default. + variable_set('contact_default_status', TRUE); + $this->web_user = $this->drupalCreateUser(array('access user contact forms')); + $this->contact_user = $this->drupalCreateUser(); + } + + /** + * Tests access to the personal contact form. + */ + function testPersonalContactAccess() { + // Test allowed access to user with contact form enabled. + $this->drupalLogin($this->web_user); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + + // Test denied access to the user's own contact form. + $this->drupalGet('user/' . $this->web_user->uid . '/contact'); + $this->assertResponse(403); + + // Test always denied access to the anonymous user contact form. + $this->drupalGet('user/0/contact'); + $this->assertResponse(403); + + // Test that anonymous users can access the contact form. + $this->drupalLogout(); + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + + // Revoke the personal contact permission for the anonymous user. + user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Disable the personal contact form. + $this->drupalLogin($this->admin_user); + $edit = array('contact_default_status' => FALSE); + $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.')); + $this->drupalLogout(); + + // Re-create our contacted user with personal contact forms disabled by + // default. + $this->contact_user = $this->drupalCreateUser(); + + // Test denied access to a user with contact form disabled. + $this->drupalLogin($this->web_user); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + + // Test allowed access for admin user to a user with contact form disabled. + $this->drupalLogin($this->admin_user); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + + // Re-create our contacted user as a blocked user. + $this->contact_user = $this->drupalCreateUser(); + $this->contact_user->status = 0; + $this->contact_user->save(); + + // Test that blocked users can still be contacted by admin. + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(200); + + // Test that blocked users cannot be contacted by non-admins. + $this->drupalLogin($this->web_user); + $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); + $this->assertResponse(403); + } + + /** + * Tests the personal contact form flood protection. + */ + function testPersonalContactFlood() { + $flood_limit = 3; + variable_set('contact_threshold_limit', $flood_limit); + + // Clear flood table in preparation for flood test and allow other checks to complete. + db_delete('flood')->execute(); + $num_records_flood = db_query("SELECT COUNT(*) FROM {flood}")->fetchField(); + $this->assertIdentical($num_records_flood, '0', 'Flood table emptied.'); + + $this->drupalLogin($this->web_user); + + // Submit contact form with correct values and check flood interval. + for ($i = 0; $i < $flood_limit; $i++) { + $this->submitPersonalContact($this->contact_user); + $this->assertText(t('Your message has been sent.'), 'Message sent.'); + } + + // Submit contact form one over limit. + $this->drupalGet('user/' . $this->contact_user->uid. '/contact'); + $this->assertRaw(t('You cannot send more than %number messages in @interval. Try again later.', array('%number' => $flood_limit, '@interval' => format_interval(variable_get('contact_threshold_window', 3600)))), 'Normal user denied access to flooded contact form.'); + + // Test that the admin user can still access the contact form even though + // the flood limit was reached. + $this->drupalLogin($this->admin_user); + $this->assertNoText('Try again later.', 'Admin user not denied access to flooded contact form.'); + } + + /** + * Fills out a user's personal contact form and submits it. + * + * @param $account + * A user object of the user being contacted. + * @param $message + * An optional array with the form fields being used. + */ + protected function submitPersonalContact($account, array $message = array()) { + $message += array( + 'subject' => $this->randomName(16), + 'message' => $this->randomName(64), + ); + $this->drupalPost('user/' . $account->uid . '/contact', $message, t('Send message')); + } +} diff --git a/core/modules/contact/contact.test b/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php similarity index 72% rename from core/modules/contact/contact.test rename to core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php index 3c7f61af257eca64568978999ad3dd30e29c4167..61361339f77e33a7ff6ad6753ca422870b3e9660 100644 --- a/core/modules/contact/contact.test +++ b/core/modules/contact/lib/Drupal/contact/Tests/ContactSitewideTest.php @@ -1,15 +1,18 @@ <?php + /** * @file - * Tests for the Contact module. + * Definition of Drupal\contact\Tests\ContactSitewideTest. */ +namespace Drupal\contact\Tests; + use Drupal\simpletest\WebTestBase; /** * Tests the site-wide contact form. */ -class ContactSitewideTestCase extends WebTestBase { +class ContactSitewideTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'Site-wide contact form', @@ -296,142 +299,3 @@ function getCategories() { return $categories; } } - -/** - * Tests the personal contact form. - */ -class ContactPersonalTestCase extends WebTestBase { - private $admin_user; - private $web_user; - private $contact_user; - - public static function getInfo() { - return array( - 'name' => 'Personal contact form', - 'description' => 'Tests personal contact form functionality.', - 'group' => 'Contact', - ); - } - - function setUp() { - parent::setUp('contact'); - - // Create an admin user. - $this->admin_user = $this->drupalCreateUser(array('administer contact forms', 'administer users')); - - // Create some normal users with their contact forms enabled by default. - variable_set('contact_default_status', TRUE); - $this->web_user = $this->drupalCreateUser(array('access user contact forms')); - $this->contact_user = $this->drupalCreateUser(); - } - - /** - * Tests access to the personal contact form. - */ - function testPersonalContactAccess() { - // Test allowed access to user with contact form enabled. - $this->drupalLogin($this->web_user); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(200); - - // Test denied access to the user's own contact form. - $this->drupalGet('user/' . $this->web_user->uid . '/contact'); - $this->assertResponse(403); - - // Test always denied access to the anonymous user contact form. - $this->drupalGet('user/0/contact'); - $this->assertResponse(403); - - // Test that anonymous users can access the contact form. - $this->drupalLogout(); - user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(200); - - // Revoke the personal contact permission for the anonymous user. - user_role_revoke_permissions(DRUPAL_ANONYMOUS_RID, array('access user contact forms')); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(403); - - // Disable the personal contact form. - $this->drupalLogin($this->admin_user); - $edit = array('contact_default_status' => FALSE); - $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); - $this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.')); - $this->drupalLogout(); - - // Re-create our contacted user with personal contact forms disabled by - // default. - $this->contact_user = $this->drupalCreateUser(); - - // Test denied access to a user with contact form disabled. - $this->drupalLogin($this->web_user); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(403); - - // Test allowed access for admin user to a user with contact form disabled. - $this->drupalLogin($this->admin_user); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(200); - - // Re-create our contacted user as a blocked user. - $this->contact_user = $this->drupalCreateUser(); - $this->contact_user->status = 0; - $this->contact_user->save(); - - // Test that blocked users can still be contacted by admin. - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(200); - - // Test that blocked users cannot be contacted by non-admins. - $this->drupalLogin($this->web_user); - $this->drupalGet('user/' . $this->contact_user->uid . '/contact'); - $this->assertResponse(403); - } - - /** - * Tests the personal contact form flood protection. - */ - function testPersonalContactFlood() { - $flood_limit = 3; - variable_set('contact_threshold_limit', $flood_limit); - - // Clear flood table in preparation for flood test and allow other checks to complete. - db_delete('flood')->execute(); - $num_records_flood = db_query("SELECT COUNT(*) FROM {flood}")->fetchField(); - $this->assertIdentical($num_records_flood, '0', 'Flood table emptied.'); - - $this->drupalLogin($this->web_user); - - // Submit contact form with correct values and check flood interval. - for ($i = 0; $i < $flood_limit; $i++) { - $this->submitPersonalContact($this->contact_user); - $this->assertText(t('Your message has been sent.'), 'Message sent.'); - } - - // Submit contact form one over limit. - $this->drupalGet('user/' . $this->contact_user->uid. '/contact'); - $this->assertRaw(t('You cannot send more than %number messages in @interval. Try again later.', array('%number' => $flood_limit, '@interval' => format_interval(variable_get('contact_threshold_window', 3600)))), 'Normal user denied access to flooded contact form.'); - - // Test that the admin user can still access the contact form even though - // the flood limit was reached. - $this->drupalLogin($this->admin_user); - $this->assertNoText('Try again later.', 'Admin user not denied access to flooded contact form.'); - } - - /** - * Fills out a user's personal contact form and submits it. - * - * @param $account - * A user object of the user being contacted. - * @param $message - * An optional array with the form fields being used. - */ - protected function submitPersonalContact($account, array $message = array()) { - $message += array( - 'subject' => $this->randomName(16), - 'message' => $this->randomName(64), - ); - $this->drupalPost('user/' . $account->uid . '/contact', $message, t('Send message')); - } -} diff --git a/core/modules/contextual/contextual.info b/core/modules/contextual/contextual.info index c9434619be30384e95698cb1419b6d7f64bfc099..8a99dea732c8830e5a76618ca95b16f1bdf57ddf 100644 --- a/core/modules/contextual/contextual.info +++ b/core/modules/contextual/contextual.info @@ -3,4 +3,3 @@ description = Provides contextual links to perform actions related to elements o package = Core version = VERSION core = 8.x -files[] = contextual.test diff --git a/core/modules/contextual/contextual.test b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php similarity index 91% rename from core/modules/contextual/contextual.test rename to core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php index 8749f3a889cdd90b9b7b9dfd813968292165414e..f1f0df618c9728809746b0f0bac734957778eafc 100644 --- a/core/modules/contextual/contextual.test +++ b/core/modules/contextual/lib/Drupal/contextual/Tests/ContextualDynamicContextTest.php @@ -2,15 +2,17 @@ /** * @file - * Tests for contextual.module. + * Definition of Drupal\contextual\Tests\ContextualDynamicContextTest. */ +namespace Drupal\contextual\Tests; + use Drupal\simpletest\WebTestBase; /** * Tests accessible links after inaccessible links on dynamic context. */ -class ContextualDynamicContextTestCase extends WebTestBase { +class ContextualDynamicContextTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'Contextual links on node lists', diff --git a/core/modules/filter/filter.info b/core/modules/filter/filter.info index 022e9ed1702e4fb41fa931c0b74f601c8ac73616..03ed179ef31f73446f857534ac0c7d29cd5c8184 100644 --- a/core/modules/filter/filter.info +++ b/core/modules/filter/filter.info @@ -3,6 +3,5 @@ description = Filters content in preparation for display. package = Core version = VERSION core = 8.x -files[] = filter.test required = TRUE configure = admin/config/content/formats diff --git a/core/modules/filter/filter.test b/core/modules/filter/filter.test deleted file mode 100644 index d021332b169c655414e79d36ebbe574ace83992b..0000000000000000000000000000000000000000 --- a/core/modules/filter/filter.test +++ /dev/null @@ -1,1925 +0,0 @@ -<?php - -/** - * @file - * Tests for filter.module. - */ - -use Drupal\simpletest\WebTestBase; -use Drupal\simpletest\UnitTestBase; - -/** - * Tests for text format and filter CRUD operations. - */ -class FilterCRUDTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Filter CRUD operations', - 'description' => 'Test creation, loading, updating, deleting of text formats and filters.', - 'group' => 'Filter', - ); - } - - function setUp() { - parent::setUp('filter_test'); - } - - /** - * Test CRUD operations for text formats and filters. - */ - function testTextFormatCRUD() { - // Add a text format with minimum data only. - $format = new stdClass(); - $format->format = 'empty_format'; - $format->name = 'Empty format'; - filter_format_save($format); - $this->verifyTextFormat($format); - $this->verifyFilters($format); - - // Add another text format specifying all possible properties. - $format = new stdClass(); - $format->format = 'custom_format'; - $format->name = 'Custom format'; - $format->filters = array( - 'filter_url' => array( - 'status' => 1, - 'settings' => array( - 'filter_url_length' => 30, - ), - ), - ); - filter_format_save($format); - $this->verifyTextFormat($format); - $this->verifyFilters($format); - - // Alter some text format properties and save again. - $format->name = 'Altered format'; - $format->filters['filter_url']['status'] = 0; - $format->filters['filter_autop']['status'] = 1; - filter_format_save($format); - $this->verifyTextFormat($format); - $this->verifyFilters($format); - - // Add a uncacheable filter and save again. - $format->filters['filter_test_uncacheable']['status'] = 1; - filter_format_save($format); - $this->verifyTextFormat($format); - $this->verifyFilters($format); - - // Disable the text format. - filter_format_disable($format); - - $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject(); - $this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.')); - $formats = filter_formats(); - $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.')); - } - - /** - * Verify that a text format is properly stored. - */ - function verifyTextFormat($format) { - $t_args = array('%format' => $format->name); - // Verify text format database record. - $db_format = db_select('filter_format', 'ff') - ->fields('ff') - ->condition('format', $format->format) - ->execute() - ->fetchObject(); - $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args)); - $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args)); - $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args)); - $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args)); - - // Verify filter_format_load(). - $filter_format = filter_format_load($format->format); - $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args)); - $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args)); - $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args)); - $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args)); - - // Verify the 'cache' text format property according to enabled filters. - $filter_info = filter_get_filters(); - $filters = filter_list_format($filter_format->format); - $cacheable = TRUE; - foreach ($filters as $name => $filter) { - // If this filter is not cacheable, update $cacheable accordingly, so we - // can verify $format->cache after iterating over all filters. - if ($filter->status && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) { - $cacheable = FALSE; - break; - } - } - $this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.')); - } - - /** - * Verify that filters are properly stored for a text format. - */ - function verifyFilters($format) { - // Verify filter database records. - $filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name'); - $format_filters = $format->filters; - foreach ($filters as $name => $filter) { - $t_args = array('%format' => $format->name, '%filter' => $name); - - // Verify that filter status is properly stored. - $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args)); - - // Verify that filter settings were properly stored. - $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args)); - - // Verify that each filter has a module name assigned. - $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args)); - - // Remove the filter from the copy of saved $format to check whether all - // filters have been processed later. - unset($format_filters[$name]); - } - // Verify that all filters have been processed. - $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.')); - - // Verify filter_list_format(). - $filters = filter_list_format($format->format); - $format_filters = $format->filters; - foreach ($filters as $name => $filter) { - $t_args = array('%format' => $format->name, '%filter' => $name); - - // Verify that filter status is properly stored. - $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args)); - - // Verify that filter settings were properly stored. - $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args)); - - // Verify that each filter has a module name assigned. - $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args)); - - // Remove the filter from the copy of saved $format to check whether all - // filters have been processed later. - unset($format_filters[$name]); - } - // Verify that all filters have been processed. - $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.')); - } -} - -class FilterAdminTestCase extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Filter administration functionality', - 'description' => 'Thoroughly test the administrative interface of the filter module.', - 'group' => 'Filter', - ); - } - - function setUp() { - parent::setUp(); - - // Create users. - $filtered_html_format = filter_format_load('filtered_html'); - $full_html_format = filter_format_load('full_html'); - $this->admin_user = $this->drupalCreateUser(array( - 'administer filters', - filter_permission_name($filtered_html_format), - filter_permission_name($full_html_format), - )); - - $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); - $this->drupalLogin($this->admin_user); - } - - function testFormatAdmin() { - // Add text format. - $this->drupalGet('admin/config/content/formats'); - $this->clickLink('Add text format'); - $format_id = drupal_strtolower($this->randomName()); - $name = $this->randomName(); - $edit = array( - 'format' => $format_id, - 'name' => $name, - ); - $this->drupalPost(NULL, $edit, t('Save configuration')); - - // Verify default weight of the text format. - $this->drupalGet('admin/config/content/formats'); - $this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.')); - - // Change the weight of the text format. - $edit = array( - "formats[$format_id][weight]" => 5, - ); - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.')); - - // Edit text format. - $this->drupalGet('admin/config/content/formats'); - $this->assertLinkByHref('admin/config/content/formats/' . $format_id); - $this->drupalGet('admin/config/content/formats/' . $format_id); - $this->drupalPost(NULL, array(), t('Save configuration')); - - // Verify that the custom weight of the text format has been retained. - $this->drupalGet('admin/config/content/formats'); - $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.')); - - // Disable text format. - $this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable'); - $this->drupalGet('admin/config/content/formats/' . $format_id . '/disable'); - $this->drupalPost(NULL, array(), t('Disable')); - - // Verify that disabled text format no longer exists. - $this->drupalGet('admin/config/content/formats/' . $format_id); - $this->assertResponse(404, t('Disabled text format no longer exists.')); - - // Attempt to create a format of the same machine name as the disabled - // format but with a different human readable name. - $edit = array( - 'format' => $format_id, - 'name' => 'New format', - ); - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->assertText('The machine-readable name is already in use. It must be unique.'); - - // Attempt to create a format of the same human readable name as the - // disabled format but with a different machine name. - $edit = array( - 'format' => 'new_format', - 'name' => $name, - ); - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->assertRaw(t('Text format names must be unique. A format named %name already exists.', array( - '%name' => $name, - ))); - } - - /** - * Test filter administration functionality. - */ - function testFilterAdmin() { - // URL filter. - $first_filter = 'filter_url'; - // Line filter. - $second_filter = 'filter_autop'; - - $filtered = 'filtered_html'; - $full = 'full_html'; - $plain = 'plain_text'; - - // Check that the fallback format exists and cannot be disabled. - $this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.')); - $this->drupalGet('admin/config/content/formats'); - $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.')); - $this->drupalGet('admin/config/content/formats/' . $plain . '/disable'); - $this->assertResponse(403, t('The fallback format cannot be disabled.')); - - // Verify access permissions to Full HTML format. - $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.')); - $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.')); - - // Add an additional tag. - $edit = array(); - $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>'; - $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); - $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.')); - - $result = db_query('SELECT * FROM {cache_filter}')->fetchObject(); - $this->assertFalse($result, t('Cache cleared.')); - - $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array( - ':first' => 'filters[' . $first_filter . '][weight]', - ':second' => 'filters[' . $second_filter . '][weight]', - )); - $this->assertTrue(!empty($elements), t('Order confirmed in admin interface.')); - - // Reorder filters. - $edit = array(); - $edit['filters[' . $second_filter . '][weight]'] = 1; - $edit['filters[' . $first_filter . '][weight]'] = 2; - $this->drupalPost(NULL, $edit, t('Save configuration')); - $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.')); - $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.')); - - $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array( - ':first' => 'filters[' . $second_filter . '][weight]', - ':second' => 'filters[' . $first_filter . '][weight]', - )); - $this->assertTrue(!empty($elements), t('Reorder confirmed in admin interface.')); - - $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered)); - $filters = array(); - foreach ($result as $filter) { - if ($filter->name == $second_filter || $filter->name == $first_filter) { - $filters[] = $filter; - } - } - $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed in database.')); - - // Add format. - $edit = array(); - $edit['format'] = drupal_strtolower($this->randomName()); - $edit['name'] = $this->randomName(); - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; - $edit['filters[' . $second_filter . '][status]'] = TRUE; - $edit['filters[' . $first_filter . '][status]'] = TRUE; - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.')); - - drupal_static_reset('filter_formats'); - $format = filter_format_load($edit['format']); - $this->assertNotNull($format, t('Format found in database.')); - - $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', t('Role found.')); - $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.')); - $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.')); - - // Disable new filter. - $this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable')); - $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), t('Format successfully disabled.')); - - // Allow authenticated users on full HTML. - $format = filter_format_load($full); - $edit = array(); - $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 0; - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; - $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); - $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.')); - - // Switch user. - $this->drupalLogout(); - $this->drupalLogin($this->web_user); - - $this->drupalGet('node/add/page'); - $this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.')); - - // Use filtered HTML and see if it removes tags that are not allowed. - $body = '<em>' . $this->randomName() . '</em>'; - $extra_text = 'text'; - $text = $body . '<random>' . $extra_text . '</random>'; - - $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["title"] = $this->randomName(); - $edit["body[$langcode][0][value]"] = $text; - $edit["body[$langcode][0][format]"] = $filtered; - $this->drupalPost('node/add/page', $edit, t('Save')); - $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), t('Filtered node created.')); - - $node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue($node, t('Node found in database.')); - - $this->drupalGet('node/' . $node->nid); - $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.')); - - // Use plain text and see if it escapes all tags, whether allowed or not. - $edit = array(); - $edit["body[$langcode][0][format]"] = $plain; - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->drupalGet('node/' . $node->nid); - $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.')); - - // Switch user. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - - // Clean up. - // Allowed tags. - $edit = array(); - $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'; - $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); - $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.')); - - // Full HTML. - $edit = array(); - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = FALSE; - $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); - $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.')); - $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], t('Changes reverted.')); - - // Filter order. - $edit = array(); - $edit['filters[' . $second_filter . '][weight]'] = 2; - $edit['filters[' . $first_filter . '][weight]'] = 1; - $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); - $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.')); - $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.')); - } - - /** - * Tests the URL filter settings form is properly validated. - */ - function testUrlFilterAdmin() { - // The form does not save with an invalid filter URL length. - $edit = array( - 'filters[filter_url][settings][filter_url_length]' => $this->randomName(4), - ); - $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration')); - $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML'))); - } -} - -class FilterFormatAccessTestCase extends WebTestBase { - protected $admin_user; - protected $filter_admin_user; - protected $web_user; - protected $allowed_format; - protected $disallowed_format; - - public static function getInfo() { - return array( - 'name' => 'Filter format access', - 'description' => 'Tests access to text formats.', - 'group' => 'Filter', - ); - } - - function setUp() { - parent::setUp(); - - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - // Create a user who can administer text formats, but does not have - // specific permission to use any of them. - $this->filter_admin_user = $this->drupalCreateUser(array( - 'administer filters', - 'create page content', - 'edit any page content', - )); - - // Create two text formats. - $this->drupalLogin($this->filter_admin_user); - $formats = array(); - for ($i = 0; $i < 2; $i++) { - $edit = array( - 'format' => drupal_strtolower($this->randomName()), - 'name' => $this->randomName(), - ); - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->resetFilterCaches(); - $formats[] = filter_format_load($edit['format']); - } - list($this->allowed_format, $this->disallowed_format) = $formats; - $this->drupalLogout(); - - // Create a regular user with access to one of the formats. - $this->web_user = $this->drupalCreateUser(array( - 'create page content', - 'edit any page content', - filter_permission_name($this->allowed_format), - )); - - // Create an administrative user who has access to use both formats. - $this->admin_user = $this->drupalCreateUser(array( - 'administer filters', - 'create page content', - 'edit any page content', - filter_permission_name($this->allowed_format), - filter_permission_name($this->disallowed_format), - )); - } - - function testFormatPermissions() { - // Make sure that a regular user only has access to the text format they - // were granted access to, as well to the fallback format. - $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.')); - $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.')); - $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.')); - - // Perform similar checks as above, but now against the entire list of - // available formats for this user. - $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.')); - $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.')); - $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.')); - - // Make sure that a regular user only has permission to use the format - // they were granted access to. - $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.')); - $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.')); - - // Make sure that the allowed format appears on the node form and that - // the disallowed format does not. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/add/page'); - $langcode = LANGUAGE_NOT_SPECIFIED; - $elements = $this->xpath('//select[@name=:name]/option', array( - ':name' => "body[$langcode][0][format]", - ':option' => $this->allowed_format->format, - )); - $options = array(); - foreach ($elements as $element) { - $options[(string) $element['value']] = $element; - } - $this->assertTrue(isset($options[$this->allowed_format->format]), t('The allowed text format appears as an option when adding a new node.')); - $this->assertFalse(isset($options[$this->disallowed_format->format]), t('The disallowed text format does not appear as an option when adding a new node.')); - $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.')); - } - - function testFormatRoles() { - // Get the role ID assigned to the regular user; it must be the maximum. - $rid = max(array_keys($this->web_user->roles)); - - // Check that this role appears in the list of roles that have access to an - // allowed text format, but does not appear in the list of roles that have - // access to a disallowed text format. - $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.')); - $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.')); - - // Check that the correct text format appears in the list of formats - // available to that role. - $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.')); - $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.')); - - // Check that the fallback format is always allowed. - $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.')); - $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.')); - } - - /** - * Test editing a page using a disallowed text format. - * - * Verifies that regular users and administrators are able to edit a page, - * but not allowed to change the fields which use an inaccessible text - * format. Also verifies that fields which use a text format that does not - * exist can be edited by administrators only, but that the administrator is - * forced to choose a new format before saving the page. - */ - function testFormatWidgetPermissions() { - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $body_value_key = "body[$langcode][0][value]"; - $body_format_key = "body[$langcode][0][format]"; - - // Create node to edit. - $this->drupalLogin($this->admin_user); - $edit = array(); - $edit['title'] = $this->randomName(8); - $edit[$body_value_key] = $this->randomName(16); - $edit[$body_format_key] = $this->disallowed_format->format; - $this->drupalPost('node/add/page', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($edit['title']); - - // Try to edit with a less privileged user. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/' . $node->nid); - $this->clickLink(t('Edit')); - - // Verify that body field is read-only and contains replacement value. - $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); - - // Verify that title can be changed, but preview displays original body. - $new_edit = array(); - $new_edit['title'] = $this->randomName(8); - $this->drupalPost(NULL, $new_edit, t('Preview')); - $this->assertText($edit[$body_value_key], t('Old body found in preview.')); - - // Save and verify that only the title was changed. - $this->drupalPost(NULL, $new_edit, t('Save')); - $this->assertNoText($edit['title'], t('Old title not found.')); - $this->assertText($new_edit['title'], t('New title found.')); - $this->assertText($edit[$body_value_key], t('Old body found.')); - - // Check that even an administrator with "administer filters" permission - // cannot edit the body field if they do not have specific permission to - // use its stored format. (This must be disallowed so that the - // administrator is never forced to switch the text format to something - // else.) - $this->drupalLogin($this->filter_admin_user); - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); - - // Disable the text format used above. - filter_format_disable($this->disallowed_format); - $this->resetFilterCaches(); - - // Log back in as the less privileged user and verify that the body field - // is still disabled, since the less privileged user should not be able to - // edit content that does not have an assigned format. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); - - // Log back in as the filter administrator and verify that the body field - // can be edited. - $this->drupalLogin($this->filter_admin_user); - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.')); - $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.')); - - // Verify that trying to save the node without selecting a new text format - // produces an error message, and does not result in the node being saved. - $old_title = $new_edit['title']; - $new_title = $this->randomName(8); - $edit = array('title' => $new_title); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.')); - $this->drupalGet('node/' . $node->nid); - $this->assertText($old_title, t('Old title found.')); - $this->assertNoText($new_title, t('New title not found.')); - - // Now select a new text format and make sure the node can be saved. - $edit[$body_format_key] = filter_fallback_format(); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertUrl('node/' . $node->nid); - $this->assertText($new_title, t('New title found.')); - $this->assertNoText($old_title, t('Old title not found.')); - - // Switch the text format to a new one, then disable that format and all - // other formats on the site (leaving only the fallback format). - $this->drupalLogin($this->admin_user); - $edit = array($body_format_key => $this->allowed_format->format); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertUrl('node/' . $node->nid); - foreach (filter_formats() as $format) { - if ($format->format != filter_fallback_format()) { - filter_format_disable($format); - } - } - - // Since there is now only one available text format, the widget for - // selecting a text format would normally not display when the content is - // edited. However, we need to verify that the filter administrator still - // is forced to make a conscious choice to reassign the text to a different - // format. - $this->drupalLogin($this->filter_admin_user); - $old_title = $new_title; - $new_title = $this->randomName(8); - $edit = array('title' => $new_title); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.')); - $this->drupalGet('node/' . $node->nid); - $this->assertText($old_title, t('Old title found.')); - $this->assertNoText($new_title, t('New title not found.')); - $edit[$body_format_key] = filter_fallback_format(); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertUrl('node/' . $node->nid); - $this->assertText($new_title, t('New title found.')); - $this->assertNoText($old_title, t('Old title not found.')); - } - - /** - * Rebuild text format and permission caches in the thread running the tests. - */ - protected function resetFilterCaches() { - filter_formats_reset(); - $this->checkPermissions(array(), TRUE); - } -} - -class FilterDefaultFormatTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Default text format functionality', - 'description' => 'Test the default text formats for different users.', - 'group' => 'Filter', - ); - } - - function testDefaultTextFormats() { - // Create two text formats, and two users. The first user has access to - // both formats, but the second user only has access to the second one. - $admin_user = $this->drupalCreateUser(array('administer filters')); - $this->drupalLogin($admin_user); - $formats = array(); - for ($i = 0; $i < 2; $i++) { - $edit = array( - 'format' => drupal_strtolower($this->randomName()), - 'name' => $this->randomName(), - ); - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->resetFilterCaches(); - $formats[] = filter_format_load($edit['format']); - } - list($first_format, $second_format) = $formats; - $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format))); - $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format))); - - // Adjust the weights so that the first and second formats (in that order) - // are the two lowest weighted formats available to any user. - $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField(); - $edit = array(); - $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2; - $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->resetFilterCaches(); - - // Check that each user's default format is the lowest weighted format that - // the user has access to. - $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to.")); - $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's.")); - - // Reorder the two formats, and check that both users now have the same - // default. - $edit = array(); - $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3; - $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); - $this->resetFilterCaches(); - $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.')); - } - - /** - * Rebuild text format and permission caches in the thread running the tests. - */ - protected function resetFilterCaches() { - filter_formats_reset(); - $this->checkPermissions(array(), TRUE); - } -} - -class FilterNoFormatTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Unassigned text format functionality', - 'description' => 'Test the behavior of check_markup() when it is called without a text format.', - 'group' => 'Filter', - ); - } - - function testCheckMarkupNoFormat() { - // Create some text. Include some HTML and line breaks, so we get a good - // test of the filtering that is applied to it. - $text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>"; - - // Make sure that when this text is run through check_markup() with no text - // format, it is filtered as though it is in the fallback format. - $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.')); - } -} - -/** - * Security tests for missing/vanished text formats or filters. - */ -class FilterSecurityTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Security', - 'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.', - 'group' => 'Filter', - ); - } - - function setUp() { - parent::setUp(array('node', 'php', 'filter_test')); - - // Create Basic page node type. - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - // Create Filtered HTML format. - $filtered_html_format = array( - 'format' => 'filtered_html', - 'name' => 'Filtered HTML', - ); - $filtered_html_format = (object) $filtered_html_format; - filter_format_save($filtered_html_format); - - $filtered_html_permission = filter_permission_name($filtered_html_format); - user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission)); - - $this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration')); - $this->drupalLogin($this->admin_user); - } - - /** - * Test that filtered content is emptied when an actively used filter module is disabled. - */ - function testDisableFilterModule() { - // Create a new node. - $node = $this->drupalCreateNode(array('promote' => 1)); - $body_raw = $node->body[LANGUAGE_NOT_SPECIFIED][0]['value']; - $format_id = $node->body[LANGUAGE_NOT_SPECIFIED][0]['format']; - $this->drupalGet('node/' . $node->nid); - $this->assertText($body_raw, t('Node body found.')); - - // Enable the filter_test_replace filter. - $edit = array( - 'filters[filter_test_replace][status]' => 1, - ); - $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration')); - - // Verify that filter_test_replace filter replaced the content. - $this->drupalGet('node/' . $node->nid); - $this->assertNoText($body_raw, t('Node body not found.')); - $this->assertText('Filter: Testing filter', t('Testing filter output found.')); - - // Disable the text format entirely. - $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable')); - - // Verify that the content is empty, because the text format does not exist. - $this->drupalGet('node/' . $node->nid); - $this->assertNoText($body_raw, t('Node body not found.')); - } -} - -/** - * Unit tests for core filters. - */ -class FilterUnitTestCase extends UnitTestBase { - public static function getInfo() { - return array( - 'name' => 'Filter module filters', - 'description' => 'Tests Filter module filters individually.', - 'group' => 'Filter', - ); - } - - /** - * Test the line break filter. - */ - function testLineBreakFilter() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->callback = '_filter_autop'; - - // Since the line break filter naturally needs plenty of newlines in test - // strings and expectations, we're using "\n" instead of regular newlines - // here. - $tests = array( - // Single line breaks should be changed to <br /> tags, while paragraphs - // separated with double line breaks should be enclosed with <p></p> tags. - "aaa\nbbb\n\nccc" => array( - "<p>aaa<br />\nbbb</p>\n<p>ccc</p>" => TRUE, - ), - // Skip contents of certain block tags entirely. - "<script>aaa\nbbb\n\nccc</script> -<style>aaa\nbbb\n\nccc</style> -<pre>aaa\nbbb\n\nccc</pre> -<object>aaa\nbbb\n\nccc</object> -<iframe>aaa\nbbb\n\nccc</iframe> -" => array( - "<script>aaa\nbbb\n\nccc</script>" => TRUE, - "<style>aaa\nbbb\n\nccc</style>" => TRUE, - "<pre>aaa\nbbb\n\nccc</pre>" => TRUE, - "<object>aaa\nbbb\n\nccc</object>" => TRUE, - "<iframe>aaa\nbbb\n\nccc</iframe>" => TRUE, - ), - // Skip comments entirely. - "One. <!-- comment --> Two.\n<!--\nThree.\n-->\n" => array( - '<!-- comment -->' => TRUE, - "<!--\nThree.\n-->" => TRUE, - ), - // Resulting HTML should produce matching paragraph tags. - '<p><div> </div></p>' => array( - "<p>\n<div> </div>\n</p>" => TRUE, - ), - '<div><p> </p></div>' => array( - "<div>\n</div>" => TRUE, - ), - '<blockquote><pre>aaa</pre></blockquote>' => array( - "<blockquote><pre>aaa</pre></blockquote>" => TRUE, - ), - "<pre>aaa\nbbb\nccc</pre>\nddd\neee" => array( - "<pre>aaa\nbbb\nccc</pre>" => TRUE, - "<p>ddd<br />\neee</p>" => TRUE, - ), - // Comments remain unchanged and subsequent lines/paragraphs are - // transformed normally. - "aaa<!--comment-->\n\nbbb\n\nccc\n\nddd<!--comment\nwith linebreak-->\n\neee\n\nfff" => array( - "<p>aaa</p>\n<!--comment--><p>\nbbb</p>\n<p>ccc</p>\n<p>ddd</p>" => TRUE, - "<!--comment\nwith linebreak--><p>\neee</p>\n<p>fff</p>" => TRUE, - ), - // Check that a comment in a PRE will result that the text after - // the comment, but still in PRE, is not transformed. - "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>\nddd" => array( - "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>" => TRUE, - ), - // Bug 810824, paragraphs were appearing around iframe tags. - "<iframe>aaa</iframe>\n\n" => array( - "<p><iframe>aaa</iframe></p>" => FALSE, - ), - ); - $this->assertFilteredString($filter, $tests); - - // Very long string hitting PCRE limits. - $limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit')); - $source = $this->randomName($limit); - $result = _filter_autop($source); - $success = $this->assertEqual($result, '<p>' . $source . "</p>\n", t('Line break filter can process very long strings.')); - if (!$success) { - $this->verbose("\n" . $source . "\n<hr />\n" . $result); - } - } - - /** - * Tests limiting allowed tags and XSS prevention. - * - * XSS tests assume that script is disallowed by default and src is allowed - * by default, but on* and style attributes are disallowed. - * - * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html. - * - * Relevant CVEs: - * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973, - * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740. - */ - function testFilterXSS() { - // Tag stripping, different ways to work around removal of HTML tags. - $f = filter_xss('<script>alert(0)</script>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.')); - - $f = filter_xss('<script src="http://www.example.com" />'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.')); - - $f = filter_xss('<ScRipt sRc=http://www.example.com/>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- varying case.')); - - $f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>"); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- multiline tag.')); - - $f = filter_xss('<script/a src=http://www.example.com/a.js></script>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.')); - - $f = filter_xss('<script/src=http://www.example.com/a.js></script>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.')); - - // Null between < and tag name works at least with IE6. - $f = filter_xss("<\0scr\0ipt>alert(0)</script>"); - $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.')); - - $f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>"); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".')); - - $f = filter_xss('<<script>alert(0);//<</script>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.')); - - $f = filter_xss('<script src=http://www.example.com/a.js?<b>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing tag.')); - - // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should - // work consistently. - $f = filter_xss('<script>>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double closing tag.')); - - $f = filter_xss('<script src=//www.example.com/.a>'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no scheme or ending slash.')); - - $f = filter_xss('<script src=http://www.example.com/.a'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing bracket.')); - - $f = filter_xss('<script src=http://www.example.com/ <'); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- opening instead of closing bracket.')); - - $f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">'); - $this->assertNoNormalized($f, 'nosuchtag', t('HTML tag stripping evasion -- unknown tag.')); - - $f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">'); - $this->assertTrue(stripos($f, '<?xml') === FALSE, t('HTML tag stripping evasion -- starting with a question sign (processing instructions).')); - - $f = filter_xss('<t:set attributeName="innerHTML" to="<script defer>alert(0)</script>">'); - $this->assertNoNormalized($f, 't:set', t('HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).')); - - $f = filter_xss('<img """><script>alert(0)</script>', array('img')); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.')); - - $f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote')); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.')); - - $f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->"); - $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.')); - - // Dangerous attributes removal. - $f = filter_xss('<p onmouseover="http://www.example.com/">', array('p')); - $this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.')); - - $f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li')); - $this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.')); - - $f = filter_xss('<img onerror =alert(0)>', array('img')); - $this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.')); - - $f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img')); - $this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.')); - - $f = filter_xss('<img oNmediAError=alert(0)>', array('img')); - $this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.')); - - // Works at least with IE6. - $f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img')); - $this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.')); - - // Only whitelisted scheme names allowed in attributes. - $f = filter_xss('<img src="javascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.')); - - $f = filter_xss('<img src=javascript:alert(0)>', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.')); - - // A bit like CVE-2006-0070. - $f = filter_xss('<img src="javascript:confirm(0)">', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)')); - - $f = filter_xss('<img src=`javascript:alert(0)`>', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.')); - - $f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.')); - - $f = filter_xss('<table background="javascript:alert(0)">', array('table')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- another tag.')); - - $f = filter_xss('<base href="javascript:alert(0);//">', array('base')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- one more attribute and tag.')); - - $f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- varying case.')); - - $f = filter_xss('<img src=javascript:alert(0)>', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 decimal encoding.')); - - $f = filter_xss('<img src=javascript:alert(0)>', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- long UTF-8 encoding.')); - - $f = filter_xss('<img src=javascript:alert(0)>', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 hex encoding.')); - - $f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img')); - $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an embedded tab.')); - - $f = filter_xss('<img src="jav	ascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded tab.')); - - $f = filter_xss('<img src="jav
ascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded newline.')); - - // With 
 this test would fail, but the entity gets turned into - // &#xD;, so it's OK. - $f = filter_xss('<img src="jav
ascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded carriage return.')); - - $f = filter_xss("<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", array('img')); - $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- broken into many lines.')); - - $f = filter_xss("<img src=\"jav\0a\0\0cript:alert(0)\">", array('img')); - $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- embedded nulls.')); - - $f = filter_xss('<img src="  javascript:alert(0)">', array('img')); - $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- spaces and metacharacters before scheme.')); - - $f = filter_xss('<img src="vbscript:msgbox(0)">', array('img')); - $this->assertNoNormalized($f, 'vbscript', t('HTML scheme clearing evasion -- another scheme.')); - - $f = filter_xss('<img src="nosuchscheme:notice(0)">', array('img')); - $this->assertNoNormalized($f, 'nosuchscheme', t('HTML scheme clearing evasion -- unknown scheme.')); - - // Netscape 4.x javascript entities. - $f = filter_xss('<br size="&{alert(0)}">', array('br')); - $this->assertNoNormalized($f, 'alert', t('Netscape 4.x javascript entities.')); - - // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with - // Internet Explorer 6. - $f = filter_xss("<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", array('p')); - $this->assertNoNormalized($f, 'style', t('HTML filter -- invalid UTF-8.')); - - $f = filter_xss("\xc0aaa"); - $this->assertEqual($f, '', t('HTML filter -- overlong UTF-8 sequences.')); - - $f = filter_xss("Who's Online"); - $this->assertNormalized($f, "who's online", t('HTML filter -- html entity number')); - - $f = filter_xss("Who&#039;s Online"); - $this->assertNormalized($f, "who's online", t('HTML filter -- encoded html entity number')); - - $f = filter_xss("Who&amp;#039; Online"); - $this->assertNormalized($f, "who&#039; online", t('HTML filter -- double encoded html entity number')); - } - - /** - * Test filter settings, defaults, access restrictions and similar. - * - * @todo This is for functions like filter_filter and check_markup, whose - * functionality is not completely focused on filtering. Some ideas: - * restricting formats according to user permissions, proper cache - * handling, defaults -- allowed tags/attributes/protocols. - * - * @todo It is possible to add script, iframe etc. to allowed tags, but this - * makes HTML filter completely ineffective. - * - * @todo Class, id, name and xmlns should be added to disallowed attributes, - * or better a whitelist approach should be used for that too. - */ - function testHtmlFilter() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->settings = array( - 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>', - 'filter_html_help' => 1, - 'filter_html_nofollow' => 0, - ); - - // HTML filter is not able to secure some tags, these should never be - // allowed. - $f = _filter_html('<script />', $filter); - $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.')); - - $f = _filter_html('<iframe />', $filter); - $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.')); - - $f = _filter_html('<object />', $filter); - $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.')); - - $f = _filter_html('<style />', $filter); - $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.')); - - // Some tags make CSRF attacks easier, let the user take the risk herself. - $f = _filter_html('<img />', $filter); - $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.')); - - $f = _filter_html('<input />', $filter); - $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.')); - - // Filtering content of some attributes is infeasible, these shouldn't be - // allowed too. - $f = _filter_html('<p style="display: none;" />', $filter); - $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.')); - - $f = _filter_html('<p onerror="alert(0);" />', $filter); - $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.')); - - $f = _filter_html('<code onerror> </code>', $filter); - $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove empty on* attributes on default.')); - } - - /** - * Test the spam deterrent. - */ - function testNoFollowFilter() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->settings = array( - 'allowed_html' => '<a>', - 'filter_html_help' => 1, - 'filter_html_nofollow' => 1, - ); - - // Test if the rel="nofollow" attribute is added, even if we try to prevent - // it. - $f = _filter_html('<a href="http://www.example.com/">text</a>', $filter); - $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.')); - - $f = _filter_html('<A href="http://www.example.com/">text</a>', $filter); - $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.')); - - $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter); - $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.')); - - $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter); - $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.')); - - $f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter); - $this->assertNoNormalized($f, 'rel="follow"', t('Spam deterrent evasion -- with rel set - rel="follow" removed.')); - $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- with rel set - rel="nofollow" added.')); - } - - /** - * Test the loose, admin HTML filter. - */ - function testFilterXSSAdmin() { - // DRUPAL-SA-2008-044 - $f = filter_xss_admin('<object />'); - $this->assertNoNormalized($f, 'object', t('Admin HTML filter -- should not allow object tag.')); - - $f = filter_xss_admin('<script />'); - $this->assertNoNormalized($f, 'script', t('Admin HTML filter -- should not allow script tag.')); - - $f = filter_xss_admin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />'); - $this->assertEqual($f, '', t('Admin HTML filter -- should never allow some tags.')); - } - - /** - * Tests the HTML escaping filter. - * - * check_plain() is not tested here. - */ - function testHtmlEscapeFilter() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->callback = '_filter_html_escape'; - - $tests = array( - " One. <!-- \"comment\" --> Two'.\n<p>Three.</p>\n " => array( - "One. <!-- "comment" --> Two'.\n<p>Three.</p>" => TRUE, - ' One.' => FALSE, - "</p>\n " => FALSE, - ), - ); - $this->assertFilteredString($filter, $tests); - } - - /** - * Tests the URL filter. - */ - function testUrlFilter() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->callback = '_filter_url'; - $filter->settings = array( - 'filter_url_length' => 496, - ); - // @todo Possible categories: - // - absolute, mail, partial - // - characters/encoding, surrounding markup, security - - // Create a e-mail that is too long. - $long_email = str_repeat('a', 254) . '@example.com'; - $too_long_email = str_repeat('b', 255) . '@example.com'; - - - // Filter selection/pattern matching. - $tests = array( - // HTTP URLs. - ' -http://example.com or www.example.com -' => array( - '<a href="http://example.com">http://example.com</a>' => TRUE, - '<a href="http://www.example.com">www.example.com</a>' => TRUE, - ), - // MAILTO URLs. - ' -person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . ' -' => array( - '<a href="mailto:person@example.com">person@example.com</a>' => TRUE, - '<a href="mailto:person2@example.com">mailto:person2@example.com</a>' => TRUE, - '<a href="mailto:' . $long_email . '">' . $long_email . '</a>' => TRUE, - '<a href="mailto:' . $too_long_email . '">' . $too_long_email . '</a>' => FALSE, - ), - // URI parts and special characters. - ' -http://trailingslash.com/ or www.trailingslash.com/ -http://host.com/some/path?query=foo&bar[baz]=beer#fragment or www.host.com/some/path?query=foo&bar[baz]=beer#fragment -http://twitter.com/#!/example/status/22376963142324226 -ftp://user:pass@ftp.example.com/~home/dir1 -sftp://user@nonstandardport:222/dir -ssh://192.168.0.100/srv/git/drupal.git -' => array( - '<a href="http://trailingslash.com/">http://trailingslash.com/</a>' => TRUE, - '<a href="http://www.trailingslash.com/">www.trailingslash.com/</a>' => TRUE, - '<a href="http://host.com/some/path?query=foo&bar[baz]=beer#fragment">http://host.com/some/path?query=foo&bar[baz]=beer#fragment</a>' => TRUE, - '<a href="http://www.host.com/some/path?query=foo&bar[baz]=beer#fragment">www.host.com/some/path?query=foo&bar[baz]=beer#fragment</a>' => TRUE, - '<a href="http://twitter.com/#!/example/status/22376963142324226">http://twitter.com/#!/example/status/22376963142324226</a>' => TRUE, - '<a href="ftp://user:pass@ftp.example.com/~home/dir1">ftp://user:pass@ftp.example.com/~home/dir1</a>' => TRUE, - '<a href="sftp://user@nonstandardport:222/dir">sftp://user@nonstandardport:222/dir</a>' => TRUE, - '<a href="ssh://192.168.0.100/srv/git/drupal.git">ssh://192.168.0.100/srv/git/drupal.git</a>' => TRUE, - ), - // Encoding. - ' -http://ampersand.com/?a=1&b=2 -http://encoded.com/?a=1&b=2 -' => array( - '<a href="http://ampersand.com/?a=1&b=2">http://ampersand.com/?a=1&b=2</a>' => TRUE, - '<a href="http://encoded.com/?a=1&b=2">http://encoded.com/?a=1&b=2</a>' => TRUE, - ), - // Domain name length. - ' -www.ex.ex or www.example.example or www.toolongdomainexampledomainexampledomainexampledomainexampledomain or -me@me.tv -' => array( - '<a href="http://www.ex.ex">www.ex.ex</a>' => TRUE, - '<a href="http://www.example.example">www.example.example</a>' => TRUE, - 'http://www.toolong' => FALSE, - '<a href="mailto:me@me.tv">me@me.tv</a>' => TRUE, - ), - // Absolute URL protocols. - // The list to test is found in the beginning of _filter_url() at - // $protocols = variable_get('filter_allowed_protocols'... (approx line 1325). - ' -https://example.com, -ftp://ftp.example.com, -news://example.net, -telnet://example, -irc://example.host, -ssh://odd.geek, -sftp://secure.host?, -webcal://calendar, -rtsp://127.0.0.1, -not foo://disallowed.com. -' => array( - 'href="https://example.com"' => TRUE, - 'href="ftp://ftp.example.com"' => TRUE, - 'href="news://example.net"' => TRUE, - 'href="telnet://example"' => TRUE, - 'href="irc://example.host"' => TRUE, - 'href="ssh://odd.geek"' => TRUE, - 'href="sftp://secure.host"' => TRUE, - 'href="webcal://calendar"' => TRUE, - 'href="rtsp://127.0.0.1"' => TRUE, - 'href="foo://disallowed.com"' => FALSE, - 'not foo://disallowed.com.' => TRUE, - ), - ); - $this->assertFilteredString($filter, $tests); - - // Surrounding text/punctuation. - $tests = array( - ' -Partial URL with trailing period www.partial.com. -E-mail with trailing comma person@example.com, -Absolute URL with trailing question http://www.absolute.com? -Query string with trailing exclamation www.query.com/index.php?a=! -Partial URL with 3 trailing www.partial.periods... -E-mail with 3 trailing exclamations@example.com!!! -Absolute URL and query string with 2 different punctuation characters (http://www.example.com/q=abc). -' => array( - 'period <a href="http://www.partial.com">www.partial.com</a>.' => TRUE, - 'comma <a href="mailto:person@example.com">person@example.com</a>,' => TRUE, - 'question <a href="http://www.absolute.com">http://www.absolute.com</a>?' => TRUE, - 'exclamation <a href="http://www.query.com/index.php?a=">www.query.com/index.php?a=</a>!' => TRUE, - 'trailing <a href="http://www.partial.periods">www.partial.periods</a>...' => TRUE, - 'trailing <a href="mailto:exclamations@example.com">exclamations@example.com</a>!!!' => TRUE, - 'characters (<a href="http://www.example.com/q=abc">http://www.example.com/q=abc</a>).' => TRUE, - ), - ' -(www.parenthesis.com/dir?a=1&b=2#a) -' => array( - '(<a href="http://www.parenthesis.com/dir?a=1&b=2#a">www.parenthesis.com/dir?a=1&b=2#a</a>)' => TRUE, - ), - ); - $this->assertFilteredString($filter, $tests); - - // Surrounding markup. - $tests = array( - ' -<p xmlns="www.namespace.com" /> -<p xmlns="http://namespace.com"> -An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>. -</p> -' => array( - '<p xmlns="www.namespace.com" />' => TRUE, - '<p xmlns="http://namespace.com">' => TRUE, - 'href="http://www.namespace.com"' => FALSE, - 'href="http://namespace.com"' => FALSE, - 'An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.' => TRUE, - ), - ' -Not <a href="foo">www.relative.com</a> or <a href="http://absolute.com">www.absolute.com</a> -but <strong>http://www.strong.net</strong> or <em>www.emphasis.info</em> -' => array( - '<a href="foo">www.relative.com</a>' => TRUE, - 'href="http://www.relative.com"' => FALSE, - '<a href="http://absolute.com">www.absolute.com</a>' => TRUE, - '<strong><a href="http://www.strong.net">http://www.strong.net</a></strong>' => TRUE, - '<em><a href="http://www.emphasis.info">www.emphasis.info</a></em>' => TRUE, - ), - ' -Test <code>using www.example.com the code tag</code>. -' => array( - 'href' => FALSE, - 'http' => FALSE, - ), - ' -Intro. -<blockquote> -Quoted text linking to www.example.com, written by person@example.com, originating from http://origin.example.com. <code>@see www.usage.example.com or <em>www.example.info</em> bla bla</code>. -</blockquote> - -Outro. -' => array( - 'href="http://www.example.com"' => TRUE, - 'href="mailto:person@example.com"' => TRUE, - 'href="http://origin.example.com"' => TRUE, - 'http://www.usage.example.com' => FALSE, - 'http://www.example.info' => FALSE, - 'Intro.' => TRUE, - 'Outro.' => TRUE, - ), - ' -Unknown tag <x>containing x and www.example.com</x>? And a tag <pooh>beginning with p and containing www.example.pooh with p?</pooh> -' => array( - 'href="http://www.example.com"' => TRUE, - 'href="http://www.example.pooh"' => TRUE, - ), - ' -<p>Test <br/>: This is a www.example17.com example <strong>with</strong> various http://www.example18.com tags. *<br/> - It is important www.example19.com to *<br/>test different URLs and http://www.example20.com in the same paragraph. *<br> -HTML www.example21.com soup by person@example22.com can litererally http://www.example23.com contain *img*<img> anything. Just a www.example24.com with http://www.example25.com thrown in. www.example26.com from person@example27.com with extra http://www.example28.com. -' => array( - 'href="http://www.example17.com"' => TRUE, - 'href="http://www.example18.com"' => TRUE, - 'href="http://www.example19.com"' => TRUE, - 'href="http://www.example20.com"' => TRUE, - 'href="http://www.example21.com"' => TRUE, - 'href="mailto:person@example22.com"' => TRUE, - 'href="http://www.example23.com"' => TRUE, - 'href="http://www.example24.com"' => TRUE, - 'href="http://www.example25.com"' => TRUE, - 'href="http://www.example26.com"' => TRUE, - 'href="mailto:person@example27.com"' => TRUE, - 'href="http://www.example28.com"' => TRUE, - ), - ' -<script> -<!-- - // @see www.example.com - var exampleurl = "http://example.net"; ---> -<!--//--><![CDATA[//><!-- - // @see www.example.com - var exampleurl = "http://example.net"; -//--><!]]> -</script> -' => array( - 'href="http://www.example.com"' => FALSE, - 'href="http://example.net"' => FALSE, - ), - ' -<style>body { - background: url(http://example.com/pixel.gif); -}</style> -' => array( - 'href' => FALSE, - ), - ' -<!-- Skip any URLs like www.example.com in comments --> -' => array( - 'href' => FALSE, - ), - ' -<!-- Skip any URLs like -www.example.com with a newline in comments --> -' => array( - 'href' => FALSE, - ), - ' -<!-- Skip any URLs like www.comment.com in comments. <p>Also ignore http://commented.out/markup.</p> --> -' => array( - 'href' => FALSE, - ), - ' -<dl> -<dt>www.example.com</dt> -<dd>http://example.com</dd> -<dd>person@example.com</dd> -<dt>Check www.example.net</dt> -<dd>Some text around http://www.example.info by person@example.info?</dd> -</dl> -' => array( - 'href="http://www.example.com"' => TRUE, - 'href="http://example.com"' => TRUE, - 'href="mailto:person@example.com"' => TRUE, - 'href="http://www.example.net"' => TRUE, - 'href="http://www.example.info"' => TRUE, - 'href="mailto:person@example.info"' => TRUE, - ), - ' -<div>www.div.com</div> -<ul> -<li>http://listitem.com</li> -<li class="odd">www.class.listitem.com</li> -</ul> -' => array( - '<div><a href="http://www.div.com">www.div.com</a></div>' => TRUE, - '<li><a href="http://listitem.com">http://listitem.com</a></li>' => TRUE, - '<li class="odd"><a href="http://www.class.listitem.com">www.class.listitem.com</a></li>' => TRUE, - ), - ); - $this->assertFilteredString($filter, $tests); - - // URL trimming. - $filter->settings['filter_url_length'] = 20; - $tests = array( - 'www.trimmed.com/d/ff.ext?a=1&b=2#a1' => array( - '<a href="http://www.trimmed.com/d/ff.ext?a=1&b=2#a1">www.trimmed.com/d/ff...</a>' => TRUE, - ), - ); - $this->assertFilteredString($filter, $tests); - } - - /** - * Asserts multiple filter output expectations for multiple input strings. - * - * @param $filter - * A input filter object. - * @param $tests - * An associative array, whereas each key is an arbitrary input string and - * each value is again an associative array whose keys are filter output - * strings and whose values are Booleans indicating whether the output is - * expected or not. - * - * For example: - * @code - * $tests = array( - * 'Input string' => array( - * '<p>Input string</p>' => TRUE, - * 'Input string<br' => FALSE, - * ), - * ); - * @endcode - */ - function assertFilteredString($filter, $tests) { - foreach ($tests as $source => $tasks) { - $function = $filter->callback; - $result = $function($source, $filter); - foreach ($tasks as $value => $is_expected) { - // Not using assertIdentical, since combination with strpos() is hard to grok. - if ($is_expected) { - $success = $this->assertTrue(strpos($result, $value) !== FALSE, t('@source: @value found.', array( - '@source' => var_export($source, TRUE), - '@value' => var_export($value, TRUE), - ))); - } - else { - $success = $this->assertTrue(strpos($result, $value) === FALSE, t('@source: @value not found.', array( - '@source' => var_export($source, TRUE), - '@value' => var_export($value, TRUE), - ))); - } - if (!$success) { - $this->verbose('Source:<pre>' . check_plain(var_export($source, TRUE)) . '</pre>' - . '<hr />' . 'Result:<pre>' . check_plain(var_export($result, TRUE)) . '</pre>' - . '<hr />' . ($is_expected ? 'Expected:' : 'Not expected:') - . '<pre>' . check_plain(var_export($value, TRUE)) . '</pre>' - ); - } - } - } - } - - /** - * Tests URL filter on longer content. - * - * Filters based on regular expressions should also be tested with a more - * complex content than just isolated test lines. - * The most common errors are: - * - accidental '*' (greedy) match instead of '*?' (minimal) match. - * - only matching first occurrence instead of all. - * - newlines not matching '.*'. - * - * This test covers: - * - Document with multiple newlines and paragraphs (two newlines). - * - Mix of several HTML tags, invalid non-HTML tags, tags to ignore and HTML - * comments. - * - Empty HTML tags (BR, IMG). - * - Mix of absolute and partial URLs, and e-mail addresses in one content. - */ - function testUrlFilterContent() { - // Setup dummy filter object. - $filter = new stdClass(); - $filter->settings = array( - 'filter_url_length' => 496, - ); - $path = drupal_get_path('module', 'filter') . '/tests'; - - $input = file_get_contents($path . '/filter.url-input.txt'); - $expected = file_get_contents($path . '/filter.url-output.txt'); - $result = _filter_url($input, $filter); - $this->assertIdentical($result, $expected, 'Complex HTML document was correctly processed.'); - } - - /** - * Test the HTML corrector filter. - * - * @todo This test could really use some validity checking function. - */ - function testHtmlCorrectorFilter() { - // Tag closing. - $f = _filter_htmlcorrector('<p>text'); - $this->assertEqual($f, '<p>text</p>', t('HTML corrector -- tag closing at the end of input.')); - - $f = _filter_htmlcorrector('<p>text<p><p>text'); - $this->assertEqual($f, '<p>text</p><p></p><p>text</p>', t('HTML corrector -- tag closing.')); - - $f = _filter_htmlcorrector("<ul><li>e1<li>e2"); - $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", t('HTML corrector -- unclosed list tags.')); - - $f = _filter_htmlcorrector('<div id="d">content'); - $this->assertEqual($f, '<div id="d">content</div>', t('HTML corrector -- unclosed tag with attribute.')); - - // XHTML slash for empty elements. - $f = _filter_htmlcorrector('<hr><br>'); - $this->assertEqual($f, '<hr /><br />', t('HTML corrector -- XHTML closing slash.')); - - $f = _filter_htmlcorrector('<P>test</P>'); - $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.')); - - $f = _filter_htmlcorrector('<P>test</p>'); - $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.')); - - $f = _filter_htmlcorrector('test<hr />'); - $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through.')); - - $f = _filter_htmlcorrector('test<hr/>'); - $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.')); - - $f = _filter_htmlcorrector('test<hr />'); - $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.')); - - $f = _filter_htmlcorrector('<span class="test" />'); - $this->assertEqual($f, '<span class="test"></span>', t('HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.')); - - $f = _filter_htmlcorrector('test1<br class="test">test2'); - $this->assertEqual($f, 'test1<br class="test" />test2', t('HTML corrector -- Automatically close single tags.')); - - $f = _filter_htmlcorrector('line1<hr>line2'); - $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.')); - - $f = _filter_htmlcorrector('line1<HR>line2'); - $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.')); - - $f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>'); - $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', t('HTML corrector -- Automatically close single tags.')); - - $f = _filter_htmlcorrector('<br></br>'); - $this->assertEqual($f, '<br />', t("HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY.")); - - $f = _filter_htmlcorrector('<div></div>'); - $this->assertEqual($f, '<div></div>', t("HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY.")); - - $f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>'); - $this->assertEqual($f, '<p>line1<br /></p><hr />line2', t('HTML corrector -- Move non-inline elements outside of inline containers.')); - - $f = _filter_htmlcorrector('<p>line1<div>line2</div></p>'); - $this->assertEqual($f, '<p>line1</p><div>line2</div>', t('HTML corrector -- Move non-inline elements outside of inline containers.')); - - $f = _filter_htmlcorrector('<p>test<p>test</p>\n'); - $this->assertEqual($f, '<p>test</p><p>test</p>\n', t('HTML corrector -- Auto-close improperly nested tags.')); - - $f = _filter_htmlcorrector('<p>Line1<br><STRONG>bold stuff</b>'); - $this->assertEqual($f, '<p>Line1<br /><strong>bold stuff</strong></p>', t('HTML corrector -- Properly close unclosed tags, and remove useless closing tags.')); - - $f = _filter_htmlcorrector('test <!-- this is a comment -->'); - $this->assertEqual($f, 'test <!-- this is a comment -->', t('HTML corrector -- Do not touch HTML comments.')); - - $f = _filter_htmlcorrector('test <!--this is a comment-->'); - $this->assertEqual($f, 'test <!--this is a comment-->', t('HTML corrector -- Do not touch HTML comments.')); - - $f = _filter_htmlcorrector('test <!-- comment <p>another - <strong>multiple</strong> line - comment</p> -->'); - $this->assertEqual($f, 'test <!-- comment <p>another - <strong>multiple</strong> line - comment</p> -->', t('HTML corrector -- Do not touch HTML comments.')); - - $f = _filter_htmlcorrector('test <!-- comment <p>another comment</p> -->'); - $this->assertEqual($f, 'test <!-- comment <p>another comment</p> -->', t('HTML corrector -- Do not touch HTML comments.')); - - $f = _filter_htmlcorrector('test <!--break-->'); - $this->assertEqual($f, 'test <!--break-->', t('HTML corrector -- Do not touch HTML comments.')); - - $f = _filter_htmlcorrector('<p>test\n</p>\n'); - $this->assertEqual($f, '<p>test\n</p>\n', t('HTML corrector -- New-lines are accepted and kept as-is.')); - - $f = _filter_htmlcorrector('<p>دروبال'); - $this->assertEqual($f, '<p>دروبال</p>', t('HTML corrector -- Encoding is correctly kept.')); - - $f = _filter_htmlcorrector('<script type="text/javascript">alert("test")</script>'); - $this->assertEqual($f, '<script type="text/javascript"> -<!--//--><![CDATA[// ><!-- -alert("test") -//--><!]]> -</script>', t('HTML corrector -- CDATA added to script element')); - - $f = _filter_htmlcorrector('<p><script type="text/javascript">alert("test")</script></p>'); - $this->assertEqual($f, '<p><script type="text/javascript"> -<!--//--><![CDATA[// ><!-- -alert("test") -//--><!]]> -</script></p>', t('HTML corrector -- CDATA added to a nested script element')); - - $f = _filter_htmlcorrector('<p><style> /* Styling */ body {color:red}</style></p>'); - $this->assertEqual($f, '<p><style> -<!--/*--><![CDATA[/* ><!--*/ - /* Styling */ body {color:red} -/*--><!]]>*/ -</style></p>', t('HTML corrector -- CDATA added to a style element.')); - - $filtered_data = _filter_htmlcorrector('<p><style> -/*<![CDATA[*/ -/* Styling */ -body {color:red} -/*]]>*/ -</style></p>'); - $this->assertEqual($filtered_data, '<p><style> -<!--/*--><![CDATA[/* ><!--*/ - -/*<![CDATA[*/ -/* Styling */ -body {color:red} -/*]]]]><![CDATA[>*/ - -/*--><!]]>*/ -</style></p>', - t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '/*<![CDATA[*/')) - ); - - $filtered_data = _filter_htmlcorrector('<p><style> - <!--/*--><![CDATA[/* ><!--*/ - /* Styling */ - body {color:red} - /*--><!]]>*/ -</style></p>'); - $this->assertEqual($filtered_data, '<p><style> -<!--/*--><![CDATA[/* ><!--*/ - - <!--/*--><![CDATA[/* ><!--*/ - /* Styling */ - body {color:red} - /*--><!]]]]><![CDATA[>*/ - -/*--><!]]>*/ -</style></p>', - t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/')) - ); - - $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript"> -<!--//--><![CDATA[// ><!-- - alert("test"); -//--><!]]> -</script></p>'); - $this->assertEqual($filtered_data, '<p><script type="text/javascript"> -<!--//--><![CDATA[// ><!-- - -<!--//--><![CDATA[// ><!-- - alert("test"); -//--><!]]]]><![CDATA[> - -//--><!]]> -</script></p>', - t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--//--><![CDATA[// ><!--')) - ); - - $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript"> -// <![CDATA[ - alert("test"); -// ]]> -</script></p>'); - $this->assertEqual($filtered_data, '<p><script type="text/javascript"> -<!--//--><![CDATA[// ><!-- - -// <![CDATA[ - alert("test"); -// ]]]]><![CDATA[> - -//--><!]]> -</script></p>', - t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '// <![CDATA[')) - ); - - } - - /** - * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string. - * - * Otherwise fails the test with a given message, similar to all the - * SimpleTest assert* functions. - * - * Note that this does not remove nulls, new lines and other characters that - * could be used to obscure a tag or an attribute name. - * - * @param $haystack - * Text to look in. - * @param $needle - * Lowercase, plain text to look for. - * @param $message - * Message to display if failed. - * @param $group - * The group this message belongs to, defaults to 'Other'. - * @return - * TRUE on pass, FALSE on fail. - */ - function assertNormalized($haystack, $needle, $message = '', $group = 'Other') { - return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) !== FALSE, $message, $group); - } - - /** - * Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string. - * - * Otherwise fails the test with a given message, similar to all the - * SimpleTest assert* functions. - * - * Note that this does not remove nulls, new lines, and other character that - * could be used to obscure a tag or an attribute name. - * - * @param $haystack - * Text to look in. - * @param $needle - * Lowercase, plain text to look for. - * @param $message - * Message to display if failed. - * @param $group - * The group this message belongs to, defaults to 'Other'. - * @return - * TRUE on pass, FALSE on fail. - */ - function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') { - return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) === FALSE, $message, $group); - } -} - -/** - * Tests for filter hook invocation. - */ -class FilterHooksTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Filter format hooks', - 'description' => 'Test hooks for text formats insert/update/disable.', - 'group' => 'Filter', - ); - } - - function setUp() { - parent::setUp('block', 'filter_test'); - $admin_user = $this->drupalCreateUser(array('administer filters', 'administer blocks')); - $this->drupalLogin($admin_user); - } - - /** - * Test that hooks run correctly on creating, editing, and deleting a text format. - */ - function testFilterHooks() { - // Add a text format. - $name = $this->randomName(); - $edit = array(); - $edit['format'] = drupal_strtolower($this->randomName()); - $edit['name'] = $name; - $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 1; - $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); - $this->assertRaw(t('Added text format %format.', array('%format' => $name)), t('New format created.')); - $this->assertText('hook_filter_format_insert invoked.', t('hook_filter_format_insert was invoked.')); - - $format_id = $edit['format']; - - // Update text format. - $edit = array(); - $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; - $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration')); - $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), t('Format successfully updated.')); - $this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.')); - - // Add a new custom block. - $custom_block = array(); - $custom_block['info'] = $this->randomName(8); - $custom_block['title'] = $this->randomName(8); - $custom_block['body[value]'] = $this->randomName(32); - // Use the format created. - $custom_block['body[format]'] = $format_id; - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - $this->assertText(t('The block has been created.'), t('New block successfully created.')); - - // Verify the new block is in the database. - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $this->assertNotNull($bid, t('New block found in database')); - - // Disable the text format. - $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable')); - $this->assertRaw(t('Disabled text format %format.', array('%format' => $name)), t('Format successfully disabled.')); - $this->assertText('hook_filter_format_disable invoked.', t('hook_filter_format_disable() was invoked.')); - } -} - -/** - * Tests filter settings. - */ -class FilterSettingsTestCase extends WebTestBase { - protected $profile = 'testing'; - - public static function getInfo() { - return array( - 'name' => 'Filter settings', - 'description' => 'Tests filter settings.', - 'group' => 'Filter', - ); - } - - /** - * Tests explicit and implicit default settings for filters. - */ - function testFilterDefaults() { - $filter_info = filter_filter_info(); - $filters = array_fill_keys(array_keys($filter_info), array()); - - // Create text format using filter default settings. - $filter_defaults_format = (object) array( - 'format' => 'filter_defaults', - 'name' => 'Filter defaults', - 'filters' => $filters, - ); - filter_format_save($filter_defaults_format); - - // Verify that default weights defined in hook_filter_info() were applied. - $saved_settings = array(); - foreach ($filter_defaults_format->filters as $name => $settings) { - $expected_weight = (isset($filter_info[$name]['weight']) ? $filter_info[$name]['weight'] : 0); - $this->assertEqual($settings['weight'], $expected_weight, format_string('@name filter weight %saved equals %default', array( - '@name' => $name, - '%saved' => $settings['weight'], - '%default' => $expected_weight, - ))); - $saved_settings[$name]['weight'] = $expected_weight; - } - - // Re-save the text format. - filter_format_save($filter_defaults_format); - // Reload it from scratch. - filter_formats_reset(); - $filter_defaults_format = filter_format_load($filter_defaults_format->format); - $filter_defaults_format->filters = filter_list_format($filter_defaults_format->format); - - // Verify that saved filter settings have not been changed. - foreach ($filter_defaults_format->filters as $name => $settings) { - $this->assertEqual($settings->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', array( - '@name' => $name, - '%saved' => $settings->weight, - '%previous' => $saved_settings[$name]['weight'], - ))); - } - } -} - diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d6bcb65b28ab1b4138f79581d183b230d9b5414e --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterAdminTest.php @@ -0,0 +1,265 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterAdminTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +class FilterAdminTest extends WebTestBase { + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Filter administration functionality', + 'description' => 'Thoroughly test the administrative interface of the filter module.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp(); + + // Create users. + $filtered_html_format = filter_format_load('filtered_html'); + $full_html_format = filter_format_load('full_html'); + $this->admin_user = $this->drupalCreateUser(array( + 'administer filters', + filter_permission_name($filtered_html_format), + filter_permission_name($full_html_format), + )); + + $this->web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); + $this->drupalLogin($this->admin_user); + } + + function testFormatAdmin() { + // Add text format. + $this->drupalGet('admin/config/content/formats'); + $this->clickLink('Add text format'); + $format_id = drupal_strtolower($this->randomName()); + $name = $this->randomName(); + $edit = array( + 'format' => $format_id, + 'name' => $name, + ); + $this->drupalPost(NULL, $edit, t('Save configuration')); + + // Verify default weight of the text format. + $this->drupalGet('admin/config/content/formats'); + $this->assertFieldByName("formats[$format_id][weight]", 0, t('Text format weight was saved.')); + + // Change the weight of the text format. + $edit = array( + "formats[$format_id][weight]" => 5, + ); + $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); + $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was saved.')); + + // Edit text format. + $this->drupalGet('admin/config/content/formats'); + $this->assertLinkByHref('admin/config/content/formats/' . $format_id); + $this->drupalGet('admin/config/content/formats/' . $format_id); + $this->drupalPost(NULL, array(), t('Save configuration')); + + // Verify that the custom weight of the text format has been retained. + $this->drupalGet('admin/config/content/formats'); + $this->assertFieldByName("formats[$format_id][weight]", 5, t('Text format weight was retained.')); + + // Disable text format. + $this->assertLinkByHref('admin/config/content/formats/' . $format_id . '/disable'); + $this->drupalGet('admin/config/content/formats/' . $format_id . '/disable'); + $this->drupalPost(NULL, array(), t('Disable')); + + // Verify that disabled text format no longer exists. + $this->drupalGet('admin/config/content/formats/' . $format_id); + $this->assertResponse(404, t('Disabled text format no longer exists.')); + + // Attempt to create a format of the same machine name as the disabled + // format but with a different human readable name. + $edit = array( + 'format' => $format_id, + 'name' => 'New format', + ); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->assertText('The machine-readable name is already in use. It must be unique.'); + + // Attempt to create a format of the same human readable name as the + // disabled format but with a different machine name. + $edit = array( + 'format' => 'new_format', + 'name' => $name, + ); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->assertRaw(t('Text format names must be unique. A format named %name already exists.', array( + '%name' => $name, + ))); + } + + /** + * Test filter administration functionality. + */ + function testFilterAdmin() { + // URL filter. + $first_filter = 'filter_url'; + // Line filter. + $second_filter = 'filter_autop'; + + $filtered = 'filtered_html'; + $full = 'full_html'; + $plain = 'plain_text'; + + // Check that the fallback format exists and cannot be disabled. + $this->assertTrue($plain == filter_fallback_format(), t('The fallback format is set to plain text.')); + $this->drupalGet('admin/config/content/formats'); + $this->assertNoRaw('admin/config/content/formats/' . $plain . '/disable', t('Disable link for the fallback format not found.')); + $this->drupalGet('admin/config/content/formats/' . $plain . '/disable'); + $this->assertResponse(403, t('The fallback format cannot be disabled.')); + + // Verify access permissions to Full HTML format. + $this->assertTrue(filter_access(filter_format_load($full), $this->admin_user), t('Admin user may use Full HTML.')); + $this->assertFalse(filter_access(filter_format_load($full), $this->web_user), t('Web user may not use Full HTML.')); + + // Add an additional tag. + $edit = array(); + $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd> <quote>'; + $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); + $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Allowed HTML tag added.')); + + $result = db_query('SELECT * FROM {cache_filter}')->fetchObject(); + $this->assertFalse($result, t('Cache cleared.')); + + $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array( + ':first' => 'filters[' . $first_filter . '][weight]', + ':second' => 'filters[' . $second_filter . '][weight]', + )); + $this->assertTrue(!empty($elements), t('Order confirmed in admin interface.')); + + // Reorder filters. + $edit = array(); + $edit['filters[' . $second_filter . '][weight]'] = 1; + $edit['filters[' . $first_filter . '][weight]'] = 2; + $this->drupalPost(NULL, $edit, t('Save configuration')); + $this->assertFieldByName('filters[' . $second_filter . '][weight]', 1, t('Order saved successfully.')); + $this->assertFieldByName('filters[' . $first_filter . '][weight]', 2, t('Order saved successfully.')); + + $elements = $this->xpath('//select[@name=:first]/following::select[@name=:second]', array( + ':first' => 'filters[' . $second_filter . '][weight]', + ':second' => 'filters[' . $first_filter . '][weight]', + )); + $this->assertTrue(!empty($elements), t('Reorder confirmed in admin interface.')); + + $result = db_query('SELECT * FROM {filter} WHERE format = :format ORDER BY weight ASC', array(':format' => $filtered)); + $filters = array(); + foreach ($result as $filter) { + if ($filter->name == $second_filter || $filter->name == $first_filter) { + $filters[] = $filter; + } + } + $this->assertTrue(($filters[0]->name == $second_filter && $filters[1]->name == $first_filter), t('Order confirmed in database.')); + + // Add format. + $edit = array(); + $edit['format'] = drupal_strtolower($this->randomName()); + $edit['name'] = $this->randomName(); + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; + $edit['filters[' . $second_filter . '][status]'] = TRUE; + $edit['filters[' . $first_filter . '][status]'] = TRUE; + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->assertRaw(t('Added text format %format.', array('%format' => $edit['name'])), t('New filter created.')); + + drupal_static_reset('filter_formats'); + $format = filter_format_load($edit['format']); + $this->assertNotNull($format, t('Format found in database.')); + + $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', '', t('Role found.')); + $this->assertFieldByName('filters[' . $second_filter . '][status]', '', t('Line break filter found.')); + $this->assertFieldByName('filters[' . $first_filter . '][status]', '', t('Url filter found.')); + + // Disable new filter. + $this->drupalPost('admin/config/content/formats/' . $format->format . '/disable', array(), t('Disable')); + $this->assertRaw(t('Disabled text format %format.', array('%format' => $edit['name'])), t('Format successfully disabled.')); + + // Allow authenticated users on full HTML. + $format = filter_format_load($full); + $edit = array(); + $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 0; + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; + $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully updated.')); + + // Switch user. + $this->drupalLogout(); + $this->drupalLogin($this->web_user); + + $this->drupalGet('node/add/page'); + $this->assertRaw('<option value="' . $full . '">Full HTML</option>', t('Full HTML filter accessible.')); + + // Use filtered HTML and see if it removes tags that are not allowed. + $body = '<em>' . $this->randomName() . '</em>'; + $extra_text = 'text'; + $text = $body . '<random>' . $extra_text . '</random>'; + + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit["title"] = $this->randomName(); + $edit["body[$langcode][0][value]"] = $text; + $edit["body[$langcode][0][format]"] = $filtered; + $this->drupalPost('node/add/page', $edit, t('Save')); + $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit["title"])), t('Filtered node created.')); + + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->assertTrue($node, t('Node found in database.')); + + $this->drupalGet('node/' . $node->nid); + $this->assertRaw($body . $extra_text, t('Filter removed invalid tag.')); + + // Use plain text and see if it escapes all tags, whether allowed or not. + $edit = array(); + $edit["body[$langcode][0][format]"] = $plain; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->drupalGet('node/' . $node->nid); + $this->assertText(check_plain($text), t('The "Plain text" text format escapes all HTML tags.')); + + // Switch user. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + + // Clean up. + // Allowed tags. + $edit = array(); + $edit['filters[filter_html][settings][allowed_html]'] = '<a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>'; + $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); + $this->assertFieldByName('filters[filter_html][settings][allowed_html]', $edit['filters[filter_html][settings][allowed_html]'], t('Changes reverted.')); + + // Full HTML. + $edit = array(); + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = FALSE; + $this->drupalPost('admin/config/content/formats/' . $full, $edit, t('Save configuration')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $format->name)), t('Full HTML format successfully reverted.')); + $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'], t('Changes reverted.')); + + // Filter order. + $edit = array(); + $edit['filters[' . $second_filter . '][weight]'] = 2; + $edit['filters[' . $first_filter . '][weight]'] = 1; + $this->drupalPost('admin/config/content/formats/' . $filtered, $edit, t('Save configuration')); + $this->assertFieldByName('filters[' . $second_filter . '][weight]', $edit['filters[' . $second_filter . '][weight]'], t('Changes reverted.')); + $this->assertFieldByName('filters[' . $first_filter . '][weight]', $edit['filters[' . $first_filter . '][weight]'], t('Changes reverted.')); + } + + /** + * Tests the URL filter settings form is properly validated. + */ + function testUrlFilterAdmin() { + // The form does not save with an invalid filter URL length. + $edit = array( + 'filters[filter_url][settings][filter_url_length]' => $this->randomName(4), + ); + $this->drupalPost('admin/config/content/formats/filtered_html', $edit, t('Save configuration')); + $this->assertNoRaw(t('The text format %format has been updated.', array('%format' => 'Filtered HTML'))); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php new file mode 100644 index 0000000000000000000000000000000000000000..32c706b498dad7b8417fd2186d144beea1ff4636 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterCrudTest.php @@ -0,0 +1,166 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterCrudTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; +use stdClass; + +/** + * Tests for text format and filter CRUD operations. + */ +class FilterCrudTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Filter CRUD operations', + 'description' => 'Test creation, loading, updating, deleting of text formats and filters.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp('filter_test'); + } + + /** + * Test CRUD operations for text formats and filters. + */ + function testTextFormatCrud() { + // Add a text format with minimum data only. + $format = new stdClass(); + $format->format = 'empty_format'; + $format->name = 'Empty format'; + filter_format_save($format); + $this->verifyTextFormat($format); + $this->verifyFilters($format); + + // Add another text format specifying all possible properties. + $format = new stdClass(); + $format->format = 'custom_format'; + $format->name = 'Custom format'; + $format->filters = array( + 'filter_url' => array( + 'status' => 1, + 'settings' => array( + 'filter_url_length' => 30, + ), + ), + ); + filter_format_save($format); + $this->verifyTextFormat($format); + $this->verifyFilters($format); + + // Alter some text format properties and save again. + $format->name = 'Altered format'; + $format->filters['filter_url']['status'] = 0; + $format->filters['filter_autop']['status'] = 1; + filter_format_save($format); + $this->verifyTextFormat($format); + $this->verifyFilters($format); + + // Add a uncacheable filter and save again. + $format->filters['filter_test_uncacheable']['status'] = 1; + filter_format_save($format); + $this->verifyTextFormat($format); + $this->verifyFilters($format); + + // Disable the text format. + filter_format_disable($format); + + $db_format = db_query("SELECT * FROM {filter_format} WHERE format = :format", array(':format' => $format->format))->fetchObject(); + $this->assertFalse($db_format->status, t('Database: Disabled text format is marked as disabled.')); + $formats = filter_formats(); + $this->assertTrue(!isset($formats[$format->format]), t('filter_formats: Disabled text format no longer exists.')); + } + + /** + * Verify that a text format is properly stored. + */ + function verifyTextFormat($format) { + $t_args = array('%format' => $format->name); + // Verify text format database record. + $db_format = db_select('filter_format', 'ff') + ->fields('ff') + ->condition('format', $format->format) + ->execute() + ->fetchObject(); + $this->assertEqual($db_format->format, $format->format, t('Database: Proper format id for text format %format.', $t_args)); + $this->assertEqual($db_format->name, $format->name, t('Database: Proper title for text format %format.', $t_args)); + $this->assertEqual($db_format->cache, $format->cache, t('Database: Proper cache indicator for text format %format.', $t_args)); + $this->assertEqual($db_format->weight, $format->weight, t('Database: Proper weight for text format %format.', $t_args)); + + // Verify filter_format_load(). + $filter_format = filter_format_load($format->format); + $this->assertEqual($filter_format->format, $format->format, t('filter_format_load: Proper format id for text format %format.', $t_args)); + $this->assertEqual($filter_format->name, $format->name, t('filter_format_load: Proper title for text format %format.', $t_args)); + $this->assertEqual($filter_format->cache, $format->cache, t('filter_format_load: Proper cache indicator for text format %format.', $t_args)); + $this->assertEqual($filter_format->weight, $format->weight, t('filter_format_load: Proper weight for text format %format.', $t_args)); + + // Verify the 'cache' text format property according to enabled filters. + $filter_info = filter_get_filters(); + $filters = filter_list_format($filter_format->format); + $cacheable = TRUE; + foreach ($filters as $name => $filter) { + // If this filter is not cacheable, update $cacheable accordingly, so we + // can verify $format->cache after iterating over all filters. + if ($filter->status && isset($filter_info[$name]['cache']) && !$filter_info[$name]['cache']) { + $cacheable = FALSE; + break; + } + } + $this->assertEqual($filter_format->cache, $cacheable, t('Text format contains proper cache property.')); + } + + /** + * Verify that filters are properly stored for a text format. + */ + function verifyFilters($format) { + // Verify filter database records. + $filters = db_query("SELECT * FROM {filter} WHERE format = :format", array(':format' => $format->format))->fetchAllAssoc('name'); + $format_filters = $format->filters; + foreach ($filters as $name => $filter) { + $t_args = array('%format' => $format->name, '%filter' => $name); + + // Verify that filter status is properly stored. + $this->assertEqual($filter->status, $format_filters[$name]['status'], t('Database: Proper status for %filter in text format %format.', $t_args)); + + // Verify that filter settings were properly stored. + $this->assertEqual(unserialize($filter->settings), isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('Database: Proper filter settings for %filter in text format %format.', $t_args)); + + // Verify that each filter has a module name assigned. + $this->assertTrue(!empty($filter->module), t('Database: Proper module name for %filter in text format %format.', $t_args)); + + // Remove the filter from the copy of saved $format to check whether all + // filters have been processed later. + unset($format_filters[$name]); + } + // Verify that all filters have been processed. + $this->assertTrue(empty($format_filters), t('Database contains values for all filters in the saved format.')); + + // Verify filter_list_format(). + $filters = filter_list_format($format->format); + $format_filters = $format->filters; + foreach ($filters as $name => $filter) { + $t_args = array('%format' => $format->name, '%filter' => $name); + + // Verify that filter status is properly stored. + $this->assertEqual($filter->status, $format_filters[$name]['status'], t('filter_list_format: Proper status for %filter in text format %format.', $t_args)); + + // Verify that filter settings were properly stored. + $this->assertEqual($filter->settings, isset($format_filters[$name]['settings']) ? $format_filters[$name]['settings'] : array(), t('filter_list_format: Proper filter settings for %filter in text format %format.', $t_args)); + + // Verify that each filter has a module name assigned. + $this->assertTrue(!empty($filter->module), t('filter_list_format: Proper module name for %filter in text format %format.', $t_args)); + + // Remove the filter from the copy of saved $format to check whether all + // filters have been processed later. + unset($format_filters[$name]); + } + // Verify that all filters have been processed. + $this->assertTrue(empty($format_filters), t('filter_list_format: Loaded filters contain values for all filters in the saved format.')); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultFormatTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultFormatTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d4733bcfb26d5c09701faa71abe1964d0664d423 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterDefaultFormatTest.php @@ -0,0 +1,70 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterDefaultFormatTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +class FilterDefaultFormatTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Default text format functionality', + 'description' => 'Test the default text formats for different users.', + 'group' => 'Filter', + ); + } + + function testDefaultTextFormats() { + // Create two text formats, and two users. The first user has access to + // both formats, but the second user only has access to the second one. + $admin_user = $this->drupalCreateUser(array('administer filters')); + $this->drupalLogin($admin_user); + $formats = array(); + for ($i = 0; $i < 2; $i++) { + $edit = array( + 'format' => drupal_strtolower($this->randomName()), + 'name' => $this->randomName(), + ); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->resetFilterCaches(); + $formats[] = filter_format_load($edit['format']); + } + list($first_format, $second_format) = $formats; + $first_user = $this->drupalCreateUser(array(filter_permission_name($first_format), filter_permission_name($second_format))); + $second_user = $this->drupalCreateUser(array(filter_permission_name($second_format))); + + // Adjust the weights so that the first and second formats (in that order) + // are the two lowest weighted formats available to any user. + $minimum_weight = db_query("SELECT MIN(weight) FROM {filter_format}")->fetchField(); + $edit = array(); + $edit['formats[' . $first_format->format . '][weight]'] = $minimum_weight - 2; + $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 1; + $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); + $this->resetFilterCaches(); + + // Check that each user's default format is the lowest weighted format that + // the user has access to. + $this->assertEqual(filter_default_format($first_user), $first_format->format, t("The first user's default format is the lowest weighted format that the user has access to.")); + $this->assertEqual(filter_default_format($second_user), $second_format->format, t("The second user's default format is the lowest weighted format that the user has access to, and is different than the first user's.")); + + // Reorder the two formats, and check that both users now have the same + // default. + $edit = array(); + $edit['formats[' . $second_format->format . '][weight]'] = $minimum_weight - 3; + $this->drupalPost('admin/config/content/formats', $edit, t('Save changes')); + $this->resetFilterCaches(); + $this->assertEqual(filter_default_format($first_user), filter_default_format($second_user), t('After the formats are reordered, both users have the same default format.')); + } + + /** + * Rebuild text format and permission caches in the thread running the tests. + */ + protected function resetFilterCaches() { + filter_formats_reset(); + $this->checkPermissions(array(), TRUE); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3d59cccecd2861786c0d4d4107533f4afcc863b2 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterFormatAccessTest.php @@ -0,0 +1,257 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterFormatAccessTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +class FilterFormatAccessTest extends WebTestBase { + protected $admin_user; + protected $filter_admin_user; + protected $web_user; + protected $allowed_format; + protected $disallowed_format; + + public static function getInfo() { + return array( + 'name' => 'Filter format access', + 'description' => 'Tests access to text formats.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp(); + + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Create a user who can administer text formats, but does not have + // specific permission to use any of them. + $this->filter_admin_user = $this->drupalCreateUser(array( + 'administer filters', + 'create page content', + 'edit any page content', + )); + + // Create two text formats. + $this->drupalLogin($this->filter_admin_user); + $formats = array(); + for ($i = 0; $i < 2; $i++) { + $edit = array( + 'format' => drupal_strtolower($this->randomName()), + 'name' => $this->randomName(), + ); + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->resetFilterCaches(); + $formats[] = filter_format_load($edit['format']); + } + list($this->allowed_format, $this->disallowed_format) = $formats; + $this->drupalLogout(); + + // Create a regular user with access to one of the formats. + $this->web_user = $this->drupalCreateUser(array( + 'create page content', + 'edit any page content', + filter_permission_name($this->allowed_format), + )); + + // Create an administrative user who has access to use both formats. + $this->admin_user = $this->drupalCreateUser(array( + 'administer filters', + 'create page content', + 'edit any page content', + filter_permission_name($this->allowed_format), + filter_permission_name($this->disallowed_format), + )); + } + + function testFormatPermissions() { + // Make sure that a regular user only has access to the text format they + // were granted access to, as well to the fallback format. + $this->assertTrue(filter_access($this->allowed_format, $this->web_user), t('A regular user has access to a text format they were granted access to.')); + $this->assertFalse(filter_access($this->disallowed_format, $this->web_user), t('A regular user does not have access to a text format they were not granted access to.')); + $this->assertTrue(filter_access(filter_format_load(filter_fallback_format()), $this->web_user), t('A regular user has access to the fallback format.')); + + // Perform similar checks as above, but now against the entire list of + // available formats for this user. + $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_formats($this->web_user))), t('The allowed format appears in the list of available formats for a regular user.')); + $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_formats($this->web_user))), t('The disallowed format does not appear in the list of available formats for a regular user.')); + $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_formats($this->web_user))), t('The fallback format appears in the list of available formats for a regular user.')); + + // Make sure that a regular user only has permission to use the format + // they were granted access to. + $this->assertTrue(user_access(filter_permission_name($this->allowed_format), $this->web_user), t('A regular user has permission to use the allowed text format.')); + $this->assertFalse(user_access(filter_permission_name($this->disallowed_format), $this->web_user), t('A regular user does not have permission to use the disallowed text format.')); + + // Make sure that the allowed format appears on the node form and that + // the disallowed format does not. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/add/page'); + $langcode = LANGUAGE_NOT_SPECIFIED; + $elements = $this->xpath('//select[@name=:name]/option', array( + ':name' => "body[$langcode][0][format]", + ':option' => $this->allowed_format->format, + )); + $options = array(); + foreach ($elements as $element) { + $options[(string) $element['value']] = $element; + } + $this->assertTrue(isset($options[$this->allowed_format->format]), t('The allowed text format appears as an option when adding a new node.')); + $this->assertFalse(isset($options[$this->disallowed_format->format]), t('The disallowed text format does not appear as an option when adding a new node.')); + $this->assertTrue(isset($options[filter_fallback_format()]), t('The fallback format appears as an option when adding a new node.')); + } + + function testFormatRoles() { + // Get the role ID assigned to the regular user; it must be the maximum. + $rid = max(array_keys($this->web_user->roles)); + + // Check that this role appears in the list of roles that have access to an + // allowed text format, but does not appear in the list of roles that have + // access to a disallowed text format. + $this->assertTrue(in_array($rid, array_keys(filter_get_roles_by_format($this->allowed_format))), t('A role which has access to a text format appears in the list of roles that have access to that format.')); + $this->assertFalse(in_array($rid, array_keys(filter_get_roles_by_format($this->disallowed_format))), t('A role which does not have access to a text format does not appear in the list of roles that have access to that format.')); + + // Check that the correct text format appears in the list of formats + // available to that role. + $this->assertTrue(in_array($this->allowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role has access to appears in the list of formats available to that role.')); + $this->assertFalse(in_array($this->disallowed_format->format, array_keys(filter_get_formats_by_role($rid))), t('A text format which a role does not have access to does not appear in the list of formats available to that role.')); + + // Check that the fallback format is always allowed. + $this->assertEqual(filter_get_roles_by_format(filter_format_load(filter_fallback_format())), user_roles(), t('All roles have access to the fallback format.')); + $this->assertTrue(in_array(filter_fallback_format(), array_keys(filter_get_formats_by_role($rid))), t('The fallback format appears in the list of allowed formats for any role.')); + } + + /** + * Test editing a page using a disallowed text format. + * + * Verifies that regular users and administrators are able to edit a page, + * but not allowed to change the fields which use an inaccessible text + * format. Also verifies that fields which use a text format that does not + * exist can be edited by administrators only, but that the administrator is + * forced to choose a new format before saving the page. + */ + function testFormatWidgetPermissions() { + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $body_value_key = "body[$langcode][0][value]"; + $body_format_key = "body[$langcode][0][format]"; + + // Create node to edit. + $this->drupalLogin($this->admin_user); + $edit = array(); + $edit['title'] = $this->randomName(8); + $edit[$body_value_key] = $this->randomName(16); + $edit[$body_format_key] = $this->disallowed_format->format; + $this->drupalPost('node/add/page', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($edit['title']); + + // Try to edit with a less privileged user. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/' . $node->nid); + $this->clickLink(t('Edit')); + + // Verify that body field is read-only and contains replacement value. + $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); + + // Verify that title can be changed, but preview displays original body. + $new_edit = array(); + $new_edit['title'] = $this->randomName(8); + $this->drupalPost(NULL, $new_edit, t('Preview')); + $this->assertText($edit[$body_value_key], t('Old body found in preview.')); + + // Save and verify that only the title was changed. + $this->drupalPost(NULL, $new_edit, t('Save')); + $this->assertNoText($edit['title'], t('Old title not found.')); + $this->assertText($new_edit['title'], t('New title found.')); + $this->assertText($edit[$body_value_key], t('Old body found.')); + + // Check that even an administrator with "administer filters" permission + // cannot edit the body field if they do not have specific permission to + // use its stored format. (This must be disallowed so that the + // administrator is never forced to switch the text format to something + // else.) + $this->drupalLogin($this->filter_admin_user); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); + + // Disable the text format used above. + filter_format_disable($this->disallowed_format); + $this->resetFilterCaches(); + + // Log back in as the less privileged user and verify that the body field + // is still disabled, since the less privileged user should not be able to + // edit content that does not have an assigned format. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), t('Text format access denied message found.')); + + // Log back in as the filter administrator and verify that the body field + // can be edited. + $this->drupalLogin($this->filter_admin_user); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertNoFieldByXPath("//textarea[@name='$body_value_key' and @disabled='disabled']", NULL, t('Text format access denied message not found.')); + $this->assertFieldByXPath("//select[@name='$body_format_key']", NULL, t('Text format selector found.')); + + // Verify that trying to save the node without selecting a new text format + // produces an error message, and does not result in the node being saved. + $old_title = $new_edit['title']; + $new_title = $this->randomName(8); + $edit = array('title' => $new_title); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.')); + $this->drupalGet('node/' . $node->nid); + $this->assertText($old_title, t('Old title found.')); + $this->assertNoText($new_title, t('New title not found.')); + + // Now select a new text format and make sure the node can be saved. + $edit[$body_format_key] = filter_fallback_format(); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertUrl('node/' . $node->nid); + $this->assertText($new_title, t('New title found.')); + $this->assertNoText($old_title, t('Old title not found.')); + + // Switch the text format to a new one, then disable that format and all + // other formats on the site (leaving only the fallback format). + $this->drupalLogin($this->admin_user); + $edit = array($body_format_key => $this->allowed_format->format); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertUrl('node/' . $node->nid); + foreach (filter_formats() as $format) { + if ($format->format != filter_fallback_format()) { + filter_format_disable($format); + } + } + + // Since there is now only one available text format, the widget for + // selecting a text format would normally not display when the content is + // edited. However, we need to verify that the filter administrator still + // is forced to make a conscious choice to reassign the text to a different + // format. + $this->drupalLogin($this->filter_admin_user); + $old_title = $new_title; + $new_title = $this->randomName(8); + $edit = array('title' => $new_title); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertText(t('!name field is required.', array('!name' => t('Text format'))), t('Error message is displayed.')); + $this->drupalGet('node/' . $node->nid); + $this->assertText($old_title, t('Old title found.')); + $this->assertNoText($new_title, t('New title not found.')); + $edit[$body_format_key] = filter_fallback_format(); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertUrl('node/' . $node->nid); + $this->assertText($new_title, t('New title found.')); + $this->assertNoText($old_title, t('Old title not found.')); + } + + /** + * Rebuild text format and permission caches in the thread running the tests. + */ + protected function resetFilterCaches() { + filter_formats_reset(); + $this->checkPermissions(array(), TRUE); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterHooksTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterHooksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2679132d22288caf9867302ba0df3814d4f4eedb --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterHooksTest.php @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterHooksTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests for filter hook invocation. + */ +class FilterHooksTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Filter format hooks', + 'description' => 'Test hooks for text formats insert/update/disable.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp('block', 'filter_test'); + $admin_user = $this->drupalCreateUser(array('administer filters', 'administer blocks')); + $this->drupalLogin($admin_user); + } + + /** + * Test that hooks run correctly on creating, editing, and deleting a text format. + */ + function testFilterHooks() { + // Add a text format. + $name = $this->randomName(); + $edit = array(); + $edit['format'] = drupal_strtolower($this->randomName()); + $edit['name'] = $name; + $edit['roles[' . DRUPAL_ANONYMOUS_RID . ']'] = 1; + $this->drupalPost('admin/config/content/formats/add', $edit, t('Save configuration')); + $this->assertRaw(t('Added text format %format.', array('%format' => $name)), t('New format created.')); + $this->assertText('hook_filter_format_insert invoked.', t('hook_filter_format_insert was invoked.')); + + $format_id = $edit['format']; + + // Update text format. + $edit = array(); + $edit['roles[' . DRUPAL_AUTHENTICATED_RID . ']'] = 1; + $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration')); + $this->assertRaw(t('The text format %format has been updated.', array('%format' => $name)), t('Format successfully updated.')); + $this->assertText('hook_filter_format_update invoked.', t('hook_filter_format_update() was invoked.')); + + // Add a new custom block. + $custom_block = array(); + $custom_block['info'] = $this->randomName(8); + $custom_block['title'] = $this->randomName(8); + $custom_block['body[value]'] = $this->randomName(32); + // Use the format created. + $custom_block['body[format]'] = $format_id; + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + $this->assertText(t('The block has been created.'), t('New block successfully created.')); + + // Verify the new block is in the database. + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $this->assertNotNull($bid, t('New block found in database')); + + // Disable the text format. + $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable')); + $this->assertRaw(t('Disabled text format %format.', array('%format' => $name)), t('Format successfully disabled.')); + $this->assertText('hook_filter_format_disable invoked.', t('hook_filter_format_disable() was invoked.')); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterNoFormatTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterNoFormatTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fa87f7c58d6950d54600d4a8ad2fb989c00650b6 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterNoFormatTest.php @@ -0,0 +1,30 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterNoFormatTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +class FilterNoFormatTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Unassigned text format functionality', + 'description' => 'Test the behavior of check_markup() when it is called without a text format.', + 'group' => 'Filter', + ); + } + + function testCheckMarkupNoFormat() { + // Create some text. Include some HTML and line breaks, so we get a good + // test of the filtering that is applied to it. + $text = "<strong>" . $this->randomName(32) . "</strong>\n\n<div>" . $this->randomName(32) . "</div>"; + + // Make sure that when this text is run through check_markup() with no text + // format, it is filtered as though it is in the fallback format. + $this->assertEqual(check_markup($text), check_markup($text, filter_fallback_format()), t('Text with no format is filtered the same as text in the fallback format.')); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..46bb7b66b2ccf8da5106372b876cb6883b18be77 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterSecurityTest.php @@ -0,0 +1,74 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterSecurityTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Security tests for missing/vanished text formats or filters. + */ +class FilterSecurityTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Security', + 'description' => 'Test the behavior of check_markup() when a filter or text format vanishes.', + 'group' => 'Filter', + ); + } + + function setUp() { + parent::setUp(array('node', 'php', 'filter_test')); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Create Filtered HTML format. + $filtered_html_format = array( + 'format' => 'filtered_html', + 'name' => 'Filtered HTML', + ); + $filtered_html_format = (object) $filtered_html_format; + filter_format_save($filtered_html_format); + + $filtered_html_permission = filter_permission_name($filtered_html_format); + user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array($filtered_html_permission)); + + $this->admin_user = $this->drupalCreateUser(array('administer modules', 'administer filters', 'administer site configuration')); + $this->drupalLogin($this->admin_user); + } + + /** + * Test that filtered content is emptied when an actively used filter module is disabled. + */ + function testDisableFilterModule() { + // Create a new node. + $node = $this->drupalCreateNode(array('promote' => 1)); + $body_raw = $node->body[LANGUAGE_NOT_SPECIFIED][0]['value']; + $format_id = $node->body[LANGUAGE_NOT_SPECIFIED][0]['format']; + $this->drupalGet('node/' . $node->nid); + $this->assertText($body_raw, t('Node body found.')); + + // Enable the filter_test_replace filter. + $edit = array( + 'filters[filter_test_replace][status]' => 1, + ); + $this->drupalPost('admin/config/content/formats/' . $format_id, $edit, t('Save configuration')); + + // Verify that filter_test_replace filter replaced the content. + $this->drupalGet('node/' . $node->nid); + $this->assertNoText($body_raw, t('Node body not found.')); + $this->assertText('Filter: Testing filter', t('Testing filter output found.')); + + // Disable the text format entirely. + $this->drupalPost('admin/config/content/formats/' . $format_id . '/disable', array(), t('Disable')); + + // Verify that the content is empty, because the text format does not exist. + $this->drupalGet('node/' . $node->nid); + $this->assertNoText($body_raw, t('Node body not found.')); + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0a6c00c70ce1d83a61d862853c47ef99b86b4964 --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php @@ -0,0 +1,69 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterSettingsTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests filter settings. + */ +class FilterSettingsTest extends WebTestBase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Filter settings', + 'description' => 'Tests filter settings.', + 'group' => 'Filter', + ); + } + + /** + * Tests explicit and implicit default settings for filters. + */ + function testFilterDefaults() { + $filter_info = filter_filter_info(); + $filters = array_fill_keys(array_keys($filter_info), array()); + + // Create text format using filter default settings. + $filter_defaults_format = (object) array( + 'format' => 'filter_defaults', + 'name' => 'Filter defaults', + 'filters' => $filters, + ); + filter_format_save($filter_defaults_format); + + // Verify that default weights defined in hook_filter_info() were applied. + $saved_settings = array(); + foreach ($filter_defaults_format->filters as $name => $settings) { + $expected_weight = (isset($filter_info[$name]['weight']) ? $filter_info[$name]['weight'] : 0); + $this->assertEqual($settings['weight'], $expected_weight, format_string('@name filter weight %saved equals %default', array( + '@name' => $name, + '%saved' => $settings['weight'], + '%default' => $expected_weight, + ))); + $saved_settings[$name]['weight'] = $expected_weight; + } + + // Re-save the text format. + filter_format_save($filter_defaults_format); + // Reload it from scratch. + filter_formats_reset(); + $filter_defaults_format = filter_format_load($filter_defaults_format->format); + $filter_defaults_format->filters = filter_list_format($filter_defaults_format->format); + + // Verify that saved filter settings have not been changed. + foreach ($filter_defaults_format->filters as $name => $settings) { + $this->assertEqual($settings->weight, $saved_settings[$name]['weight'], format_string('@name filter weight %saved equals %previous', array( + '@name' => $name, + '%saved' => $settings->weight, + '%previous' => $saved_settings[$name]['weight'], + ))); + } + } +} diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterUnitTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterUnitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9daac0f1549ed5e7de6e4f07c7efeda943828b0a --- /dev/null +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterUnitTest.php @@ -0,0 +1,1004 @@ +<?php + +/** + * @file + * Definition of Drupal\filter\Tests\FilterUnitTest. + */ + +namespace Drupal\filter\Tests; + +use Drupal\simpletest\UnitTestBase; +use stdClass; + +/** + * Unit tests for core filters. + */ +class FilterUnitTest extends UnitTestBase { + public static function getInfo() { + return array( + 'name' => 'Filter module filters', + 'description' => 'Tests Filter module filters individually.', + 'group' => 'Filter', + ); + } + + /** + * Test the line break filter. + */ + function testLineBreakFilter() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->callback = '_filter_autop'; + + // Since the line break filter naturally needs plenty of newlines in test + // strings and expectations, we're using "\n" instead of regular newlines + // here. + $tests = array( + // Single line breaks should be changed to <br /> tags, while paragraphs + // separated with double line breaks should be enclosed with <p></p> tags. + "aaa\nbbb\n\nccc" => array( + "<p>aaa<br />\nbbb</p>\n<p>ccc</p>" => TRUE, + ), + // Skip contents of certain block tags entirely. + "<script>aaa\nbbb\n\nccc</script> +<style>aaa\nbbb\n\nccc</style> +<pre>aaa\nbbb\n\nccc</pre> +<object>aaa\nbbb\n\nccc</object> +<iframe>aaa\nbbb\n\nccc</iframe> +" => array( + "<script>aaa\nbbb\n\nccc</script>" => TRUE, + "<style>aaa\nbbb\n\nccc</style>" => TRUE, + "<pre>aaa\nbbb\n\nccc</pre>" => TRUE, + "<object>aaa\nbbb\n\nccc</object>" => TRUE, + "<iframe>aaa\nbbb\n\nccc</iframe>" => TRUE, + ), + // Skip comments entirely. + "One. <!-- comment --> Two.\n<!--\nThree.\n-->\n" => array( + '<!-- comment -->' => TRUE, + "<!--\nThree.\n-->" => TRUE, + ), + // Resulting HTML should produce matching paragraph tags. + '<p><div> </div></p>' => array( + "<p>\n<div> </div>\n</p>" => TRUE, + ), + '<div><p> </p></div>' => array( + "<div>\n</div>" => TRUE, + ), + '<blockquote><pre>aaa</pre></blockquote>' => array( + "<blockquote><pre>aaa</pre></blockquote>" => TRUE, + ), + "<pre>aaa\nbbb\nccc</pre>\nddd\neee" => array( + "<pre>aaa\nbbb\nccc</pre>" => TRUE, + "<p>ddd<br />\neee</p>" => TRUE, + ), + // Comments remain unchanged and subsequent lines/paragraphs are + // transformed normally. + "aaa<!--comment-->\n\nbbb\n\nccc\n\nddd<!--comment\nwith linebreak-->\n\neee\n\nfff" => array( + "<p>aaa</p>\n<!--comment--><p>\nbbb</p>\n<p>ccc</p>\n<p>ddd</p>" => TRUE, + "<!--comment\nwith linebreak--><p>\neee</p>\n<p>fff</p>" => TRUE, + ), + // Check that a comment in a PRE will result that the text after + // the comment, but still in PRE, is not transformed. + "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>\nddd" => array( + "<pre>aaa\nbbb<!-- comment -->\n\nccc</pre>" => TRUE, + ), + // Bug 810824, paragraphs were appearing around iframe tags. + "<iframe>aaa</iframe>\n\n" => array( + "<p><iframe>aaa</iframe></p>" => FALSE, + ), + ); + $this->assertFilteredString($filter, $tests); + + // Very long string hitting PCRE limits. + $limit = max(ini_get('pcre.backtrack_limit'), ini_get('pcre.recursion_limit')); + $source = $this->randomName($limit); + $result = _filter_autop($source); + $success = $this->assertEqual($result, '<p>' . $source . "</p>\n", t('Line break filter can process very long strings.')); + if (!$success) { + $this->verbose("\n" . $source . "\n<hr />\n" . $result); + } + } + + /** + * Tests limiting allowed tags and XSS prevention. + * + * XSS tests assume that script is disallowed by default and src is allowed + * by default, but on* and style attributes are disallowed. + * + * Script injection vectors mostly adopted from http://ha.ckers.org/xss.html. + * + * Relevant CVEs: + * - CVE-2002-1806, ~CVE-2005-0682, ~CVE-2005-2106, CVE-2005-3973, + * CVE-2006-1226 (= rev. 1.112?), CVE-2008-0273, CVE-2008-3740. + */ + function testFilterXSS() { + // Tag stripping, different ways to work around removal of HTML tags. + $f = filter_xss('<script>alert(0)</script>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- simple script without special characters.')); + + $f = filter_xss('<script src="http://www.example.com" />'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping -- empty script with source.')); + + $f = filter_xss('<ScRipt sRc=http://www.example.com/>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- varying case.')); + + $f = filter_xss("<script\nsrc\n=\nhttp://www.example.com/\n>"); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- multiline tag.')); + + $f = filter_xss('<script/a src=http://www.example.com/a.js></script>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- non whitespace character after tag name.')); + + $f = filter_xss('<script/src=http://www.example.com/a.js></script>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no space between tag and attribute.')); + + // Null between < and tag name works at least with IE6. + $f = filter_xss("<\0scr\0ipt>alert(0)</script>"); + $this->assertNoNormalized($f, 'ipt', t('HTML tag stripping evasion -- breaking HTML with nulls.')); + + $f = filter_xss("<scrscriptipt src=http://www.example.com/a.js>"); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- filter just removing "script".')); + + $f = filter_xss('<<script>alert(0);//<</script>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double opening brackets.')); + + $f = filter_xss('<script src=http://www.example.com/a.js?<b>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing tag.')); + + // DRUPAL-SA-2008-047: This doesn't seem exploitable, but the filter should + // work consistently. + $f = filter_xss('<script>>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- double closing tag.')); + + $f = filter_xss('<script src=//www.example.com/.a>'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no scheme or ending slash.')); + + $f = filter_xss('<script src=http://www.example.com/.a'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- no closing bracket.')); + + $f = filter_xss('<script src=http://www.example.com/ <'); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- opening instead of closing bracket.')); + + $f = filter_xss('<nosuchtag attribute="newScriptInjectionVector">'); + $this->assertNoNormalized($f, 'nosuchtag', t('HTML tag stripping evasion -- unknown tag.')); + + $f = filter_xss('<?xml:namespace ns="urn:schemas-microsoft-com:time">'); + $this->assertTrue(stripos($f, '<?xml') === FALSE, t('HTML tag stripping evasion -- starting with a question sign (processing instructions).')); + + $f = filter_xss('<t:set attributeName="innerHTML" to="<script defer>alert(0)</script>">'); + $this->assertNoNormalized($f, 't:set', t('HTML tag stripping evasion -- colon in the tag name (namespaces\' tricks).')); + + $f = filter_xss('<img """><script>alert(0)</script>', array('img')); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- a malformed image tag.')); + + $f = filter_xss('<blockquote><script>alert(0)</script></blockquote>', array('blockquote')); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script in a blockqoute.')); + + $f = filter_xss("<!--[if true]><script>alert(0)</script><![endif]-->"); + $this->assertNoNormalized($f, 'script', t('HTML tag stripping evasion -- script within a comment.')); + + // Dangerous attributes removal. + $f = filter_xss('<p onmouseover="http://www.example.com/">', array('p')); + $this->assertNoNormalized($f, 'onmouseover', t('HTML filter attributes removal -- events, no evasion.')); + + $f = filter_xss('<li style="list-style-image: url(javascript:alert(0))">', array('li')); + $this->assertNoNormalized($f, 'style', t('HTML filter attributes removal -- style, no evasion.')); + + $f = filter_xss('<img onerror =alert(0)>', array('img')); + $this->assertNoNormalized($f, 'onerror', t('HTML filter attributes removal evasion -- spaces before equals sign.')); + + $f = filter_xss('<img onabort!#$%&()*~+-_.,:;?@[/|\]^`=alert(0)>', array('img')); + $this->assertNoNormalized($f, 'onabort', t('HTML filter attributes removal evasion -- non alphanumeric characters before equals sign.')); + + $f = filter_xss('<img oNmediAError=alert(0)>', array('img')); + $this->assertNoNormalized($f, 'onmediaerror', t('HTML filter attributes removal evasion -- varying case.')); + + // Works at least with IE6. + $f = filter_xss("<img o\0nfocus\0=alert(0)>", array('img')); + $this->assertNoNormalized($f, 'focus', t('HTML filter attributes removal evasion -- breaking with nulls.')); + + // Only whitelisted scheme names allowed in attributes. + $f = filter_xss('<img src="javascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- no evasion.')); + + $f = filter_xss('<img src=javascript:alert(0)>', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no quotes.')); + + // A bit like CVE-2006-0070. + $f = filter_xss('<img src="javascript:confirm(0)">', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- no alert ;)')); + + $f = filter_xss('<img src=`javascript:alert(0)`>', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- grave accents.')); + + $f = filter_xss('<img dynsrc="javascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- rare attribute.')); + + $f = filter_xss('<table background="javascript:alert(0)">', array('table')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- another tag.')); + + $f = filter_xss('<base href="javascript:alert(0);//">', array('base')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing -- one more attribute and tag.')); + + $f = filter_xss('<img src="jaVaSCriPt:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- varying case.')); + + $f = filter_xss('<img src=javascript:alert(0)>', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 decimal encoding.')); + + $f = filter_xss('<img src=javascript:alert(0)>', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- long UTF-8 encoding.')); + + $f = filter_xss('<img src=javascript:alert(0)>', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- UTF-8 hex encoding.')); + + $f = filter_xss("<img src=\"jav\tascript:alert(0)\">", array('img')); + $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an embedded tab.')); + + $f = filter_xss('<img src="jav	ascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded tab.')); + + $f = filter_xss('<img src="jav
ascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded newline.')); + + // With 
 this test would fail, but the entity gets turned into + // &#xD;, so it's OK. + $f = filter_xss('<img src="jav
ascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'script', t('HTML scheme clearing evasion -- an encoded, embedded carriage return.')); + + $f = filter_xss("<img src=\"\n\n\nj\na\nva\ns\ncript:alert(0)\">", array('img')); + $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- broken into many lines.')); + + $f = filter_xss("<img src=\"jav\0a\0\0cript:alert(0)\">", array('img')); + $this->assertNoNormalized($f, 'cript', t('HTML scheme clearing evasion -- embedded nulls.')); + + $f = filter_xss('<img src="  javascript:alert(0)">', array('img')); + $this->assertNoNormalized($f, 'javascript', t('HTML scheme clearing evasion -- spaces and metacharacters before scheme.')); + + $f = filter_xss('<img src="vbscript:msgbox(0)">', array('img')); + $this->assertNoNormalized($f, 'vbscript', t('HTML scheme clearing evasion -- another scheme.')); + + $f = filter_xss('<img src="nosuchscheme:notice(0)">', array('img')); + $this->assertNoNormalized($f, 'nosuchscheme', t('HTML scheme clearing evasion -- unknown scheme.')); + + // Netscape 4.x javascript entities. + $f = filter_xss('<br size="&{alert(0)}">', array('br')); + $this->assertNoNormalized($f, 'alert', t('Netscape 4.x javascript entities.')); + + // DRUPAL-SA-2008-006: Invalid UTF-8, these only work as reflected XSS with + // Internet Explorer 6. + $f = filter_xss("<p arg=\"\xe0\">\" style=\"background-image: url(javascript:alert(0));\"\xe0<p>", array('p')); + $this->assertNoNormalized($f, 'style', t('HTML filter -- invalid UTF-8.')); + + $f = filter_xss("\xc0aaa"); + $this->assertEqual($f, '', t('HTML filter -- overlong UTF-8 sequences.')); + + $f = filter_xss("Who's Online"); + $this->assertNormalized($f, "who's online", t('HTML filter -- html entity number')); + + $f = filter_xss("Who&#039;s Online"); + $this->assertNormalized($f, "who's online", t('HTML filter -- encoded html entity number')); + + $f = filter_xss("Who&amp;#039; Online"); + $this->assertNormalized($f, "who&#039; online", t('HTML filter -- double encoded html entity number')); + } + + /** + * Test filter settings, defaults, access restrictions and similar. + * + * @todo This is for functions like filter_filter and check_markup, whose + * functionality is not completely focused on filtering. Some ideas: + * restricting formats according to user permissions, proper cache + * handling, defaults -- allowed tags/attributes/protocols. + * + * @todo It is possible to add script, iframe etc. to allowed tags, but this + * makes HTML filter completely ineffective. + * + * @todo Class, id, name and xmlns should be added to disallowed attributes, + * or better a whitelist approach should be used for that too. + */ + function testHtmlFilter() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->settings = array( + 'allowed_html' => '<a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>', + 'filter_html_help' => 1, + 'filter_html_nofollow' => 0, + ); + + // HTML filter is not able to secure some tags, these should never be + // allowed. + $f = _filter_html('<script />', $filter); + $this->assertNoNormalized($f, 'script', t('HTML filter should always remove script tags.')); + + $f = _filter_html('<iframe />', $filter); + $this->assertNoNormalized($f, 'iframe', t('HTML filter should always remove iframe tags.')); + + $f = _filter_html('<object />', $filter); + $this->assertNoNormalized($f, 'object', t('HTML filter should always remove object tags.')); + + $f = _filter_html('<style />', $filter); + $this->assertNoNormalized($f, 'style', t('HTML filter should always remove style tags.')); + + // Some tags make CSRF attacks easier, let the user take the risk herself. + $f = _filter_html('<img />', $filter); + $this->assertNoNormalized($f, 'img', t('HTML filter should remove img tags on default.')); + + $f = _filter_html('<input />', $filter); + $this->assertNoNormalized($f, 'img', t('HTML filter should remove input tags on default.')); + + // Filtering content of some attributes is infeasible, these shouldn't be + // allowed too. + $f = _filter_html('<p style="display: none;" />', $filter); + $this->assertNoNormalized($f, 'style', t('HTML filter should remove style attribute on default.')); + + $f = _filter_html('<p onerror="alert(0);" />', $filter); + $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove on* attributes on default.')); + + $f = _filter_html('<code onerror> </code>', $filter); + $this->assertNoNormalized($f, 'onerror', t('HTML filter should remove empty on* attributes on default.')); + } + + /** + * Test the spam deterrent. + */ + function testNoFollowFilter() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->settings = array( + 'allowed_html' => '<a>', + 'filter_html_help' => 1, + 'filter_html_nofollow' => 1, + ); + + // Test if the rel="nofollow" attribute is added, even if we try to prevent + // it. + $f = _filter_html('<a href="http://www.example.com/">text</a>', $filter); + $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent -- no evasion.')); + + $f = _filter_html('<A href="http://www.example.com/">text</a>', $filter); + $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- capital A.')); + + $f = _filter_html("<a/href=\"http://www.example.com/\">text</a>", $filter); + $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- non whitespace character after tag name.')); + + $f = _filter_html("<\0a\0 href=\"http://www.example.com/\">text</a>", $filter); + $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- some nulls.')); + + $f = _filter_html('<a href="http://www.example.com/" rel="follow">text</a>', $filter); + $this->assertNoNormalized($f, 'rel="follow"', t('Spam deterrent evasion -- with rel set - rel="follow" removed.')); + $this->assertNormalized($f, 'rel="nofollow"', t('Spam deterrent evasion -- with rel set - rel="nofollow" added.')); + } + + /** + * Test the loose, admin HTML filter. + */ + function testFilterXSSAdmin() { + // DRUPAL-SA-2008-044 + $f = filter_xss_admin('<object />'); + $this->assertNoNormalized($f, 'object', t('Admin HTML filter -- should not allow object tag.')); + + $f = filter_xss_admin('<script />'); + $this->assertNoNormalized($f, 'script', t('Admin HTML filter -- should not allow script tag.')); + + $f = filter_xss_admin('<style /><iframe /><frame /><frameset /><meta /><link /><embed /><applet /><param /><layer />'); + $this->assertEqual($f, '', t('Admin HTML filter -- should never allow some tags.')); + } + + /** + * Tests the HTML escaping filter. + * + * check_plain() is not tested here. + */ + function testHtmlEscapeFilter() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->callback = '_filter_html_escape'; + + $tests = array( + " One. <!-- \"comment\" --> Two'.\n<p>Three.</p>\n " => array( + "One. <!-- "comment" --> Two'.\n<p>Three.</p>" => TRUE, + ' One.' => FALSE, + "</p>\n " => FALSE, + ), + ); + $this->assertFilteredString($filter, $tests); + } + + /** + * Tests the URL filter. + */ + function testUrlFilter() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->callback = '_filter_url'; + $filter->settings = array( + 'filter_url_length' => 496, + ); + // @todo Possible categories: + // - absolute, mail, partial + // - characters/encoding, surrounding markup, security + + // Create a e-mail that is too long. + $long_email = str_repeat('a', 254) . '@example.com'; + $too_long_email = str_repeat('b', 255) . '@example.com'; + + + // Filter selection/pattern matching. + $tests = array( + // HTTP URLs. + ' +http://example.com or www.example.com +' => array( + '<a href="http://example.com">http://example.com</a>' => TRUE, + '<a href="http://www.example.com">www.example.com</a>' => TRUE, + ), + // MAILTO URLs. + ' +person@example.com or mailto:person2@example.com or ' . $long_email . ' but not ' . $too_long_email . ' +' => array( + '<a href="mailto:person@example.com">person@example.com</a>' => TRUE, + '<a href="mailto:person2@example.com">mailto:person2@example.com</a>' => TRUE, + '<a href="mailto:' . $long_email . '">' . $long_email . '</a>' => TRUE, + '<a href="mailto:' . $too_long_email . '">' . $too_long_email . '</a>' => FALSE, + ), + // URI parts and special characters. + ' +http://trailingslash.com/ or www.trailingslash.com/ +http://host.com/some/path?query=foo&bar[baz]=beer#fragment or www.host.com/some/path?query=foo&bar[baz]=beer#fragment +http://twitter.com/#!/example/status/22376963142324226 +ftp://user:pass@ftp.example.com/~home/dir1 +sftp://user@nonstandardport:222/dir +ssh://192.168.0.100/srv/git/drupal.git +' => array( + '<a href="http://trailingslash.com/">http://trailingslash.com/</a>' => TRUE, + '<a href="http://www.trailingslash.com/">www.trailingslash.com/</a>' => TRUE, + '<a href="http://host.com/some/path?query=foo&bar[baz]=beer#fragment">http://host.com/some/path?query=foo&bar[baz]=beer#fragment</a>' => TRUE, + '<a href="http://www.host.com/some/path?query=foo&bar[baz]=beer#fragment">www.host.com/some/path?query=foo&bar[baz]=beer#fragment</a>' => TRUE, + '<a href="http://twitter.com/#!/example/status/22376963142324226">http://twitter.com/#!/example/status/22376963142324226</a>' => TRUE, + '<a href="ftp://user:pass@ftp.example.com/~home/dir1">ftp://user:pass@ftp.example.com/~home/dir1</a>' => TRUE, + '<a href="sftp://user@nonstandardport:222/dir">sftp://user@nonstandardport:222/dir</a>' => TRUE, + '<a href="ssh://192.168.0.100/srv/git/drupal.git">ssh://192.168.0.100/srv/git/drupal.git</a>' => TRUE, + ), + // Encoding. + ' +http://ampersand.com/?a=1&b=2 +http://encoded.com/?a=1&b=2 +' => array( + '<a href="http://ampersand.com/?a=1&b=2">http://ampersand.com/?a=1&b=2</a>' => TRUE, + '<a href="http://encoded.com/?a=1&b=2">http://encoded.com/?a=1&b=2</a>' => TRUE, + ), + // Domain name length. + ' +www.ex.ex or www.example.example or www.toolongdomainexampledomainexampledomainexampledomainexampledomain or +me@me.tv +' => array( + '<a href="http://www.ex.ex">www.ex.ex</a>' => TRUE, + '<a href="http://www.example.example">www.example.example</a>' => TRUE, + 'http://www.toolong' => FALSE, + '<a href="mailto:me@me.tv">me@me.tv</a>' => TRUE, + ), + // Absolute URL protocols. + // The list to test is found in the beginning of _filter_url() at + // $protocols = variable_get('filter_allowed_protocols'... (approx line 1325). + ' +https://example.com, +ftp://ftp.example.com, +news://example.net, +telnet://example, +irc://example.host, +ssh://odd.geek, +sftp://secure.host?, +webcal://calendar, +rtsp://127.0.0.1, +not foo://disallowed.com. +' => array( + 'href="https://example.com"' => TRUE, + 'href="ftp://ftp.example.com"' => TRUE, + 'href="news://example.net"' => TRUE, + 'href="telnet://example"' => TRUE, + 'href="irc://example.host"' => TRUE, + 'href="ssh://odd.geek"' => TRUE, + 'href="sftp://secure.host"' => TRUE, + 'href="webcal://calendar"' => TRUE, + 'href="rtsp://127.0.0.1"' => TRUE, + 'href="foo://disallowed.com"' => FALSE, + 'not foo://disallowed.com.' => TRUE, + ), + ); + $this->assertFilteredString($filter, $tests); + + // Surrounding text/punctuation. + $tests = array( + ' +Partial URL with trailing period www.partial.com. +E-mail with trailing comma person@example.com, +Absolute URL with trailing question http://www.absolute.com? +Query string with trailing exclamation www.query.com/index.php?a=! +Partial URL with 3 trailing www.partial.periods... +E-mail with 3 trailing exclamations@example.com!!! +Absolute URL and query string with 2 different punctuation characters (http://www.example.com/q=abc). +' => array( + 'period <a href="http://www.partial.com">www.partial.com</a>.' => TRUE, + 'comma <a href="mailto:person@example.com">person@example.com</a>,' => TRUE, + 'question <a href="http://www.absolute.com">http://www.absolute.com</a>?' => TRUE, + 'exclamation <a href="http://www.query.com/index.php?a=">www.query.com/index.php?a=</a>!' => TRUE, + 'trailing <a href="http://www.partial.periods">www.partial.periods</a>...' => TRUE, + 'trailing <a href="mailto:exclamations@example.com">exclamations@example.com</a>!!!' => TRUE, + 'characters (<a href="http://www.example.com/q=abc">http://www.example.com/q=abc</a>).' => TRUE, + ), + ' +(www.parenthesis.com/dir?a=1&b=2#a) +' => array( + '(<a href="http://www.parenthesis.com/dir?a=1&b=2#a">www.parenthesis.com/dir?a=1&b=2#a</a>)' => TRUE, + ), + ); + $this->assertFilteredString($filter, $tests); + + // Surrounding markup. + $tests = array( + ' +<p xmlns="www.namespace.com" /> +<p xmlns="http://namespace.com"> +An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>. +</p> +' => array( + '<p xmlns="www.namespace.com" />' => TRUE, + '<p xmlns="http://namespace.com">' => TRUE, + 'href="http://www.namespace.com"' => FALSE, + 'href="http://namespace.com"' => FALSE, + 'An <a href="http://example.com" title="Read more at www.example.info...">anchor</a>.' => TRUE, + ), + ' +Not <a href="foo">www.relative.com</a> or <a href="http://absolute.com">www.absolute.com</a> +but <strong>http://www.strong.net</strong> or <em>www.emphasis.info</em> +' => array( + '<a href="foo">www.relative.com</a>' => TRUE, + 'href="http://www.relative.com"' => FALSE, + '<a href="http://absolute.com">www.absolute.com</a>' => TRUE, + '<strong><a href="http://www.strong.net">http://www.strong.net</a></strong>' => TRUE, + '<em><a href="http://www.emphasis.info">www.emphasis.info</a></em>' => TRUE, + ), + ' +Test <code>using www.example.com the code tag</code>. +' => array( + 'href' => FALSE, + 'http' => FALSE, + ), + ' +Intro. +<blockquote> +Quoted text linking to www.example.com, written by person@example.com, originating from http://origin.example.com. <code>@see www.usage.example.com or <em>www.example.info</em> bla bla</code>. +</blockquote> + +Outro. +' => array( + 'href="http://www.example.com"' => TRUE, + 'href="mailto:person@example.com"' => TRUE, + 'href="http://origin.example.com"' => TRUE, + 'http://www.usage.example.com' => FALSE, + 'http://www.example.info' => FALSE, + 'Intro.' => TRUE, + 'Outro.' => TRUE, + ), + ' +Unknown tag <x>containing x and www.example.com</x>? And a tag <pooh>beginning with p and containing www.example.pooh with p?</pooh> +' => array( + 'href="http://www.example.com"' => TRUE, + 'href="http://www.example.pooh"' => TRUE, + ), + ' +<p>Test <br/>: This is a www.example17.com example <strong>with</strong> various http://www.example18.com tags. *<br/> + It is important www.example19.com to *<br/>test different URLs and http://www.example20.com in the same paragraph. *<br> +HTML www.example21.com soup by person@example22.com can litererally http://www.example23.com contain *img*<img> anything. Just a www.example24.com with http://www.example25.com thrown in. www.example26.com from person@example27.com with extra http://www.example28.com. +' => array( + 'href="http://www.example17.com"' => TRUE, + 'href="http://www.example18.com"' => TRUE, + 'href="http://www.example19.com"' => TRUE, + 'href="http://www.example20.com"' => TRUE, + 'href="http://www.example21.com"' => TRUE, + 'href="mailto:person@example22.com"' => TRUE, + 'href="http://www.example23.com"' => TRUE, + 'href="http://www.example24.com"' => TRUE, + 'href="http://www.example25.com"' => TRUE, + 'href="http://www.example26.com"' => TRUE, + 'href="mailto:person@example27.com"' => TRUE, + 'href="http://www.example28.com"' => TRUE, + ), + ' +<script> +<!-- + // @see www.example.com + var exampleurl = "http://example.net"; +--> +<!--//--><![CDATA[//><!-- + // @see www.example.com + var exampleurl = "http://example.net"; +//--><!]]> +</script> +' => array( + 'href="http://www.example.com"' => FALSE, + 'href="http://example.net"' => FALSE, + ), + ' +<style>body { + background: url(http://example.com/pixel.gif); +}</style> +' => array( + 'href' => FALSE, + ), + ' +<!-- Skip any URLs like www.example.com in comments --> +' => array( + 'href' => FALSE, + ), + ' +<!-- Skip any URLs like +www.example.com with a newline in comments --> +' => array( + 'href' => FALSE, + ), + ' +<!-- Skip any URLs like www.comment.com in comments. <p>Also ignore http://commented.out/markup.</p> --> +' => array( + 'href' => FALSE, + ), + ' +<dl> +<dt>www.example.com</dt> +<dd>http://example.com</dd> +<dd>person@example.com</dd> +<dt>Check www.example.net</dt> +<dd>Some text around http://www.example.info by person@example.info?</dd> +</dl> +' => array( + 'href="http://www.example.com"' => TRUE, + 'href="http://example.com"' => TRUE, + 'href="mailto:person@example.com"' => TRUE, + 'href="http://www.example.net"' => TRUE, + 'href="http://www.example.info"' => TRUE, + 'href="mailto:person@example.info"' => TRUE, + ), + ' +<div>www.div.com</div> +<ul> +<li>http://listitem.com</li> +<li class="odd">www.class.listitem.com</li> +</ul> +' => array( + '<div><a href="http://www.div.com">www.div.com</a></div>' => TRUE, + '<li><a href="http://listitem.com">http://listitem.com</a></li>' => TRUE, + '<li class="odd"><a href="http://www.class.listitem.com">www.class.listitem.com</a></li>' => TRUE, + ), + ); + $this->assertFilteredString($filter, $tests); + + // URL trimming. + $filter->settings['filter_url_length'] = 20; + $tests = array( + 'www.trimmed.com/d/ff.ext?a=1&b=2#a1' => array( + '<a href="http://www.trimmed.com/d/ff.ext?a=1&b=2#a1">www.trimmed.com/d/ff...</a>' => TRUE, + ), + ); + $this->assertFilteredString($filter, $tests); + } + + /** + * Asserts multiple filter output expectations for multiple input strings. + * + * @param $filter + * A input filter object. + * @param $tests + * An associative array, whereas each key is an arbitrary input string and + * each value is again an associative array whose keys are filter output + * strings and whose values are Booleans indicating whether the output is + * expected or not. + * + * For example: + * @code + * $tests = array( + * 'Input string' => array( + * '<p>Input string</p>' => TRUE, + * 'Input string<br' => FALSE, + * ), + * ); + * @endcode + */ + function assertFilteredString($filter, $tests) { + foreach ($tests as $source => $tasks) { + $function = $filter->callback; + $result = $function($source, $filter); + foreach ($tasks as $value => $is_expected) { + // Not using assertIdentical, since combination with strpos() is hard to grok. + if ($is_expected) { + $success = $this->assertTrue(strpos($result, $value) !== FALSE, t('@source: @value found.', array( + '@source' => var_export($source, TRUE), + '@value' => var_export($value, TRUE), + ))); + } + else { + $success = $this->assertTrue(strpos($result, $value) === FALSE, t('@source: @value not found.', array( + '@source' => var_export($source, TRUE), + '@value' => var_export($value, TRUE), + ))); + } + if (!$success) { + $this->verbose('Source:<pre>' . check_plain(var_export($source, TRUE)) . '</pre>' + . '<hr />' . 'Result:<pre>' . check_plain(var_export($result, TRUE)) . '</pre>' + . '<hr />' . ($is_expected ? 'Expected:' : 'Not expected:') + . '<pre>' . check_plain(var_export($value, TRUE)) . '</pre>' + ); + } + } + } + } + + /** + * Tests URL filter on longer content. + * + * Filters based on regular expressions should also be tested with a more + * complex content than just isolated test lines. + * The most common errors are: + * - accidental '*' (greedy) match instead of '*?' (minimal) match. + * - only matching first occurrence instead of all. + * - newlines not matching '.*'. + * + * This test covers: + * - Document with multiple newlines and paragraphs (two newlines). + * - Mix of several HTML tags, invalid non-HTML tags, tags to ignore and HTML + * comments. + * - Empty HTML tags (BR, IMG). + * - Mix of absolute and partial URLs, and e-mail addresses in one content. + */ + function testUrlFilterContent() { + // Setup dummy filter object. + $filter = new stdClass(); + $filter->settings = array( + 'filter_url_length' => 496, + ); + $path = drupal_get_path('module', 'filter') . '/tests'; + + $input = file_get_contents($path . '/filter.url-input.txt'); + $expected = file_get_contents($path . '/filter.url-output.txt'); + $result = _filter_url($input, $filter); + $this->assertIdentical($result, $expected, 'Complex HTML document was correctly processed.'); + } + + /** + * Test the HTML corrector filter. + * + * @todo This test could really use some validity checking function. + */ + function testHtmlCorrectorFilter() { + // Tag closing. + $f = _filter_htmlcorrector('<p>text'); + $this->assertEqual($f, '<p>text</p>', t('HTML corrector -- tag closing at the end of input.')); + + $f = _filter_htmlcorrector('<p>text<p><p>text'); + $this->assertEqual($f, '<p>text</p><p></p><p>text</p>', t('HTML corrector -- tag closing.')); + + $f = _filter_htmlcorrector("<ul><li>e1<li>e2"); + $this->assertEqual($f, "<ul><li>e1</li><li>e2</li></ul>", t('HTML corrector -- unclosed list tags.')); + + $f = _filter_htmlcorrector('<div id="d">content'); + $this->assertEqual($f, '<div id="d">content</div>', t('HTML corrector -- unclosed tag with attribute.')); + + // XHTML slash for empty elements. + $f = _filter_htmlcorrector('<hr><br>'); + $this->assertEqual($f, '<hr /><br />', t('HTML corrector -- XHTML closing slash.')); + + $f = _filter_htmlcorrector('<P>test</P>'); + $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.')); + + $f = _filter_htmlcorrector('<P>test</p>'); + $this->assertEqual($f, '<p>test</p>', t('HTML corrector -- Convert uppercased tags to proper lowercased ones.')); + + $f = _filter_htmlcorrector('test<hr />'); + $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through.')); + + $f = _filter_htmlcorrector('test<hr/>'); + $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there is a single space before the closing slash.')); + + $f = _filter_htmlcorrector('test<hr />'); + $this->assertEqual($f, 'test<hr />', t('HTML corrector -- Let proper XHTML pass through, but ensure there are not too many spaces before the closing slash.')); + + $f = _filter_htmlcorrector('<span class="test" />'); + $this->assertEqual($f, '<span class="test"></span>', t('HTML corrector -- Convert XHTML that is properly formed but that would not be compatible with typical HTML user agents.')); + + $f = _filter_htmlcorrector('test1<br class="test">test2'); + $this->assertEqual($f, 'test1<br class="test" />test2', t('HTML corrector -- Automatically close single tags.')); + + $f = _filter_htmlcorrector('line1<hr>line2'); + $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.')); + + $f = _filter_htmlcorrector('line1<HR>line2'); + $this->assertEqual($f, 'line1<hr />line2', t('HTML corrector -- Automatically close single tags.')); + + $f = _filter_htmlcorrector('<img src="http://example.com/test.jpg">test</img>'); + $this->assertEqual($f, '<img src="http://example.com/test.jpg" />test', t('HTML corrector -- Automatically close single tags.')); + + $f = _filter_htmlcorrector('<br></br>'); + $this->assertEqual($f, '<br />', t("HTML corrector -- Transform empty tags to a single closed tag if the tag's content model is EMPTY.")); + + $f = _filter_htmlcorrector('<div></div>'); + $this->assertEqual($f, '<div></div>', t("HTML corrector -- Do not transform empty tags to a single closed tag if the tag's content model is not EMPTY.")); + + $f = _filter_htmlcorrector('<p>line1<br/><hr/>line2</p>'); + $this->assertEqual($f, '<p>line1<br /></p><hr />line2', t('HTML corrector -- Move non-inline elements outside of inline containers.')); + + $f = _filter_htmlcorrector('<p>line1<div>line2</div></p>'); + $this->assertEqual($f, '<p>line1</p><div>line2</div>', t('HTML corrector -- Move non-inline elements outside of inline containers.')); + + $f = _filter_htmlcorrector('<p>test<p>test</p>\n'); + $this->assertEqual($f, '<p>test</p><p>test</p>\n', t('HTML corrector -- Auto-close improperly nested tags.')); + + $f = _filter_htmlcorrector('<p>Line1<br><STRONG>bold stuff</b>'); + $this->assertEqual($f, '<p>Line1<br /><strong>bold stuff</strong></p>', t('HTML corrector -- Properly close unclosed tags, and remove useless closing tags.')); + + $f = _filter_htmlcorrector('test <!-- this is a comment -->'); + $this->assertEqual($f, 'test <!-- this is a comment -->', t('HTML corrector -- Do not touch HTML comments.')); + + $f = _filter_htmlcorrector('test <!--this is a comment-->'); + $this->assertEqual($f, 'test <!--this is a comment-->', t('HTML corrector -- Do not touch HTML comments.')); + + $f = _filter_htmlcorrector('test <!-- comment <p>another + <strong>multiple</strong> line + comment</p> -->'); + $this->assertEqual($f, 'test <!-- comment <p>another + <strong>multiple</strong> line + comment</p> -->', t('HTML corrector -- Do not touch HTML comments.')); + + $f = _filter_htmlcorrector('test <!-- comment <p>another comment</p> -->'); + $this->assertEqual($f, 'test <!-- comment <p>another comment</p> -->', t('HTML corrector -- Do not touch HTML comments.')); + + $f = _filter_htmlcorrector('test <!--break-->'); + $this->assertEqual($f, 'test <!--break-->', t('HTML corrector -- Do not touch HTML comments.')); + + $f = _filter_htmlcorrector('<p>test\n</p>\n'); + $this->assertEqual($f, '<p>test\n</p>\n', t('HTML corrector -- New-lines are accepted and kept as-is.')); + + $f = _filter_htmlcorrector('<p>دروبال'); + $this->assertEqual($f, '<p>دروبال</p>', t('HTML corrector -- Encoding is correctly kept.')); + + $f = _filter_htmlcorrector('<script type="text/javascript">alert("test")</script>'); + $this->assertEqual($f, '<script type="text/javascript"> +<!--//--><![CDATA[// ><!-- +alert("test") +//--><!]]> +</script>', t('HTML corrector -- CDATA added to script element')); + + $f = _filter_htmlcorrector('<p><script type="text/javascript">alert("test")</script></p>'); + $this->assertEqual($f, '<p><script type="text/javascript"> +<!--//--><![CDATA[// ><!-- +alert("test") +//--><!]]> +</script></p>', t('HTML corrector -- CDATA added to a nested script element')); + + $f = _filter_htmlcorrector('<p><style> /* Styling */ body {color:red}</style></p>'); + $this->assertEqual($f, '<p><style> +<!--/*--><![CDATA[/* ><!--*/ + /* Styling */ body {color:red} +/*--><!]]>*/ +</style></p>', t('HTML corrector -- CDATA added to a style element.')); + + $filtered_data = _filter_htmlcorrector('<p><style> +/*<![CDATA[*/ +/* Styling */ +body {color:red} +/*]]>*/ +</style></p>'); + $this->assertEqual($filtered_data, '<p><style> +<!--/*--><![CDATA[/* ><!--*/ + +/*<![CDATA[*/ +/* Styling */ +body {color:red} +/*]]]]><![CDATA[>*/ + +/*--><!]]>*/ +</style></p>', + t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '/*<![CDATA[*/')) + ); + + $filtered_data = _filter_htmlcorrector('<p><style> + <!--/*--><![CDATA[/* ><!--*/ + /* Styling */ + body {color:red} + /*--><!]]>*/ +</style></p>'); + $this->assertEqual($filtered_data, '<p><style> +<!--/*--><![CDATA[/* ><!--*/ + + <!--/*--><![CDATA[/* ><!--*/ + /* Styling */ + body {color:red} + /*--><!]]]]><![CDATA[>*/ + +/*--><!]]>*/ +</style></p>', + t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--/*--><![CDATA[/* ><!--*/')) + ); + + $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript"> +<!--//--><![CDATA[// ><!-- + alert("test"); +//--><!]]> +</script></p>'); + $this->assertEqual($filtered_data, '<p><script type="text/javascript"> +<!--//--><![CDATA[// ><!-- + +<!--//--><![CDATA[// ><!-- + alert("test"); +//--><!]]]]><![CDATA[> + +//--><!]]> +</script></p>', + t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '<!--//--><![CDATA[// ><!--')) + ); + + $filtered_data = _filter_htmlcorrector('<p><script type="text/javascript"> +// <![CDATA[ + alert("test"); +// ]]> +</script></p>'); + $this->assertEqual($filtered_data, '<p><script type="text/javascript"> +<!--//--><![CDATA[// ><!-- + +// <![CDATA[ + alert("test"); +// ]]]]><![CDATA[> + +//--><!]]> +</script></p>', + t('HTML corrector -- Existing cdata section @pattern_name properly escaped', array('@pattern_name' => '// <![CDATA[')) + ); + + } + + /** + * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string. + * + * Otherwise fails the test with a given message, similar to all the + * SimpleTest assert* functions. + * + * Note that this does not remove nulls, new lines and other characters that + * could be used to obscure a tag or an attribute name. + * + * @param $haystack + * Text to look in. + * @param $needle + * Lowercase, plain text to look for. + * @param $message + * Message to display if failed. + * @param $group + * The group this message belongs to, defaults to 'Other'. + * @return + * TRUE on pass, FALSE on fail. + */ + function assertNormalized($haystack, $needle, $message = '', $group = 'Other') { + return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) !== FALSE, $message, $group); + } + + /** + * Asserts that text transformed to lowercase with HTML entities decoded does not contain a given string. + * + * Otherwise fails the test with a given message, similar to all the + * SimpleTest assert* functions. + * + * Note that this does not remove nulls, new lines, and other character that + * could be used to obscure a tag or an attribute name. + * + * @param $haystack + * Text to look in. + * @param $needle + * Lowercase, plain text to look for. + * @param $message + * Message to display if failed. + * @param $group + * The group this message belongs to, defaults to 'Other'. + * @return + * TRUE on pass, FALSE on fail. + */ + function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') { + return $this->assertTrue(strpos(strtolower(decode_entities($haystack)), $needle) === FALSE, $message, $group); + } +} diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..28ca83213342a67cdb16175cd3c750b754634ed7 --- /dev/null +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuNodeTest.php @@ -0,0 +1,137 @@ +<?php + +/** + * @file + * Definition of Drupal\menu\Tests\MenuNodeTest. + */ + +namespace Drupal\menu\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test menu settings for nodes. + */ +class MenuNodeTest extends WebTestBase { + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Menu settings for nodes', + 'description' => 'Add, edit, and delete a node with menu link.', + 'group' => 'Menu', + ); + } + + function setUp() { + parent::setUp('menu'); + + $this->admin_user = $this->drupalCreateUser(array( + 'access administration pages', + 'administer content types', + 'administer menu', + 'create page content', + 'edit any page content', + 'delete any page content', + )); + $this->drupalLogin($this->admin_user); + } + + /** + * Test creating, editing, deleting menu links via node form widget. + */ + function testMenuNodeFormWidget() { + // Enable Navigation menu as available menu. + $edit = array( + 'menu_options[navigation]' => 1, + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + // Change default parent item to Navigation menu, so we can assert more + // easily. + $edit = array( + 'menu_parent' => 'navigation:0', + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + + // Create a node. + $node_title = $this->randomName(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit = array( + "title" => $node_title, + "body[$langcode][0][value]" => $this->randomString(), + ); + $this->drupalPost('node/add/page', $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($node_title); + // Assert that there is no link for the node. + $this->drupalGet(''); + $this->assertNoLink($node_title); + + // Edit the node, enable the menu link setting, but skip the link title. + $edit = array( + 'menu[enabled]' => 1, + ); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + // Assert that there is no link for the node. + $this->drupalGet(''); + $this->assertNoLink($node_title); + + // Edit the node and create a menu link. + $edit = array( + 'menu[enabled]' => 1, + 'menu[link_title]' => $node_title, + 'menu[weight]' => 17, + ); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + // Assert that the link exists. + $this->drupalGet(''); + $this->assertLink($node_title); + + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertOptionSelected('edit-menu-weight', 17, t('Menu weight correct in edit form')); + + // Edit the node and remove the menu link. + $edit = array( + 'menu[enabled]' => FALSE, + ); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + // Assert that there is no link for the node. + $this->drupalGet(''); + $this->assertNoLink($node_title); + + // Add a menu link to the Management menu. + $item = array( + 'link_path' => 'node/' . $node->nid, + 'link_title' => $this->randomName(16), + 'menu_name' => 'management', + ); + menu_link_save($item); + + // Assert that disabled Management menu is not shown on the node/$nid/edit page. + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertText('Provide a menu link', t('Link in not allowed menu not shown in node edit form')); + // Assert that the link is still in the management menu after save. + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $link = menu_link_load($item['mlid']); + $this->assertTrue($link, t('Link in not allowed menu still exists after saving node')); + + // Move the menu link back to the Navigation menu. + $item['menu_name'] = 'navigation'; + menu_link_save($item); + // Create a second node. + $child_node = $this->drupalCreateNode(array('type' => 'article')); + // Assign a menu link to the second node, being a child of the first one. + $child_item = array( + 'link_path' => 'node/'. $child_node->nid, + 'link_title' => $this->randomName(16), + 'plid' => $item['mlid'], + ); + menu_link_save($child_item); + // Edit the first node. + $this->drupalGet('node/'. $node->nid .'/edit'); + // Assert that it is not possible to set the parent of the first node to itself or the second node. + $this->assertNoOption('edit-menu-parent', 'navigation:'. $item['mlid']); + $this->assertNoOption('edit-menu-parent', 'navigation:'. $child_item['mlid']); + // Assert that unallowed Management menu is not available in options. + $this->assertNoOption('edit-menu-parent', 'management:0'); + } +} diff --git a/core/modules/menu/menu.test b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php similarity index 81% rename from core/modules/menu/menu.test rename to core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php index 14fe96bc463091e0d05260fb0125eddb8763c518..4cdf809dfd01f22a9a1ac0b75690e0c9bca792c8 100644 --- a/core/modules/menu/menu.test +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php @@ -2,12 +2,14 @@ /** * @file - * Tests for menu.module. + * Definition of Drupal\menu\Tests\MenuTest. */ +namespace Drupal\menu\Tests; + use Drupal\simpletest\WebTestBase; -class MenuTestCase extends WebTestBase { +class MenuTest extends WebTestBase { protected $profile = 'standard'; protected $big_user; @@ -581,130 +583,3 @@ private function verifyAccess($response = 200) { } } } - -/** - * Test menu settings for nodes. - */ -class MenuNodeTestCase extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Menu settings for nodes', - 'description' => 'Add, edit, and delete a node with menu link.', - 'group' => 'Menu', - ); - } - - function setUp() { - parent::setUp('menu'); - - $this->admin_user = $this->drupalCreateUser(array( - 'access administration pages', - 'administer content types', - 'administer menu', - 'create page content', - 'edit any page content', - 'delete any page content', - )); - $this->drupalLogin($this->admin_user); - } - - /** - * Test creating, editing, deleting menu links via node form widget. - */ - function testMenuNodeFormWidget() { - // Enable Navigation menu as available menu. - $edit = array( - 'menu_options[navigation]' => 1, - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - // Change default parent item to Navigation menu, so we can assert more - // easily. - $edit = array( - 'menu_parent' => 'navigation:0', - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - - // Create a node. - $node_title = $this->randomName(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit = array( - "title" => $node_title, - "body[$langcode][0][value]" => $this->randomString(), - ); - $this->drupalPost('node/add/page', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($node_title); - // Assert that there is no link for the node. - $this->drupalGet(''); - $this->assertNoLink($node_title); - - // Edit the node, enable the menu link setting, but skip the link title. - $edit = array( - 'menu[enabled]' => 1, - ); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - // Assert that there is no link for the node. - $this->drupalGet(''); - $this->assertNoLink($node_title); - - // Edit the node and create a menu link. - $edit = array( - 'menu[enabled]' => 1, - 'menu[link_title]' => $node_title, - 'menu[weight]' => 17, - ); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - // Assert that the link exists. - $this->drupalGet(''); - $this->assertLink($node_title); - - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertOptionSelected('edit-menu-weight', 17, t('Menu weight correct in edit form')); - - // Edit the node and remove the menu link. - $edit = array( - 'menu[enabled]' => FALSE, - ); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - // Assert that there is no link for the node. - $this->drupalGet(''); - $this->assertNoLink($node_title); - - // Add a menu link to the Management menu. - $item = array( - 'link_path' => 'node/' . $node->nid, - 'link_title' => $this->randomName(16), - 'menu_name' => 'management', - ); - menu_link_save($item); - - // Assert that disabled Management menu is not shown on the node/$nid/edit page. - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertText('Provide a menu link', t('Link in not allowed menu not shown in node edit form')); - // Assert that the link is still in the management menu after save. - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $link = menu_link_load($item['mlid']); - $this->assertTrue($link, t('Link in not allowed menu still exists after saving node')); - - // Move the menu link back to the Navigation menu. - $item['menu_name'] = 'navigation'; - menu_link_save($item); - // Create a second node. - $child_node = $this->drupalCreateNode(array('type' => 'article')); - // Assign a menu link to the second node, being a child of the first one. - $child_item = array( - 'link_path' => 'node/'. $child_node->nid, - 'link_title' => $this->randomName(16), - 'plid' => $item['mlid'], - ); - menu_link_save($child_item); - // Edit the first node. - $this->drupalGet('node/'. $node->nid .'/edit'); - // Assert that it is not possible to set the parent of the first node to itself or the second node. - $this->assertNoOption('edit-menu-parent', 'navigation:'. $item['mlid']); - $this->assertNoOption('edit-menu-parent', 'navigation:'. $child_item['mlid']); - // Assert that unallowed Management menu is not available in options. - $this->assertNoOption('edit-menu-parent', 'management:0'); - } -} diff --git a/core/modules/menu/menu.info b/core/modules/menu/menu.info index d93c54268e7096c85f2edfe88f35045f93119ce9..e5e2c8b77a457ebfdc197914cf089e640b018e6c 100644 --- a/core/modules/menu/menu.info +++ b/core/modules/menu/menu.info @@ -3,5 +3,4 @@ description = Allows administrators to customize the site navigation menu. package = Core version = VERSION core = 8.x -files[] = menu.test configure = admin/structure/menu diff --git a/core/modules/node/lib/Drupal/node/Tests/MultiStepNodeFormBasicOptionsTest.php b/core/modules/node/lib/Drupal/node/Tests/MultiStepNodeFormBasicOptionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d84d1f30570aac2ece41864a891750b3d755ffa0 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/MultiStepNodeFormBasicOptionsTest.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\MultiStepNodeFormBasicOptionsTest. + */ + +namespace Drupal\node\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test multistep node forms basic options. + */ +class MultiStepNodeFormBasicOptionsTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Multistep node form basic options', + 'description' => 'Test the persistence of basic options through multiple steps.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp('poll'); + $web_user = $this->drupalCreateUser(array('administer nodes', 'create poll content')); + $this->drupalLogin($web_user); + } + + /** + * Change the default values of basic options to ensure they persist. + */ + function testMultiStepNodeFormBasicOptions() { + $edit = array( + 'title' => 'a', + 'status' => FALSE, + 'promote' => FALSE, + 'sticky' => 1, + 'choice[new:0][chtext]' => 'a', + 'choice[new:1][chtext]' => 'a', + ); + $this->drupalPost('node/add/poll', $edit, t('Add another choice')); + $this->assertNoFieldChecked('edit-status', 'status stayed unchecked'); + $this->assertNoFieldChecked('edit-promote', 'promote stayed unchecked'); + $this->assertFieldChecked('edit-sticky', 'sticky stayed checked'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..10cdc544e86bc4777b9b2c744c7d7e636a5e2c3e --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessBaseTableTest.php @@ -0,0 +1,169 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessBaseTableTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests for Node Access with a non-node base table. + */ +class NodeAccessBaseTableTest extends NodeTestBase { + // Requires tags taxonomy field. + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Node access on any table', + 'description' => 'Checks behavior of the node access subsystem if the base table is not node.', + 'group' => 'Node', + ); + } + + /** + * Enable modules and create user with specific permissions. + */ + public function setUp() { + parent::setUp('node_access_test'); + node_access_rebuild(); + variable_set('node_access_test_private', TRUE); + } + + /** + * Test the "private" node access. + * + * - Create 2 users with "access content" and "create article" permissions. + * - Each user creates one private and one not private article. + * + * - Test that each user can view the other user's non-private article. + * - Test that each user cannot view the other user's private article. + * - Test that each user finds only appropriate (non-private + own private) + * in taxonomy listing. + * - Create another user with 'view any private content'. + * - Test that user 4 can view all content created above. + * - Test that user 4 can view all content on taxonomy listing. + */ + function testNodeAccessBasic() { + $num_simple_users = 2; + $simple_users = array(); + + // nodes keyed by uid and nid: $nodes[$uid][$nid] = $is_private; + $this->nodesByUser = array(); + $titles = array(); // Titles keyed by nid + $private_nodes = array(); // Array of nids marked private. + for ($i = 0; $i < $num_simple_users; $i++) { + $simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content')); + } + foreach ($simple_users as $this->webUser) { + $this->drupalLogin($this->webUser); + foreach (array(0 => 'Public', 1 => 'Private') as $is_private => $type) { + $edit = array( + 'title' => t('@private_public Article created by @user', array('@private_public' => $type, '@user' => $this->webUser->name)), + ); + if ($is_private) { + $edit['private'] = TRUE; + $edit['body[und][0][value]'] = 'private node'; + $edit['field_tags[und]'] = 'private'; + } + else { + $edit['body[und][0][value]'] = 'public node'; + $edit['field_tags[und]'] = 'public'; + } + + $this->drupalPost('node/add/article', $edit, t('Save')); + $nid = db_query('SELECT nid FROM {node} WHERE title = :title', array(':title' => $edit['title']))->fetchField(); + $private_status = db_query('SELECT private FROM {node_access_test} where nid = :nid', array(':nid' => $nid))->fetchField(); + $this->assertTrue($is_private == $private_status, t('The private status of the node was properly set in the node_access_test table.')); + if ($is_private) { + $private_nodes[] = $nid; + } + $titles[$nid] = $edit['title']; + $this->nodesByUser[$this->webUser->uid][$nid] = $is_private; + } + } + $this->publicTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'public'))->fetchField(); + $this->privateTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'private'))->fetchField(); + $this->assertTrue($this->publicTid, t('Public tid was found')); + $this->assertTrue($this->privateTid, t('Private tid was found')); + foreach ($simple_users as $this->webUser) { + $this->drupalLogin($this->webUser); + // Check own nodes to see that all are readable. + foreach ($this->nodesByUser as $uid => $data) { + foreach ($data as $nid => $is_private) { + $this->drupalGet('node/' . $nid); + if ($is_private) { + $should_be_visible = $uid == $this->webUser->uid; + } + else { + $should_be_visible = TRUE; + } + $this->assertResponse($should_be_visible ? 200 : 403, strtr('A %private node by user %uid is %visible for user %current_uid.', array( + '%private' => $is_private ? 'private' : 'public', + '%uid' => $uid, + '%visible' => $should_be_visible ? 'visible' : 'not visible', + '%current_uid' => $this->webUser->uid, + ))); + } + } + + // Check to see that the correct nodes are shown on taxonomy/private + // and taxonomy/public. + $this->assertTaxonomyPage(FALSE); + } + + // Now test that a user with 'access any private content' can view content. + $access_user = $this->drupalCreateUser(array('access content', 'create article content', 'node test view', 'search content')); + $this->drupalLogin($access_user); + + foreach ($this->nodesByUser as $uid => $private_status) { + foreach ($private_status as $nid => $is_private) { + $this->drupalGet('node/' . $nid); + $this->assertResponse(200); + } + } + + // This user should be able to see all of the nodes on the relevant + // taxonomy pages. + $this->assertTaxonomyPage(TRUE); + } + + /** + * Checks taxonomy/term listings to ensure only accessible nodes are listed. + * + * @param $is_admin + * A boolean indicating whether the current user is an administrator. If + * TRUE, all nodes should be listed. If FALSE, only public nodes and the + * user's own private nodes should be listed. + */ + protected function assertTaxonomyPage($is_admin) { + foreach (array($this->publicTid, $this->privateTid) as $tid_is_private => $tid) { + $this->drupalGet("taxonomy/term/$tid"); + $this->nids_visible = array(); + foreach ($this->xpath("//a[text()='Read more']") as $link) { + $this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Read more points to a node'); + $this->nids_visible[$matches[1]] = TRUE; + } + foreach ($this->nodesByUser as $uid => $data) { + foreach ($data as $nid => $is_private) { + // Private nodes should be visible on the private term page, + // public nodes should be visible on the public term page. + $should_be_visible = $tid_is_private == $is_private; + // Non-administrators can only see their own nodes on the private + // term page. + if (!$is_admin && $tid_is_private) { + $should_be_visible = $should_be_visible && $uid == $this->webUser->uid; + } + $this->assertIdentical(isset($this->nids_visible[$nid]), $should_be_visible, strtr('A %private node by user %uid is %visible for user %current_uid on the %tid_is_private page.', array( + '%private' => $is_private ? 'private' : 'public', + '%uid' => $uid, + '%visible' => isset($this->nids_visible[$nid]) ? 'visible' : 'not visible', + '%current_uid' => $this->webUser->uid, + '%tid_is_private' => $tid_is_private ? 'private' : 'public', + ))); + } + } + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessFieldTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessFieldTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cc7d7927e55161ad8c6c083883740afe93c34c3b --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessFieldTest.php @@ -0,0 +1,82 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessFieldTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests the interaction of the node access system with fields. + */ +class NodeAccessFieldTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Node access and fields', + 'description' => 'Tests the interaction of the node access system with fields.', + 'group' => 'Node', + ); + } + + public function setUp() { + parent::setUp('node_access_test', 'field_ui'); + node_access_rebuild(); + + // Create some users. + $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); + $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); + + // Add a custom field to the page content type. + $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); + $this->field = field_create_field(array('field_name' => $this->field_name, 'type' => 'text')); + $this->instance = field_create_instance(array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => 'page', + )); + } + + /** + * Tests administering fields when node access is restricted. + */ + function testNodeAccessAdministerField() { + // Create a page node. + $langcode = LANGUAGE_NOT_SPECIFIED; + $field_data = array(); + $value = $field_data[$langcode][0]['value'] = $this->randomName(); + $node = $this->drupalCreateNode(array($this->field_name => $field_data)); + + // Log in as the administrator and confirm that the field value is present. + $this->drupalLogin($this->admin_user); + $this->drupalGet("node/{$node->nid}"); + $this->assertText($value, 'The saved field value is visible to an administrator.'); + + // Log in as the content admin and try to view the node. + $this->drupalLogin($this->content_admin_user); + $this->drupalGet("node/{$node->nid}"); + $this->assertText('Access denied', 'Access is denied for the content admin.'); + + // Modify the field default as the content admin. + $edit = array(); + $default = 'Sometimes words have two meanings'; + $edit["{$this->field_name}[$langcode][0][value]"] = $default; + $this->drupalPost( + "admin/structure/types/manage/page/fields/{$this->field_name}", + $edit, + t('Save settings') + ); + + // Log in as the administrator. + $this->drupalLogin($this->admin_user); + + // Confirm that the existing node still has the correct field value. + $this->drupalGet("node/{$node->nid}"); + $this->assertText($value, 'The original field value is visible to an administrator.'); + + // Confirm that the new default value appears when creating a new node. + $this->drupalGet('node/add/page'); + $this->assertRaw($default, 'The updated default value is displayed when creating a new node.'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1d2c58855d5b233bd96a2c1840831fe5ff2cc112 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessPagerTest.php @@ -0,0 +1,96 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessPagerTest. + */ + +namespace Drupal\node\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests pagination with a node access module enabled. + */ +class NodeAccessPagerTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'Node access pagination', + 'description' => 'Test access controlled node views have the right amount of comment pages.', + 'group' => 'Node', + ); + } + + public function setUp() { + parent::setUp('node_access_test', 'comment', 'forum'); + node_access_rebuild(); + $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'node test view')); + } + + /** + * Tests the comment pager for nodes with multiple grants per realm. + */ + public function testCommentPager() { + // Create a node. + $node = $this->drupalCreateNode(); + + // Create 60 comments. + for ($i = 0; $i < 60; $i++) { + $comment = entity_create('comment', array( + 'nid' => $node->nid, + 'subject' => $this->randomName(), + 'comment_body' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('value' => $this->randomName()), + ), + ), + )); + $comment->save(); + } + + $this->drupalLogin($this->web_user); + + // View the node page. With the default 50 comments per page there should + // be two pages (0, 1) but no third (2) page. + $this->drupalGet('node/' . $node->nid); + $this->assertText($node->title, t('Node title found.')); + $this->assertText(t('Comments'), t('Has a comments section.')); + $this->assertRaw('page=1', t('Secound page exists.')); + $this->assertNoRaw('page=2', t('No third page exists.')); + } + + /** + * Tests the forum node pager for nodes with multiple grants per realm. + */ + public function testForumPager() { + // Lookup the forums vocabulary vid. + $vid = variable_get('forum_nav_vocabulary', 0); + $this->assertTrue($vid, t('Forum navigation vocabulary found.')); + + // Lookup the general discussion term. + $tree = taxonomy_get_tree($vid, 0, 1); + $tid = reset($tree)->tid; + $this->assertTrue($tid, t('General discussion term found.')); + + // Create 30 nodes. + for ($i = 0; $i < 30; $i++) { + $this->drupalCreateNode(array( + 'nid' => NULL, + 'type' => 'forum', + 'taxonomy_forums' => array( + LANGUAGE_NOT_SPECIFIED => array( + array('tid' => $tid, 'vid' => $vid, 'vocabulary_machine_name' => 'forums'), + ), + ), + )); + } + + // View the general discussion forum page. With the default 25 nodes per + // page there should be two pages for 30 nodes, no more. + $this->drupalLogin($this->web_user); + $this->drupalGet('forum/' . $tid); + $this->assertRaw('page=1', t('Secound page exists.')); + $this->assertNoRaw('page=2', t('No third page exists.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessRebuildTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessRebuildTest.php new file mode 100644 index 0000000000000000000000000000000000000000..612ba362c4efc90b8c61ed5125d0bf1aef82fc5a --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessRebuildTest.php @@ -0,0 +1,36 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessRebuildTest. + */ + +namespace Drupal\node\Tests; + +/** + * Rebuild the node_access table. + */ +class NodeAccessRebuildTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node access rebuild', + 'description' => 'Ensures that node access rebuild functions work correctly.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + $web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports')); + $this->drupalLogin($web_user); + $this->web_user = $web_user; + } + + function testNodeAccessRebuild() { + $this->drupalGet('admin/reports/status'); + $this->clickLink(t('Rebuild permissions')); + $this->drupalPost(NULL, array(), t('Rebuild permissions')); + $this->assertText(t('Content permissions have been rebuilt.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessRecordsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessRecordsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7bd8c1de2dbb9407d1ad2ceda667b2ca968c0c17 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessRecordsTest.php @@ -0,0 +1,91 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessRecordsTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test case to verify hook_node_access_records functionality. + */ +class NodeAccessRecordsTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node access records', + 'description' => 'Test hook_node_access_records when acquiring grants.', + 'group' => 'Node', + ); + } + + function setUp() { + // Enable dummy module that implements hook_node_grants(), + // hook_node_access_records(), hook_node_grants_alter() and + // hook_node_access_records_alter(). + parent::setUp('node_test'); + } + + /** + * Create a node and test the creation of node access rules. + */ + function testNodeAccessRecords() { + // Create an article node. + $node1 = $this->drupalCreateNode(array('type' => 'article')); + $this->assertTrue(node_load($node1->nid), t('Article node created.')); + + // Check to see if grants added by node_test_node_access_records made it in. + $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->nid))->fetchAll(); + $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); + $this->assertEqual($records[0]->realm, 'test_article_realm', t('Grant with article_realm acquired for node without alteration.')); + $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); + + // Create an unpromoted "Basic page" node. + $node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0)); + $this->assertTrue(node_load($node2->nid), t('Unpromoted basic page node created.')); + + // Check to see if grants added by node_test_node_access_records made it in. + $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node2->nid))->fetchAll(); + $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); + $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.')); + $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); + + // Create an unpromoted, unpublished "Basic page" node. + $node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0, 'status' => 0)); + $this->assertTrue(node_load($node3->nid), t('Unpromoted, unpublished basic page node created.')); + + // Check to see if grants added by node_test_node_access_records made it in. + $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll(); + $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); + $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.')); + $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); + + // Create a promoted "Basic page" node. + $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); + $this->assertTrue(node_load($node4->nid), t('Promoted basic page node created.')); + + // Check to see if grant added by node_test_node_access_records was altered + // by node_test_node_access_records_alter. + $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node4->nid))->fetchAll(); + $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); + $this->assertEqual($records[0]->realm, 'test_alter_realm', t('Altered grant with alter_realm acquired for node.')); + $this->assertEqual($records[0]->gid, 2, t('Altered grant with gid = 2 acquired for node.')); + + // Check to see if we can alter grants with hook_node_grants_alter(). + $operations = array('view', 'update', 'delete'); + // Create a user that is allowed to access content. + $web_user = $this->drupalCreateUser(array('access content')); + foreach ($operations as $op) { + $grants = node_test_node_grants($op, $web_user); + $altered_grants = $grants; + drupal_alter('node_grants', $altered_grants, $web_user, $op); + $this->assertNotEqual($grants, $altered_grants, t('Altered the %op grant for a user.', array('%op' => $op))); + } + + // Check that core does not grant access to an unpublished node when an + // empty $grants array is returned. + $node6 = $this->drupalCreateNode(array('status' => 0, 'disable_node_access' => TRUE)); + $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node6->nid))->fetchAll(); + $this->assertEqual(count($records), 0, t('Returned no records for unpublished node.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0c784ae0fc1afb26a94d59cf3f545c386fc9008a --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAccessTest.php @@ -0,0 +1,77 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAccessTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test case to verify basic node_access functionality. + * @todo Cover hook_node_access in a separate test class. + * hook_node_access_records is covered in another test class. + */ +class NodeAccessTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node access', + 'description' => 'Test node_access function', + 'group' => 'Node', + ); + } + + /** + * Asserts node_access correctly grants or denies access. + */ + function assertNodeAccess($ops, $node, $account) { + foreach ($ops as $op => $result) { + $msg = t("node_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op)); + $this->assertEqual($result, node_access($op, $node, $account), $msg); + } + } + + function setUp() { + parent::setUp(); + // Clear permissions for authenticated users. + db_delete('role_permission') + ->condition('rid', DRUPAL_AUTHENTICATED_RID) + ->execute(); + } + + /** + * Runs basic tests for node_access function. + */ + function testNodeAccess() { + // Ensures user without 'access content' permission can do nothing. + $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content')); + $node1 = $this->drupalCreateNode(array('type' => 'page')); + $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user1); + $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node1, $web_user1); + + // Ensures user with 'bypass node access' permission can do everything. + $web_user2 = $this->drupalCreateUser(array('bypass node access')); + $node2 = $this->drupalCreateNode(array('type' => 'page')); + $this->assertNodeAccess(array('create' => TRUE), 'page', $web_user2); + $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node2, $web_user2); + + // User cannot 'view own unpublished content'. + $web_user3 = $this->drupalCreateUser(array('access content')); + $node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->uid)); + $this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3); + + // User cannot create content without permission. + $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user3); + + // User can 'view own unpublished content', but another user cannot. + $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); + $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); + $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->uid)); + $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4); + $this->assertNodeAccess(array('view' => FALSE), $node4, $web_user5); + + // Tests the default access provided for a published node. + $node5 = $this->drupalCreateNode(); + $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d89100d9d8e8e9175f15b67f1f5281c331d5bc9d --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeAdminTest.php @@ -0,0 +1,173 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeAdminTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test node administration page functionality. + */ +class NodeAdminTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node administration', + 'description' => 'Test node administration page functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + // Remove the "view own unpublished content" permission which is set + // by default for authenticated users so we can test this permission + // correctly. + user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('view own unpublished content')); + + $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); + $this->base_user_1 = $this->drupalCreateUser(array('access content overview')); + $this->base_user_2 = $this->drupalCreateUser(array('access content overview', 'view own unpublished content')); + $this->base_user_3 = $this->drupalCreateUser(array('access content overview', 'bypass node access')); + } + + /** + * Tests that the table sorting works on the content admin pages. + */ + function testContentAdminSort() { + $this->drupalLogin($this->admin_user); + foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) { + $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6))); + } + + // Test that the default sort by node.changed DESC actually fires properly. + $nodes_query = db_select('node', 'n') + ->fields('n', array('nid')) + ->orderBy('changed', 'DESC') + ->execute() + ->fetchCol(); + + $nodes_form = array(); + $this->drupalGet('admin/content'); + foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { + $nodes_form[] = $input; + } + $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form according to the default query.'); + + // Compare the rendered HTML node list to a query for the nodes ordered by + // title to account for possible database-dependent sort order. + $nodes_query = db_select('node', 'n') + ->fields('n', array('nid')) + ->orderBy('title') + ->execute() + ->fetchCol(); + + $nodes_form = array(); + $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'Title'))); + foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { + $nodes_form[] = $input; + } + $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form the same as they are in the query.'); + } + + /** + * Tests content overview with different user permissions. + * + * Taxonomy filters are tested separately. + * @see TaxonomyNodeFilterTestCase + */ + function testContentAdminPages() { + $this->drupalLogin($this->admin_user); + + $nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page')); + $nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article')); + $nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_1->uid, 'status' => 0)); + $nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_2->uid, 'status' => 0)); + + // Verify view, edit, and delete links for any content. + $this->drupalGet('admin/content'); + $this->assertResponse(200); + foreach ($nodes as $node) { + $this->assertLinkByHref('node/' . $node->nid); + $this->assertLinkByHref('node/' . $node->nid . '/edit'); + $this->assertLinkByHref('node/' . $node->nid . '/delete'); + // Verify tableselect. + $this->assertFieldByName('nodes[' . $node->nid . ']', '', t('Tableselect found.')); + } + + // Verify filtering by publishing status. + $edit = array( + 'status' => 'status-1', + ); + $this->drupalPost(NULL, $edit, t('Filter')); + + $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), t('Content list is filtered by status.')); + + $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); + $this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); + + // Verify filtering by status and content type. + $edit = array( + 'type' => 'page', + ); + $this->drupalPost(NULL, $edit, t('Refine')); + + $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), t('Content list is filtered by status.')); + $this->assertRaw(t('and where %property is %value', array('%property' => t('type'), '%value' => 'Basic page')), t('Content list is filtered by content type.')); + + $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); + + // Verify no operation links are displayed for regular users. + $this->drupalLogout(); + $this->drupalLogin($this->base_user_1); + $this->drupalGet('admin/content'); + $this->assertResponse(200); + $this->assertLinkByHref('node/' . $nodes['published_page']->nid); + $this->assertLinkByHref('node/' . $nodes['published_article']->nid); + $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/delete'); + $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/delete'); + + // Verify no unpublished content is displayed without permission. + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete'); + + // Verify no tableselect. + $this->assertNoFieldByName('nodes[' . $nodes['published_page']->nid . ']', '', t('No tableselect found.')); + + // Verify unpublished content is displayed with permission. + $this->drupalLogout(); + $this->drupalLogin($this->base_user_2); + $this->drupalGet('admin/content'); + $this->assertResponse(200); + $this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->nid); + // Verify no operation links are displayed. + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/delete'); + + // Verify user cannot see unpublished content of other users. + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); + $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete'); + + // Verify no tableselect. + $this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->nid . ']', '', t('No tableselect found.')); + + // Verify node access can be bypassed. + $this->drupalLogout(); + $this->drupalLogin($this->base_user_3); + $this->drupalGet('admin/content'); + $this->assertResponse(200); + foreach ($nodes as $node) { + $this->assertLinkByHref('node/' . $node->nid); + $this->assertLinkByHref('node/' . $node->nid . '/edit'); + $this->assertLinkByHref('node/' . $node->nid . '/delete'); + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php new file mode 100644 index 0000000000000000000000000000000000000000..38ef8b3d8d826ae0fa255ffe453600a0a7707fed --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php @@ -0,0 +1,145 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeBlockFunctionalTest. + */ + +namespace Drupal\node\Tests; + +/** + * Functional tests for the node module blocks. + */ +class NodeBlockFunctionalTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node blocks', + 'description' => 'Test node block functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Create users and test node. + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks')); + $this->web_user = $this->drupalCreateUser(array('access content', 'create article content')); + } + + /** + * Test the recent comments block. + */ + function testRecentNodeBlock() { + $this->drupalLogin($this->admin_user); + + // Disallow anonymous users to view content. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access content' => FALSE, + )); + + // Set the block to a region to confirm block is available. + $edit = array( + 'blocks[node_recent][region]' => 'sidebar_first', + ); + $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); + + // Set block title and variables. + $block = array( + 'title' => $this->randomName(), + 'node_recent_block_count' => 2, + ); + $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Test that block is not visible without nodes + $this->drupalGet(''); + $this->assertText(t('No content available.'), t('Block with "No content available." found.')); + + // Add some test nodes. + $default_settings = array('uid' => $this->web_user->uid, 'type' => 'article'); + $node1 = $this->drupalCreateNode($default_settings); + $node2 = $this->drupalCreateNode($default_settings); + $node3 = $this->drupalCreateNode($default_settings); + + // Change the changed time for node so that we can test ordering. + db_update('node') + ->fields(array( + 'changed' => $node1->changed + 100, + )) + ->condition('nid', $node2->nid) + ->execute(); + db_update('node') + ->fields(array( + 'changed' => $node1->changed + 200, + )) + ->condition('nid', $node3->nid) + ->execute(); + + // Test that a user without the 'access content' permission cannot + // see the block. + $this->drupalLogout(); + $this->drupalGet(''); + $this->assertNoText($block['title'], t('Block was not found.')); + + // Test that only the 2 latest nodes are shown. + $this->drupalLogin($this->web_user); + $this->assertNoText($node1->title, t('Node not found in block.')); + $this->assertText($node2->title, t('Node found in block.')); + $this->assertText($node3->title, t('Node found in block.')); + + // Check to make sure nodes are in the right order. + $this->assertTrue($this->xpath('//div[@id="block-node-recent"]/div/table/tbody/tr[position() = 1]/td/div/a[text() = "' . $node3->title . '"]'), t('Nodes were ordered correctly in block.')); + + // Set the number of recent nodes to show to 10. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $block = array( + 'node_recent_block_count' => 10, + ); + $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); + + // Post an additional node. + $node4 = $this->drupalCreateNode($default_settings); + // drupalCreateNode() does not automatically flush content caches unlike + // posting a node from a node form. + cache_clear_all(); + + // Test that all four nodes are shown. + $this->drupalGet(''); + $this->assertText($node1->title, t('Node found in block.')); + $this->assertText($node2->title, t('Node found in block.')); + $this->assertText($node3->title, t('Node found in block.')); + $this->assertText($node4->title, t('Node found in block.')); + + // Create the custom block. + $custom_block = array(); + $custom_block['info'] = $this->randomName(); + $custom_block['title'] = $this->randomName(); + $custom_block['types[article]'] = TRUE; + $custom_block['body[value]'] = $this->randomName(32); + $custom_block['regions[' . variable_get('theme_default', 'stark') . ']'] = 'content'; + if ($admin_theme = variable_get('admin_theme')) { + $custom_block['regions[' . $admin_theme . ']'] = 'content'; + } + $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); + + $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); + $this->assertTrue($bid, t('Custom block with visibility rule was created.')); + + // Verify visibility rules. + $this->drupalGet(''); + $this->assertNoText($custom_block['title'], t('Block was displayed on the front page.')); + $this->drupalGet('node/add/article'); + $this->assertText($custom_block['title'], t('Block was displayed on the node/add/article page.')); + $this->drupalGet('node/' . $node1->nid); + $this->assertText($custom_block['title'], t('Block was displayed on the node/N.')); + + // Delete the created custom block & verify that it's been deleted. + $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete')); + $bid = db_query("SELECT 1 FROM {block_node_type} WHERE module = 'block' AND delta = :delta", array(':delta' => $bid))->fetchField(); + $this->assertFalse($bid, t('Custom block was deleted.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3a7229308c75bec1520ff03d01d4f0e34095fa41 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockTest.php @@ -0,0 +1,38 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeBlockTest. + */ + +namespace Drupal\node\Tests; + +class NodeBlockTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Block availability', + 'description' => 'Check if the syndicate block is available.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Create and login user + $admin_user = $this->drupalCreateUser(array('administer blocks')); + $this->drupalLogin($admin_user); + } + + function testSearchFormBlock() { + // Set block title to confirm that the interface is available. + $this->drupalPost('admin/structure/block/manage/node/syndicate/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 available. + $edit = array(); + $edit['blocks[node_syndicate][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.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2b0dbebb1ee680844ac424b4960e490b4c935629 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeBuildContentTest.php @@ -0,0 +1,36 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeBuildContentTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test to ensure that a node's content is always rebuilt. + */ +class NodeBuildContentTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Rebuild content', + 'description' => 'Test the rebuilding of content for different build modes.', + 'group' => 'Node', + ); + } + + /** + * Test to ensure that a node's content array is rebuilt on every call to node_build_content(). + */ + function testNodeRebuildContent() { + $node = $this->drupalCreateNode(); + + // Set a property in the content array so we can test for its existence later on. + $node->content['test_content_property'] = array('#value' => $this->randomString()); + $content = node_build_content($node); + + // If the property doesn't exist it means the node->content was rebuilt. + $this->assertFalse(isset($content['test_content_property']), t('Node content was emptied prior to being built.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f66b2acecdaf1a069a159a0b37e15d77a4410f52 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeCreationTest.php @@ -0,0 +1,89 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeCreationTest. + */ + +namespace Drupal\node\Tests; + +use Drupal\Core\Database\Database; +use Exception; + +class NodeCreationTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node creation', + 'description' => 'Create a node and test saving it.', + 'group' => 'Node', + ); + } + + function setUp() { + // Enable dummy module that implements hook_node_insert for exceptions. + parent::setUp(array('node_test_exception', 'dblog')); + + $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); + $this->drupalLogin($web_user); + } + + /** + * Create a "Basic page" node and verify its consistency in the database. + */ + function testNodeCreation() { + // Create a node. + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit["title"] = $this->randomName(8); + $edit["body[$langcode][0][value]"] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the Basic page has been created. + $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), t('Basic page created.')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->assertTrue($node, t('Node found in database.')); + } + + /** + * Create a page node and verify that a transaction rolls back the failed creation + */ + function testFailedPageCreation() { + // Create a node. + $edit = array( + 'uid' => $this->loggedInUser->uid, + 'name' => $this->loggedInUser->name, + 'type' => 'page', + 'langcode' => LANGUAGE_NOT_SPECIFIED, + 'title' => 'testing_transaction_exception', + ); + + try { + entity_create('node', $edit)->save(); + $this->fail(t('Expected exception has not been thrown.')); + } + catch (Exception $e) { + $this->pass(t('Expected exception has been thrown.')); + } + + if (Database::getConnection()->supportsTransactions()) { + // Check that the node does not exist in the database. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertFalse($node, t('Transactions supported, and node not found in database.')); + } + else { + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertTrue($node, t('Transactions not supported, and node found in database.')); + + // Check that the failed rollback was logged. + $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); + $this->assertTrue(count($records) > 0, t('Transactions not supported, and rollback error logged to watchdog.')); + } + + // Check that the rollback error was logged. + $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll(); + $this->assertTrue(count($records) > 0, t('Rollback explanatory error logged to watchdog.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeEntityFieldQueryAlterTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeEntityFieldQueryAlterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5e3f0b8f6e303c8961b4f9c9f03dfdd2bac14876 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeEntityFieldQueryAlterTest.php @@ -0,0 +1,72 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeEntityFieldQueryAlterTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests node_query_entity_field_access_alter(). + */ +class NodeEntityFieldQueryAlterTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Node entity query alter', + 'description' => 'Test that node access entity queries are properly altered by the node module.', + 'group' => 'Node', + ); + } + + /** + * User with permission to view content. + */ + protected $accessUser; + + /** + * User without permission to view content. + */ + protected $noAccessUser; + + function setUp() { + parent::setUp('node_access_test'); + node_access_rebuild(); + + // Creating 4 nodes with an entity field so we can test that sort of query + // alter. All field values starts with 'A' so we can identify and fetch them + // in the node_access_test module. + $settings = array('langcode' => LANGUAGE_NOT_SPECIFIED); + for ($i = 0; $i < 4; $i++) { + $body = array( + 'value' => 'A' . $this->randomName(32), + 'format' => filter_default_format(), + ); + $settings['body'][LANGUAGE_NOT_SPECIFIED][0] = $body; + $this->drupalCreateNode($settings); + } + + // Create user with simple node access permission. The 'node test view' + // permission is implemented and granted by the node_access_test module. + $this->accessUser = $this->drupalCreateUser(array('access content', 'node test view')); + $this->noAccessUser = $this->drupalCreateUser(array('access content')); + } + + /** + * Tests that node access permissions are followed. + */ + function testNodeQueryAlterWithUI() { + // Verify that a user with access permission can see at least one node. + $this->drupalLogin($this->accessUser); + $this->drupalGet('node_access_entity_test_page'); + $this->assertText('Yes, 4 nodes', "4 nodes were found for access user"); + $this->assertNoText('Exception', "No database exception"); + + // Verify that a user with no access permission cannot see nodes. + $this->drupalLogin($this->noAccessUser); + $this->drupalGet('node_access_entity_test_page'); + $this->assertText('No nodes', "No nodes were found for no access user"); + $this->assertNoText('Exception', "No database exception"); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ca660e4453965107456b4c0644322635bd58a83f --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeFeedTest.php @@ -0,0 +1,31 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeFeedTest. + */ + +namespace Drupal\node\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test the node_feed() functionality. + */ +class NodeFeedTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Node feed', + 'description' => 'Ensures that node_feed() functions correctly.', + 'group' => 'Node', + ); + } + + /** + * Ensure that node_feed accepts and prints extra channel elements. + */ + function testNodeFeedExtraChannelElements() { + $response = node_feed(array(), array('copyright' => 'Drupal is a registered trademark of Dries Buytaert.')); + $this->assertTrue(strpos($response->getContent(), '<copyright>Drupal is a registered trademark of Dries Buytaert.</copyright>') !== FALSE); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeLoadHooksTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeLoadHooksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4e94992c7b460ae4aae4428d97bf8aa0e150148a --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeLoadHooksTest.php @@ -0,0 +1,52 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeLoadHooksTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests for the hooks invoked during node_load(). + */ +class NodeLoadHooksTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node load hooks', + 'description' => 'Test the hooks invoked when a node is being loaded.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp('node_test'); + } + + /** + * Tests that hook_node_load() is invoked correctly. + */ + function testHookNodeLoad() { + // Create some sample articles and pages. + $node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED)); + $node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED)); + $node3 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_NOT_PUBLISHED)); + $node4 = $this->drupalCreateNode(array('type' => 'page', 'status' => NODE_NOT_PUBLISHED)); + + // Check that when a set of nodes that only contains articles is loaded, + // the properties added to the node by node_test_load_node() correctly + // reflect the expected values. + $nodes = node_load_multiple(array(), array('status' => NODE_PUBLISHED)); + $loaded_node = end($nodes); + $this->assertEqual($loaded_node->node_test_loaded_nids, array($node1->nid, $node2->nid), t('hook_node_load() received the correct list of node IDs the first time it was called.')); + $this->assertEqual($loaded_node->node_test_loaded_types, array('article'), t('hook_node_load() received the correct list of node types the first time it was called.')); + + // Now, as part of the same page request, load a set of nodes that contain + // both articles and pages, and make sure the parameters passed to + // node_test_node_load() are correctly updated. + $nodes = node_load_multiple(array(), array('status' => NODE_NOT_PUBLISHED)); + $loaded_node = end($nodes); + $this->assertEqual($loaded_node->node_test_loaded_nids, array($node3->nid, $node4->nid), t('hook_node_load() received the correct list of node IDs the second time it was called.')); + $this->assertEqual($loaded_node->node_test_loaded_types, array('article', 'page'), t('hook_node_load() received the correct list of node types the second time it was called.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeLoadMultipleTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeLoadMultipleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d08553ca66a8ecc32c764cfe90b20e415b900573 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeLoadMultipleTest.php @@ -0,0 +1,88 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeLoadMultipleTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests the node_load_multiple() function. + */ +class NodeLoadMultipleTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Load multiple nodes', + 'description' => 'Test the loading of multiple nodes.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + $web_user = $this->drupalCreateUser(array('create article content', 'create page content')); + $this->drupalLogin($web_user); + } + + /** + * Creates four nodes and ensures that they are loaded correctly. + */ + function testNodeMultipleLoad() { + $node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + $node3 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 0)); + $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0)); + + // Confirm that promoted nodes appear in the default node listing. + $this->drupalGet('node'); + $this->assertText($node1->title, t('Node title appears on the default listing.')); + $this->assertText($node2->title, t('Node title appears on the default listing.')); + $this->assertNoText($node3->title, t('Node title does not appear in the default listing.')); + $this->assertNoText($node4->title, t('Node title does not appear in the default listing.')); + + // Load nodes with only a condition. Nodes 3 and 4 will be loaded. + $nodes = node_load_multiple(FALSE, array('promote' => 0)); + $this->assertEqual($node3->title, $nodes[$node3->nid]->title, t('Node was loaded.')); + $this->assertEqual($node4->title, $nodes[$node4->nid]->title, t('Node was loaded.')); + $count = count($nodes); + $this->assertTrue($count == 2, t('@count nodes loaded.', array('@count' => $count))); + + // Load nodes by nid. Nodes 1, 2 and 4 will be loaded. + $nodes = node_load_multiple(array(1, 2, 4)); + $count = count($nodes); + $this->assertTrue(count($nodes) == 3, t('@count nodes loaded', array('@count' => $count))); + $this->assertTrue(isset($nodes[$node1->nid]), t('Node is correctly keyed in the array')); + $this->assertTrue(isset($nodes[$node2->nid]), t('Node is correctly keyed in the array')); + $this->assertTrue(isset($nodes[$node4->nid]), t('Node is correctly keyed in the array')); + foreach ($nodes as $node) { + $this->assertTrue(is_object($node), t('Node is an object')); + } + + // Load nodes by nid, where type = article. Nodes 1, 2 and 3 will be loaded. + $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article')); + $count = count($nodes); + $this->assertTrue($count == 3, t('@count nodes loaded', array('@count' => $count))); + $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded.')); + $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded.')); + $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.')); + $this->assertFalse(isset($nodes[$node4->nid])); + + // Now that all nodes have been loaded into the static cache, ensure that + // they are loaded correctly again when a condition is passed. + $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article')); + $count = count($nodes); + $this->assertTrue($count == 3, t('@count nodes loaded.', array('@count' => $count))); + $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded')); + $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded')); + $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded')); + $this->assertFalse(isset($nodes[$node4->nid]), t('Node was not loaded')); + + // Load nodes by nid, where type = article and promote = 0. + $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article', 'promote' => 0)); + $count = count($nodes); + $this->assertTrue($count == 1, t('@count node loaded', array('@count' => $count))); + $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9ce688a603c96ccdee1c5da1228863c810db5e17 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodePostSettingsTest.php @@ -0,0 +1,73 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodePostSettingsTest. + */ + +namespace Drupal\node\Tests; + +/** + * Check that the post information displays when enabled for a content type. + */ +class NodePostSettingsTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node post information display', + 'description' => 'Check that the post information (submitted by Username on date) text displays appropriately.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + $web_user = $this->drupalCreateUser(array('create page content', 'administer content types', 'access user profiles')); + $this->drupalLogin($web_user); + } + + /** + * Set "Basic page" content type to display post information and confirm its presence on a new node. + */ + function testPagePostInfo() { + + // Set "Basic page" content type to display post information. + $edit = array(); + $edit['node_submitted'] = TRUE; + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + + // Create a node. + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit["title"] = $this->randomName(8); + $edit["body[$langcode][0][value]"] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the post information is displayed. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $elements = $this->xpath('//*[contains(@class,:class)]', array(':class' => 'submitted')); + $this->assertEqual(count($elements), 1, t('Post information is displayed.')); + } + + /** + * Set "Basic page" content type to not display post information and confirm its absence on a new node. + */ + function testPageNotPostInfo() { + + // Set "Basic page" content type to display post information. + $edit = array(); + $edit['node_submitted'] = FALSE; + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + + // Create a node. + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit["title"] = $this->randomName(8); + $edit["body[$langcode][0][value]"] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the post information is displayed. + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->assertNoRaw('<span class="submitted">', t('Post information is not displayed.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeQueryAlterTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeQueryAlterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..dc6d95aba5b94a792572b59ba1b64d1a037ede52 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeQueryAlterTest.php @@ -0,0 +1,200 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeQueryAlterTest. + */ + +namespace Drupal\node\Tests; + +use Exception; + +/** + * Tests node_query_node_access_alter(). + */ +class NodeQueryAlterTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Node query alter', + 'description' => 'Test that node access queries are properly altered by the node module.', + 'group' => 'Node', + ); + } + + /** + * User with permission to view content. + */ + protected $accessUser; + + /** + * User without permission to view content. + */ + protected $noAccessUser; + + function setUp() { + parent::setUp('node_access_test'); + node_access_rebuild(); + + // Create some content. + $this->drupalCreateNode(); + $this->drupalCreateNode(); + $this->drupalCreateNode(); + $this->drupalCreateNode(); + + // Create user with simple node access permission. The 'node test view' + // permission is implemented and granted by the node_access_test module. + $this->accessUser = $this->drupalCreateUser(array('access content', 'node test view')); + $this->noAccessUser = $this->drupalCreateUser(array('access content')); + $this->noAccessUser2 = $this->drupalCreateUser(array('access content')); + } + + /** + * Tests that node access permissions are followed. + */ + function testNodeQueryAlterWithUI() { + // Verify that a user with access permission can see at least one node. + $this->drupalLogin($this->accessUser); + $this->drupalGet('node_access_test_page'); + $this->assertText('Yes, 4 nodes', "4 nodes were found for access user"); + $this->assertNoText('Exception', "No database exception"); + + // Verify that a user with no access permission cannot see nodes. + $this->drupalLogin($this->noAccessUser); + $this->drupalGet('node_access_test_page'); + $this->assertText('No nodes', "No nodes were found for no access user"); + $this->assertNoText('Exception', "No database exception"); + } + + /** + * Lower-level test of 'node_access' query alter, for user with access. + * + * Verifies that a non-standard table alias can be used, and that a + * user with node access can view the nodes. + */ + function testNodeQueryAlterLowLevelWithAccess() { + // User with access should be able to view 4 nodes. + try { + $query = db_select('node', 'mytab') + ->fields('mytab'); + $query->addTag('node_access'); + $query->addMetaData('op', 'view'); + $query->addMetaData('account', $this->accessUser); + + $result = $query->execute()->fetchAll(); + $this->assertEqual(count($result), 4, t('User with access can see correct nodes')); + } + catch (Exception $e) { + $this->fail(t('Altered query is malformed')); + } + } + + /** + * Lower-level test of 'node_access' query alter, for user without access. + * + * Verifies that a non-standard table alias can be used, and that a + * user without node access cannot view the nodes. + */ + function testNodeQueryAlterLowLevelNoAccess() { + // User without access should be able to view 0 nodes. + try { + $query = db_select('node', 'mytab') + ->fields('mytab'); + $query->addTag('node_access'); + $query->addMetaData('op', 'view'); + $query->addMetaData('account', $this->noAccessUser); + + $result = $query->execute()->fetchAll(); + $this->assertEqual(count($result), 0, t('User with no access cannot see nodes')); + } + catch (Exception $e) { + $this->fail(t('Altered query is malformed')); + } + } + + /** + * Lower-level test of 'node_access' query alter, for edit access. + * + * Verifies that a non-standard table alias can be used, and that a + * user with view-only node access cannot edit the nodes. + */ + function testNodeQueryAlterLowLevelEditAccess() { + // User with view-only access should not be able to edit nodes. + try { + $query = db_select('node', 'mytab') + ->fields('mytab'); + $query->addTag('node_access'); + $query->addMetaData('op', 'update'); + $query->addMetaData('account', $this->accessUser); + + $result = $query->execute()->fetchAll(); + $this->assertEqual(count($result), 0, t('User with view-only access cannot edit nodes')); + } + catch (Exception $e) { + $this->fail($e->getMessage()); + $this->fail((string) $query); + $this->fail(t('Altered query is malformed')); + } + } + + /** + * Lower-level test of 'node_access' query alter override. + * + * Verifies that node_access_view_all_nodes() is called from + * node_query_node_access_alter(). We do this by checking that + * a user which normally would not have view privileges is able + * to view the nodes when we add a record to {node_access} paired + * with a corresponding privilege in hook_node_grants(). + */ + function testNodeQueryAlterOverride() { + $record = array( + 'nid' => 0, + 'gid' => 0, + 'realm' => 'node_access_all', + 'grant_view' => 1, + 'grant_update' => 0, + 'grant_delete' => 0, + ); + drupal_write_record('node_access', $record); + + // Test that the noAccessUser still doesn't have the 'view' + // privilege after adding the node_access record. + drupal_static_reset('node_access_view_all_nodes'); + try { + $query = db_select('node', 'mytab') + ->fields('mytab'); + $query->addTag('node_access'); + $query->addMetaData('op', 'view'); + $query->addMetaData('account', $this->noAccessUser); + + $result = $query->execute()->fetchAll(); + $this->assertEqual(count($result), 0, t('User view privileges are not overridden')); + } + catch (Exception $e) { + $this->fail(t('Altered query is malformed')); + } + + // Have node_test_node_grants return a node_access_all privilege, + // to grant the noAccessUser 'view' access. To verify that + // node_access_view_all_nodes is properly checking the specified + // $account instead of the global $user, we will log in as + // noAccessUser2. + $this->drupalLogin($this->noAccessUser2); + variable_set('node_test_node_access_all_uid', $this->noAccessUser->uid); + drupal_static_reset('node_access_view_all_nodes'); + try { + $query = db_select('node', 'mytab') + ->fields('mytab'); + $query->addTag('node_access'); + $query->addMetaData('op', 'view'); + $query->addMetaData('account', $this->noAccessUser); + + $result = $query->execute()->fetchAll(); + $this->assertEqual(count($result), 4, t('User view privileges are overridden')); + } + catch (Exception $e) { + $this->fail(t('Altered query is malformed')); + } + variable_del('node_test_node_access_all_uid'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8577b2bf70fbadffa2302d51aab30c8e718d0d8a --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRSSContentTest.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeRSSContentTest. + */ + +namespace Drupal\node\Tests; + +/** + * Ensure that data added to nodes by other modules appears in RSS feeds. + * + * Create a node, enable the node_test module to ensure that extra data is + * added to the node->content array, then verify that the data appears on the + * sitewide RSS feed at rss.xml. + */ +class NodeRSSContentTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node RSS Content', + 'description' => 'Ensure that data added to nodes by other modules appears in RSS feeds.', + 'group' => 'Node', + ); + } + + function setUp() { + // Enable dummy module that implements hook_node_view. + parent::setUp('node_test'); + + // Use bypass node access permission here, because the test class uses + // hook_grants_alter() to deny access to everyone on node_access + // queries. + $user = $this->drupalCreateUser(array('bypass node access', 'access content', 'create article content')); + $this->drupalLogin($user); + } + + /** + * Create a new node and ensure that it includes the custom data when added + * to an RSS feed. + */ + function testNodeRSSContent() { + // Create a node. + $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); + + $this->drupalGet('rss.xml'); + + // Check that content added in 'rss' view mode appear in RSS feed. + $rss_only_content = t('Extra data that should appear only in the RSS feed for node !nid.', array('!nid' => $node->nid)); + $this->assertText($rss_only_content, t('Node content designated for RSS appear in RSS feed.')); + + // Check that content added in view modes other than 'rss' doesn't + // appear in RSS feed. + $non_rss_content = t('Extra data that should appear everywhere except the RSS feed for node !nid.', array('!nid' => $node->nid)); + $this->assertNoText($non_rss_content, t('Node content not designed for RSS doesn\'t appear in RSS feed.')); + + // Check that extra RSS elements and namespaces are added to RSS feed. + $test_element = array( + 'key' => 'testElement', + 'value' => t('Value of testElement RSS element for node !nid.', array('!nid' => $node->nid)), + ); + $test_ns = 'xmlns:drupaltest="http://example.com/test-namespace"'; + $this->assertRaw(format_xml_elements(array($test_element)), t('Extra RSS elements appear in RSS feed.')); + $this->assertRaw($test_ns, t('Extra namespaces appear in RSS feed.')); + + // Check that content added in 'rss' view mode doesn't appear when + // viewing node. + $this->drupalGet("node/$node->nid"); + $this->assertNoText($rss_only_content, t('Node content designed for RSS doesn\'t appear when viewing node.')); + + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7156e10917eb2196c859229f70238c7896754078 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionPermissionsTest.php @@ -0,0 +1,106 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeRevisionPermissionsTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests user permissions for node revisions. + */ +class NodeRevisionPermissionsTest extends NodeTestBase { + protected $node_revisions = array(); + protected $accounts = array(); + + // Map revision permission names to node revision access ops. + protected $map = array( + 'view' => 'view revisions', + 'update' => 'revert revisions', + 'delete' => 'delete revisions', + ); + + public static function getInfo() { + return array( + 'name' => 'Node revision permissions', + 'description' => 'Tests user permissions for node revision operations.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + // Create a node with several revisions. + $node = $this->drupalCreateNode(); + $this->node_revisions[] = $node; + + for ($i = 0; $i < 3; $i++) { + // Create a revision for the same nid and settings with a random log. + $revision = clone $node; + $revision->revision = 1; + $revision->log = $this->randomName(32); + node_save($revision); + $this->node_revisions[] = $revision; + } + + // Create three users, one with each revision permission. + foreach ($this->map as $op => $permission) { + // Create the user. + $account = $this->drupalCreateUser( + array( + 'access content', + 'edit any page content', + 'delete any page content', + $permission, + ) + ); + $account->op = $op; + $this->accounts[] = $account; + } + + // Create an admin account (returns TRUE for all revision permissions). + $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes')); + $admin_account->is_admin = TRUE; + $this->accounts['admin'] = $admin_account; + + // Create a normal account (returns FALSE for all revision permissions). + $normal_account = $this->drupalCreateUser(); + $normal_account->op = FALSE; + $this->accounts[] = $normal_account; + } + + /** + * Tests the _node_revision_access() function. + */ + function testNodeRevisionAccess() { + $revision = $this->node_revisions[1]; + + $parameters = array( + 'op' => array_keys($this->map), + 'account' => $this->accounts, + ); + + $permutations = $this->generatePermutations($parameters); + foreach ($permutations as $case) { + if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) { + $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted."); + } + else { + $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted."); + } + } + + // Test that access is FALSE for a node administrator with an invalid $node + // or $op parameters. + $admin_account = $this->accounts['admin']; + $this->assertFalse(_node_revision_access($revision, 'invalid-op', $admin_account), '_node_revision_access() returns FALSE with an invalid op.'); + + // Test that the $account parameter defaults to the "logged in" user. + $original_user = $GLOBALS['user']; + $GLOBALS['user'] = $admin_account; + $this->assertTrue(_node_revision_access($revision, 'view'), '_node_revision_access() returns TRUE when used with global user.'); + $GLOBALS['user'] = $original_user; + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c7f29faac66f4f16781e65be4e4b7bf449869ff9 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeRevisionsTest.php @@ -0,0 +1,153 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeRevisionsTest. + */ + +namespace Drupal\node\Tests; + +class NodeRevisionsTest extends NodeTestBase { + protected $nodes; + protected $logs; + + public static function getInfo() { + return array( + 'name' => 'Node revisions', + 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + // Create and login user. + $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content', + 'delete revisions', 'delete any page content')); + $this->drupalLogin($web_user); + + // Create initial node. + $node = $this->drupalCreateNode(); + $settings = get_object_vars($node); + $settings['revision'] = 1; + + $nodes = array(); + $logs = array(); + + // Get original node. + $nodes[] = $node; + + // Create three revisions. + $revision_count = 3; + for ($i = 0; $i < $revision_count; $i++) { + $logs[] = $settings['log'] = $this->randomName(32); + + // Create revision with random title and body and update variables. + $this->drupalCreateNode($settings); + $node = node_load($node->nid); // Make sure we get revision information. + $settings = get_object_vars($node); + + $nodes[] = $node; + } + + $this->nodes = $nodes; + $this->logs = $logs; + } + + /** + * Check node revision related operations. + */ + function testRevisions() { + $nodes = $this->nodes; + $logs = $this->logs; + + // Get last node for simple checks. + $node = $nodes[3]; + + // Confirm the correct revision text appears on "view revisions" page. + $this->drupalGet("node/$node->nid/revisions/$node->vid/view"); + $this->assertText($node->body[LANGUAGE_NOT_SPECIFIED][0]['value'], t('Correct text displays for version.')); + + // Confirm the correct log message appears on "revisions overview" page. + $this->drupalGet("node/$node->nid/revisions"); + foreach ($logs as $log) { + $this->assertText($log, t('Log message found.')); + } + + // Confirm that revisions revert properly. + $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert')); + $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', + array('@type' => 'Basic page', '%title' => $nodes[1]->title, + '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.')); + $reverted_node = node_load($node->nid); + $this->assertTrue(($nodes[1]->body[LANGUAGE_NOT_SPECIFIED][0]['value'] == $reverted_node->body[LANGUAGE_NOT_SPECIFIED][0]['value']), t('Node reverted correctly.')); + + // Confirm revisions delete properly. + $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); + $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', + array('%revision-date' => format_date($nodes[1]->revision_timestamp), + '@type' => 'Basic page', '%title' => $nodes[1]->title)), t('Revision deleted.')); + $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, t('Revision not found.')); + + // Set the revision timestamp to an older date to make sure that the + // confirmation message correctly displays the stored revision date. + $old_revision_date = REQUEST_TIME - 86400; + db_update('node_revision') + ->condition('vid', $nodes[2]->vid) + ->fields(array( + 'timestamp' => $old_revision_date, + )) + ->execute(); + $this->drupalPost("node/$node->nid/revisions/{$nodes[2]->vid}/revert", array(), t('Revert')); + $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', array( + '@type' => 'Basic page', + '%title' => $nodes[2]->title, + '%revision-date' => format_date($old_revision_date), + ))); + } + + /** + * Checks that revisions are correctly saved without log messages. + */ + function testNodeRevisionWithoutLogMessage() { + // Create a node with an initial log message. + $log = $this->randomName(10); + $node = $this->drupalCreateNode(array('log' => $log)); + + // Save over the same revision and explicitly provide an empty log message + // (for example, to mimic the case of a node form submitted with no text in + // the "log message" field), and check that the original log message is + // preserved. + $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage1'; + + $node = clone $node; + $node->title = $new_title; + $node->log = ''; + $node->revision = FALSE; + + $node->save(); + $this->drupalGet('node/' . $node->nid); + $this->assertText($new_title, t('New node title appears on the page.')); + $node_revision = node_load($node->nid, NULL, TRUE); + $this->assertEqual($node_revision->log, $log, t('After an existing node revision is re-saved without a log message, the original log message is preserved.')); + + // Create another node with an initial log message. + $node = $this->drupalCreateNode(array('log' => $log)); + + // Save a new node revision without providing a log message, and check that + // this revision has an empty log message. + $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage2'; + + $node = clone $node; + $node->title = $new_title; + $node->revision = TRUE; + $node->log = NULL; + + $node->save(); + $this->drupalGet('node/' . $node->nid); + $this->assertText($new_title, 'New node title appears on the page.'); + $node_revision = node_load($node->nid, NULL, TRUE); + $this->assertTrue(empty($node_revision->log), 'After a new node revision is saved with an empty log message, the log message for the node is empty.'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3bf6a19bf54e4d44eced27f1ddd8e89300d2c807 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeSaveTest.php @@ -0,0 +1,151 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeSaveTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test case to check node save related functionality, including import-save + */ +class NodeSaveTest extends NodeTestBase { + + public static function getInfo() { + return array( + 'name' => 'Node save', + 'description' => 'Test $node->save() for saving content.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp('node_test'); + // Create a user that is allowed to post; we'll use this to test the submission. + $web_user = $this->drupalCreateUser(array('create article content')); + $this->drupalLogin($web_user); + $this->web_user = $web_user; + } + + /** + * Import test, to check if custom node ids are saved properly. + * Workflow: + * - first create a piece of content + * - save the content + * - check if node exists + */ + function testImport() { + // Node ID must be a number that is not in the database. + $max_nid = db_query('SELECT MAX(nid) FROM {node}')->fetchField(); + $test_nid = $max_nid + mt_rand(1000, 1000000); + $title = $this->randomName(8); + $node = array( + 'title' => $title, + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32)))), + 'uid' => $this->web_user->uid, + 'type' => 'article', + 'nid' => $test_nid, + 'enforceIsNew' => TRUE, + ); + $node = node_submit(entity_create('node', $node)); + + // Verify that node_submit did not overwrite the user ID. + $this->assertEqual($node->uid, $this->web_user->uid, t('Function node_submit() preserves user ID')); + + $node->save(); + // Test the import. + $node_by_nid = node_load($test_nid); + $this->assertTrue($node_by_nid, t('Node load by node ID.')); + + $node_by_title = $this->drupalGetNodeByTitle($title); + $this->assertTrue($node_by_title, t('Node load by node title.')); + } + + /** + * Check that the "created" and "changed" timestamps are set correctly when + * saving a new node or updating an existing node. + */ + function testTimestamps() { + // Use the default timestamps. + $edit = array( + 'uid' => $this->web_user->uid, + 'type' => 'article', + 'title' => $this->randomName(8), + ); + + entity_create('node', $edit)->save(); + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertEqual($node->created, REQUEST_TIME, t('Creating a node sets default "created" timestamp.')); + $this->assertEqual($node->changed, REQUEST_TIME, t('Creating a node sets default "changed" timestamp.')); + + // Store the timestamps. + $created = $node->created; + $changed = $node->changed; + + $node->save(); + $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); + $this->assertEqual($node->created, $created, t('Updating a node preserves "created" timestamp.')); + + // Programmatically set the timestamps using hook_node_presave. + $node->title = 'testing_node_presave'; + + $node->save(); + $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE); + $this->assertEqual($node->created, 280299600, t('Saving a node uses "created" timestamp set in presave hook.')); + $this->assertEqual($node->changed, 979534800, t('Saving a node uses "changed" timestamp set in presave hook.')); + + // Programmatically set the timestamps on the node. + $edit = array( + 'uid' => $this->web_user->uid, + 'type' => 'article', + 'title' => $this->randomName(8), + 'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT + 'changed' => 979534800, // Drupal 1.0 release. + ); + + entity_create('node', $edit)->save(); + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertEqual($node->created, 280299600, t('Creating a node uses user-set "created" timestamp.')); + $this->assertNotEqual($node->changed, 979534800, t('Creating a node doesn\'t use user-set "changed" timestamp.')); + + // Update the timestamps. + $node->created = 979534800; + $node->changed = 280299600; + + $node->save(); + $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); + $this->assertEqual($node->created, 979534800, t('Updating a node uses user-set "created" timestamp.')); + $this->assertNotEqual($node->changed, 280299600, t('Updating a node doesn\'t use user-set "changed" timestamp.')); + } + + /** + * Tests determing changes in hook_node_presave() and verifies the static node + * load cache is cleared upon save. + */ + function testDeterminingChanges() { + // Initial creation. + $node = entity_create('node', array( + 'uid' => $this->web_user->uid, + 'type' => 'article', + 'title' => 'test_changes', + )); + $node->save(); + + // Update the node without applying changes. + $node->save(); + $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.'); + + // Apply changes. + $node->title = 'updated'; + $node->save(); + + // The hook implementations node_test_node_presave() and + // node_test_node_update() determine changes and change the title. + $this->assertEqual($node->title, 'updated_presave_update', 'Changes have been determined.'); + + // Test the static node load cache to be cleared. + $node = node_load($node->nid); + $this->assertEqual($node->title, 'updated_presave', 'Static cache has been cleared.'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..6e16adba7d8681153d01f42c8d4670bdc1c4a024 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTestBase.php @@ -0,0 +1,27 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTestBase. + */ + +namespace Drupal\node\Tests; + +use Drupal\simpletest\WebTestBase; + +class NodeTestBase extends WebTestBase { + function setUp() { + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'node'; + parent::setUp($modules); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a195731df40e5c303c0d6bb64b21e366a60b4cf4 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTitleTest.php @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTitleTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test node title. + */ +class NodeTitleTest extends NodeTestBase { + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Node title', + 'description' => 'Test node title.', + 'group' => 'Node' + ); + } + + function setUp() { + parent::setUp(array('comment')); + $this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content', 'post comments')); + $this->drupalLogin($this->admin_user); + } + + /** + * Create one node and test if the node title has the correct value. + */ + function testNodeTitle() { + // Create "Basic page" content with title. + // Add the node to the frontpage so we can test if teaser links are clickable. + $settings = array( + 'title' => $this->randomName(8), + 'promote' => 1, + ); + $node = $this->drupalCreateNode($settings); + + // Test <title> tag. + $this->drupalGet("node/$node->nid"); + $xpath = '//title'; + $this->assertEqual(current($this->xpath($xpath)), $node->title .' | Drupal', 'Page title is equal to node title.', 'Node'); + + // Test breadcrumb in comment preview. + $this->drupalGet("comment/reply/$node->nid"); + $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a'; + $this->assertEqual(current($this->xpath($xpath)), $node->title, 'Node breadcrumb is equal to node title.', 'Node'); + + // Test node title in comment preview. + $this->assertEqual(current($this->xpath('//article[@id=:id]/h2/a', array(':id' => 'node-' . $node->nid))), $node->title, 'Node preview title is equal to node title.', 'Node'); + + // Test node title is clickable on teaser list (/node). + $this->drupalGet('node'); + $this->clickLink($node->title); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTitleXSSTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTitleXSSTest.php new file mode 100644 index 0000000000000000000000000000000000000000..610c41ff9f5a5cff327d4abe4dc477b51419f7b7 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTitleXSSTest.php @@ -0,0 +1,42 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTitleXSSTest. + */ + +namespace Drupal\node\Tests; + +class NodeTitleXSSTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node title XSS filtering', + 'description' => 'Create a node with dangerous tags in its title and test that they are escaped.', + 'group' => 'Node', + ); + } + + function testNodeTitleXSS() { + // Prepare a user to do the stuff. + $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); + $this->drupalLogin($web_user); + + $xss = '<script>alert("xss")</script>'; + $title = $xss . $this->randomName(); + $edit = array("title" => $title); + + $this->drupalPost('node/add/page', $edit, t('Preview')); + $this->assertNoRaw($xss, t('Harmful tags are escaped when previewing a node.')); + + $settings = array('title' => $title); + $node = $this->drupalCreateNode($settings); + + $this->drupalGet('node/' . $node->nid); + // assertTitle() decodes HTML-entities inside the <title> element. + $this->assertTitle($edit["title"] . ' | Drupal', t('Title is diplayed when viewing a node.')); + $this->assertNoRaw($xss, t('Harmful tags are escaped when viewing a node.')); + + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertNoRaw($xss, t('Harmful tags are escaped when editing a node.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9d985505afd3558c6c62795fa21137e76d4bad8e --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTokenReplaceTest.php @@ -0,0 +1,85 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTokenReplaceTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test node token replacement in strings. + */ +class NodeTokenReplaceTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node token replacement', + 'description' => 'Generates text using placeholders for dummy content to check node token replacement.', + 'group' => 'Node', + ); + } + + /** + * Creates a node, then tests the tokens generated from it. + */ + function testNodeTokenReplacement() { + $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); + $url_options = array( + 'absolute' => TRUE, + 'language' => $language_interface, + ); + + // Create a user and a node. + $account = $this->drupalCreateUser(); + $settings = array( + 'type' => 'article', + 'uid' => $account->uid, + 'title' => '<blink>Blinking Text</blink>', + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32), 'summary' => $this->randomName(16)))), + ); + $node = $this->drupalCreateNode($settings); + + // Load node so that the body and summary fields are structured properly. + $node = node_load($node->nid); + $instance = field_info_instance('node', 'body', $node->type); + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[node:nid]'] = $node->nid; + $tests['[node:vid]'] = $node->vid; + $tests['[node:tnid]'] = $node->tnid; + $tests['[node:type]'] = 'article'; + $tests['[node:type-name]'] = 'Article'; + $tests['[node:title]'] = check_plain($node->title); + $tests['[node:body]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value'); + $tests['[node:summary]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'summary'); + $tests['[node:langcode]'] = check_plain($node->langcode); + $tests['[node:url]'] = url('node/' . $node->nid, $url_options); + $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options); + $tests['[node:author]'] = check_plain(user_format_name($account)); + $tests['[node:author:uid]'] = $node->uid; + $tests['[node:author:name]'] = check_plain(user_format_name($account)); + $tests['[node:created:since]'] = format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode); + $tests['[node:changed:since]'] = format_interval(REQUEST_TIME - $node->changed, 2, $language_interface->langcode); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Sanitized node token %token replaced.', array('%token' => $input))); + } + + // Generate and test unsanitized tokens. + $tests['[node:title]'] = $node->title; + $tests['[node:body]'] = $node->body[$node->langcode][0]['value']; + $tests['[node:summary]'] = $node->body[$node->langcode][0]['summary']; + $tests['[node:langcode]'] = $node->langcode; + $tests['[node:author:name]'] = user_format_name($account); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $node), array('language' => $language_interface, 'sanitize' => FALSE)); + $this->assertEqual($output, $expected, t('Unsanitized node token %token replaced.', array('%token' => $input))); + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b02bf7d67cf906acd7313a71dea0a1e5c0bf0bac --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypePersistenceTest.php @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTypePersistenceTest. + */ + +namespace Drupal\node\Tests; + +/** + * Test node type customizations persistence. + */ +class NodeTypePersistenceTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node type persist', + 'description' => 'Ensures that node type customization survives module enabling and disabling.', + 'group' => 'Node', + ); + } + + /** + * Test node type customizations persist through disable and uninstall. + */ + function testNodeTypeCustomizationPersistence() { + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); + $this->drupalLogin($web_user); + $poll_key = 'modules[Core][poll][enable]'; + $poll_enable = array($poll_key => "1"); + $poll_disable = array($poll_key => FALSE); + + // Enable poll and verify that the node type is in the DB and is not + // disabled. + $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); + $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); + $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database')); + $this->assertEqual($disabled, 0, t('Poll node type is not disabled')); + + // Check that poll node type (uncustomized) shows up. + $this->drupalGet('node/add'); + $this->assertText('poll', t('poll type is found on node/add')); + + // Customize poll description. + $description = $this->randomName(); + $edit = array('description' => $description); + $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type')); + + // Check that poll node type customization shows up. + $this->drupalGet('node/add'); + $this->assertText($description, t('Customized description found')); + + // Disable poll and check that the node type gets disabled. + $this->drupalPost('admin/modules', $poll_disable, t('Save configuration')); + $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); + $this->assertEqual($disabled, 1, t('Poll node type is disabled')); + $this->drupalGet('node/add'); + $this->assertNoText('poll', t('poll type is not found on node/add')); + + // Reenable poll and check that the customization survived the module + // disable. + $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); + $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); + $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database')); + $this->assertEqual($disabled, 0, t('Poll node type is not disabled')); + $this->drupalGet('node/add'); + $this->assertText($description, t('Customized description found')); + + // Disable and uninstall poll. + $this->drupalPost('admin/modules', $poll_disable, t('Save configuration')); + $edit = array('uninstall[poll]' => 'poll'); + $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); + $this->drupalPost(NULL, array(), t('Uninstall')); + $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); + $this->assertTrue($disabled, t('Poll node type is in the database and is disabled')); + $this->drupalGet('node/add'); + $this->assertNoText('poll', t('poll type is no longer found on node/add')); + + // Reenable poll and check that the customization survived the module + // uninstall. + $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); + $this->drupalGet('node/add'); + $this->assertText($description, t('Customized description is found even after uninstall and reenable.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e84a93081366b9eafe5f5bff17baa1f460c90cb0 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/NodeTypeTest.php @@ -0,0 +1,166 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\NodeTypeTest. + */ + +namespace Drupal\node\Tests; + +/** + * Tests related to node types. + */ +class NodeTypeTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node types', + 'description' => 'Ensures that node type functions work correctly.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(array('field_ui')); + } + + /** + * Ensure that node type functions (node_type_get_*) work correctly. + * + * Load available node types and validate the returned data. + */ + function testNodeTypeGetFunctions() { + $node_types = node_type_get_types(); + $node_names = node_type_get_names(); + + $this->assertTrue(isset($node_types['article']), t('Node type article is available.')); + $this->assertTrue(isset($node_types['page']), t('Node type basic page is available.')); + + $this->assertEqual($node_types['article']->name, $node_names['article'], t('Correct node type base has been returned.')); + + $this->assertEqual($node_types['article'], node_type_load('article'), t('Correct node type has been returned.')); + $this->assertEqual($node_types['article']->name, node_type_get_name('article'), t('Correct node type name has been returned.')); + $this->assertEqual($node_types['page']->base, node_type_get_base('page'), t('Correct node type base has been returned.')); + } + + /** + * Test creating a content type programmatically and via a form. + */ + function testNodeTypeCreation() { + // Create a content type programmaticaly. + $type = $this->drupalCreateContentType(); + + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField(); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + + // Login a test user. + $web_user = $this->drupalCreateUser(array('create ' . $type->name . ' content')); + $this->drupalLogin($web_user); + + $this->drupalGet('node/add/' . $type->type); + $this->assertResponse(200, 'The new content type can be accessed at node/add.'); + + // Create a content type via the user interface. + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $this->drupalLogin($web_user); + $edit = array( + 'name' => 'foo', + 'title_label' => 'title for foo', + 'type' => 'foo', + ); + $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); + $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => 'foo'))->fetchField(); + $this->assertTrue($type_exists, 'The new content type has been created in the database.'); + } + + /** + * Test editing a node type using the UI. + */ + function testNodeTypeEditing() { + $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); + $this->drupalLogin($web_user); + + $instance = field_info_instance('node', 'body', 'page'); + $this->assertEqual($instance['label'], 'Body', t('Body field was found.')); + + // Verify that title and body fields are displayed. + $this->drupalGet('node/add/page'); + $this->assertRaw('Title', t('Title field was found.')); + $this->assertRaw('Body', t('Body field was found.')); + + // Rename the title field. + $edit = array( + 'title_label' => 'Foo', + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + // Refresh the field information for the rest of the test. + field_info_cache_clear(); + + $this->drupalGet('node/add/page'); + $this->assertRaw('Foo', t('New title label was displayed.')); + $this->assertNoRaw('Title', t('Old title label was not displayed.')); + + // Change the name, machine name and description. + $edit = array( + 'name' => 'Bar', + 'type' => 'bar', + 'description' => 'Lorem ipsum.', + ); + $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); + field_info_cache_clear(); + + $this->drupalGet('node/add'); + $this->assertRaw('Bar', t('New name was displayed.')); + $this->assertRaw('Lorem ipsum', t('New description was displayed.')); + $this->clickLink('Bar'); + $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), t('New machine name was used in URL.')); + $this->assertRaw('Foo', t('Title field was found.')); + $this->assertRaw('Body', t('Body field was found.')); + + // Remove the body field. + $this->drupalPost('admin/structure/types/manage/bar/fields/body/delete', array(), t('Delete')); + // Resave the settings for this type. + $this->drupalPost('admin/structure/types/manage/bar', array(), t('Save content type')); + // Check that the body field doesn't exist. + $this->drupalGet('node/add/bar'); + $this->assertNoRaw('Body', t('Body field was not found.')); + } + + /** + * Test that node_types_rebuild() correctly handles the 'disabled' flag. + */ + function testNodeTypeStatus() { + // Enable all core node modules, and all types should be active. + module_enable(array('book', 'poll'), FALSE); + node_types_rebuild(); + $types = node_type_get_types(); + foreach (array('book', 'poll', 'article', 'page') as $type) { + $this->assertTrue(isset($types[$type]), t('%type is found in node types.', array('%type' => $type))); + $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), t('%type type is enabled.', array('%type' => $type))); + } + + // Disable poll module and the respective type should be marked as disabled. + module_disable(array('poll'), FALSE); + node_types_rebuild(); + $types = node_type_get_types(); + $this->assertTrue(!empty($types['poll']->disabled), t("Poll module's node type disabled.")); + + // Disable book module and the respective type should still be active, since + // it is not provided by hook_node_info(). + module_disable(array('book'), FALSE); + node_types_rebuild(); + $types = node_type_get_types(); + $this->assertTrue(isset($types['book']) && empty($types['book']->disabled), t("Book module's node type still active.")); + $this->assertTrue(!empty($types['poll']->disabled), t("Poll module's node type still disabled.")); + $this->assertTrue(isset($types['article']) && empty($types['article']->disabled), t("Article node type still active.")); + $this->assertTrue(isset($types['page']) && empty($types['page']->disabled), t("Basic page node type still active.")); + + // Re-enable the modules and verify that the types are active again. + module_enable(array('book', 'poll'), FALSE); + node_types_rebuild(); + $types = node_type_get_types(); + foreach (array('book', 'poll', 'article', 'page') as $type) { + $this->assertTrue(isset($types[$type]), t('%type is found in node types.', array('%type' => $type))); + $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), t('%type type is enabled.', array('%type' => $type))); + } + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9fa1061311f8ebdad70cfd0759dba23aa4d260e0 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/PageEditTest.php @@ -0,0 +1,140 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\PageEditTest. + */ + +namespace Drupal\node\Tests; + +class PageEditTest extends NodeTestBase { + protected $web_user; + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => 'Node edit', + 'description' => 'Create a node and test node edit functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); + $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes')); + } + + /** + * Check node edit functionality. + */ + function testPageEdit() { + $this->drupalLogin($this->web_user); + + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $body_key = "body[$langcode][0][value]"; + // Create node to edit. + $edit = array(); + $edit[$title_key] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the node exists in the database. + $node = $this->drupalGetNodeByTitle($edit[$title_key]); + $this->assertTrue($node, t('Node found in database.')); + + // Check that "edit" link points to correct page. + $this->clickLink(t('Edit')); + $edit_url = url("node/$node->nid/edit", array('absolute' => TRUE)); + $actual_url = $this->getURL(); + $this->assertEqual($edit_url, $actual_url, t('On edit page.')); + + // Check that the title and body fields are displayed with the correct values. + $active = '<span class="element-invisible">' . t('(active tab)') . '</span>'; + $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active)); + $this->assertText(strip_tags($link_text), 0, t('Edit tab found and marked active.')); + $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); + + // Edit the content of the node. + $edit = array(); + $edit[$title_key] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + // Stay on the current page, without reloading. + $this->drupalPost(NULL, $edit, t('Save')); + + // Check that the title and body fields are displayed with the updated values. + $this->assertText($edit[$title_key], t('Title displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); + + // Login as a second administrator user. + $second_web_user = $this->drupalCreateUser(array('administer nodes', 'edit any page content')); + $this->drupalLogin($second_web_user); + // Edit the same node, creating a new revision. + $this->drupalGet("node/$node->nid/edit"); + $edit = array(); + $edit['title'] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $edit['revision'] = TRUE; + $this->drupalPost(NULL, $edit, t('Save')); + + // Ensure that the node revision has been created. + $revised_node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertNotIdentical($node->vid, $revised_node->vid, 'A new revision has been created.'); + // Ensure that the node author is preserved when it was not changed in the + // edit form. + $this->assertIdentical($node->uid, $revised_node->uid, 'The node author has been preserved.'); + // Ensure that the revision authors are different since the revisions were + // made by different users. + $first_node_version = node_load($node->nid, $node->vid); + $second_node_version = node_load($node->nid, $revised_node->vid); + $this->assertNotIdentical($first_node_version->revision_uid, $second_node_version->revision_uid, 'Each revision has a distinct user.'); + } + + /** + * Check changing node authored by fields. + */ + function testPageAuthoredBy() { + $this->drupalLogin($this->admin_user); + + // Create node to edit. + $langcode = LANGUAGE_NOT_SPECIFIED; + $body_key = "body[$langcode][0][value]"; + $edit = array(); + $edit['title'] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Save')); + + // Check that the node was authored by the currently logged in user. + $node = $this->drupalGetNodeByTitle($edit['title']); + $this->assertIdentical($node->uid, $this->admin_user->uid, 'Node authored by admin user.'); + + // Try to change the 'authored by' field to an invalid user name. + $edit = array( + 'name' => 'invalid-name', + ); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertText('The username invalid-name does not exist.'); + + // Change the authored by field to an empty string, which should assign + // authorship to the anonymous user (uid 0). + $edit['name'] = ''; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $node = node_load($node->nid, NULL, TRUE); + $this->assertIdentical($node->uid, '0', 'Node authored by anonymous user.'); + + // Change the authored by field to another user's name (that is not + // logged in). + $edit['name'] = $this->web_user->name; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $node = node_load($node->nid, NULL, TRUE); + $this->assertIdentical($node->uid, $this->web_user->uid, 'Node authored by normal user.'); + + // Check that normal users cannot change the authored by information. + $this->drupalLogin($this->web_user); + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertNoFieldByName('name'); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5bfd342b4d309cc4b0e87f57b9ccabe9bf63c188 --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/PagePreviewTest.php @@ -0,0 +1,79 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\PagePreviewTest. + */ + +namespace Drupal\node\Tests; + +class PagePreviewTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node preview', + 'description' => 'Test node preview functionality.', + 'group' => 'Node', + ); + } + + function setUp() { + parent::setUp(); + + $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); + $this->drupalLogin($web_user); + } + + /** + * Check the node preview functionality. + */ + function testPagePreview() { + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $body_key = "body[$langcode][0][value]"; + + // Fill in node creation form and preview node. + $edit = array(); + $edit[$title_key] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $this->drupalPost('node/add/page', $edit, t('Preview')); + + // Check that the preview is displaying the title and body. + $this->assertTitle(t('Preview | Drupal'), t('Basic page title is preview.')); + $this->assertText($edit[$title_key], t('Title displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); + + // Check that the title and body fields are displayed with the correct values. + $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); + } + + /** + * Check the node preview functionality, when using revisions. + */ + function testPagePreviewWithRevisions() { + $langcode = LANGUAGE_NOT_SPECIFIED; + $title_key = "title"; + $body_key = "body[$langcode][0][value]"; + // Force revision on "Basic page" content. + variable_set('node_options_page', array('status', 'revision')); + + // Fill in node creation form and preview node. + $edit = array(); + $edit[$title_key] = $this->randomName(8); + $edit[$body_key] = $this->randomName(16); + $edit['log'] = $this->randomName(32); + $this->drupalPost('node/add/page', $edit, t('Preview')); + + // Check that the preview is displaying the title and body. + $this->assertTitle(t('Preview | Drupal'), t('Basic page title is preview.')); + $this->assertText($edit[$title_key], t('Title displayed.')); + $this->assertText($edit[$body_key], t('Body displayed.')); + + // Check that the title and body fields are displayed with the correct values. + $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); + $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); + + // Check that the log field has the correct value. + $this->assertFieldByName('log', $edit['log'], t('Log field displayed.')); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/PageViewTest.php b/core/modules/node/lib/Drupal/node/Tests/PageViewTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e3f3e54f4bbe290c97e6ada3d481bf4ac5ee5f1a --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/PageViewTest.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\PageViewTest. + */ + +namespace Drupal\node\Tests; + +class PageViewTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Node edit permissions', + 'description' => 'Create a node and test edit permissions.', + 'group' => 'Node', + ); + } + + /** + * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + */ + function testPageView() { + // Create a node to view. + $node = $this->drupalCreateNode(); + $this->assertTrue(node_load($node->nid), t('Node created.')); + + // Try to edit with anonymous user. + $html = $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(403); + + // Create a user without permission to edit node. + $web_user = $this->drupalCreateUser(array('access content')); + $this->drupalLogin($web_user); + + // Attempt to access edit page. + $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(403); + + // Create user with permission to edit node. + $web_user = $this->drupalCreateUser(array('bypass node access')); + $this->drupalLogin($web_user); + + // Attempt to access edit page. + $this->drupalGet("node/$node->nid/edit"); + $this->assertResponse(200); + } +} diff --git a/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php b/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ca666785edacf0036a05b8dbe55e1630f01bb73b --- /dev/null +++ b/core/modules/node/lib/Drupal/node/Tests/SummaryLengthTest.php @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Definition of Drupal\node\Tests\SummaryLengthTest. + */ + +namespace Drupal\node\Tests; + +class SummaryLengthTest extends NodeTestBase { + public static function getInfo() { + return array( + 'name' => 'Summary length', + 'description' => 'Test summary length.', + 'group' => 'Node', + ); + } + + /** + * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. + */ + function testSummaryLength() { + // Create a node to view. + $settings = array( + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero. Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci. Curabitur feugiat egestas nisl sed accumsan.'))), + 'promote' => 1, + ); + $node = $this->drupalCreateNode($settings); + $this->assertTrue(node_load($node->nid), t('Node created.')); + + // Create user with permission to view the node. + $web_user = $this->drupalCreateUser(array('access content', 'administer content types')); + $this->drupalLogin($web_user); + + // Attempt to access the front page. + $this->drupalGet("node"); + // The node teaser when it has 600 characters in length + $expected = 'What is a Drupalism?'; + $this->assertRaw($expected, t('Check that the summary is 600 characters in length'), 'Node'); + + // Change the teaser length for "Basic page" content type. + $instance = field_info_instance('node', 'body', $node->type); + $instance['display']['teaser']['settings']['trim_length'] = 200; + field_update_instance($instance); + + // Attempt to access the front page again and check if the summary is now only 200 characters in length. + $this->drupalGet("node"); + $this->assertNoRaw($expected, t('Check that the summary is not longer than 200 characters'), 'Node'); + } +} diff --git a/core/modules/node/node.info b/core/modules/node/node.info index 0e9a3af3b58c51e32e3109864c8b4e29c88f679b..c422456d98290aca85323871cdca81845304dce5 100644 --- a/core/modules/node/node.info +++ b/core/modules/node/node.info @@ -3,7 +3,5 @@ description = Allows content to be submitted to the site and displayed on pages. package = Core version = VERSION core = 8.x -files[] = node.module -files[] = node.test dependencies[] = entity configure = admin/structure/types diff --git a/core/modules/node/node.test b/core/modules/node/node.test deleted file mode 100644 index 8512d27baff5da518064b64f6ec02ccc2348fedd..0000000000000000000000000000000000000000 --- a/core/modules/node/node.test +++ /dev/null @@ -1,2595 +0,0 @@ -<?php - -use Drupal\Core\Database\Database; -use Drupal\simpletest\WebTestBase; - -/** - * @file - * Tests for node.module. - */ - -class NodeWebTestCase extends WebTestBase { - function setUp() { - $modules = func_get_args(); - if (isset($modules[0]) && is_array($modules[0])) { - $modules = $modules[0]; - } - $modules[] = 'node'; - parent::setUp($modules); - - // Create Basic page and Article node types. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - } - } -} - -/** - * Tests the node_load_multiple() function. - */ -class NodeLoadMultipleUnitTest extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Load multiple nodes', - 'description' => 'Test the loading of multiple nodes.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - $web_user = $this->drupalCreateUser(array('create article content', 'create page content')); - $this->drupalLogin($web_user); - } - - /** - * Creates four nodes and ensures that they are loaded correctly. - */ - function testNodeMultipleLoad() { - $node1 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $node2 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - $node3 = $this->drupalCreateNode(array('type' => 'article', 'promote' => 0)); - $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0)); - - // Confirm that promoted nodes appear in the default node listing. - $this->drupalGet('node'); - $this->assertText($node1->title, t('Node title appears on the default listing.')); - $this->assertText($node2->title, t('Node title appears on the default listing.')); - $this->assertNoText($node3->title, t('Node title does not appear in the default listing.')); - $this->assertNoText($node4->title, t('Node title does not appear in the default listing.')); - - // Load nodes with only a condition. Nodes 3 and 4 will be loaded. - $nodes = node_load_multiple(FALSE, array('promote' => 0)); - $this->assertEqual($node3->title, $nodes[$node3->nid]->title, t('Node was loaded.')); - $this->assertEqual($node4->title, $nodes[$node4->nid]->title, t('Node was loaded.')); - $count = count($nodes); - $this->assertTrue($count == 2, t('@count nodes loaded.', array('@count' => $count))); - - // Load nodes by nid. Nodes 1, 2 and 4 will be loaded. - $nodes = node_load_multiple(array(1, 2, 4)); - $count = count($nodes); - $this->assertTrue(count($nodes) == 3, t('@count nodes loaded', array('@count' => $count))); - $this->assertTrue(isset($nodes[$node1->nid]), t('Node is correctly keyed in the array')); - $this->assertTrue(isset($nodes[$node2->nid]), t('Node is correctly keyed in the array')); - $this->assertTrue(isset($nodes[$node4->nid]), t('Node is correctly keyed in the array')); - foreach ($nodes as $node) { - $this->assertTrue(is_object($node), t('Node is an object')); - } - - // Load nodes by nid, where type = article. Nodes 1, 2 and 3 will be loaded. - $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article')); - $count = count($nodes); - $this->assertTrue($count == 3, t('@count nodes loaded', array('@count' => $count))); - $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded.')); - $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded.')); - $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.')); - $this->assertFalse(isset($nodes[$node4->nid])); - - // Now that all nodes have been loaded into the static cache, ensure that - // they are loaded correctly again when a condition is passed. - $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article')); - $count = count($nodes); - $this->assertTrue($count == 3, t('@count nodes loaded.', array('@count' => $count))); - $this->assertEqual($nodes[$node1->nid]->title, $node1->title, t('Node successfully loaded')); - $this->assertEqual($nodes[$node2->nid]->title, $node2->title, t('Node successfully loaded')); - $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded')); - $this->assertFalse(isset($nodes[$node4->nid]), t('Node was not loaded')); - - // Load nodes by nid, where type = article and promote = 0. - $nodes = node_load_multiple(array(1, 2, 3, 4), array('type' => 'article', 'promote' => 0)); - $count = count($nodes); - $this->assertTrue($count == 1, t('@count node loaded', array('@count' => $count))); - $this->assertEqual($nodes[$node3->nid]->title, $node3->title, t('Node successfully loaded.')); - } -} - -/** - * Tests for the hooks invoked during node_load(). - */ -class NodeLoadHooksTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node load hooks', - 'description' => 'Test the hooks invoked when a node is being loaded.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp('node_test'); - } - - /** - * Tests that hook_node_load() is invoked correctly. - */ - function testHookNodeLoad() { - // Create some sample articles and pages. - $node1 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED)); - $node2 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_PUBLISHED)); - $node3 = $this->drupalCreateNode(array('type' => 'article', 'status' => NODE_NOT_PUBLISHED)); - $node4 = $this->drupalCreateNode(array('type' => 'page', 'status' => NODE_NOT_PUBLISHED)); - - // Check that when a set of nodes that only contains articles is loaded, - // the properties added to the node by node_test_load_node() correctly - // reflect the expected values. - $nodes = node_load_multiple(array(), array('status' => NODE_PUBLISHED)); - $loaded_node = end($nodes); - $this->assertEqual($loaded_node->node_test_loaded_nids, array($node1->nid, $node2->nid), t('hook_node_load() received the correct list of node IDs the first time it was called.')); - $this->assertEqual($loaded_node->node_test_loaded_types, array('article'), t('hook_node_load() received the correct list of node types the first time it was called.')); - - // Now, as part of the same page request, load a set of nodes that contain - // both articles and pages, and make sure the parameters passed to - // node_test_node_load() are correctly updated. - $nodes = node_load_multiple(array(), array('status' => NODE_NOT_PUBLISHED)); - $loaded_node = end($nodes); - $this->assertEqual($loaded_node->node_test_loaded_nids, array($node3->nid, $node4->nid), t('hook_node_load() received the correct list of node IDs the second time it was called.')); - $this->assertEqual($loaded_node->node_test_loaded_types, array('article', 'page'), t('hook_node_load() received the correct list of node types the second time it was called.')); - } -} - -class NodeRevisionsTestCase extends NodeWebTestCase { - protected $nodes; - protected $logs; - - public static function getInfo() { - return array( - 'name' => 'Node revisions', - 'description' => 'Create a node with revisions and test viewing, saving, reverting, and deleting revisions.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - // Create and login user. - $web_user = $this->drupalCreateUser(array('view revisions', 'revert revisions', 'edit any page content', - 'delete revisions', 'delete any page content')); - $this->drupalLogin($web_user); - - // Create initial node. - $node = $this->drupalCreateNode(); - $settings = get_object_vars($node); - $settings['revision'] = 1; - - $nodes = array(); - $logs = array(); - - // Get original node. - $nodes[] = $node; - - // Create three revisions. - $revision_count = 3; - for ($i = 0; $i < $revision_count; $i++) { - $logs[] = $settings['log'] = $this->randomName(32); - - // Create revision with random title and body and update variables. - $this->drupalCreateNode($settings); - $node = node_load($node->nid); // Make sure we get revision information. - $settings = get_object_vars($node); - - $nodes[] = $node; - } - - $this->nodes = $nodes; - $this->logs = $logs; - } - - /** - * Check node revision related operations. - */ - function testRevisions() { - $nodes = $this->nodes; - $logs = $this->logs; - - // Get last node for simple checks. - $node = $nodes[3]; - - // Confirm the correct revision text appears on "view revisions" page. - $this->drupalGet("node/$node->nid/revisions/$node->vid/view"); - $this->assertText($node->body[LANGUAGE_NOT_SPECIFIED][0]['value'], t('Correct text displays for version.')); - - // Confirm the correct log message appears on "revisions overview" page. - $this->drupalGet("node/$node->nid/revisions"); - foreach ($logs as $log) { - $this->assertText($log, t('Log message found.')); - } - - // Confirm that revisions revert properly. - $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/revert", array(), t('Revert')); - $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', - array('@type' => 'Basic page', '%title' => $nodes[1]->title, - '%revision-date' => format_date($nodes[1]->revision_timestamp))), t('Revision reverted.')); - $reverted_node = node_load($node->nid); - $this->assertTrue(($nodes[1]->body[LANGUAGE_NOT_SPECIFIED][0]['value'] == $reverted_node->body[LANGUAGE_NOT_SPECIFIED][0]['value']), t('Node reverted correctly.')); - - // Confirm revisions delete properly. - $this->drupalPost("node/$node->nid/revisions/{$nodes[1]->vid}/delete", array(), t('Delete')); - $this->assertRaw(t('Revision from %revision-date of @type %title has been deleted.', - array('%revision-date' => format_date($nodes[1]->revision_timestamp), - '@type' => 'Basic page', '%title' => $nodes[1]->title)), t('Revision deleted.')); - $this->assertTrue(db_query('SELECT COUNT(vid) FROM {node_revision} WHERE nid = :nid and vid = :vid', array(':nid' => $node->nid, ':vid' => $nodes[1]->vid))->fetchField() == 0, t('Revision not found.')); - - // Set the revision timestamp to an older date to make sure that the - // confirmation message correctly displays the stored revision date. - $old_revision_date = REQUEST_TIME - 86400; - db_update('node_revision') - ->condition('vid', $nodes[2]->vid) - ->fields(array( - 'timestamp' => $old_revision_date, - )) - ->execute(); - $this->drupalPost("node/$node->nid/revisions/{$nodes[2]->vid}/revert", array(), t('Revert')); - $this->assertRaw(t('@type %title has been reverted back to the revision from %revision-date.', array( - '@type' => 'Basic page', - '%title' => $nodes[2]->title, - '%revision-date' => format_date($old_revision_date), - ))); - } - - /** - * Checks that revisions are correctly saved without log messages. - */ - function testNodeRevisionWithoutLogMessage() { - // Create a node with an initial log message. - $log = $this->randomName(10); - $node = $this->drupalCreateNode(array('log' => $log)); - - // Save over the same revision and explicitly provide an empty log message - // (for example, to mimic the case of a node form submitted with no text in - // the "log message" field), and check that the original log message is - // preserved. - $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage1'; - - $node = clone $node; - $node->title = $new_title; - $node->log = ''; - $node->revision = FALSE; - - $node->save(); - $this->drupalGet('node/' . $node->nid); - $this->assertText($new_title, t('New node title appears on the page.')); - $node_revision = node_load($node->nid, NULL, TRUE); - $this->assertEqual($node_revision->log, $log, t('After an existing node revision is re-saved without a log message, the original log message is preserved.')); - - // Create another node with an initial log message. - $node = $this->drupalCreateNode(array('log' => $log)); - - // Save a new node revision without providing a log message, and check that - // this revision has an empty log message. - $new_title = $this->randomName(10) . 'testNodeRevisionWithoutLogMessage2'; - - $node = clone $node; - $node->title = $new_title; - $node->revision = TRUE; - $node->log = NULL; - - $node->save(); - $this->drupalGet('node/' . $node->nid); - $this->assertText($new_title, 'New node title appears on the page.'); - $node_revision = node_load($node->nid, NULL, TRUE); - $this->assertTrue(empty($node_revision->log), 'After a new node revision is saved with an empty log message, the log message for the node is empty.'); - } -} - -class PageEditTestCase extends NodeWebTestCase { - protected $web_user; - protected $admin_user; - - public static function getInfo() { - return array( - 'name' => 'Node edit', - 'description' => 'Create a node and test node edit functionality.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - $this->web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); - $this->admin_user = $this->drupalCreateUser(array('bypass node access', 'administer nodes')); - } - - /** - * Check node edit functionality. - */ - function testPageEdit() { - $this->drupalLogin($this->web_user); - - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $body_key = "body[$langcode][0][value]"; - // Create node to edit. - $edit = array(); - $edit[$title_key] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the node exists in the database. - $node = $this->drupalGetNodeByTitle($edit[$title_key]); - $this->assertTrue($node, t('Node found in database.')); - - // Check that "edit" link points to correct page. - $this->clickLink(t('Edit')); - $edit_url = url("node/$node->nid/edit", array('absolute' => TRUE)); - $actual_url = $this->getURL(); - $this->assertEqual($edit_url, $actual_url, t('On edit page.')); - - // Check that the title and body fields are displayed with the correct values. - $active = '<span class="element-invisible">' . t('(active tab)') . '</span>'; - $link_text = t('!local-task-title!active', array('!local-task-title' => t('Edit'), '!active' => $active)); - $this->assertText(strip_tags($link_text), 0, t('Edit tab found and marked active.')); - $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); - $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); - - // Edit the content of the node. - $edit = array(); - $edit[$title_key] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - // Stay on the current page, without reloading. - $this->drupalPost(NULL, $edit, t('Save')); - - // Check that the title and body fields are displayed with the updated values. - $this->assertText($edit[$title_key], t('Title displayed.')); - $this->assertText($edit[$body_key], t('Body displayed.')); - - // Login as a second administrator user. - $second_web_user = $this->drupalCreateUser(array('administer nodes', 'edit any page content')); - $this->drupalLogin($second_web_user); - // Edit the same node, creating a new revision. - $this->drupalGet("node/$node->nid/edit"); - $edit = array(); - $edit['title'] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - $edit['revision'] = TRUE; - $this->drupalPost(NULL, $edit, t('Save')); - - // Ensure that the node revision has been created. - $revised_node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertNotIdentical($node->vid, $revised_node->vid, 'A new revision has been created.'); - // Ensure that the node author is preserved when it was not changed in the - // edit form. - $this->assertIdentical($node->uid, $revised_node->uid, 'The node author has been preserved.'); - // Ensure that the revision authors are different since the revisions were - // made by different users. - $first_node_version = node_load($node->nid, $node->vid); - $second_node_version = node_load($node->nid, $revised_node->vid); - $this->assertNotIdentical($first_node_version->revision_uid, $second_node_version->revision_uid, 'Each revision has a distinct user.'); - } - - /** - * Check changing node authored by fields. - */ - function testPageAuthoredBy() { - $this->drupalLogin($this->admin_user); - - // Create node to edit. - $langcode = LANGUAGE_NOT_SPECIFIED; - $body_key = "body[$langcode][0][value]"; - $edit = array(); - $edit['title'] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the node was authored by the currently logged in user. - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertIdentical($node->uid, $this->admin_user->uid, 'Node authored by admin user.'); - - // Try to change the 'authored by' field to an invalid user name. - $edit = array( - 'name' => 'invalid-name', - ); - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertText('The username invalid-name does not exist.'); - - // Change the authored by field to an empty string, which should assign - // authorship to the anonymous user (uid 0). - $edit['name'] = ''; - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $node = node_load($node->nid, NULL, TRUE); - $this->assertIdentical($node->uid, '0', 'Node authored by anonymous user.'); - - // Change the authored by field to another user's name (that is not - // logged in). - $edit['name'] = $this->web_user->name; - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $node = node_load($node->nid, NULL, TRUE); - $this->assertIdentical($node->uid, $this->web_user->uid, 'Node authored by normal user.'); - - // Check that normal users cannot change the authored by information. - $this->drupalLogin($this->web_user); - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertNoFieldByName('name'); - } -} - -class PagePreviewTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node preview', - 'description' => 'Test node preview functionality.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - $web_user = $this->drupalCreateUser(array('edit own page content', 'create page content')); - $this->drupalLogin($web_user); - } - - /** - * Check the node preview functionality. - */ - function testPagePreview() { - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $body_key = "body[$langcode][0][value]"; - - // Fill in node creation form and preview node. - $edit = array(); - $edit[$title_key] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Preview')); - - // Check that the preview is displaying the title and body. - $this->assertTitle(t('Preview | Drupal'), t('Basic page title is preview.')); - $this->assertText($edit[$title_key], t('Title displayed.')); - $this->assertText($edit[$body_key], t('Body displayed.')); - - // Check that the title and body fields are displayed with the correct values. - $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); - $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); - } - - /** - * Check the node preview functionality, when using revisions. - */ - function testPagePreviewWithRevisions() { - $langcode = LANGUAGE_NOT_SPECIFIED; - $title_key = "title"; - $body_key = "body[$langcode][0][value]"; - // Force revision on "Basic page" content. - variable_set('node_options_page', array('status', 'revision')); - - // Fill in node creation form and preview node. - $edit = array(); - $edit[$title_key] = $this->randomName(8); - $edit[$body_key] = $this->randomName(16); - $edit['log'] = $this->randomName(32); - $this->drupalPost('node/add/page', $edit, t('Preview')); - - // Check that the preview is displaying the title and body. - $this->assertTitle(t('Preview | Drupal'), t('Basic page title is preview.')); - $this->assertText($edit[$title_key], t('Title displayed.')); - $this->assertText($edit[$body_key], t('Body displayed.')); - - // Check that the title and body fields are displayed with the correct values. - $this->assertFieldByName($title_key, $edit[$title_key], t('Title field displayed.')); - $this->assertFieldByName($body_key, $edit[$body_key], t('Body field displayed.')); - - // Check that the log field has the correct value. - $this->assertFieldByName('log', $edit['log'], t('Log field displayed.')); - } -} - -class NodeCreationTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node creation', - 'description' => 'Create a node and test saving it.', - 'group' => 'Node', - ); - } - - function setUp() { - // Enable dummy module that implements hook_node_insert for exceptions. - parent::setUp(array('node_test_exception', 'dblog')); - - $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content')); - $this->drupalLogin($web_user); - } - - /** - * Create a "Basic page" node and verify its consistency in the database. - */ - function testNodeCreation() { - // Create a node. - $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["title"] = $this->randomName(8); - $edit["body[$langcode][0][value]"] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the Basic page has been created. - $this->assertRaw(t('!post %title has been created.', array('!post' => 'Basic page', '%title' => $edit["title"])), t('Basic page created.')); - - // Check that the node exists in the database. - $node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue($node, t('Node found in database.')); - } - - /** - * Create a page node and verify that a transaction rolls back the failed creation - */ - function testFailedPageCreation() { - // Create a node. - $edit = array( - 'uid' => $this->loggedInUser->uid, - 'name' => $this->loggedInUser->name, - 'type' => 'page', - 'langcode' => LANGUAGE_NOT_SPECIFIED, - 'title' => 'testing_transaction_exception', - ); - - try { - entity_create('node', $edit)->save(); - $this->fail(t('Expected exception has not been thrown.')); - } - catch (Exception $e) { - $this->pass(t('Expected exception has been thrown.')); - } - - if (Database::getConnection()->supportsTransactions()) { - // Check that the node does not exist in the database. - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertFalse($node, t('Transactions supported, and node not found in database.')); - } - else { - // Check that the node exists in the database. - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertTrue($node, t('Transactions not supported, and node found in database.')); - - // Check that the failed rollback was logged. - $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll(); - $this->assertTrue(count($records) > 0, t('Transactions not supported, and rollback error logged to watchdog.')); - } - - // Check that the rollback error was logged. - $records = db_query("SELECT wid FROM {watchdog} WHERE variables LIKE '%Test exception for rollback.%'")->fetchAll(); - $this->assertTrue(count($records) > 0, t('Rollback explanatory error logged to watchdog.')); - } -} - -class PageViewTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node edit permissions', - 'description' => 'Create a node and test edit permissions.', - 'group' => 'Node', - ); - } - - /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. - */ - function testPageView() { - // Create a node to view. - $node = $this->drupalCreateNode(); - $this->assertTrue(node_load($node->nid), t('Node created.')); - - // Try to edit with anonymous user. - $html = $this->drupalGet("node/$node->nid/edit"); - $this->assertResponse(403); - - // Create a user without permission to edit node. - $web_user = $this->drupalCreateUser(array('access content')); - $this->drupalLogin($web_user); - - // Attempt to access edit page. - $this->drupalGet("node/$node->nid/edit"); - $this->assertResponse(403); - - // Create user with permission to edit node. - $web_user = $this->drupalCreateUser(array('bypass node access')); - $this->drupalLogin($web_user); - - // Attempt to access edit page. - $this->drupalGet("node/$node->nid/edit"); - $this->assertResponse(200); - } -} - -class SummaryLengthTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Summary length', - 'description' => 'Test summary length.', - 'group' => 'Node', - ); - } - - /** - * Creates a node and then an anonymous and unpermissioned user attempt to edit the node. - */ - function testSummaryLength() { - // Create a node to view. - $settings = array( - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero. Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci. Curabitur feugiat egestas nisl sed accumsan.'))), - 'promote' => 1, - ); - $node = $this->drupalCreateNode($settings); - $this->assertTrue(node_load($node->nid), t('Node created.')); - - // Create user with permission to view the node. - $web_user = $this->drupalCreateUser(array('access content', 'administer content types')); - $this->drupalLogin($web_user); - - // Attempt to access the front page. - $this->drupalGet("node"); - // The node teaser when it has 600 characters in length - $expected = 'What is a Drupalism?'; - $this->assertRaw($expected, t('Check that the summary is 600 characters in length'), 'Node'); - - // Change the teaser length for "Basic page" content type. - $instance = field_info_instance('node', 'body', $node->type); - $instance['display']['teaser']['settings']['trim_length'] = 200; - field_update_instance($instance); - - // Attempt to access the front page again and check if the summary is now only 200 characters in length. - $this->drupalGet("node"); - $this->assertNoRaw($expected, t('Check that the summary is not longer than 200 characters'), 'Node'); - } -} - -class NodeTitleXSSTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node title XSS filtering', - 'description' => 'Create a node with dangerous tags in its title and test that they are escaped.', - 'group' => 'Node', - ); - } - - function testNodeTitleXSS() { - // Prepare a user to do the stuff. - $web_user = $this->drupalCreateUser(array('create page content', 'edit any page content')); - $this->drupalLogin($web_user); - - $xss = '<script>alert("xss")</script>'; - $title = $xss . $this->randomName(); - $edit = array("title" => $title); - - $this->drupalPost('node/add/page', $edit, t('Preview')); - $this->assertNoRaw($xss, t('Harmful tags are escaped when previewing a node.')); - - $settings = array('title' => $title); - $node = $this->drupalCreateNode($settings); - - $this->drupalGet('node/' . $node->nid); - // assertTitle() decodes HTML-entities inside the <title> element. - $this->assertTitle($edit["title"] . ' | Drupal', t('Title is diplayed when viewing a node.')); - $this->assertNoRaw($xss, t('Harmful tags are escaped when viewing a node.')); - - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertNoRaw($xss, t('Harmful tags are escaped when editing a node.')); - } -} - -class NodeBlockTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Block availability', - 'description' => 'Check if the syndicate block is available.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(array('block')); - - // Create and login user - $admin_user = $this->drupalCreateUser(array('administer blocks')); - $this->drupalLogin($admin_user); - } - - function testSearchFormBlock() { - // Set block title to confirm that the interface is available. - $this->drupalPost('admin/structure/block/manage/node/syndicate/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 available. - $edit = array(); - $edit['blocks[node_syndicate][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.')); - } -} - -/** - * Check that the post information displays when enabled for a content type. - */ -class NodePostSettingsTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node post information display', - 'description' => 'Check that the post information (submitted by Username on date) text displays appropriately.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - $web_user = $this->drupalCreateUser(array('create page content', 'administer content types', 'access user profiles')); - $this->drupalLogin($web_user); - } - - /** - * Set "Basic page" content type to display post information and confirm its presence on a new node. - */ - function testPagePostInfo() { - - // Set "Basic page" content type to display post information. - $edit = array(); - $edit['node_submitted'] = TRUE; - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - - // Create a node. - $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["title"] = $this->randomName(8); - $edit["body[$langcode][0][value]"] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the post information is displayed. - $node = $this->drupalGetNodeByTitle($edit["title"]); - $elements = $this->xpath('//*[contains(@class,:class)]', array(':class' => 'submitted')); - $this->assertEqual(count($elements), 1, t('Post information is displayed.')); - } - - /** - * Set "Basic page" content type to not display post information and confirm its absence on a new node. - */ - function testPageNotPostInfo() { - - // Set "Basic page" content type to display post information. - $edit = array(); - $edit['node_submitted'] = FALSE; - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - - // Create a node. - $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["title"] = $this->randomName(8); - $edit["body[$langcode][0][value]"] = $this->randomName(16); - $this->drupalPost('node/add/page', $edit, t('Save')); - - // Check that the post information is displayed. - $node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertNoRaw('<span class="submitted">', t('Post information is not displayed.')); - } -} - -/** - * Ensure that data added to nodes by other modules appears in RSS feeds. - * - * Create a node, enable the node_test module to ensure that extra data is - * added to the node->content array, then verify that the data appears on the - * sitewide RSS feed at rss.xml. - */ -class NodeRSSContentTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node RSS Content', - 'description' => 'Ensure that data added to nodes by other modules appears in RSS feeds.', - 'group' => 'Node', - ); - } - - function setUp() { - // Enable dummy module that implements hook_node_view. - parent::setUp('node_test'); - - // Use bypass node access permission here, because the test class uses - // hook_grants_alter() to deny access to everyone on node_access - // queries. - $user = $this->drupalCreateUser(array('bypass node access', 'access content', 'create article content')); - $this->drupalLogin($user); - } - - /** - * Create a new node and ensure that it includes the custom data when added - * to an RSS feed. - */ - function testNodeRSSContent() { - // Create a node. - $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); - - $this->drupalGet('rss.xml'); - - // Check that content added in 'rss' view mode appear in RSS feed. - $rss_only_content = t('Extra data that should appear only in the RSS feed for node !nid.', array('!nid' => $node->nid)); - $this->assertText($rss_only_content, t('Node content designated for RSS appear in RSS feed.')); - - // Check that content added in view modes other than 'rss' doesn't - // appear in RSS feed. - $non_rss_content = t('Extra data that should appear everywhere except the RSS feed for node !nid.', array('!nid' => $node->nid)); - $this->assertNoText($non_rss_content, t('Node content not designed for RSS doesn\'t appear in RSS feed.')); - - // Check that extra RSS elements and namespaces are added to RSS feed. - $test_element = array( - 'key' => 'testElement', - 'value' => t('Value of testElement RSS element for node !nid.', array('!nid' => $node->nid)), - ); - $test_ns = 'xmlns:drupaltest="http://example.com/test-namespace"'; - $this->assertRaw(format_xml_elements(array($test_element)), t('Extra RSS elements appear in RSS feed.')); - $this->assertRaw($test_ns, t('Extra namespaces appear in RSS feed.')); - - // Check that content added in 'rss' view mode doesn't appear when - // viewing node. - $this->drupalGet("node/$node->nid"); - $this->assertNoText($rss_only_content, t('Node content designed for RSS doesn\'t appear when viewing node.')); - - } -} - -/** - * Test case to verify basic node_access functionality. - * @todo Cover hook_node_access in a separate test class. - * hook_node_access_records is covered in another test class. - */ -class NodeAccessUnitTest extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node access', - 'description' => 'Test node_access function', - 'group' => 'Node', - ); - } - - /** - * Asserts node_access correctly grants or denies access. - */ - function assertNodeAccess($ops, $node, $account) { - foreach ($ops as $op => $result) { - $msg = t("node_access returns @result with operation '@op'.", array('@result' => $result ? 'true' : 'false', '@op' => $op)); - $this->assertEqual($result, node_access($op, $node, $account), $msg); - } - } - - function setUp() { - parent::setUp(); - // Clear permissions for authenticated users. - db_delete('role_permission') - ->condition('rid', DRUPAL_AUTHENTICATED_RID) - ->execute(); - } - - /** - * Runs basic tests for node_access function. - */ - function testNodeAccess() { - // Ensures user without 'access content' permission can do nothing. - $web_user1 = $this->drupalCreateUser(array('create page content', 'edit any page content', 'delete any page content')); - $node1 = $this->drupalCreateNode(array('type' => 'page')); - $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user1); - $this->assertNodeAccess(array('view' => FALSE, 'update' => FALSE, 'delete' => FALSE), $node1, $web_user1); - - // Ensures user with 'bypass node access' permission can do everything. - $web_user2 = $this->drupalCreateUser(array('bypass node access')); - $node2 = $this->drupalCreateNode(array('type' => 'page')); - $this->assertNodeAccess(array('create' => TRUE), 'page', $web_user2); - $this->assertNodeAccess(array('view' => TRUE, 'update' => TRUE, 'delete' => TRUE), $node2, $web_user2); - - // User cannot 'view own unpublished content'. - $web_user3 = $this->drupalCreateUser(array('access content')); - $node3 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user3->uid)); - $this->assertNodeAccess(array('view' => FALSE), $node3, $web_user3); - - // User cannot create content without permission. - $this->assertNodeAccess(array('create' => FALSE), 'page', $web_user3); - - // User can 'view own unpublished content', but another user cannot. - $web_user4 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); - $web_user5 = $this->drupalCreateUser(array('access content', 'view own unpublished content')); - $node4 = $this->drupalCreateNode(array('status' => 0, 'uid' => $web_user4->uid)); - $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE), $node4, $web_user4); - $this->assertNodeAccess(array('view' => FALSE), $node4, $web_user5); - - // Tests the default access provided for a published node. - $node5 = $this->drupalCreateNode(); - $this->assertNodeAccess(array('view' => TRUE, 'update' => FALSE, 'delete' => FALSE), $node5, $web_user3); - } -} - -/** - * Test case to verify hook_node_access_records functionality. - */ -class NodeAccessRecordsUnitTest extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node access records', - 'description' => 'Test hook_node_access_records when acquiring grants.', - 'group' => 'Node', - ); - } - - function setUp() { - // Enable dummy module that implements hook_node_grants(), - // hook_node_access_records(), hook_node_grants_alter() and - // hook_node_access_records_alter(). - parent::setUp('node_test'); - } - - /** - * Create a node and test the creation of node access rules. - */ - function testNodeAccessRecords() { - // Create an article node. - $node1 = $this->drupalCreateNode(array('type' => 'article')); - $this->assertTrue(node_load($node1->nid), t('Article node created.')); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node1->nid))->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->realm, 'test_article_realm', t('Grant with article_realm acquired for node without alteration.')); - $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); - - // Create an unpromoted "Basic page" node. - $node2 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0)); - $this->assertTrue(node_load($node2->nid), t('Unpromoted basic page node created.')); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node2->nid))->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.')); - $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); - - // Create an unpromoted, unpublished "Basic page" node. - $node3 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 0, 'status' => 0)); - $this->assertTrue(node_load($node3->nid), t('Unpromoted, unpublished basic page node created.')); - - // Check to see if grants added by node_test_node_access_records made it in. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node3->nid))->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->realm, 'test_page_realm', t('Grant with page_realm acquired for node without alteration.')); - $this->assertEqual($records[0]->gid, 1, t('Grant with gid = 1 acquired for node without alteration.')); - - // Create a promoted "Basic page" node. - $node4 = $this->drupalCreateNode(array('type' => 'page', 'promote' => 1)); - $this->assertTrue(node_load($node4->nid), t('Promoted basic page node created.')); - - // Check to see if grant added by node_test_node_access_records was altered - // by node_test_node_access_records_alter. - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node4->nid))->fetchAll(); - $this->assertEqual(count($records), 1, t('Returned the correct number of rows.')); - $this->assertEqual($records[0]->realm, 'test_alter_realm', t('Altered grant with alter_realm acquired for node.')); - $this->assertEqual($records[0]->gid, 2, t('Altered grant with gid = 2 acquired for node.')); - - // Check to see if we can alter grants with hook_node_grants_alter(). - $operations = array('view', 'update', 'delete'); - // Create a user that is allowed to access content. - $web_user = $this->drupalCreateUser(array('access content')); - foreach ($operations as $op) { - $grants = node_test_node_grants($op, $web_user); - $altered_grants = $grants; - drupal_alter('node_grants', $altered_grants, $web_user, $op); - $this->assertNotEqual($grants, $altered_grants, t('Altered the %op grant for a user.', array('%op' => $op))); - } - - // Check that core does not grant access to an unpublished node when an - // empty $grants array is returned. - $node6 = $this->drupalCreateNode(array('status' => 0, 'disable_node_access' => TRUE)); - $records = db_query('SELECT realm, gid FROM {node_access} WHERE nid = :nid', array(':nid' => $node6->nid))->fetchAll(); - $this->assertEqual(count($records), 0, t('Returned no records for unpublished node.')); - } -} - -/** - * Tests for Node Access with a non-node base table. - */ -class NodeAccessBaseTableTestCase extends NodeWebTestCase { - // Requires tags taxonomy field. - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Node Access on any table', - 'description' => 'Checks behavior of the node access subsystem if the base table is not node.', - 'group' => 'Node', - ); - } - - /** - * Enable modules and create user with specific permissions. - */ - public function setUp() { - parent::setUp('node_access_test'); - node_access_rebuild(); - variable_set('node_access_test_private', TRUE); - } - - /** - * Test the "private" node access. - * - * - Create 2 users with "access content" and "create article" permissions. - * - Each user creates one private and one not private article. - - * - Test that each user can view the other user's non-private article. - * - Test that each user cannot view the other user's private article. - * - Test that each user finds only appropriate (non-private + own private) - * in taxonomy listing. - * - Create another user with 'view any private content'. - * - Test that user 4 can view all content created above. - * - Test that user 4 can view all content on taxonomy listing. - */ - function testNodeAccessBasic() { - $num_simple_users = 2; - $simple_users = array(); - - // nodes keyed by uid and nid: $nodes[$uid][$nid] = $is_private; - $this->nodesByUser = array(); - $titles = array(); // Titles keyed by nid - $private_nodes = array(); // Array of nids marked private. - for ($i = 0; $i < $num_simple_users; $i++) { - $simple_users[$i] = $this->drupalCreateUser(array('access content', 'create article content')); - } - foreach ($simple_users as $this->webUser) { - $this->drupalLogin($this->webUser); - foreach (array(0 => 'Public', 1 => 'Private') as $is_private => $type) { - $edit = array( - 'title' => t('@private_public Article created by @user', array('@private_public' => $type, '@user' => $this->webUser->name)), - ); - if ($is_private) { - $edit['private'] = TRUE; - $edit['body[und][0][value]'] = 'private node'; - $edit['field_tags[und]'] = 'private'; - } - else { - $edit['body[und][0][value]'] = 'public node'; - $edit['field_tags[und]'] = 'public'; - } - - $this->drupalPost('node/add/article', $edit, t('Save')); - $nid = db_query('SELECT nid FROM {node} WHERE title = :title', array(':title' => $edit['title']))->fetchField(); - $private_status = db_query('SELECT private FROM {node_access_test} where nid = :nid', array(':nid' => $nid))->fetchField(); - $this->assertTrue($is_private == $private_status, t('The private status of the node was properly set in the node_access_test table.')); - if ($is_private) { - $private_nodes[] = $nid; - } - $titles[$nid] = $edit['title']; - $this->nodesByUser[$this->webUser->uid][$nid] = $is_private; - } - } - $this->publicTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'public'))->fetchField(); - $this->privateTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'private'))->fetchField(); - $this->assertTrue($this->publicTid, t('Public tid was found')); - $this->assertTrue($this->privateTid, t('Private tid was found')); - foreach ($simple_users as $this->webUser) { - $this->drupalLogin($this->webUser); - // Check own nodes to see that all are readable. - foreach ($this->nodesByUser as $uid => $data) { - foreach ($data as $nid => $is_private) { - $this->drupalGet('node/' . $nid); - if ($is_private) { - $should_be_visible = $uid == $this->webUser->uid; - } - else { - $should_be_visible = TRUE; - } - $this->assertResponse($should_be_visible ? 200 : 403, strtr('A %private node by user %uid is %visible for user %current_uid.', array( - '%private' => $is_private ? 'private' : 'public', - '%uid' => $uid, - '%visible' => $should_be_visible ? 'visible' : 'not visible', - '%current_uid' => $this->webUser->uid, - ))); - } - } - - // Check to see that the correct nodes are shown on taxonomy/private - // and taxonomy/public. - $this->assertTaxonomyPage(FALSE); - } - - // Now test that a user with 'access any private content' can view content. - $access_user = $this->drupalCreateUser(array('access content', 'create article content', 'node test view', 'search content')); - $this->drupalLogin($access_user); - - foreach ($this->nodesByUser as $uid => $private_status) { - foreach ($private_status as $nid => $is_private) { - $this->drupalGet('node/' . $nid); - $this->assertResponse(200); - } - } - - // This user should be able to see all of the nodes on the relevant - // taxonomy pages. - $this->assertTaxonomyPage(TRUE); - } - - /** - * Checks taxonomy/term listings to ensure only accessible nodes are listed. - * - * @param $is_admin - * A boolean indicating whether the current user is an administrator. If - * TRUE, all nodes should be listed. If FALSE, only public nodes and the - * user's own private nodes should be listed. - */ - protected function assertTaxonomyPage($is_admin) { - foreach (array($this->publicTid, $this->privateTid) as $tid_is_private => $tid) { - $this->drupalGet("taxonomy/term/$tid"); - $this->nids_visible = array(); - foreach ($this->xpath("//a[text()='Read more']") as $link) { - $this->assertTrue(preg_match('|node/(\d+)$|', (string) $link['href'], $matches), 'Read more points to a node'); - $this->nids_visible[$matches[1]] = TRUE; - } - foreach ($this->nodesByUser as $uid => $data) { - foreach ($data as $nid => $is_private) { - // Private nodes should be visible on the private term page, - // public nodes should be visible on the public term page. - $should_be_visible = $tid_is_private == $is_private; - // Non-administrators can only see their own nodes on the private - // term page. - if (!$is_admin && $tid_is_private) { - $should_be_visible = $should_be_visible && $uid == $this->webUser->uid; - } - $this->assertIdentical(isset($this->nids_visible[$nid]), $should_be_visible, strtr('A %private node by user %uid is %visible for user %current_uid on the %tid_is_private page.', array( - '%private' => $is_private ? 'private' : 'public', - '%uid' => $uid, - '%visible' => isset($this->nids_visible[$nid]) ? 'visible' : 'not visible', - '%current_uid' => $this->webUser->uid, - '%tid_is_private' => $tid_is_private ? 'private' : 'public', - ))); - } - } - } - } -} - -/** - * Test case to check node save related functionality, including import-save - */ -class NodeSaveTestCase extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Node save', - 'description' => 'Test $node->save() for saving content.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp('node_test'); - // Create a user that is allowed to post; we'll use this to test the submission. - $web_user = $this->drupalCreateUser(array('create article content')); - $this->drupalLogin($web_user); - $this->web_user = $web_user; - } - - /** - * Import test, to check if custom node ids are saved properly. - * Workflow: - * - first create a piece of content - * - save the content - * - check if node exists - */ - function testImport() { - // Node ID must be a number that is not in the database. - $max_nid = db_query('SELECT MAX(nid) FROM {node}')->fetchField(); - $test_nid = $max_nid + mt_rand(1000, 1000000); - $title = $this->randomName(8); - $node = array( - 'title' => $title, - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32)))), - 'uid' => $this->web_user->uid, - 'type' => 'article', - 'nid' => $test_nid, - 'enforceIsNew' => TRUE, - ); - $node = node_submit(entity_create('node', $node)); - - // Verify that node_submit did not overwrite the user ID. - $this->assertEqual($node->uid, $this->web_user->uid, t('Function node_submit() preserves user ID')); - - $node->save(); - // Test the import. - $node_by_nid = node_load($test_nid); - $this->assertTrue($node_by_nid, t('Node load by node ID.')); - - $node_by_title = $this->drupalGetNodeByTitle($title); - $this->assertTrue($node_by_title, t('Node load by node title.')); - } - - /** - * Check that the "created" and "changed" timestamps are set correctly when - * saving a new node or updating an existing node. - */ - function testTimestamps() { - // Use the default timestamps. - $edit = array( - 'uid' => $this->web_user->uid, - 'type' => 'article', - 'title' => $this->randomName(8), - ); - - entity_create('node', $edit)->save(); - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertEqual($node->created, REQUEST_TIME, t('Creating a node sets default "created" timestamp.')); - $this->assertEqual($node->changed, REQUEST_TIME, t('Creating a node sets default "changed" timestamp.')); - - // Store the timestamps. - $created = $node->created; - $changed = $node->changed; - - $node->save(); - $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); - $this->assertEqual($node->created, $created, t('Updating a node preserves "created" timestamp.')); - - // Programmatically set the timestamps using hook_node_presave. - $node->title = 'testing_node_presave'; - - $node->save(); - $node = $this->drupalGetNodeByTitle('testing_node_presave', TRUE); - $this->assertEqual($node->created, 280299600, t('Saving a node uses "created" timestamp set in presave hook.')); - $this->assertEqual($node->changed, 979534800, t('Saving a node uses "changed" timestamp set in presave hook.')); - - // Programmatically set the timestamps on the node. - $edit = array( - 'uid' => $this->web_user->uid, - 'type' => 'article', - 'title' => $this->randomName(8), - 'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT - 'changed' => 979534800, // Drupal 1.0 release. - ); - - entity_create('node', $edit)->save(); - $node = $this->drupalGetNodeByTitle($edit['title']); - $this->assertEqual($node->created, 280299600, t('Creating a node uses user-set "created" timestamp.')); - $this->assertNotEqual($node->changed, 979534800, t('Creating a node doesn\'t use user-set "changed" timestamp.')); - - // Update the timestamps. - $node->created = 979534800; - $node->changed = 280299600; - - $node->save(); - $node = $this->drupalGetNodeByTitle($edit['title'], TRUE); - $this->assertEqual($node->created, 979534800, t('Updating a node uses user-set "created" timestamp.')); - $this->assertNotEqual($node->changed, 280299600, t('Updating a node doesn\'t use user-set "changed" timestamp.')); - } - - /** - * Tests determing changes in hook_node_presave() and verifies the static node - * load cache is cleared upon save. - */ - function testDeterminingChanges() { - // Initial creation. - $node = entity_create('node', array( - 'uid' => $this->web_user->uid, - 'type' => 'article', - 'title' => 'test_changes', - )); - $node->save(); - - // Update the node without applying changes. - $node->save(); - $this->assertEqual($node->title, 'test_changes', 'No changes have been determined.'); - - // Apply changes. - $node->title = 'updated'; - $node->save(); - - // The hook implementations node_test_node_presave() and - // node_test_node_update() determine changes and change the title. - $this->assertEqual($node->title, 'updated_presave_update', 'Changes have been determined.'); - - // Test the static node load cache to be cleared. - $node = node_load($node->nid); - $this->assertEqual($node->title, 'updated_presave', 'Static cache has been cleared.'); - } -} - -/** - * Tests related to node types. - */ -class NodeTypeTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node types', - 'description' => 'Ensures that node type functions work correctly.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(array('field_ui')); - } - - /** - * Ensure that node type functions (node_type_get_*) work correctly. - * - * Load available node types and validate the returned data. - */ - function testNodeTypeGetFunctions() { - $node_types = node_type_get_types(); - $node_names = node_type_get_names(); - - $this->assertTrue(isset($node_types['article']), t('Node type article is available.')); - $this->assertTrue(isset($node_types['page']), t('Node type basic page is available.')); - - $this->assertEqual($node_types['article']->name, $node_names['article'], t('Correct node type base has been returned.')); - - $this->assertEqual($node_types['article'], node_type_load('article'), t('Correct node type has been returned.')); - $this->assertEqual($node_types['article']->name, node_type_get_name('article'), t('Correct node type name has been returned.')); - $this->assertEqual($node_types['page']->base, node_type_get_base('page'), t('Correct node type base has been returned.')); - } - - /** - * Test creating a content type programmatically and via a form. - */ - function testNodeTypeCreation() { - // Create a content type programmaticaly. - $type = $this->drupalCreateContentType(); - - $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => $type->type))->fetchField(); - $this->assertTrue($type_exists, 'The new content type has been created in the database.'); - - // Login a test user. - $web_user = $this->drupalCreateUser(array('create ' . $type->name . ' content')); - $this->drupalLogin($web_user); - - $this->drupalGet('node/add/' . $type->type); - $this->assertResponse(200, 'The new content type can be accessed at node/add.'); - - // Create a content type via the user interface. - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); - $this->drupalLogin($web_user); - $edit = array( - 'name' => 'foo', - 'title_label' => 'title for foo', - 'type' => 'foo', - ); - $this->drupalPost('admin/structure/types/add', $edit, t('Save content type')); - $type_exists = db_query('SELECT 1 FROM {node_type} WHERE type = :type', array(':type' => 'foo'))->fetchField(); - $this->assertTrue($type_exists, 'The new content type has been created in the database.'); - } - - /** - * Test editing a node type using the UI. - */ - function testNodeTypeEditing() { - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types')); - $this->drupalLogin($web_user); - - $instance = field_info_instance('node', 'body', 'page'); - $this->assertEqual($instance['label'], 'Body', t('Body field was found.')); - - // Verify that title and body fields are displayed. - $this->drupalGet('node/add/page'); - $this->assertRaw('Title', t('Title field was found.')); - $this->assertRaw('Body', t('Body field was found.')); - - // Rename the title field. - $edit = array( - 'title_label' => 'Foo', - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - // Refresh the field information for the rest of the test. - field_info_cache_clear(); - - $this->drupalGet('node/add/page'); - $this->assertRaw('Foo', t('New title label was displayed.')); - $this->assertNoRaw('Title', t('Old title label was not displayed.')); - - // Change the name, machine name and description. - $edit = array( - 'name' => 'Bar', - 'type' => 'bar', - 'description' => 'Lorem ipsum.', - ); - $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); - field_info_cache_clear(); - - $this->drupalGet('node/add'); - $this->assertRaw('Bar', t('New name was displayed.')); - $this->assertRaw('Lorem ipsum', t('New description was displayed.')); - $this->clickLink('Bar'); - $this->assertEqual(url('node/add/bar', array('absolute' => TRUE)), $this->getUrl(), t('New machine name was used in URL.')); - $this->assertRaw('Foo', t('Title field was found.')); - $this->assertRaw('Body', t('Body field was found.')); - - // Remove the body field. - $this->drupalPost('admin/structure/types/manage/bar/fields/body/delete', array(), t('Delete')); - // Resave the settings for this type. - $this->drupalPost('admin/structure/types/manage/bar', array(), t('Save content type')); - // Check that the body field doesn't exist. - $this->drupalGet('node/add/bar'); - $this->assertNoRaw('Body', t('Body field was not found.')); - } - - /** - * Test that node_types_rebuild() correctly handles the 'disabled' flag. - */ - function testNodeTypeStatus() { - // Enable all core node modules, and all types should be active. - module_enable(array('book', 'poll'), FALSE); - node_types_rebuild(); - $types = node_type_get_types(); - foreach (array('book', 'poll', 'article', 'page') as $type) { - $this->assertTrue(isset($types[$type]), t('%type is found in node types.', array('%type' => $type))); - $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), t('%type type is enabled.', array('%type' => $type))); - } - - // Disable poll module and the respective type should be marked as disabled. - module_disable(array('poll'), FALSE); - node_types_rebuild(); - $types = node_type_get_types(); - $this->assertTrue(!empty($types['poll']->disabled), t("Poll module's node type disabled.")); - - // Disable book module and the respective type should still be active, since - // it is not provided by hook_node_info(). - module_disable(array('book'), FALSE); - node_types_rebuild(); - $types = node_type_get_types(); - $this->assertTrue(isset($types['book']) && empty($types['book']->disabled), t("Book module's node type still active.")); - $this->assertTrue(!empty($types['poll']->disabled), t("Poll module's node type still disabled.")); - $this->assertTrue(isset($types['article']) && empty($types['article']->disabled), t("Article node type still active.")); - $this->assertTrue(isset($types['page']) && empty($types['page']->disabled), t("Basic page node type still active.")); - - // Re-enable the modules and verify that the types are active again. - module_enable(array('book', 'poll'), FALSE); - node_types_rebuild(); - $types = node_type_get_types(); - foreach (array('book', 'poll', 'article', 'page') as $type) { - $this->assertTrue(isset($types[$type]), t('%type is found in node types.', array('%type' => $type))); - $this->assertTrue(isset($types[$type]->disabled) && empty($types[$type]->disabled), t('%type type is enabled.', array('%type' => $type))); - } - } -} - -/** - * Test node type customizations persistence. - */ -class NodeTypePersistenceTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node type persist', - 'description' => 'Ensures that node type customization survives module enabling and disabling.', - 'group' => 'Node', - ); - } - - /** - * Test node type customizations persist through disable and uninstall. - */ - function testNodeTypeCustomizationPersistence() { - $web_user = $this->drupalCreateUser(array('bypass node access', 'administer content types', 'administer modules')); - $this->drupalLogin($web_user); - $poll_key = 'modules[Core][poll][enable]'; - $poll_enable = array($poll_key => "1"); - $poll_disable = array($poll_key => FALSE); - - // Enable poll and verify that the node type is in the DB and is not - // disabled. - $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database')); - $this->assertEqual($disabled, 0, t('Poll node type is not disabled')); - - // Check that poll node type (uncustomized) shows up. - $this->drupalGet('node/add'); - $this->assertText('poll', t('poll type is found on node/add')); - - // Customize poll description. - $description = $this->randomName(); - $edit = array('description' => $description); - $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type')); - - // Check that poll node type customization shows up. - $this->drupalGet('node/add'); - $this->assertText($description, t('Customized description found')); - - // Disable poll and check that the node type gets disabled. - $this->drupalPost('admin/modules', $poll_disable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $this->assertEqual($disabled, 1, t('Poll node type is disabled')); - $this->drupalGet('node/add'); - $this->assertNoText('poll', t('poll type is not found on node/add')); - - // Reenable poll and check that the customization survived the module - // disable. - $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $this->assertNotIdentical($disabled, FALSE, t('Poll node type found in the database')); - $this->assertEqual($disabled, 0, t('Poll node type is not disabled')); - $this->drupalGet('node/add'); - $this->assertText($description, t('Customized description found')); - - // Disable and uninstall poll. - $this->drupalPost('admin/modules', $poll_disable, t('Save configuration')); - $edit = array('uninstall[poll]' => 'poll'); - $this->drupalPost('admin/modules/uninstall', $edit, t('Uninstall')); - $this->drupalPost(NULL, array(), t('Uninstall')); - $disabled = db_query('SELECT disabled FROM {node_type} WHERE type = :type', array(':type' => 'poll'))->fetchField(); - $this->assertTrue($disabled, t('Poll node type is in the database and is disabled')); - $this->drupalGet('node/add'); - $this->assertNoText('poll', t('poll type is no longer found on node/add')); - - // Reenable poll and check that the customization survived the module - // uninstall. - $this->drupalPost('admin/modules', $poll_enable, t('Save configuration')); - $this->drupalGet('node/add'); - $this->assertText($description, t('Customized description is found even after uninstall and reenable.')); - } -} - -/** - * Rebuild the node_access table. - */ -class NodeAccessRebuildTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node access rebuild', - 'description' => 'Ensures that node access rebuild functions work correctly.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - $web_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports')); - $this->drupalLogin($web_user); - $this->web_user = $web_user; - } - - function testNodeAccessRebuild() { - $this->drupalGet('admin/reports/status'); - $this->clickLink(t('Rebuild permissions')); - $this->drupalPost(NULL, array(), t('Rebuild permissions')); - $this->assertText(t('Content permissions have been rebuilt.')); - } -} - -/** - * Test node administration page functionality. - */ -class NodeAdminTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node administration', - 'description' => 'Test node administration page functionality.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - // Remove the "view own unpublished content" permission which is set - // by default for authenticated users so we can test this permission - // correctly. - user_role_revoke_permissions(DRUPAL_AUTHENTICATED_RID, array('view own unpublished content')); - - $this->admin_user = $this->drupalCreateUser(array('access administration pages', 'access content overview', 'administer nodes', 'bypass node access')); - $this->base_user_1 = $this->drupalCreateUser(array('access content overview')); - $this->base_user_2 = $this->drupalCreateUser(array('access content overview', 'view own unpublished content')); - $this->base_user_3 = $this->drupalCreateUser(array('access content overview', 'bypass node access')); - } - - /** - * Tests that the table sorting works on the content admin pages. - */ - function testContentAdminSort() { - $this->drupalLogin($this->admin_user); - foreach (array('dd', 'aa', 'DD', 'bb', 'cc', 'CC', 'AA', 'BB') as $prefix) { - $this->drupalCreateNode(array('title' => $prefix . $this->randomName(6))); - } - - // Test that the default sort by node.changed DESC actually fires properly. - $nodes_query = db_select('node', 'n') - ->fields('n', array('nid')) - ->orderBy('changed', 'DESC') - ->execute() - ->fetchCol(); - - $nodes_form = array(); - $this->drupalGet('admin/content'); - foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { - $nodes_form[] = $input; - } - $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form according to the default query.'); - - // Compare the rendered HTML node list to a query for the nodes ordered by - // title to account for possible database-dependent sort order. - $nodes_query = db_select('node', 'n') - ->fields('n', array('nid')) - ->orderBy('title') - ->execute() - ->fetchCol(); - - $nodes_form = array(); - $this->drupalGet('admin/content', array('query' => array('sort' => 'asc', 'order' => 'Title'))); - foreach ($this->xpath('//table/tbody/tr/td/div/input/@value') as $input) { - $nodes_form[] = $input; - } - $this->assertEqual($nodes_query, $nodes_form, 'Nodes are sorted in the form the same as they are in the query.'); - } - - /** - * Tests content overview with different user permissions. - * - * Taxonomy filters are tested separately. - * @see TaxonomyNodeFilterTestCase - */ - function testContentAdminPages() { - $this->drupalLogin($this->admin_user); - - $nodes['published_page'] = $this->drupalCreateNode(array('type' => 'page')); - $nodes['published_article'] = $this->drupalCreateNode(array('type' => 'article')); - $nodes['unpublished_page_1'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_1->uid, 'status' => 0)); - $nodes['unpublished_page_2'] = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->base_user_2->uid, 'status' => 0)); - - // Verify view, edit, and delete links for any content. - $this->drupalGet('admin/content'); - $this->assertResponse(200); - foreach ($nodes as $node) { - $this->assertLinkByHref('node/' . $node->nid); - $this->assertLinkByHref('node/' . $node->nid . '/edit'); - $this->assertLinkByHref('node/' . $node->nid . '/delete'); - // Verify tableselect. - $this->assertFieldByName('nodes[' . $node->nid . ']', '', t('Tableselect found.')); - } - - // Verify filtering by publishing status. - $edit = array( - 'status' => 'status-1', - ); - $this->drupalPost(NULL, $edit, t('Filter')); - - $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), t('Content list is filtered by status.')); - - $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); - $this->assertLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); - - // Verify filtering by status and content type. - $edit = array( - 'type' => 'page', - ); - $this->drupalPost(NULL, $edit, t('Refine')); - - $this->assertRaw(t('where %property is %value', array('%property' => t('status'), '%value' => 'published')), t('Content list is filtered by status.')); - $this->assertRaw(t('and where %property is %value', array('%property' => t('type'), '%value' => 'Basic page')), t('Content list is filtered by content type.')); - - $this->assertLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); - - // Verify no operation links are displayed for regular users. - $this->drupalLogout(); - $this->drupalLogin($this->base_user_1); - $this->drupalGet('admin/content'); - $this->assertResponse(200); - $this->assertLinkByHref('node/' . $nodes['published_page']->nid); - $this->assertLinkByHref('node/' . $nodes['published_article']->nid); - $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['published_page']->nid . '/delete'); - $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['published_article']->nid . '/delete'); - - // Verify no unpublished content is displayed without permission. - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete'); - - // Verify no tableselect. - $this->assertNoFieldByName('nodes[' . $nodes['published_page']->nid . ']', '', t('No tableselect found.')); - - // Verify unpublished content is displayed with permission. - $this->drupalLogout(); - $this->drupalLogin($this->base_user_2); - $this->drupalGet('admin/content'); - $this->assertResponse(200); - $this->assertLinkByHref('node/' . $nodes['unpublished_page_2']->nid); - // Verify no operation links are displayed. - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_2']->nid . '/delete'); - - // Verify user cannot see unpublished content of other users. - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/edit'); - $this->assertNoLinkByHref('node/' . $nodes['unpublished_page_1']->nid . '/delete'); - - // Verify no tableselect. - $this->assertNoFieldByName('nodes[' . $nodes['unpublished_page_2']->nid . ']', '', t('No tableselect found.')); - - // Verify node access can be bypassed. - $this->drupalLogout(); - $this->drupalLogin($this->base_user_3); - $this->drupalGet('admin/content'); - $this->assertResponse(200); - foreach ($nodes as $node) { - $this->assertLinkByHref('node/' . $node->nid); - $this->assertLinkByHref('node/' . $node->nid . '/edit'); - $this->assertLinkByHref('node/' . $node->nid . '/delete'); - } - } -} - -/** - * Test node title. - */ -class NodeTitleTestCase extends NodeWebTestCase { - protected $admin_user; - - public static function getInfo() { - return array( - 'name' => 'Node title', - 'description' => 'Test node title.', - 'group' => 'Node' - ); - } - - function setUp() { - parent::setUp(array('comment')); - $this->admin_user = $this->drupalCreateUser(array('administer nodes', 'create article content', 'create page content', 'post comments')); - $this->drupalLogin($this->admin_user); - } - - /** - * Create one node and test if the node title has the correct value. - */ - function testNodeTitle() { - // Create "Basic page" content with title. - // Add the node to the frontpage so we can test if teaser links are clickable. - $settings = array( - 'title' => $this->randomName(8), - 'promote' => 1, - ); - $node = $this->drupalCreateNode($settings); - - // Test <title> tag. - $this->drupalGet("node/$node->nid"); - $xpath = '//title'; - $this->assertEqual(current($this->xpath($xpath)), $node->title .' | Drupal', 'Page title is equal to node title.', 'Node'); - - // Test breadcrumb in comment preview. - $this->drupalGet("comment/reply/$node->nid"); - $xpath = '//nav[@class="breadcrumb"]/ol/li[last()]/a'; - $this->assertEqual(current($this->xpath($xpath)), $node->title, 'Node breadcrumb is equal to node title.', 'Node'); - - // Test node title in comment preview. - $this->assertEqual(current($this->xpath('//article[@id=:id]/h2/a', array(':id' => 'node-' . $node->nid))), $node->title, 'Node preview title is equal to node title.', 'Node'); - - // Test node title is clickable on teaser list (/node). - $this->drupalGet('node'); - $this->clickLink($node->title); - } -} - -/** - * Test the node_feed() functionality. - */ -class NodeFeedTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Node feed', - 'description' => 'Ensures that node_feed() functions correctly.', - 'group' => 'Node', - ); - } - - /** - * Ensure that node_feed accepts and prints extra channel elements. - */ - function testNodeFeedExtraChannelElements() { - $response = node_feed(array(), array('copyright' => 'Drupal is a registered trademark of Dries Buytaert.')); - $this->assertTrue(strpos($response->getContent(), '<copyright>Drupal is a registered trademark of Dries Buytaert.</copyright>') !== FALSE); - } -} - -/** - * Functional tests for the node module blocks. - */ -class NodeBlockFunctionalTest extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node blocks', - 'description' => 'Test node block functionality.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(array('block')); - - // Create users and test node. - $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer nodes', 'administer blocks')); - $this->web_user = $this->drupalCreateUser(array('access content', 'create article content')); - } - - /** - * Test the recent comments block. - */ - function testRecentNodeBlock() { - $this->drupalLogin($this->admin_user); - - // Disallow anonymous users to view content. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access content' => FALSE, - )); - - // Set the block to a region to confirm block is available. - $edit = array( - 'blocks[node_recent][region]' => 'sidebar_first', - ); - $this->drupalPost('admin/structure/block', $edit, t('Save blocks')); - $this->assertText(t('The block settings have been updated.'), t('Block saved to first sidebar region.')); - - // Set block title and variables. - $block = array( - 'title' => $this->randomName(), - 'node_recent_block_count' => 2, - ); - $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); - - // Test that block is not visible without nodes - $this->drupalGet(''); - $this->assertText(t('No content available.'), t('Block with "No content available." found.')); - - // Add some test nodes. - $default_settings = array('uid' => $this->web_user->uid, 'type' => 'article'); - $node1 = $this->drupalCreateNode($default_settings); - $node2 = $this->drupalCreateNode($default_settings); - $node3 = $this->drupalCreateNode($default_settings); - - // Change the changed time for node so that we can test ordering. - db_update('node') - ->fields(array( - 'changed' => $node1->changed + 100, - )) - ->condition('nid', $node2->nid) - ->execute(); - db_update('node') - ->fields(array( - 'changed' => $node1->changed + 200, - )) - ->condition('nid', $node3->nid) - ->execute(); - - // Test that a user without the 'access content' permission cannot - // see the block. - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertNoText($block['title'], t('Block was not found.')); - - // Test that only the 2 latest nodes are shown. - $this->drupalLogin($this->web_user); - $this->assertNoText($node1->title, t('Node not found in block.')); - $this->assertText($node2->title, t('Node found in block.')); - $this->assertText($node3->title, t('Node found in block.')); - - // Check to make sure nodes are in the right order. - $this->assertTrue($this->xpath('//div[@id="block-node-recent"]/div/table/tbody/tr[position() = 1]/td/div/a[text() = "' . $node3->title . '"]'), t('Nodes were ordered correctly in block.')); - - // Set the number of recent nodes to show to 10. - $this->drupalLogout(); - $this->drupalLogin($this->admin_user); - $block = array( - 'node_recent_block_count' => 10, - ); - $this->drupalPost('admin/structure/block/manage/node/recent/configure', $block, t('Save block')); - $this->assertText(t('The block configuration has been saved.'), t('Block saved.')); - - // Post an additional node. - $node4 = $this->drupalCreateNode($default_settings); - // drupalCreateNode() does not automatically flush content caches unlike - // posting a node from a node form. - cache_clear_all(); - - // Test that all four nodes are shown. - $this->drupalGet(''); - $this->assertText($node1->title, t('Node found in block.')); - $this->assertText($node2->title, t('Node found in block.')); - $this->assertText($node3->title, t('Node found in block.')); - $this->assertText($node4->title, t('Node found in block.')); - - // Create the custom block. - $custom_block = array(); - $custom_block['info'] = $this->randomName(); - $custom_block['title'] = $this->randomName(); - $custom_block['types[article]'] = TRUE; - $custom_block['body[value]'] = $this->randomName(32); - $custom_block['regions[' . variable_get('theme_default', 'stark') . ']'] = 'content'; - if ($admin_theme = variable_get('admin_theme')) { - $custom_block['regions[' . $admin_theme . ']'] = 'content'; - } - $this->drupalPost('admin/structure/block/add', $custom_block, t('Save block')); - - $bid = db_query("SELECT bid FROM {block_custom} WHERE info = :info", array(':info' => $custom_block['info']))->fetchField(); - $this->assertTrue($bid, t('Custom block with visibility rule was created.')); - - // Verify visibility rules. - $this->drupalGet(''); - $this->assertNoText($custom_block['title'], t('Block was displayed on the front page.')); - $this->drupalGet('node/add/article'); - $this->assertText($custom_block['title'], t('Block was displayed on the node/add/article page.')); - $this->drupalGet('node/' . $node1->nid); - $this->assertText($custom_block['title'], t('Block was displayed on the node/N.')); - - // Delete the created custom block & verify that it's been deleted. - $this->drupalPost('admin/structure/block/manage/block/' . $bid . '/delete', array(), t('Delete')); - $bid = db_query("SELECT 1 FROM {block_node_type} WHERE module = 'block' AND delta = :delta", array(':delta' => $bid))->fetchField(); - $this->assertFalse($bid, t('Custom block was deleted.')); - } -} -/** - * Test multistep node forms basic options. - */ -class MultiStepNodeFormBasicOptionsTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Multistep node form basic options', - 'description' => 'Test the persistence of basic options through multiple steps.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp('poll'); - $web_user = $this->drupalCreateUser(array('administer nodes', 'create poll content')); - $this->drupalLogin($web_user); - } - - /** - * Change the default values of basic options to ensure they persist. - */ - function testMultiStepNodeFormBasicOptions() { - $edit = array( - 'title' => 'a', - 'status' => FALSE, - 'promote' => FALSE, - 'sticky' => 1, - 'choice[new:0][chtext]' => 'a', - 'choice[new:1][chtext]' => 'a', - ); - $this->drupalPost('node/add/poll', $edit, t('Add another choice')); - $this->assertNoFieldChecked('edit-status', 'status stayed unchecked'); - $this->assertNoFieldChecked('edit-promote', 'promote stayed unchecked'); - $this->assertFieldChecked('edit-sticky', 'sticky stayed checked'); - } -} - -/** - * Test to ensure that a node's content is always rebuilt. - */ -class NodeBuildContent extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Rebuild content', - 'description' => 'Test the rebuilding of content for different build modes.', - 'group' => 'Node', - ); - } - - /** - * Test to ensure that a node's content array is rebuilt on every call to node_build_content(). - */ - function testNodeRebuildContent() { - $node = $this->drupalCreateNode(); - - // Set a property in the content array so we can test for its existence later on. - $node->content['test_content_property'] = array('#value' => $this->randomString()); - $content = node_build_content($node); - - // If the property doesn't exist it means the node->content was rebuilt. - $this->assertFalse(isset($content['test_content_property']), t('Node content was emptied prior to being built.')); - } -} - -/** - * Tests node_query_node_access_alter(). - */ -class NodeQueryAlter extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Node query alter', - 'description' => 'Test that node access queries are properly altered by the node module.', - 'group' => 'Node', - ); - } - - /** - * User with permission to view content. - */ - protected $accessUser; - - /** - * User without permission to view content. - */ - protected $noAccessUser; - - function setUp() { - parent::setUp('node_access_test'); - node_access_rebuild(); - - // Create some content. - $this->drupalCreateNode(); - $this->drupalCreateNode(); - $this->drupalCreateNode(); - $this->drupalCreateNode(); - - // Create user with simple node access permission. The 'node test view' - // permission is implemented and granted by the node_access_test module. - $this->accessUser = $this->drupalCreateUser(array('access content', 'node test view')); - $this->noAccessUser = $this->drupalCreateUser(array('access content')); - $this->noAccessUser2 = $this->drupalCreateUser(array('access content')); - } - - /** - * Tests that node access permissions are followed. - */ - function testNodeQueryAlterWithUI() { - // Verify that a user with access permission can see at least one node. - $this->drupalLogin($this->accessUser); - $this->drupalGet('node_access_test_page'); - $this->assertText('Yes, 4 nodes', "4 nodes were found for access user"); - $this->assertNoText('Exception', "No database exception"); - - // Verify that a user with no access permission cannot see nodes. - $this->drupalLogin($this->noAccessUser); - $this->drupalGet('node_access_test_page'); - $this->assertText('No nodes', "No nodes were found for no access user"); - $this->assertNoText('Exception', "No database exception"); - } - - /** - * Lower-level test of 'node_access' query alter, for user with access. - * - * Verifies that a non-standard table alias can be used, and that a - * user with node access can view the nodes. - */ - function testNodeQueryAlterLowLevelWithAccess() { - // User with access should be able to view 4 nodes. - try { - $query = db_select('node', 'mytab') - ->fields('mytab'); - $query->addTag('node_access'); - $query->addMetaData('op', 'view'); - $query->addMetaData('account', $this->accessUser); - - $result = $query->execute()->fetchAll(); - $this->assertEqual(count($result), 4, t('User with access can see correct nodes')); - } - catch (Exception $e) { - $this->fail(t('Altered query is malformed')); - } - } - - /** - * Lower-level test of 'node_access' query alter, for user without access. - * - * Verifies that a non-standard table alias can be used, and that a - * user without node access cannot view the nodes. - */ - function testNodeQueryAlterLowLevelNoAccess() { - // User without access should be able to view 0 nodes. - try { - $query = db_select('node', 'mytab') - ->fields('mytab'); - $query->addTag('node_access'); - $query->addMetaData('op', 'view'); - $query->addMetaData('account', $this->noAccessUser); - - $result = $query->execute()->fetchAll(); - $this->assertEqual(count($result), 0, t('User with no access cannot see nodes')); - } - catch (Exception $e) { - $this->fail(t('Altered query is malformed')); - } - } - - /** - * Lower-level test of 'node_access' query alter, for edit access. - * - * Verifies that a non-standard table alias can be used, and that a - * user with view-only node access cannot edit the nodes. - */ - function testNodeQueryAlterLowLevelEditAccess() { - // User with view-only access should not be able to edit nodes. - try { - $query = db_select('node', 'mytab') - ->fields('mytab'); - $query->addTag('node_access'); - $query->addMetaData('op', 'update'); - $query->addMetaData('account', $this->accessUser); - - $result = $query->execute()->fetchAll(); - $this->assertEqual(count($result), 0, t('User with view-only access cannot edit nodes')); - } - catch (Exception $e) { - $this->fail($e->getMessage()); - $this->fail((string) $query); - $this->fail(t('Altered query is malformed')); - } - } - - /** - * Lower-level test of 'node_access' query alter override. - * - * Verifies that node_access_view_all_nodes() is called from - * node_query_node_access_alter(). We do this by checking that - * a user which normally would not have view privileges is able - * to view the nodes when we add a record to {node_access} paired - * with a corresponding privilege in hook_node_grants(). - */ - function testNodeQueryAlterOverride() { - $record = array( - 'nid' => 0, - 'gid' => 0, - 'realm' => 'node_access_all', - 'grant_view' => 1, - 'grant_update' => 0, - 'grant_delete' => 0, - ); - drupal_write_record('node_access', $record); - - // Test that the noAccessUser still doesn't have the 'view' - // privilege after adding the node_access record. - drupal_static_reset('node_access_view_all_nodes'); - try { - $query = db_select('node', 'mytab') - ->fields('mytab'); - $query->addTag('node_access'); - $query->addMetaData('op', 'view'); - $query->addMetaData('account', $this->noAccessUser); - - $result = $query->execute()->fetchAll(); - $this->assertEqual(count($result), 0, t('User view privileges are not overridden')); - } - catch (Exception $e) { - $this->fail(t('Altered query is malformed')); - } - - // Have node_test_node_grants return a node_access_all privilege, - // to grant the noAccessUser 'view' access. To verify that - // node_access_view_all_nodes is properly checking the specified - // $account instead of the global $user, we will log in as - // noAccessUser2. - $this->drupalLogin($this->noAccessUser2); - variable_set('node_test_node_access_all_uid', $this->noAccessUser->uid); - drupal_static_reset('node_access_view_all_nodes'); - try { - $query = db_select('node', 'mytab') - ->fields('mytab'); - $query->addTag('node_access'); - $query->addMetaData('op', 'view'); - $query->addMetaData('account', $this->noAccessUser); - - $result = $query->execute()->fetchAll(); - $this->assertEqual(count($result), 4, t('User view privileges are overridden')); - } - catch (Exception $e) { - $this->fail(t('Altered query is malformed')); - } - variable_del('node_test_node_access_all_uid'); - } -} - - -/** - * Tests node_query_entity_field_access_alter(). - */ -class NodeEntityFieldQueryAlter extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Node entity query alter', - 'description' => 'Test that node access entity queries are properly altered by the node module.', - 'group' => 'Node', - ); - } - - /** - * User with permission to view content. - */ - protected $accessUser; - - /** - * User without permission to view content. - */ - protected $noAccessUser; - - function setUp() { - parent::setUp('node_access_test'); - node_access_rebuild(); - - // Creating 4 nodes with an entity field so we can test that sort of query - // alter. All field values starts with 'A' so we can identify and fetch them - // in the node_access_test module. - $settings = array('langcode' => LANGUAGE_NOT_SPECIFIED); - for ($i = 0; $i < 4; $i++) { - $body = array( - 'value' => 'A' . $this->randomName(32), - 'format' => filter_default_format(), - ); - $settings['body'][LANGUAGE_NOT_SPECIFIED][0] = $body; - $this->drupalCreateNode($settings); - } - - // Create user with simple node access permission. The 'node test view' - // permission is implemented and granted by the node_access_test module. - $this->accessUser = $this->drupalCreateUser(array('access content', 'node test view')); - $this->noAccessUser = $this->drupalCreateUser(array('access content')); - } - - /** - * Tests that node access permissions are followed. - */ - function testNodeQueryAlterWithUI() { - // Verify that a user with access permission can see at least one node. - $this->drupalLogin($this->accessUser); - $this->drupalGet('node_access_entity_test_page'); - $this->assertText('Yes, 4 nodes', "4 nodes were found for access user"); - $this->assertNoText('Exception', "No database exception"); - - // Verify that a user with no access permission cannot see nodes. - $this->drupalLogin($this->noAccessUser); - $this->drupalGet('node_access_entity_test_page'); - $this->assertText('No nodes', "No nodes were found for no access user"); - $this->assertNoText('Exception', "No database exception"); - } -} - -/** - * Test node token replacement in strings. - */ -class NodeTokenReplaceTestCase extends NodeWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Node token replacement', - 'description' => 'Generates text using placeholders for dummy content to check node token replacement.', - 'group' => 'Node', - ); - } - - /** - * Creates a node, then tests the tokens generated from it. - */ - function testNodeTokenReplacement() { - $language_interface = drupal_container()->get(LANGUAGE_TYPE_INTERFACE); - $url_options = array( - 'absolute' => TRUE, - 'language' => $language_interface, - ); - - // Create a user and a node. - $account = $this->drupalCreateUser(); - $settings = array( - 'type' => 'article', - 'uid' => $account->uid, - 'title' => '<blink>Blinking Text</blink>', - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32), 'summary' => $this->randomName(16)))), - ); - $node = $this->drupalCreateNode($settings); - - // Load node so that the body and summary fields are structured properly. - $node = node_load($node->nid); - $instance = field_info_instance('node', 'body', $node->type); - - // Generate and test sanitized tokens. - $tests = array(); - $tests['[node:nid]'] = $node->nid; - $tests['[node:vid]'] = $node->vid; - $tests['[node:tnid]'] = $node->tnid; - $tests['[node:type]'] = 'article'; - $tests['[node:type-name]'] = 'Article'; - $tests['[node:title]'] = check_plain($node->title); - $tests['[node:body]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'value'); - $tests['[node:summary]'] = _text_sanitize($instance, $node->langcode, $node->body[$node->langcode][0], 'summary'); - $tests['[node:langcode]'] = check_plain($node->langcode); - $tests['[node:url]'] = url('node/' . $node->nid, $url_options); - $tests['[node:edit-url]'] = url('node/' . $node->nid . '/edit', $url_options); - $tests['[node:author]'] = check_plain(user_format_name($account)); - $tests['[node:author:uid]'] = $node->uid; - $tests['[node:author:name]'] = check_plain(user_format_name($account)); - $tests['[node:created:since]'] = format_interval(REQUEST_TIME - $node->created, 2, $language_interface->langcode); - $tests['[node:changed:since]'] = format_interval(REQUEST_TIME - $node->changed, 2, $language_interface->langcode); - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Sanitized node token %token replaced.', array('%token' => $input))); - } - - // Generate and test unsanitized tokens. - $tests['[node:title]'] = $node->title; - $tests['[node:body]'] = $node->body[$node->langcode][0]['value']; - $tests['[node:summary]'] = $node->body[$node->langcode][0]['summary']; - $tests['[node:langcode]'] = $node->langcode; - $tests['[node:author:name]'] = user_format_name($account); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $node), array('language' => $language_interface, 'sanitize' => FALSE)); - $this->assertEqual($output, $expected, t('Unsanitized node token %token replaced.', array('%token' => $input))); - } - } -} - -/** - * Tests user permissions for node revisions. - */ -class NodeRevisionPermissionsTestCase extends NodeWebTestCase { - protected $node_revisions = array(); - protected $accounts = array(); - - // Map revision permission names to node revision access ops. - protected $map = array( - 'view' => 'view revisions', - 'update' => 'revert revisions', - 'delete' => 'delete revisions', - ); - - public static function getInfo() { - return array( - 'name' => 'Node revision permissions', - 'description' => 'Tests user permissions for node revision operations.', - 'group' => 'Node', - ); - } - - function setUp() { - parent::setUp(); - - // Create a node with several revisions. - $node = $this->drupalCreateNode(); - $this->node_revisions[] = $node; - - for ($i = 0; $i < 3; $i++) { - // Create a revision for the same nid and settings with a random log. - $revision = clone $node; - $revision->revision = 1; - $revision->log = $this->randomName(32); - node_save($revision); - $this->node_revisions[] = $revision; - } - - // Create three users, one with each revision permission. - foreach ($this->map as $op => $permission) { - // Create the user. - $account = $this->drupalCreateUser( - array( - 'access content', - 'edit any page content', - 'delete any page content', - $permission, - ) - ); - $account->op = $op; - $this->accounts[] = $account; - } - - // Create an admin account (returns TRUE for all revision permissions). - $admin_account = $this->drupalCreateUser(array('access content', 'administer nodes')); - $admin_account->is_admin = TRUE; - $this->accounts['admin'] = $admin_account; - - // Create a normal account (returns FALSE for all revision permissions). - $normal_account = $this->drupalCreateUser(); - $normal_account->op = FALSE; - $this->accounts[] = $normal_account; - } - - /** - * Tests the _node_revision_access() function. - */ - function testNodeRevisionAccess() { - $revision = $this->node_revisions[1]; - - $parameters = array( - 'op' => array_keys($this->map), - 'account' => $this->accounts, - ); - - $permutations = $this->generatePermutations($parameters); - foreach ($permutations as $case) { - if (!empty($case['account']->is_admin) || $case['op'] == $case['account']->op) { - $this->assertTrue(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} granted."); - } - else { - $this->assertFalse(_node_revision_access($revision, $case['op'], $case['account']), "{$this->map[$case['op']]} not granted."); - } - } - - // Test that access is FALSE for a node administrator with an invalid $node - // or $op parameters. - $admin_account = $this->accounts['admin']; - $this->assertFalse(_node_revision_access($revision, 'invalid-op', $admin_account), '_node_revision_access() returns FALSE with an invalid op.'); - - // Test that the $account parameter defaults to the "logged in" user. - $original_user = $GLOBALS['user']; - $GLOBALS['user'] = $admin_account; - $this->assertTrue(_node_revision_access($revision, 'view'), '_node_revision_access() returns TRUE when used with global user.'); - $GLOBALS['user'] = $original_user; - } -} - -/** - * Tests pagination with a node access module enabled. - */ -class NodeAccessPagerTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'Node access pagination', - 'description' => 'Test access controlled node views have the right amount of comment pages.', - 'group' => 'Node', - ); - } - - public function setUp() { - parent::setUp('node_access_test', 'comment', 'forum'); - node_access_rebuild(); - $this->web_user = $this->drupalCreateUser(array('access content', 'access comments', 'node test view')); - } - - /** - * Tests the comment pager for nodes with multiple grants per realm. - */ - public function testCommentPager() { - // Create a node. - $node = $this->drupalCreateNode(); - - // Create 60 comments. - for ($i = 0; $i < 60; $i++) { - $comment = entity_create('comment', array( - 'nid' => $node->nid, - 'subject' => $this->randomName(), - 'comment_body' => array( - LANGUAGE_NOT_SPECIFIED => array( - array('value' => $this->randomName()), - ), - ), - )); - $comment->save(); - } - - $this->drupalLogin($this->web_user); - - // View the node page. With the default 50 comments per page there should - // be two pages (0, 1) but no third (2) page. - $this->drupalGet('node/' . $node->nid); - $this->assertText($node->title, t('Node title found.')); - $this->assertText(t('Comments'), t('Has a comments section.')); - $this->assertRaw('page=1', t('Secound page exists.')); - $this->assertNoRaw('page=2', t('No third page exists.')); - } - - /** - * Tests the forum node pager for nodes with multiple grants per realm. - */ - public function testForumPager() { - // Lookup the forums vocabulary vid. - $vid = variable_get('forum_nav_vocabulary', 0); - $this->assertTrue($vid, t('Forum navigation vocabulary found.')); - - // Lookup the general discussion term. - $tree = taxonomy_get_tree($vid, 0, 1); - $tid = reset($tree)->tid; - $this->assertTrue($tid, t('General discussion term found.')); - - // Create 30 nodes. - for ($i = 0; $i < 30; $i++) { - $this->drupalCreateNode(array( - 'nid' => NULL, - 'type' => 'forum', - 'taxonomy_forums' => array( - LANGUAGE_NOT_SPECIFIED => array( - array('tid' => $tid, 'vid' => $vid, 'vocabulary_machine_name' => 'forums'), - ), - ), - )); - } - - // View the general discussion forum page. With the default 25 nodes per - // page there should be two pages for 30 nodes, no more. - $this->drupalLogin($this->web_user); - $this->drupalGet('forum/' . $tid); - $this->assertRaw('page=1', t('Secound page exists.')); - $this->assertNoRaw('page=2', t('No third page exists.')); - } -} - -/** - * Tests the interaction of the node access system with fields. - */ -class NodeAccessFieldTestCase extends NodeWebTestCase { - - public static function getInfo() { - return array( - 'name' => 'Node access and fields', - 'description' => 'Tests the interaction of the node access system with fields.', - 'group' => 'Node', - ); - } - - public function setUp() { - parent::setUp('node_access_test', 'field_ui'); - node_access_rebuild(); - - // Create some users. - $this->admin_user = $this->drupalCreateUser(array('access content', 'bypass node access')); - $this->content_admin_user = $this->drupalCreateUser(array('access content', 'administer content types')); - - // Add a custom field to the page content type. - $this->field_name = drupal_strtolower($this->randomName() . '_field_name'); - $this->field = field_create_field(array('field_name' => $this->field_name, 'type' => 'text')); - $this->instance = field_create_instance(array( - 'field_name' => $this->field_name, - 'entity_type' => 'node', - 'bundle' => 'page', - )); - } - - /** - * Tests administering fields when node access is restricted. - */ - function testNodeAccessAdministerField() { - // Create a page node. - $langcode = LANGUAGE_NOT_SPECIFIED; - $field_data = array(); - $value = $field_data[$langcode][0]['value'] = $this->randomName(); - $node = $this->drupalCreateNode(array($this->field_name => $field_data)); - - // Log in as the administrator and confirm that the field value is present. - $this->drupalLogin($this->admin_user); - $this->drupalGet("node/{$node->nid}"); - $this->assertText($value, 'The saved field value is visible to an administrator.'); - - // Log in as the content admin and try to view the node. - $this->drupalLogin($this->content_admin_user); - $this->drupalGet("node/{$node->nid}"); - $this->assertText('Access denied', 'Access is denied for the content admin.'); - - // Modify the field default as the content admin. - $edit = array(); - $default = 'Sometimes words have two meanings'; - $edit["{$this->field_name}[$langcode][0][value]"] = $default; - $this->drupalPost( - "admin/structure/types/manage/page/fields/{$this->field_name}", - $edit, - t('Save settings') - ); - - // Log in as the administrator. - $this->drupalLogin($this->admin_user); - - // Confirm that the existing node still has the correct field value. - $this->drupalGet("node/{$node->nid}"); - $this->assertText($value, 'The original field value is visible to an administrator.'); - - // Confirm that the new default value appears when creating a new node. - $this->drupalGet('node/add/page'); - $this->assertRaw($default, 'The updated default value is displayed when creating a new node.'); - } -} diff --git a/core/modules/php/lib/Drupal/php/Tests/PhpAccessTest.php b/core/modules/php/lib/Drupal/php/Tests/PhpAccessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ec7f740d392a25c1381f1c08863b5066ca2e57ba --- /dev/null +++ b/core/modules/php/lib/Drupal/php/Tests/PhpAccessTest.php @@ -0,0 +1,39 @@ +<?php + +/** + * @file + * Definition of Drupal\php\Tests\PhpAccessTest. + */ + +namespace Drupal\php\Tests; + +/** + * Tests to make sure access to the PHP filter is properly restricted. + */ +class PhpAccessTest extends PhpTestBase { + public static function getInfo() { + return array( + 'name' => 'PHP filter access check', + 'description' => 'Make sure that users who don\'t have access to the PHP filter can\'t see it.', + 'group' => 'PHP', + ); + } + + /** + * Makes sure that the user can't use the PHP filter when not given access. + */ + function testNoPrivileges() { + // Create node with PHP filter enabled. + $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); + $this->drupalLogin($web_user); + $node = $this->createNodeWithCode(); + + // Make sure that the PHP code shows up as text. + $this->drupalGet('node/' . $node->nid); + $this->assertText('print', t('PHP code was not evaluated.')); + + // Make sure that user doesn't have access to filter. + $this->drupalGet('node/' . $node->nid . '/edit'); + $this->assertNoRaw('<option value="' . $this->php_code_format->format . '">', t('PHP code format not available.')); + } +} diff --git a/core/modules/php/lib/Drupal/php/Tests/PhpFilterTest.php b/core/modules/php/lib/Drupal/php/Tests/PhpFilterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..71ee5beee587f942ef1999c08bac3e4c53fd1a42 --- /dev/null +++ b/core/modules/php/lib/Drupal/php/Tests/PhpFilterTest.php @@ -0,0 +1,49 @@ +<?php + +/** + * @file + * Definition of Drupal\php\Tests\PhpFilterTest. + */ + +namespace Drupal\php\Tests; + +/** + * Tests to make sure the PHP filter actually evaluates PHP code when used. + */ +class PhpFilterTest extends PhpTestBase { + public static function getInfo() { + return array( + 'name' => 'PHP filter functionality', + 'description' => 'Make sure that PHP filter properly evaluates PHP code when enabled.', + 'group' => 'PHP', + ); + } + + /** + * Makes sure that the PHP filter evaluates PHP code when used. + */ + function testPhpFilter() { + // Log in as a user with permission to use the PHP code text format. + $php_code_permission = filter_permission_name(filter_format_load('php_code')); + $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission)); + $this->drupalLogin($web_user); + + // Create a node with PHP code in it. + $node = $this->createNodeWithCode(); + + // Make sure that the PHP code shows up as text. + $this->drupalGet('node/' . $node->nid); + $this->assertText('php print'); + + // Change filter to PHP filter and see that PHP code is evaluated. + $edit = array(); + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit["body[$langcode][0][format]"] = $this->php_code_format->format; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.')); + + // Make sure that the PHP code shows up as text. + $this->assertNoText('print "SimpleTest PHP was executed!"', t("PHP code isn't displayed.")); + $this->assertText('SimpleTest PHP was executed!', t('PHP code has been evaluated.')); + } +} diff --git a/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php b/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..2af8c8b61ad7c2cafd418294c7ada529a3c0841b --- /dev/null +++ b/core/modules/php/lib/Drupal/php/Tests/PhpTestBase.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Definition of Drupal\php\Tests\PhpTestBase. + */ + +namespace Drupal\php\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Defines a base PHP test case class. + */ +class PhpTestBase extends WebTestBase { + protected $php_code_format; + + function setUp() { + parent::setUp('php'); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Create and login admin user. + $admin_user = $this->drupalCreateUser(array('administer filters')); + $this->drupalLogin($admin_user); + + // Verify that the PHP code text format was inserted. + $php_format_id = 'php_code'; + $this->php_code_format = filter_format_load($php_format_id); + $this->assertEqual($this->php_code_format->name, 'PHP code', t('PHP code text format was created.')); + + // Verify that the format has the PHP code filter enabled. + $filters = filter_list_format($php_format_id); + $this->assertTrue($filters['php_code']->status, t('PHP code filter is enabled.')); + + // Verify that the format exists on the administration page. + $this->drupalGet('admin/config/content/formats'); + $this->assertText('PHP code', t('PHP code text format was created.')); + + // Verify that anonymous and authenticated user roles do not have access. + $this->drupalGet('admin/config/content/formats/' . $php_format_id); + $this->assertFieldByName('roles[' . DRUPAL_ANONYMOUS_RID . ']', FALSE, t('Anonymous users do not have access to PHP code format.')); + $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', FALSE, t('Authenticated users do not have access to PHP code format.')); + } + + /** + * Creates a test node with PHP code in the body. + * + * @return stdObject Node object. + */ + function createNodeWithCode() { + return $this->drupalCreateNode(array('body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))))); + } +} diff --git a/core/modules/php/php.info b/core/modules/php/php.info index e1f2d570d52470045ada4db1fdfed2b52cc1d160..f155609dd20964a3d0b0cf43608f47913fa159f7 100644 --- a/core/modules/php/php.info +++ b/core/modules/php/php.info @@ -3,4 +3,3 @@ description = Allows embedded PHP code/snippets to be evaluated. package = Core version = VERSION core = 8.x -files[] = php.test diff --git a/core/modules/php/php.test b/core/modules/php/php.test deleted file mode 100644 index 980fb7bebf115e227582ea5f0e0d8672bf8ee112..0000000000000000000000000000000000000000 --- a/core/modules/php/php.test +++ /dev/null @@ -1,125 +0,0 @@ -<?php - -/** - * @file - * Tests for php.module. - */ - -use Drupal\simpletest\WebTestBase; - -/** - * Defines a base PHP test case class. - */ -class PHPTestCase extends WebTestBase { - protected $php_code_format; - - function setUp() { - parent::setUp('php'); - - // Create Basic page node type. - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - // Create and login admin user. - $admin_user = $this->drupalCreateUser(array('administer filters')); - $this->drupalLogin($admin_user); - - // Verify that the PHP code text format was inserted. - $php_format_id = 'php_code'; - $this->php_code_format = filter_format_load($php_format_id); - $this->assertEqual($this->php_code_format->name, 'PHP code', t('PHP code text format was created.')); - - // Verify that the format has the PHP code filter enabled. - $filters = filter_list_format($php_format_id); - $this->assertTrue($filters['php_code']->status, t('PHP code filter is enabled.')); - - // Verify that the format exists on the administration page. - $this->drupalGet('admin/config/content/formats'); - $this->assertText('PHP code', t('PHP code text format was created.')); - - // Verify that anonymous and authenticated user roles do not have access. - $this->drupalGet('admin/config/content/formats/' . $php_format_id); - $this->assertFieldByName('roles[' . DRUPAL_ANONYMOUS_RID . ']', FALSE, t('Anonymous users do not have access to PHP code format.')); - $this->assertFieldByName('roles[' . DRUPAL_AUTHENTICATED_RID . ']', FALSE, t('Authenticated users do not have access to PHP code format.')); - } - - /** - * Creates a test node with PHP code in the body. - * - * @return stdObject Node object. - */ - function createNodeWithCode() { - return $this->drupalCreateNode(array('body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => '<?php print "SimpleTest PHP was executed!"; ?>'))))); - } -} - -/** - * Tests to make sure the PHP filter actually evaluates PHP code when used. - */ -class PHPFilterTestCase extends PHPTestCase { - public static function getInfo() { - return array( - 'name' => 'PHP filter functionality', - 'description' => 'Make sure that PHP filter properly evaluates PHP code when enabled.', - 'group' => 'PHP', - ); - } - - /** - * Makes sure that the PHP filter evaluates PHP code when used. - */ - function testPHPFilter() { - // Log in as a user with permission to use the PHP code text format. - $php_code_permission = filter_permission_name(filter_format_load('php_code')); - $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content', $php_code_permission)); - $this->drupalLogin($web_user); - - // Create a node with PHP code in it. - $node = $this->createNodeWithCode(); - - // Make sure that the PHP code shows up as text. - $this->drupalGet('node/' . $node->nid); - $this->assertText('php print'); - - // Change filter to PHP filter and see that PHP code is evaluated. - $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["body[$langcode][0][format]"] = $this->php_code_format->format; - $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); - $this->assertRaw(t('Basic page %title has been updated.', array('%title' => $node->title)), t('PHP code filter turned on.')); - - // Make sure that the PHP code shows up as text. - $this->assertNoText('print "SimpleTest PHP was executed!"', t("PHP code isn't displayed.")); - $this->assertText('SimpleTest PHP was executed!', t('PHP code has been evaluated.')); - } -} - -/** - * Tests to make sure access to the PHP filter is properly restricted. - */ -class PHPAccessTestCase extends PHPTestCase { - public static function getInfo() { - return array( - 'name' => 'PHP filter access check', - 'description' => 'Make sure that users who don\'t have access to the PHP filter can\'t see it.', - 'group' => 'PHP', - ); - } - - /** - * Makes sure that the user can't use the PHP filter when not given access. - */ - function testNoPrivileges() { - // Create node with PHP filter enabled. - $web_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); - $this->drupalLogin($web_user); - $node = $this->createNodeWithCode(); - - // Make sure that the PHP code shows up as text. - $this->drupalGet('node/' . $node->nid); - $this->assertText('print', t('PHP code was not evaluated.')); - - // Make sure that user doesn't have access to filter. - $this->drupalGet('node/' . $node->nid . '/edit'); - $this->assertNoRaw('<option value="' . $this->php_code_format->format . '">', t('PHP code format not available.')); - } -} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollBlockTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..65380152948eacb7c03f52c3b2e95bc14f08de9e --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollBlockTest.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollBlockTest. + */ + +namespace Drupal\poll\Tests; + +class PollBlockTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Block availability', + 'description' => 'Check if the most recent poll block is available.', + 'group' => 'Poll', + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Create and login user + $admin_user = $this->drupalCreateUser(array('administer blocks')); + $this->drupalLogin($admin_user); + } + + function testRecentBlock() { + // Set block title to confirm that the interface is available. + $this->drupalPost('admin/structure/block/manage/poll/recent/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 available. + $edit = array(); + $edit['blocks[poll_recent][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.')); + + // Create a poll which should appear in recent polls block. + $title = $this->randomName(); + $choices = $this->_generateChoices(7); + $poll_nid = $this->pollCreate($title, $choices, TRUE); + + // Verify poll appears in a block. + // View user page so we're not matching the poll node on front page. + $this->drupalGet('user'); + // If a 'block' view not generated, this title would not appear even though + // the choices might. + $this->assertText($title, 'Poll appears in block.'); + + // Logout and login back in as a user who can vote. + $this->drupalLogout(); + $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); + $this->drupalLogin($vote_user); + + // Verify we can vote via the block. + $edit = array( + 'choice' => '1', + ); + $this->drupalPost('user/' . $vote_user->uid, $edit, t('Vote')); + $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); + $this->assertText('Total votes: 1', 'Vote count updated correctly.'); + $this->assertText('Older polls', 'Link to older polls appears.'); + $this->clickLink('Older polls'); + $this->assertText('1 vote - open', 'Link to poll listing correct.'); + + // Close the poll and verify block doesn't appear. + $content_user = $this->drupalCreateUser(array('create poll content', 'edit any poll content', 'access content')); + $this->drupalLogout(); + $this->drupalLogin($content_user); + $close_edit = array('active' => 0); + $this->pollUpdate($poll_nid, $title, $close_edit); + $this->drupalGet('user/' . $content_user->uid); + $this->assertNoText($title, 'Poll no longer appears in block.'); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollCreateTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollCreateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9f226cb7e736af0080eb09b7de6bb0ad6e5c740e --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollCreateTest.php @@ -0,0 +1,110 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollCreateTest. + */ + +namespace Drupal\poll\Tests; + +class PollCreateTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll create', + 'description' => 'Adds "more choices", previews and creates a poll.', + 'group' => 'Poll' + ); + } + + function testPollCreate() { + $title = $this->randomName(); + $choices = $this->_generateChoices(7); + $poll_nid = $this->pollCreate($title, $choices, TRUE); + + // Verify poll appears on 'poll' page. + $this->drupalGet('poll'); + $this->assertText($title, 'Poll appears in poll list.'); + $this->assertText('open', 'Poll is active.'); + + // Click on the poll title to go to node page. + $this->clickLink($title); + $this->assertText('Total votes: 0', 'Link to poll correct.'); + + // Now add a new option to make sure that when we update the node the + // option is displayed. + $node = node_load($poll_nid); + + $new_option = $this->randomName(); + + $vote_count = '2000'; + $node->choice[] = array( + 'chid' => '', + 'chtext' => $new_option, + 'chvotes' => (int) $vote_count, + 'weight' => 1000, + ); + + $node->save(); + + $this->drupalGet('poll'); + $this->clickLink($title); + $this->assertText($new_option, 'New option found.'); + + $option = $this->xpath('//article[@id="node-1"]//div[@class="poll"]//dt[@class="choice-title"]'); + $this->assertEqual(end($option), $new_option, 'Last item is equal to new option.'); + + $votes = $this->xpath('//article[@id="node-1"]//div[@class="poll"]//div[@class="percent"]'); + $this->assertTrue(strpos(end($votes), $vote_count) > 0, t("Votes saved.")); + } + + function testPollClose() { + $content_user = $this->drupalCreateUser(array('create poll content', 'edit any poll content', 'access content')); + $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); + + // Create poll. + $title = $this->randomName(); + $choices = $this->_generateChoices(7); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + + $this->drupalLogout(); + $this->drupalLogin($content_user); + + // Edit the poll node and close the poll. + $close_edit = array('active' => 0); + $this->pollUpdate($poll_nid, $title, $close_edit); + + // Verify 'Vote' button no longer appears. + $this->drupalGet('node/' . $poll_nid); + $elements = $this->xpath('//input[@id="edit-vote"]'); + $this->assertTrue(empty($elements), t("Vote button doesn't appear.")); + + // Verify status on 'poll' page is 'closed'. + $this->drupalGet('poll'); + $this->assertText($title, 'Poll appears in poll list.'); + $this->assertText('closed', 'Poll is closed.'); + + // Edit the poll node and re-activate. + $open_edit = array('active' => 1); + $this->pollUpdate($poll_nid, $title, $open_edit); + + // Vote on the poll. + $this->drupalLogout(); + $this->drupalLogin($vote_user); + $vote_edit = array('choice' => '1'); + $this->drupalPost('node/' . $poll_nid, $vote_edit, t('Vote')); + $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(isset($elements[0]), t("'Cancel your vote' button appears.")); + + // Edit the poll node and close the poll. + $this->drupalLogout(); + $this->drupalLogin($content_user); + $close_edit = array('active' => 0); + $this->pollUpdate($poll_nid, $title, $close_edit); + + // Verify 'Cancel your vote' button no longer appears. + $this->drupalGet('node/' . $poll_nid); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(empty($elements), t("'Cancel your vote' button no longer appears.")); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollDeleteChoiceTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollDeleteChoiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e47ec6cf6c8e937c856e19c3ddef461fe361ef3a --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollDeleteChoiceTest.php @@ -0,0 +1,40 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollDeleteChoiceTest. + */ + +namespace Drupal\poll\Tests; + +class PollDeleteChoiceTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll choice deletion', + 'description' => 'Test the poll choice deletion logic.', + 'group' => 'Poll', + ); + } + + function testChoiceRemoval() { + // Set up a poll with three choices. + $title = $this->randomName(); + $choices = array('First choice', 'Second choice', 'Third choice'); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->assertTrue($poll_nid, t('Poll for choice deletion logic test created.')); + + // Edit the poll, and try to delete first poll choice. + $this->drupalGet("node/$poll_nid/edit"); + $edit['choice[chid:1][chtext]'] = ''; + $this->drupalPost(NULL, $edit, t('Save')); + + // Click on the poll title to go to node page. + $this->drupalGet('poll'); + $this->clickLink($title); + + // Check the first poll choice is deleted, while the others remain. + $this->assertNoText('First choice', t('First choice removed.')); + $this->assertText('Second choice', t('Second choice remains.')); + $this->assertText('Third choice', t('Third choice remains.')); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f9b82e65cc58d01a5274dfb7f665b31c1de008dc --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollExpirationTest.php @@ -0,0 +1,67 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollExpirationTest. + */ + +namespace Drupal\poll\Tests; + +class PollExpirationTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll expiration', + 'description' => 'Test the poll auto-expiration logic.', + 'group' => 'Poll', + ); + } + + function testAutoExpire() { + // Set up a poll. + $title = $this->randomName(); + $choices = $this->_generateChoices(2); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->assertTrue($poll_nid, t('Poll for auto-expire test created.')); + + // Visit the poll edit page and verify that by default, expiration + // is set to unlimited. + $this->drupalGet("node/$poll_nid/edit"); + $this->assertField('runtime', t('Poll expiration setting found.')); + $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); + $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == 0, t('Poll expiration set to unlimited.')); + + // Set the expiration to one week. + $edit = array(); + $poll_expiration = 604800; // One week. + $edit['runtime'] = $poll_expiration; + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertRaw(t('Poll %title has been updated.', array('%title' => $title)), t('Poll expiration settings saved.')); + + // Make sure that the changed expiration settings is kept. + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); + $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == $poll_expiration, t('Poll expiration set to unlimited.')); + + // Force a cron run. Since the expiration date has not yet been reached, + // the poll should remain active. + drupal_cron_run(); + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//input[@id="edit-active-1"]'); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll is still active.')); + + // Test expiration. Since REQUEST_TIME is a constant and we don't + // want to keep SimpleTest waiting until the moment of expiration arrives, + // we forcibly change the expiration date in the database. + $created = db_query('SELECT created FROM {node} WHERE nid = :nid', array(':nid' => $poll_nid))->fetchField(); + db_update('node') + ->fields(array('created' => $created - ($poll_expiration * 1.01))) + ->condition('nid', $poll_nid) + ->execute(); + + // Run cron and verify that the poll is now marked as "closed". + drupal_cron_run(); + $this->drupalGet("node/$poll_nid/edit"); + $elements = $this->xpath('//input[@id="edit-active-0"]'); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll has expired.')); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollJsAddChoiceTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollJsAddChoiceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7410958f0be4f58560daf10ac7e48b88e0b01de2 --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollJsAddChoiceTest.php @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollJsAddChoiceTest. + */ + +namespace Drupal\poll\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test adding new choices. + */ +class PollJsAddChoiceTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'Poll add choice', + 'description' => 'Submits a POST request for an additional poll choice.', + 'group' => 'Poll' + ); + } + + function setUp() { + parent::setUp(array('poll')); + } + + /** + * Test adding a new choice. + */ + function testAddChoice() { + $web_user = $this->drupalCreateUser(array('create poll content', 'access content')); + $this->drupalLogin($web_user); + $this->drupalGet('node/add/poll'); + $edit = array( + "title" => $this->randomName(), + 'choice[new:0][chtext]' => $this->randomName(), + 'choice[new:1][chtext]' => $this->randomName(), + ); + + // Press 'add choice' button through Ajax, and place the expected HTML result + // as the tested content. + $commands = $this->drupalPostAJAX(NULL, $edit, array('op' => t('Add another choice'))); + $this->content = $commands[1]['data']; + + $this->assertFieldByName('choice[chid:0][chtext]', $edit['choice[new:0][chtext]'], t('Field !i found', array('!i' => 0))); + $this->assertFieldByName('choice[chid:1][chtext]', $edit['choice[new:1][chtext]'], t('Field !i found', array('!i' => 1))); + $this->assertFieldByName('choice[new:0][chtext]', '', t('Field !i found', array('!i' => 2))); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollTestBase.php b/core/modules/poll/lib/Drupal/poll/Tests/PollTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..a554dd95c112ab15e35f539c67381e972e0b2137 --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollTestBase.php @@ -0,0 +1,195 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollTestBase. + */ + +namespace Drupal\poll\Tests; + +use Drupal\simpletest\WebTestBase; + +class PollTestBase extends WebTestBase { + function setUp() { + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'node'; + $modules[] = 'poll'; + parent::setUp($modules); + } + + /** + * Creates a poll. + * + * @param string $title + * The title of the poll. + * @param array $choices + * A list of choice labels. + * @param boolean $preview + * (optional) Whether to test if the preview is working or not. Defaults to + * TRUE. + * + * @return + * The node id of the created poll, or FALSE on error. + */ + function pollCreate($title, $choices, $preview = TRUE) { + $this->assertTrue(TRUE, 'Create a poll'); + + $admin_user = $this->drupalCreateUser(array('create poll content', 'administer nodes')); + $web_user = $this->drupalCreateUser(array('create poll content', 'access content', 'edit own poll content')); + $this->drupalLogin($admin_user); + + // Get the form first to initialize the state of the internal browser. + $this->drupalGet('node/add/poll'); + + // Prepare a form with two choices. + list($edit, $index) = $this->_pollGenerateEdit($title, $choices); + + // Verify that the vote count element only allows non-negative integers. + $edit['choice[new:1][chvotes]'] = -1; + $edit['choice[new:0][chvotes]'] = $this->randomString(7); + $this->drupalPost(NULL, $edit, t('Save')); + $this->assertText(t('Vote count for new choice must be higher or equal to 0.')); + $this->assertText(t('Vote count for new choice must be a number.')); + + // Repeat steps for initializing the state of the internal browser. + $this->drupalLogin($web_user); + $this->drupalGet('node/add/poll'); + list($edit, $index) = $this->_pollGenerateEdit($title, $choices); + + // Re-submit the form until all choices are filled in. + if (count($choices) > 2) { + while ($index < count($choices)) { + $this->drupalPost(NULL, $edit, t('Add another choice')); + $this->assertPollChoiceOrder($choices, $index); + list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index); + } + } + + if ($preview) { + $this->drupalPost(NULL, $edit, t('Preview')); + $this->assertPollChoiceOrder($choices, $index, TRUE); + list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index); + } + + $this->drupalPost(NULL, $edit, t('Save')); + $node = $this->drupalGetNodeByTitle($title); + $this->assertText(t('@type @title has been created.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been created.'); + $this->assertTrue($node->nid, t('Poll has been found in the database.')); + + return isset($node->nid) ? $node->nid : FALSE; + } + + /** + * Generates POST values for the poll node form, specifically poll choices. + * + * @param $title + * The title for the poll node. + * @param $choices + * An array containing poll choices, as generated by + * PollTestBase::_generateChoices(). + * @param $index + * (optional) The amount/number of already submitted poll choices. Defaults + * to 0. + * + * @return + * An indexed array containing: + * - The generated POST values, suitable for + * Drupal\simpletest\WebTestBase::drupalPost(). + * - The number of poll choices contained in 'edit', for potential re-usage + * in subsequent invocations of this function. + */ + function _pollGenerateEdit($title, array $choices, $index = 0) { + $max_new_choices = ($index == 0 ? 2 : 1); + $already_submitted_choices = array_slice($choices, 0, $index); + $new_choices = array_values(array_slice($choices, $index, $max_new_choices)); + + $edit = array( + 'title' => $title, + ); + foreach ($already_submitted_choices as $k => $text) { + $edit['choice[chid:' . $k . '][chtext]'] = $text; + } + foreach ($new_choices as $k => $text) { + $edit['choice[new:' . $k . '][chtext]'] = $text; + } + return array($edit, count($already_submitted_choices) + count($new_choices)); + } + + function _generateChoices($count = 7) { + $choices = array(); + for ($i = 1; $i <= $count; $i++) { + $choices[] = $this->randomName(); + } + return $choices; + } + + /** + * Assert correct poll choice order in the node form after submission. + * + * Verifies both the order in the DOM and in the 'weight' form elements. + * + * @param $choices + * An array containing poll choices, as generated by + * PollTestBase::_generateChoices(). + * @param $index + * (optional) The amount/number of already submitted poll choices. Defaults + * to 0. + * @param $preview + * (optional) Whether to also check the poll preview. + * + * @see PollTestBase::_pollGenerateEdit() + */ + function assertPollChoiceOrder(array $choices, $index = 0, $preview = FALSE) { + $expected = array(); + $weight = 0; + foreach ($choices as $id => $label) { + if ($id < $index) { + // The expected weight of each choice is higher than the previous one. + $weight++; + // Directly assert the weight form element value for this choice. + $this->assertFieldByName('choice[chid:' . $id . '][weight]', $weight, t('Found choice @id with weight @weight.', array( + '@id' => $id, + '@weight' => $weight, + ))); + // Append to our (to be reversed) stack of labels. + $expected[$weight] = $label; + } + } + ksort($expected); + + // Verify DOM order of poll choices (i.e., #weight of form elements). + $elements = $this->xpath('//input[starts-with(@name, :prefix) and contains(@name, :suffix)]', array( + ':prefix' => 'choice[chid:', + ':suffix' => '][chtext]', + )); + $expected_order = $expected; + foreach ($elements as $element) { + $next_label = array_shift($expected_order); + $this->assertEqual((string) $element['value'], $next_label); + } + + // If requested, also verify DOM order in preview. + if ($preview) { + $elements = $this->xpath('//div[contains(@class, :teaser)]/descendant::div[@class=:text]', array( + ':teaser' => 'node-teaser', + ':text' => 'text', + )); + $expected_order = $expected; + foreach ($elements as $element) { + $next_label = array_shift($expected_order); + $this->assertEqual((string) $element, $next_label, t('Found choice @label in preview.', array( + '@label' => $next_label, + ))); + } + } + } + + function pollUpdate($nid, $title, $edit) { + // Edit the poll node. + $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); + $this->assertText(t('@type @title has been updated.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been updated.'); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..1e19ec01e718d54495398014830e004b9fce2bf0 --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollTokenReplaceTest.php @@ -0,0 +1,93 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollTokenReplaceTest. + */ + +namespace Drupal\poll\Tests; + +/** + * Test poll token replacement in strings. + */ +class PollTokenReplaceTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll token replacement', + 'description' => 'Generates text using placeholders for dummy content to check poll token replacement.', + 'group' => 'Poll', + ); + } + + /** + * Creates a poll, then tests the tokens generated from it. + */ + function testPollTokenReplacement() { + global $language_interface; + + // Craete a poll with three choices. + $title = $this->randomName(); + $choices = $this->_generateChoices(3); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->drupalLogout(); + + // Create four users and have each of them vote. + $vote_user1 = $this->drupalCreateUser(array('vote on polls', 'access content')); + $this->drupalLogin($vote_user1); + $edit = array( + 'choice' => '1', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->drupalLogout(); + + $vote_user2 = $this->drupalCreateUser(array('vote on polls', 'access content')); + $this->drupalLogin($vote_user2); + $edit = array( + 'choice' => '1', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->drupalLogout(); + + $vote_user3 = $this->drupalCreateUser(array('vote on polls', 'access content')); + $this->drupalLogin($vote_user3); + $edit = array( + 'choice' => '2', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->drupalLogout(); + + $vote_user4 = $this->drupalCreateUser(array('vote on polls', 'access content')); + $this->drupalLogin($vote_user4); + $edit = array( + 'choice' => '3', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->drupalLogout(); + + $poll = node_load($poll_nid, NULL, TRUE); + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[node:poll-votes]'] = 4; + $tests['[node:poll-winner]'] = filter_xss($poll->choice[1]['chtext']); + $tests['[node:poll-winner-votes]'] = 2; + $tests['[node:poll-winner-percent]'] = 50; + $tests['[node:poll-duration]'] = format_interval($poll->runtime, 1, $language_interface->langcode); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $poll), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Sanitized poll token %token replaced.', array('%token' => $input))); + } + + // Generate and test unsanitized tokens. + $tests['[node:poll-winner]'] = $poll->choice[1]['chtext']; + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $poll), array('language' => $language_interface, 'sanitize' => FALSE)); + $this->assertEqual($output, $expected, t('Unsanitized poll token %token replaced.', array('%token' => $input))); + } + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollTranslateTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollTranslateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6b89cad8b5cc2b2565a88b4b05852e498680533a --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollTranslateTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollTranslateTest. + */ + +namespace Drupal\poll\Tests; + +/** + * Tests poll translation logic. + */ +class PollTranslateTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll translation', + 'description' => 'Test the poll translation logic.', + 'group' => 'Poll', + ); + } + + function setUp() { + parent::setUp(array('translation')); + } + + /** + * Tests poll creation and translation. + * + * Checks that the choice names get copied from the original poll and that + * the vote count values are set to 0. + */ + function testPollTranslate() { + $admin_user = $this->drupalCreateUser(array('administer content types', 'administer languages', 'edit any poll content', 'create poll content', 'administer nodes', 'translate content')); + + // Set up a poll with two choices. + $title = $this->randomName(); + $choices = array($this->randomName(), $this->randomName()); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->assertTrue($poll_nid, t('Poll for translation logic test created.')); + + $this->drupalLogout(); + $this->drupalLogin($admin_user); + + // Enable a second language. + $this->drupalGet('admin/config/regional/language'); + $edit = array(); + $edit['predefined_langcode'] = 'nl'; + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertRaw(t('The language %language has been created and can now be used.', array('%language' => 'Dutch')), t('Language Dutch has been created.')); + + // Set "Poll" content type to use multilingual support with translation. + $this->drupalGet('admin/structure/types/manage/poll'); + $edit = array(); + $edit['node_type_language'] = TRANSLATION_ENABLED; + $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type')); + $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Poll')), t('Poll content type has been updated.')); + + // Edit poll. + $this->drupalGet("node/$poll_nid/edit"); + $edit = array(); + // Set the poll's first choice count to 200. + $edit['choice[chid:1][chvotes]'] = 200; + // Set the language to Dutch. + $edit['langcode'] = 'nl'; + $this->drupalPost(NULL, $edit, t('Save')); + + // Translate the Dutch poll. + $this->drupalGet('node/add/poll', array('query' => array('translation' => $poll_nid, 'target' => 'en'))); + + $dutch_poll = node_load($poll_nid); + + // Check that the vote count values didn't get copied from the Dutch poll + // and are set to 0. + $this->assertFieldByName('choice[chid:1][chvotes]', '0', ('Found choice with vote count 0')); + $this->assertFieldByName('choice[chid:2][chvotes]', '0', ('Found choice with vote count 0')); + // Check that the choice names got copied from the Dutch poll. + $this->assertFieldByName('choice[chid:1][chtext]', $dutch_poll->choice[1]['chtext'], t('Found choice with text @text', array('@text' => $dutch_poll->choice[1]['chtext']))); + $this->assertFieldByName('choice[chid:2][chtext]', $dutch_poll->choice[2]['chtext'], t('Found choice with text @text', array('@text' => $dutch_poll->choice[2]['chtext']))); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollVoteCheckHostnameTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollVoteCheckHostnameTest.php new file mode 100644 index 0000000000000000000000000000000000000000..93573311919fed38f27c7537d247790c1320d9b3 --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollVoteCheckHostnameTest.php @@ -0,0 +1,150 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollVoteCheckHostnameTest. + */ + +namespace Drupal\poll\Tests; + +class PollVoteCheckHostnameTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'User poll vote capability.', + 'description' => 'Check that users and anonymous users from specified ip-address can only vote once.', + 'group' => 'Poll' + ); + } + + function setUp() { + parent::setUp(); + + // Create and login user. + $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'create poll content')); + $this->drupalLogin($this->admin_user); + + // Allow anonymous users to vote on polls. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access content' => TRUE, + 'vote on polls' => TRUE, + 'cancel own vote' => TRUE, + )); + + // Enable page cache to verify that the result page is not saved in the + // cache when anonymous voting is allowed. + $config = config('system.performance'); + $config->set('cache', 1); + $config->save(); + + // Create poll. + $title = $this->randomName(); + $choices = $this->_generateChoices(3); + $this->poll_nid = $this->pollCreate($title, $choices, FALSE); + + $this->drupalLogout(); + + // Create web users. + $this->web_user1 = $this->drupalCreateUser(array('access content', 'vote on polls', 'cancel own vote')); + $this->web_user2 = $this->drupalCreateUser(array('access content', 'vote on polls')); + } + + /** + * Check that anonymous users with same ip cannot vote on poll more than once + * unless user is logged in. + */ + function testHostnamePollVote() { + // Login User1. + $this->drupalLogin($this->web_user1); + + $edit = array( + 'choice' => '1', + ); + + // User1 vote on Poll. + $this->drupalPost('node/' . $this->poll_nid, $edit, t('Vote')); + $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user1->name))); + $this->assertText(t('Total votes: @votes', array('@votes' => 1)), t('Vote count updated correctly.')); + + // Check to make sure User1 cannot vote again. + $this->drupalGet('node/' . $this->poll_nid); + $elements = $this->xpath('//input[@value="Vote"]'); + $this->assertTrue(empty($elements), t("%user is not able to vote again.", array('%user' => $this->web_user1->name))); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); + + // Logout User1. + $this->drupalLogout(); + + // Fill the page cache by requesting the poll. + $this->drupalGet('node/' . $this->poll_nid); + $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); + $this->drupalGet('node/' . $this->poll_nid); + $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'HIT', t('Page was cached.')); + + // Anonymous user vote on Poll. + $this->drupalPost(NULL, $edit, t('Vote')); + $this->assertText(t('Your vote was recorded.'), t('Anonymous vote was recorded.')); + $this->assertText(t('Total votes: @votes', array('@votes' => 2)), t('Vote count updated correctly.')); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); + + // Check to make sure Anonymous user cannot vote again. + $this->drupalGet('node/' . $this->poll_nid); + $this->assertFalse($this->drupalGetHeader('x-drupal-cache'), t('Page was not cacheable.')); + $elements = $this->xpath('//input[@value="Vote"]'); + $this->assertTrue(empty($elements), t("Anonymous is not able to vote again.")); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); + + // Login User2. + $this->drupalLogin($this->web_user2); + + // User2 vote on poll. + $this->drupalPost('node/' . $this->poll_nid, $edit, t('Vote')); + $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user2->name))); + $this->assertText(t('Total votes: @votes', array('@votes' => 3)), 'Vote count updated correctly.'); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); + + // Logout User2. + $this->drupalLogout(); + + // Change host name for anonymous users. + db_update('poll_vote') + ->fields(array( + 'hostname' => '123.456.789.1', + )) + ->condition('hostname', '', '<>') + ->execute(); + + // Check to make sure Anonymous user can vote again with a new session after + // a hostname change. + $this->drupalGet('node/' . $this->poll_nid); + $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); + $this->drupalPost(NULL, $edit, t('Vote')); + $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user2->name))); + $this->assertText(t('Total votes: @votes', array('@votes' => 4)), 'Vote count updated correctly.'); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); + + // Check to make sure Anonymous user cannot vote again with a new session, + // and that the vote from the previous session cannot be cancelledd. + $this->curlClose(); + $this->drupalGet('node/' . $this->poll_nid); + $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); + $elements = $this->xpath('//input[@value="Vote"]'); + $this->assertTrue(empty($elements), t('Anonymous is not able to vote again.')); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); + + // Login User1. + $this->drupalLogin($this->web_user1); + + // Check to make sure User1 still cannot vote even after hostname changed. + $this->drupalGet('node/' . $this->poll_nid); + $elements = $this->xpath('//input[@value="Vote"]'); + $this->assertTrue(empty($elements), t("%user is not able to vote again.", array('%user' => $this->web_user1->name))); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); + } +} diff --git a/core/modules/poll/lib/Drupal/poll/Tests/PollVoteTest.php b/core/modules/poll/lib/Drupal/poll/Tests/PollVoteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..51fdc941484d60575504e980dc5ff434f70633fe --- /dev/null +++ b/core/modules/poll/lib/Drupal/poll/Tests/PollVoteTest.php @@ -0,0 +1,80 @@ +<?php + +/** + * @file + * Definition of Drupal\poll\Tests\PollVoteTest. + */ + +namespace Drupal\poll\Tests; + +class PollVoteTest extends PollTestBase { + public static function getInfo() { + return array( + 'name' => 'Poll vote', + 'description' => 'Vote on a poll', + 'group' => 'Poll' + ); + } + + function tearDown() { + parent::tearDown(); + } + + function testPollVote() { + $title = $this->randomName(); + $choices = $this->_generateChoices(7); + $poll_nid = $this->pollCreate($title, $choices, FALSE); + $this->drupalLogout(); + + $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); + $restricted_vote_user = $this->drupalCreateUser(array('vote on polls', 'access content')); + + $this->drupalLogin($vote_user); + + // Record a vote for the first choice. + $edit = array( + 'choice' => '1', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); + $this->assertText('Total votes: 1', 'Vote count updated correctly.'); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(isset($elements[0]), t("'Cancel your vote' button appears.")); + + $this->drupalGet("node/$poll_nid/votes"); + $this->assertText(t('This table lists all the recorded votes for this poll. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.'), 'Vote table text.'); + $this->assertText($choices[0], 'Vote recorded'); + + // Ensure poll listing page has correct number of votes. + $this->drupalGet('poll'); + $this->assertText($title, 'Poll appears in poll list.'); + $this->assertText('1 vote', 'Poll has 1 vote.'); + + // Cancel a vote. + $this->drupalPost('node/' . $poll_nid, array(), t('Cancel your vote')); + $this->assertText('Your vote was cancelled.', 'Your vote was cancelled.'); + $this->assertNoText('Cancel your vote', "Cancel vote button doesn't appear."); + + $this->drupalGet("node/$poll_nid/votes"); + $this->assertNoText($choices[0], 'Vote cancelled'); + + // Ensure poll listing page has correct number of votes. + $this->drupalGet('poll'); + $this->assertText($title, 'Poll appears in poll list.'); + $this->assertText('0 votes', 'Poll has 0 votes.'); + + // Log in as a user who can only vote on polls. + $this->drupalLogout(); + $this->drupalLogin($restricted_vote_user); + + // Vote on a poll. + $edit = array( + 'choice' => '1', + ); + $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); + $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); + $this->assertText('Total votes: 1', 'Vote count updated correctly.'); + $elements = $this->xpath('//input[@value="Cancel your vote"]'); + $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); + } +} diff --git a/core/modules/poll/poll.info b/core/modules/poll/poll.info index 99f778caf422d0456bd0d842179b329a5fe823d8..7cd674a6665001a863f1a411a797a535ce68785c 100644 --- a/core/modules/poll/poll.info +++ b/core/modules/poll/poll.info @@ -4,6 +4,5 @@ package = Core version = VERSION core = 8.x dependencies[] = node -files[] = poll.test stylesheets[all][] = poll.base.css stylesheets[all][] = poll.theme.css diff --git a/core/modules/poll/poll.test b/core/modules/poll/poll.test deleted file mode 100644 index b43c4073fe696f031c8fce5272c96c15daa69ed5..0000000000000000000000000000000000000000 --- a/core/modules/poll/poll.test +++ /dev/null @@ -1,865 +0,0 @@ -<?php - -/** - * @file - * Tests for poll.module. - */ - -use Drupal\simpletest\WebTestBase; - -class PollWebTestCase extends WebTestBase { - function setUp() { - $modules = func_get_args(); - if (isset($modules[0]) && is_array($modules[0])) { - $modules = $modules[0]; - } - $modules[] = 'node'; - $modules[] = 'poll'; - parent::setUp($modules); - } - - /** - * Creates a poll. - * - * @param string $title - * The title of the poll. - * @param array $choices - * A list of choice labels. - * @param boolean $preview - * (optional) Whether to test if the preview is working or not. Defaults to - * TRUE. - * - * @return - * The node id of the created poll, or FALSE on error. - */ - function pollCreate($title, $choices, $preview = TRUE) { - $this->assertTrue(TRUE, 'Create a poll'); - - $admin_user = $this->drupalCreateUser(array('create poll content', 'administer nodes')); - $web_user = $this->drupalCreateUser(array('create poll content', 'access content', 'edit own poll content')); - $this->drupalLogin($admin_user); - - // Get the form first to initialize the state of the internal browser. - $this->drupalGet('node/add/poll'); - - // Prepare a form with two choices. - list($edit, $index) = $this->_pollGenerateEdit($title, $choices); - - // Verify that the vote count element only allows non-negative integers. - $edit['choice[new:1][chvotes]'] = -1; - $edit['choice[new:0][chvotes]'] = $this->randomString(7); - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertText(t('Vote count for new choice must be higher or equal to 0.')); - $this->assertText(t('Vote count for new choice must be a number.')); - - // Repeat steps for initializing the state of the internal browser. - $this->drupalLogin($web_user); - $this->drupalGet('node/add/poll'); - list($edit, $index) = $this->_pollGenerateEdit($title, $choices); - - // Re-submit the form until all choices are filled in. - if (count($choices) > 2) { - while ($index < count($choices)) { - $this->drupalPost(NULL, $edit, t('Add another choice')); - $this->assertPollChoiceOrder($choices, $index); - list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index); - } - } - - if ($preview) { - $this->drupalPost(NULL, $edit, t('Preview')); - $this->assertPollChoiceOrder($choices, $index, TRUE); - list($edit, $index) = $this->_pollGenerateEdit($title, $choices, $index); - } - - $this->drupalPost(NULL, $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($title); - $this->assertText(t('@type @title has been created.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been created.'); - $this->assertTrue($node->nid, t('Poll has been found in the database.')); - - return isset($node->nid) ? $node->nid : FALSE; - } - - /** - * Generates POST values for the poll node form, specifically poll choices. - * - * @param $title - * The title for the poll node. - * @param $choices - * An array containing poll choices, as generated by - * PollWebTestCase::_generateChoices(). - * @param $index - * (optional) The amount/number of already submitted poll choices. Defaults - * to 0. - * - * @return - * An indexed array containing: - * - The generated POST values, suitable for - * Drupal\simpletest\WebTestBase::drupalPost(). - * - The number of poll choices contained in 'edit', for potential re-usage - * in subsequent invocations of this function. - */ - function _pollGenerateEdit($title, array $choices, $index = 0) { - $max_new_choices = ($index == 0 ? 2 : 1); - $already_submitted_choices = array_slice($choices, 0, $index); - $new_choices = array_values(array_slice($choices, $index, $max_new_choices)); - - $edit = array( - 'title' => $title, - ); - foreach ($already_submitted_choices as $k => $text) { - $edit['choice[chid:' . $k . '][chtext]'] = $text; - } - foreach ($new_choices as $k => $text) { - $edit['choice[new:' . $k . '][chtext]'] = $text; - } - return array($edit, count($already_submitted_choices) + count($new_choices)); - } - - function _generateChoices($count = 7) { - $choices = array(); - for ($i = 1; $i <= $count; $i++) { - $choices[] = $this->randomName(); - } - return $choices; - } - - /** - * Assert correct poll choice order in the node form after submission. - * - * Verifies both the order in the DOM and in the 'weight' form elements. - * - * @param $choices - * An array containing poll choices, as generated by - * PollWebTestCase::_generateChoices(). - * @param $index - * (optional) The amount/number of already submitted poll choices. Defaults - * to 0. - * @param $preview - * (optional) Whether to also check the poll preview. - * - * @see PollWebTestCase::_pollGenerateEdit() - */ - function assertPollChoiceOrder(array $choices, $index = 0, $preview = FALSE) { - $expected = array(); - $weight = 0; - foreach ($choices as $id => $label) { - if ($id < $index) { - // The expected weight of each choice is higher than the previous one. - $weight++; - // Directly assert the weight form element value for this choice. - $this->assertFieldByName('choice[chid:' . $id . '][weight]', $weight, t('Found choice @id with weight @weight.', array( - '@id' => $id, - '@weight' => $weight, - ))); - // Append to our (to be reversed) stack of labels. - $expected[$weight] = $label; - } - } - ksort($expected); - - // Verify DOM order of poll choices (i.e., #weight of form elements). - $elements = $this->xpath('//input[starts-with(@name, :prefix) and contains(@name, :suffix)]', array( - ':prefix' => 'choice[chid:', - ':suffix' => '][chtext]', - )); - $expected_order = $expected; - foreach ($elements as $element) { - $next_label = array_shift($expected_order); - $this->assertEqual((string) $element['value'], $next_label); - } - - // If requested, also verify DOM order in preview. - if ($preview) { - $elements = $this->xpath('//div[contains(@class, :teaser)]/descendant::div[@class=:text]', array( - ':teaser' => 'node-teaser', - ':text' => 'text', - )); - $expected_order = $expected; - foreach ($elements as $element) { - $next_label = array_shift($expected_order); - $this->assertEqual((string) $element, $next_label, t('Found choice @label in preview.', array( - '@label' => $next_label, - ))); - } - } - } - - function pollUpdate($nid, $title, $edit) { - // Edit the poll node. - $this->drupalPost('node/' . $nid . '/edit', $edit, t('Save')); - $this->assertText(t('@type @title has been updated.', array('@type' => node_type_get_name('poll'), '@title' => $title)), 'Poll has been updated.'); - } -} - -class PollCreateTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll create', - 'description' => 'Adds "more choices", previews and creates a poll.', - 'group' => 'Poll' - ); - } - - function testPollCreate() { - $title = $this->randomName(); - $choices = $this->_generateChoices(7); - $poll_nid = $this->pollCreate($title, $choices, TRUE); - - // Verify poll appears on 'poll' page. - $this->drupalGet('poll'); - $this->assertText($title, 'Poll appears in poll list.'); - $this->assertText('open', 'Poll is active.'); - - // Click on the poll title to go to node page. - $this->clickLink($title); - $this->assertText('Total votes: 0', 'Link to poll correct.'); - - // Now add a new option to make sure that when we update the node the - // option is displayed. - $node = node_load($poll_nid); - - $new_option = $this->randomName(); - - $vote_count = '2000'; - $node->choice[] = array( - 'chid' => '', - 'chtext' => $new_option, - 'chvotes' => (int) $vote_count, - 'weight' => 1000, - ); - - $node->save(); - - $this->drupalGet('poll'); - $this->clickLink($title); - $this->assertText($new_option, 'New option found.'); - - $option = $this->xpath('//article[@id="node-1"]//div[@class="poll"]//dt[@class="choice-title"]'); - $this->assertEqual(end($option), $new_option, 'Last item is equal to new option.'); - - $votes = $this->xpath('//article[@id="node-1"]//div[@class="poll"]//div[@class="percent"]'); - $this->assertTrue(strpos(end($votes), $vote_count) > 0, t("Votes saved.")); - } - - function testPollClose() { - $content_user = $this->drupalCreateUser(array('create poll content', 'edit any poll content', 'access content')); - $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); - - // Create poll. - $title = $this->randomName(); - $choices = $this->_generateChoices(7); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - - $this->drupalLogout(); - $this->drupalLogin($content_user); - - // Edit the poll node and close the poll. - $close_edit = array('active' => 0); - $this->pollUpdate($poll_nid, $title, $close_edit); - - // Verify 'Vote' button no longer appears. - $this->drupalGet('node/' . $poll_nid); - $elements = $this->xpath('//input[@id="edit-vote"]'); - $this->assertTrue(empty($elements), t("Vote button doesn't appear.")); - - // Verify status on 'poll' page is 'closed'. - $this->drupalGet('poll'); - $this->assertText($title, 'Poll appears in poll list.'); - $this->assertText('closed', 'Poll is closed.'); - - // Edit the poll node and re-activate. - $open_edit = array('active' => 1); - $this->pollUpdate($poll_nid, $title, $open_edit); - - // Vote on the poll. - $this->drupalLogout(); - $this->drupalLogin($vote_user); - $vote_edit = array('choice' => '1'); - $this->drupalPost('node/' . $poll_nid, $vote_edit, t('Vote')); - $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(isset($elements[0]), t("'Cancel your vote' button appears.")); - - // Edit the poll node and close the poll. - $this->drupalLogout(); - $this->drupalLogin($content_user); - $close_edit = array('active' => 0); - $this->pollUpdate($poll_nid, $title, $close_edit); - - // Verify 'Cancel your vote' button no longer appears. - $this->drupalGet('node/' . $poll_nid); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(empty($elements), t("'Cancel your vote' button no longer appears.")); - } -} - -class PollVoteTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll vote', - 'description' => 'Vote on a poll', - 'group' => 'Poll' - ); - } - - function tearDown() { - parent::tearDown(); - } - - function testPollVote() { - $title = $this->randomName(); - $choices = $this->_generateChoices(7); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - $this->drupalLogout(); - - $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); - $restricted_vote_user = $this->drupalCreateUser(array('vote on polls', 'access content')); - - $this->drupalLogin($vote_user); - - // Record a vote for the first choice. - $edit = array( - 'choice' => '1', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); - $this->assertText('Total votes: 1', 'Vote count updated correctly.'); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(isset($elements[0]), t("'Cancel your vote' button appears.")); - - $this->drupalGet("node/$poll_nid/votes"); - $this->assertText(t('This table lists all the recorded votes for this poll. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.'), 'Vote table text.'); - $this->assertText($choices[0], 'Vote recorded'); - - // Ensure poll listing page has correct number of votes. - $this->drupalGet('poll'); - $this->assertText($title, 'Poll appears in poll list.'); - $this->assertText('1 vote', 'Poll has 1 vote.'); - - // Cancel a vote. - $this->drupalPost('node/' . $poll_nid, array(), t('Cancel your vote')); - $this->assertText('Your vote was cancelled.', 'Your vote was cancelled.'); - $this->assertNoText('Cancel your vote', "Cancel vote button doesn't appear."); - - $this->drupalGet("node/$poll_nid/votes"); - $this->assertNoText($choices[0], 'Vote cancelled'); - - // Ensure poll listing page has correct number of votes. - $this->drupalGet('poll'); - $this->assertText($title, 'Poll appears in poll list.'); - $this->assertText('0 votes', 'Poll has 0 votes.'); - - // Log in as a user who can only vote on polls. - $this->drupalLogout(); - $this->drupalLogin($restricted_vote_user); - - // Vote on a poll. - $edit = array( - 'choice' => '1', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); - $this->assertText('Total votes: 1', 'Vote count updated correctly.'); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); - } -} - -class PollBlockTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Block availability', - 'description' => 'Check if the most recent poll block is available.', - 'group' => 'Poll', - ); - } - - function setUp() { - parent::setUp(array('block')); - - // Create and login user - $admin_user = $this->drupalCreateUser(array('administer blocks')); - $this->drupalLogin($admin_user); - } - - function testRecentBlock() { - // Set block title to confirm that the interface is available. - $this->drupalPost('admin/structure/block/manage/poll/recent/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 available. - $edit = array(); - $edit['blocks[poll_recent][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.')); - - // Create a poll which should appear in recent polls block. - $title = $this->randomName(); - $choices = $this->_generateChoices(7); - $poll_nid = $this->pollCreate($title, $choices, TRUE); - - // Verify poll appears in a block. - // View user page so we're not matching the poll node on front page. - $this->drupalGet('user'); - // If a 'block' view not generated, this title would not appear even though - // the choices might. - $this->assertText($title, 'Poll appears in block.'); - - // Logout and login back in as a user who can vote. - $this->drupalLogout(); - $vote_user = $this->drupalCreateUser(array('cancel own vote', 'inspect all votes', 'vote on polls', 'access content')); - $this->drupalLogin($vote_user); - - // Verify we can vote via the block. - $edit = array( - 'choice' => '1', - ); - $this->drupalPost('user/' . $vote_user->uid, $edit, t('Vote')); - $this->assertText('Your vote was recorded.', 'Your vote was recorded.'); - $this->assertText('Total votes: 1', 'Vote count updated correctly.'); - $this->assertText('Older polls', 'Link to older polls appears.'); - $this->clickLink('Older polls'); - $this->assertText('1 vote - open', 'Link to poll listing correct.'); - - // Close the poll and verify block doesn't appear. - $content_user = $this->drupalCreateUser(array('create poll content', 'edit any poll content', 'access content')); - $this->drupalLogout(); - $this->drupalLogin($content_user); - $close_edit = array('active' => 0); - $this->pollUpdate($poll_nid, $title, $close_edit); - $this->drupalGet('user/' . $content_user->uid); - $this->assertNoText($title, 'Poll no longer appears in block.'); - } -} - -/** - * Test adding new choices. - */ -class PollJSAddChoice extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'Poll add choice', - 'description' => 'Submits a POST request for an additional poll choice.', - 'group' => 'Poll' - ); - } - - function setUp() { - parent::setUp(array('poll')); - } - - /** - * Test adding a new choice. - */ - function testAddChoice() { - $web_user = $this->drupalCreateUser(array('create poll content', 'access content')); - $this->drupalLogin($web_user); - $this->drupalGet('node/add/poll'); - $edit = array( - "title" => $this->randomName(), - 'choice[new:0][chtext]' => $this->randomName(), - 'choice[new:1][chtext]' => $this->randomName(), - ); - - // Press 'add choice' button through Ajax, and place the expected HTML result - // as the tested content. - $commands = $this->drupalPostAJAX(NULL, $edit, array('op' => t('Add another choice'))); - $this->content = $commands[1]['data']; - - $this->assertFieldByName('choice[chid:0][chtext]', $edit['choice[new:0][chtext]'], t('Field !i found', array('!i' => 0))); - $this->assertFieldByName('choice[chid:1][chtext]', $edit['choice[new:1][chtext]'], t('Field !i found', array('!i' => 1))); - $this->assertFieldByName('choice[new:0][chtext]', '', t('Field !i found', array('!i' => 2))); - } -} - -class PollVoteCheckHostname extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'User poll vote capability.', - 'description' => 'Check that users and anonymous users from specified ip-address can only vote once.', - 'group' => 'Poll' - ); - } - - function setUp() { - parent::setUp(); - - // Create and login user. - $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'create poll content')); - $this->drupalLogin($this->admin_user); - - // Allow anonymous users to vote on polls. - user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( - 'access content' => TRUE, - 'vote on polls' => TRUE, - 'cancel own vote' => TRUE, - )); - - // Enable page cache to verify that the result page is not saved in the - // cache when anonymous voting is allowed. - $config = config('system.performance'); - $config->set('cache', 1); - $config->save(); - - // Create poll. - $title = $this->randomName(); - $choices = $this->_generateChoices(3); - $this->poll_nid = $this->pollCreate($title, $choices, FALSE); - - $this->drupalLogout(); - - // Create web users. - $this->web_user1 = $this->drupalCreateUser(array('access content', 'vote on polls', 'cancel own vote')); - $this->web_user2 = $this->drupalCreateUser(array('access content', 'vote on polls')); - } - - /** - * Check that anonymous users with same ip cannot vote on poll more than once - * unless user is logged in. - */ - function testHostnamePollVote() { - // Login User1. - $this->drupalLogin($this->web_user1); - - $edit = array( - 'choice' => '1', - ); - - // User1 vote on Poll. - $this->drupalPost('node/' . $this->poll_nid, $edit, t('Vote')); - $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user1->name))); - $this->assertText(t('Total votes: @votes', array('@votes' => 1)), t('Vote count updated correctly.')); - - // Check to make sure User1 cannot vote again. - $this->drupalGet('node/' . $this->poll_nid); - $elements = $this->xpath('//input[@value="Vote"]'); - $this->assertTrue(empty($elements), t("%user is not able to vote again.", array('%user' => $this->web_user1->name))); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); - - // Logout User1. - $this->drupalLogout(); - - // Fill the page cache by requesting the poll. - $this->drupalGet('node/' . $this->poll_nid); - $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); - $this->drupalGet('node/' . $this->poll_nid); - $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'HIT', t('Page was cached.')); - - // Anonymous user vote on Poll. - $this->drupalPost(NULL, $edit, t('Vote')); - $this->assertText(t('Your vote was recorded.'), t('Anonymous vote was recorded.')); - $this->assertText(t('Total votes: @votes', array('@votes' => 2)), t('Vote count updated correctly.')); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); - - // Check to make sure Anonymous user cannot vote again. - $this->drupalGet('node/' . $this->poll_nid); - $this->assertFalse($this->drupalGetHeader('x-drupal-cache'), t('Page was not cacheable.')); - $elements = $this->xpath('//input[@value="Vote"]'); - $this->assertTrue(empty($elements), t("Anonymous is not able to vote again.")); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); - - // Login User2. - $this->drupalLogin($this->web_user2); - - // User2 vote on poll. - $this->drupalPost('node/' . $this->poll_nid, $edit, t('Vote')); - $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user2->name))); - $this->assertText(t('Total votes: @votes', array('@votes' => 3)), 'Vote count updated correctly.'); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); - - // Logout User2. - $this->drupalLogout(); - - // Change host name for anonymous users. - db_update('poll_vote') - ->fields(array( - 'hostname' => '123.456.789.1', - )) - ->condition('hostname', '', '<>') - ->execute(); - - // Check to make sure Anonymous user can vote again with a new session after - // a hostname change. - $this->drupalGet('node/' . $this->poll_nid); - $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); - $this->drupalPost(NULL, $edit, t('Vote')); - $this->assertText(t('Your vote was recorded.'), t('%user vote was recorded.', array('%user' => $this->web_user2->name))); - $this->assertText(t('Total votes: @votes', array('@votes' => 4)), 'Vote count updated correctly.'); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); - - // Check to make sure Anonymous user cannot vote again with a new session, - // and that the vote from the previous session cannot be cancelledd. - $this->curlClose(); - $this->drupalGet('node/' . $this->poll_nid); - $this->assertEqual($this->drupalGetHeader('x-drupal-cache'), 'MISS', t('Page was cacheable but was not in the cache.')); - $elements = $this->xpath('//input[@value="Vote"]'); - $this->assertTrue(empty($elements), t('Anonymous is not able to vote again.')); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(empty($elements), t("'Cancel your vote' button does not appear.")); - - // Login User1. - $this->drupalLogin($this->web_user1); - - // Check to make sure User1 still cannot vote even after hostname changed. - $this->drupalGet('node/' . $this->poll_nid); - $elements = $this->xpath('//input[@value="Vote"]'); - $this->assertTrue(empty($elements), t("%user is not able to vote again.", array('%user' => $this->web_user1->name))); - $elements = $this->xpath('//input[@value="Cancel your vote"]'); - $this->assertTrue(!empty($elements), t("'Cancel your vote' button appears.")); - } -} - -/** - * Test poll token replacement in strings. - */ -class PollTokenReplaceTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll token replacement', - 'description' => 'Generates text using placeholders for dummy content to check poll token replacement.', - 'group' => 'Poll', - ); - } - - /** - * Creates a poll, then tests the tokens generated from it. - */ - function testPollTokenReplacement() { - global $language_interface; - - // Craete a poll with three choices. - $title = $this->randomName(); - $choices = $this->_generateChoices(3); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - $this->drupalLogout(); - - // Create four users and have each of them vote. - $vote_user1 = $this->drupalCreateUser(array('vote on polls', 'access content')); - $this->drupalLogin($vote_user1); - $edit = array( - 'choice' => '1', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->drupalLogout(); - - $vote_user2 = $this->drupalCreateUser(array('vote on polls', 'access content')); - $this->drupalLogin($vote_user2); - $edit = array( - 'choice' => '1', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->drupalLogout(); - - $vote_user3 = $this->drupalCreateUser(array('vote on polls', 'access content')); - $this->drupalLogin($vote_user3); - $edit = array( - 'choice' => '2', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->drupalLogout(); - - $vote_user4 = $this->drupalCreateUser(array('vote on polls', 'access content')); - $this->drupalLogin($vote_user4); - $edit = array( - 'choice' => '3', - ); - $this->drupalPost('node/' . $poll_nid, $edit, t('Vote')); - $this->drupalLogout(); - - $poll = node_load($poll_nid, NULL, TRUE); - - // Generate and test sanitized tokens. - $tests = array(); - $tests['[node:poll-votes]'] = 4; - $tests['[node:poll-winner]'] = filter_xss($poll->choice[1]['chtext']); - $tests['[node:poll-winner-votes]'] = 2; - $tests['[node:poll-winner-percent]'] = 50; - $tests['[node:poll-duration]'] = format_interval($poll->runtime, 1, $language_interface->langcode); - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $poll), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Sanitized poll token %token replaced.', array('%token' => $input))); - } - - // Generate and test unsanitized tokens. - $tests['[node:poll-winner]'] = $poll->choice[1]['chtext']; - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $poll), array('language' => $language_interface, 'sanitize' => FALSE)); - $this->assertEqual($output, $expected, t('Unsanitized poll token %token replaced.', array('%token' => $input))); - } - } -} - -class PollExpirationTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll expiration', - 'description' => 'Test the poll auto-expiration logic.', - 'group' => 'Poll', - ); - } - - function testAutoExpire() { - // Set up a poll. - $title = $this->randomName(); - $choices = $this->_generateChoices(2); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - $this->assertTrue($poll_nid, t('Poll for auto-expire test created.')); - - // Visit the poll edit page and verify that by default, expiration - // is set to unlimited. - $this->drupalGet("node/$poll_nid/edit"); - $this->assertField('runtime', t('Poll expiration setting found.')); - $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); - $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == 0, t('Poll expiration set to unlimited.')); - - // Set the expiration to one week. - $edit = array(); - $poll_expiration = 604800; // One week. - $edit['runtime'] = $poll_expiration; - $this->drupalPost(NULL, $edit, t('Save')); - $this->assertRaw(t('Poll %title has been updated.', array('%title' => $title)), t('Poll expiration settings saved.')); - - // Make sure that the changed expiration settings is kept. - $this->drupalGet("node/$poll_nid/edit"); - $elements = $this->xpath('//select[@id="edit-runtime"]/option[@selected="selected"]'); - $this->assertTrue(isset($elements[0]['value']) && $elements[0]['value'] == $poll_expiration, t('Poll expiration set to unlimited.')); - - // Force a cron run. Since the expiration date has not yet been reached, - // the poll should remain active. - drupal_cron_run(); - $this->drupalGet("node/$poll_nid/edit"); - $elements = $this->xpath('//input[@id="edit-active-1"]'); - $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll is still active.')); - - // Test expiration. Since REQUEST_TIME is a constant and we don't - // want to keep SimpleTest waiting until the moment of expiration arrives, - // we forcibly change the expiration date in the database. - $created = db_query('SELECT created FROM {node} WHERE nid = :nid', array(':nid' => $poll_nid))->fetchField(); - db_update('node') - ->fields(array('created' => $created - ($poll_expiration * 1.01))) - ->condition('nid', $poll_nid) - ->execute(); - - // Run cron and verify that the poll is now marked as "closed". - drupal_cron_run(); - $this->drupalGet("node/$poll_nid/edit"); - $elements = $this->xpath('//input[@id="edit-active-0"]'); - $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Poll has expired.')); - } -} - -class PollDeleteChoiceTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll choice deletion', - 'description' => 'Test the poll choice deletion logic.', - 'group' => 'Poll', - ); - } - - function testChoiceRemoval() { - // Set up a poll with three choices. - $title = $this->randomName(); - $choices = array('First choice', 'Second choice', 'Third choice'); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - $this->assertTrue($poll_nid, t('Poll for choice deletion logic test created.')); - - // Edit the poll, and try to delete first poll choice. - $this->drupalGet("node/$poll_nid/edit"); - $edit['choice[chid:1][chtext]'] = ''; - $this->drupalPost(NULL, $edit, t('Save')); - - // Click on the poll title to go to node page. - $this->drupalGet('poll'); - $this->clickLink($title); - - // Check the first poll choice is deleted, while the others remain. - $this->assertNoText('First choice', t('First choice removed.')); - $this->assertText('Second choice', t('Second choice remains.')); - $this->assertText('Third choice', t('Third choice remains.')); - } -} - -/** - * Tests poll translation logic. - */ -class PollTranslateTestCase extends PollWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Poll translation', - 'description' => 'Test the poll translation logic.', - 'group' => 'Poll', - ); - } - - function setUp() { - parent::setUp(array('translation')); - } - - /** - * Tests poll creation and translation. - * - * Checks that the choice names get copied from the original poll and that - * the vote count values are set to 0. - */ - function testPollTranslate() { - $admin_user = $this->drupalCreateUser(array('administer content types', 'administer languages', 'edit any poll content', 'create poll content', 'administer nodes', 'translate content')); - - // Set up a poll with two choices. - $title = $this->randomName(); - $choices = array($this->randomName(), $this->randomName()); - $poll_nid = $this->pollCreate($title, $choices, FALSE); - $this->assertTrue($poll_nid, t('Poll for translation logic test created.')); - - $this->drupalLogout(); - $this->drupalLogin($admin_user); - - // Enable a second language. - $this->drupalGet('admin/config/regional/language'); - $edit = array(); - $edit['predefined_langcode'] = 'nl'; - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertRaw(t('The language %language has been created and can now be used.', array('%language' => 'Dutch')), t('Language Dutch has been created.')); - - // Set "Poll" content type to use multilingual support with translation. - $this->drupalGet('admin/structure/types/manage/poll'); - $edit = array(); - $edit['node_type_language'] = TRANSLATION_ENABLED; - $this->drupalPost('admin/structure/types/manage/poll', $edit, t('Save content type')); - $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Poll')), t('Poll content type has been updated.')); - - // Edit poll. - $this->drupalGet("node/$poll_nid/edit"); - $edit = array(); - // Set the poll's first choice count to 200. - $edit['choice[chid:1][chvotes]'] = 200; - // Set the language to Dutch. - $edit['langcode'] = 'nl'; - $this->drupalPost(NULL, $edit, t('Save')); - - // Translate the Dutch poll. - $this->drupalGet('node/add/poll', array('query' => array('translation' => $poll_nid, 'target' => 'en'))); - - $dutch_poll = node_load($poll_nid); - - // Check that the vote count values didn't get copied from the Dutch poll - // and are set to 0. - $this->assertFieldByName('choice[chid:1][chvotes]', '0', ('Found choice with vote count 0')); - $this->assertFieldByName('choice[chid:2][chvotes]', '0', ('Found choice with vote count 0')); - // Check that the choice names got copied from the Dutch poll. - $this->assertFieldByName('choice[chid:1][chtext]', $dutch_poll->choice[1]['chtext'], t('Found choice with text @text', array('@text' => $dutch_poll->choice[1]['chtext']))); - $this->assertFieldByName('choice[chid:2][chtext]', $dutch_poll->choice[2]['chtext'], t('Found choice with text @text', array('@text' => $dutch_poll->choice[2]['chtext']))); - } -} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..633238da7eb0f56f49055ca04741c82c9111e137 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchAdvancedSearchFormTest.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchAdvancedSearchFormTest. + */ + +namespace Drupal\search\Tests; + +class SearchAdvancedSearchFormTest extends SearchTestBase { + 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(); + // 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 doesn'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.')); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d2eac83c2374d516f87c2b854665825f62748862 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php @@ -0,0 +1,85 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchBlockTest. + */ + +namespace Drupal\search\Tests; + +class SearchBlockTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Block availability', + 'description' => 'Check if the search form block is available.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('block')); + + // 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 available. + $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 available. + $edit = array(); + $edit['blocks[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('blocks[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'); + + // Confirm that the user is redirected to the search page. + $this->assertEqual( + $this->getUrl(), + url('search/node/' . $terms['search_block_form'], array('absolute' => TRUE)), + t('Redirected to correct url.') + ); + + // Test an empty search via the block form, from the front page. + $terms = array('search_block_form' => ''); + $this->drupalPost('node', $terms, t('Search')); + $this->assertText('Please enter some keywords'); + + // Confirm that the user is redirected to the search page, when form is submitted empty. + $this->assertEqual( + $this->getUrl(), + url('search/node/', array('absolute' => TRUE)), + t('Redirected to correct url.') + ); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ecf16d2f0eb840794e212456055602cad12d5f4e --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentCountToggleTest.php @@ -0,0 +1,103 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchCommentCountToggleTest. + */ + +namespace Drupal\search\Tests; + +/** + * 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 SearchCommentCountToggleTest extends SearchTestBase { + // Requires node types, comment config, filter formats. + protected $profile = 'standard'; + + 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(array('comment')); + + // Create searching user. + $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); + + // Create initial nodes. + $node_params = array('type' => 'article', 'body' => array(LANGUAGE_NOT_SPECIFIED => 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_NOT_SPECIFIED . '][0][value]'] = $this->randomName(); + $filtered_html_format_id = 'filtered_html'; + $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][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')); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..415cba8542c709d69e9e7a7bb47dbefcb7794420 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchCommentTest.php @@ -0,0 +1,237 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchCommentTest. + */ + +namespace Drupal\search\Tests; + +/** + * Test integration searching comments. + */ +class SearchCommentTest extends SearchTestBase { + protected $profile = 'standard'; + + 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(array('comment')); + + // Create and log in an administrative user having access to the Full HTML + // text format. + $full_html_format = filter_format_load('full_html'); + $permissions = array( + 'administer filters', + filter_permission_name($full_html_format), + 'administer permissions', + 'create page content', + 'skip comment 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 = 'filtered_html'; + $edit = array( + 'filters[filter_html_escape][status]' => TRUE, + ); + $this->drupalPost('admin/config/content/formats/' . $filtered_html_format_id, $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_NOT_SPECIFIED . '][0][value]'] = '<h1>' . $comment_body . '</h1>'; + $full_html_format_id = 'full_html'; + $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][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_NOT_SPECIFIED . '][0][value]']), t('HTML in comment body is not escaped.')); + + // Hide comments. + $this->drupalLogin($this->admin_user); + $node->comment = 0; + $node->save(); + + // 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_NOT_SPECIFIED . '][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); + } + + /** + * Verify that 'add new comment' does not appear in search results or index. + */ + function testAddNewComment() { + // Create a node with a short body. + $settings = array( + 'type' => 'article', + 'title' => 'short title', + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'short body text'))), + ); + + $user = $this->drupalCreateUser(array('search content', 'create article content', 'access content')); + $this->drupalLogin($user); + + $node = $this->drupalCreateNode($settings); + // Verify that if you view the node on its own page, 'add new comment' + // is there. + $this->drupalGet('node/' . $node->nid); + $this->assertText(t('Add new comment'), t('Add new comment appears on node page')); + + // Run cron to index this page. + $this->drupalLogout(); + $this->cronRun(); + + // Search for 'comment'. Should be no results. + $this->drupalLogin($user); + $this->drupalPost('search/node', array('keys' => 'comment'), t('Search')); + $this->assertText(t('Your search yielded no results'), t('No results searching for the word comment')); + + // Search for the node title. Should be found, and 'Add new comment' should + // not be part of the search snippet. + $this->drupalPost('search/node', array('keys' => 'short'), t('Search')); + $this->assertText($node->title, t('Search for keyword worked')); + $this->assertNoText(t('Add new comment'), t('Add new comment does not appear on search results page')); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fe9ef10bb5008a37817466e3eb3476d5941756cb --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchConfigSettingsFormTest.php @@ -0,0 +1,168 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchConfigSettingsFormTest. + */ + +namespace Drupal\search\Tests; + +/** + * Test config page. + */ +class SearchConfigSettingsFormTest extends SearchTestBase { + 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(array('block', '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_NOT_SPECIFIED; + $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['blocks[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.')); + + // Test that the form saves with the default values. + $this->drupalPost('admin/config/search/settings', array(), t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), 'Form saves with the default values.'); + + // Test that the form does not save with an invalid word length. + $edit = array( + 'minimum_word_size' => $this->randomName(3), + ); + $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); + $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); + } + + /** + * Verify that you can disable individual search modules. + */ + function testSearchModuleDisabling() { + // Array of search modules 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'); + } + } + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9e7f134c342eb8c9d087b173d07e48ccab5babdd --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchEmbedFormTest.php @@ -0,0 +1,84 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchEmbedFormTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests that we can embed a form in search results and submit it. + */ +class SearchEmbedFormTest extends SearchTestBase { + /** + * Node used for testing. + */ + public $node; + + /** + * Count of how many times the form has been submitted. + */ + public $submit_count = 0; + + public static function getInfo() { + return array( + 'name' => 'Embedded forms', + 'description' => 'Verifies that a form embedded in search results works', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('search_embedded_form')); + + // Create a user and a node, and update the search index. + $test_user = $this->drupalCreateUser(array('access content', 'search content', 'administer nodes')); + $this->drupalLogin($test_user); + + $this->node = $this->drupalCreateNode(); + + node_update_index(); + search_update_totals(); + + // Set up a dummy initial count of times the form has been submitted. + $this->submit_count = 12; + variable_set('search_embedded_form_submitted', $this->submit_count); + $this->refreshVariables(); + } + + /** + * Tests that the embedded form appears and can be submitted. + */ + function testEmbeddedForm() { + // First verify we can submit the form from the module's page. + $this->drupalPost('search_embedded_form', + array('name' => 'John'), + t('Send away')); + $this->assertText(t('Test form was submitted'), 'Form message appears'); + $count = variable_get('search_embedded_form_submitted', 0); + $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct'); + $this->submit_count = $count; + + // Now verify that we can see and submit the form from the search results. + $this->drupalGet('search/node/' . $this->node->title); + $this->assertText(t('Your name'), 'Form is visible'); + $this->drupalPost('search/node/' . $this->node->title, + array('name' => 'John'), + t('Send away')); + $this->assertText(t('Test form was submitted'), 'Form message appears'); + $count = variable_get('search_embedded_form_submitted', 0); + $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct'); + $this->submit_count = $count; + + // Now verify that if we submit the search form, it doesn't count as + // our form being submitted. + $this->drupalPost('search', + array('keys' => 'foo'), + t('Search')); + $this->assertNoText(t('Test form was submitted'), 'Form message does not appear'); + $count = variable_get('search_embedded_form_submitted', 0); + $this->assertEqual($this->submit_count, $count, 'Form submission count is correct'); + $this->submit_count = $count; + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7d3ba4289183a63d7b6e84b6dee1a3a1b17a5da9 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExactTest.php @@ -0,0 +1,65 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchExactTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests that searching for a phrase gets the correct page count. + */ +class SearchExactTest extends SearchTestBase { + 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', + ); + } + + /** + * 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_NOT_SPECIFIED => array(array('value' => 'love pizza'))); + $this->drupalCreateNode($settings); + } + // Create nodes containing keywords. + for ($i = 0; $i <= 17; $i++) { + $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => 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.'); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b96049e33c68069e8157bab7e54a15d2b951d16d --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php @@ -0,0 +1,114 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchExcerptTest. + */ + +namespace Drupal\search\Tests; + +use Drupal\simpletest\UnitTestBase; + +/** + * Tests the search_excerpt() function. + */ +class SearchExcerptTest extends UnitTestBase { + public static function getInfo() { + return array( + 'name' => 'Search excerpt extraction', + 'description' => 'Tests that the search_excerpt() function works.', + 'group' => 'Search', + ); + } + + function setUp() { + drupal_load('module', 'search'); + parent::setUp(); + } + + /** + * 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'); + + // The node body that will produce this rendered $text is: + // 123456789 HTMLTest +123456789+‘ +‘ +‘ +‘ +12345678 +‘ +‘ +‘ ‘ + $text = "<div class=\"field field-name-body field-type-text-with-summary field-label-hidden\"><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>123456789 HTMLTest +123456789+‘ +‘ +‘ +‘ +12345678 +‘ +‘ +‘ ‘</p>\n</div></div></div> "; + $result = search_excerpt('HTMLTest', $text); + $this->assertFalse(empty($result), 'Rendered Multi-byte HTML encodings are not corrupted in search excerpts'); + } + + /** + * Tests search_excerpt() with search keywords matching simplified words. + * + * Excerpting should handle keywords that are matched only after going through + * search_simplify(). This test passes keywords that match simplified words + * and compares them with strings that contain the original unsimplified word. + */ + function testSearchExcerptSimplified() { + $lorem1 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero.'; + $lorem2 = 'Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci.'; + + // Make some text with some keywords that will get simplified. + $text = $lorem1 . ' Number: 123456.7890 Hyphenated: one-two abc,def ' . $lorem2; + // Note: The search_excerpt() function adds some extra spaces -- not + // important for HTML formatting. Remove these for comparison. + $result = preg_replace('| +|', ' ', search_excerpt('123456.7890', $text)); + $this->assertTrue(strpos($result, 'Number: <strong>123456.7890</strong>') !== FALSE, 'Numeric keyword is highlighted with exact match'); + + $result = preg_replace('| +|', ' ', search_excerpt('1234567890', $text)); + $this->assertTrue(strpos($result, 'Number: <strong>123456.7890</strong>') !== FALSE, 'Numeric keyword is highlighted with simplified match'); + + $result = preg_replace('| +|', ' ', search_excerpt('Number 1234567890', $text)); + $this->assertTrue(strpos($result, '<strong>Number</strong>: <strong>123456.7890</strong>') !== FALSE, 'Punctuated and numeric keyword is highlighted with simplified match'); + + $result = preg_replace('| +|', ' ', search_excerpt('"Number 1234567890"', $text)); + $this->assertTrue(strpos($result, '<strong>Number: 123456.7890</strong>') !== FALSE, 'Phrase with punctuated and numeric keyword is highlighted with simplified match'); + + $result = preg_replace('| +|', ' ', search_excerpt('"Hyphenated onetwo"', $text)); + $this->assertTrue(strpos($result, '<strong>Hyphenated: one-two</strong>') !== FALSE, 'Phrase with punctuated and hyphenated keyword is highlighted with simplified match'); + + $result = preg_replace('| +|', ' ', search_excerpt('"abc def"', $text)); + $this->assertTrue(strpos($result, '<strong>abc,def</strong>') !== FALSE, 'Phrase with keyword simplified into two separate words is highlighted with simplified match'); + + // Test phrases with characters which are being truncated. + $result = preg_replace('| +|', ' ', search_excerpt('"ipsum _"', $text)); + $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part containing "_" is ignored.'); + + $result = preg_replace('| +|', ' ', search_excerpt('"ipsum 0000"', $text)); + $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part "0000" is ignored.'); + + // Test combination of the valid keyword and keyword containing only + // characters which are being truncated during simplification. + $result = preg_replace('| +|', ' ', search_excerpt('ipsum _', $text)); + $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid keyword is highlighted and invalid keyword "_" is ignored.'); + + $result = preg_replace('| +|', ' ', search_excerpt('ipsum 0000', $text)); + $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid keyword is highlighted and invalid keyword "0000" is ignored.'); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExpressionInsertExtractTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExpressionInsertExtractTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b7ea84a1d7eca866be05088ac382d4a535ee1858 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExpressionInsertExtractTest.php @@ -0,0 +1,71 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchExpressionInsertExtractTest. + */ + +namespace Drupal\search\Tests; + +use Drupal\simpletest\UnitTestBase; + +/** + * Tests search_expression_insert() and search_expression_extract(). + * + * @see http://drupal.org/node/419388 (issue) + */ +class SearchExpressionInsertExtractTest extends UnitTestBase { + public static function getInfo() { + return array( + 'name' => 'Search expression insert/extract', + 'description' => 'Tests the functions search_expression_insert() and search_expression_extract()', + 'group' => 'Search', + ); + } + + function setUp() { + drupal_load('module', 'search'); + parent::setUp(); + } + + /** + * Tests search_expression_insert() and search_expression_extract(). + */ + function testInsertExtract() { + $base_expression = "mykeyword"; + // Build an array of option, value, what should be in the expression, what + // should be retrieved from expression. + $cases = array( + array('foo', 'bar', 'foo:bar', 'bar'), // Normal case. + array('foo', NULL, '', NULL), // Empty value: shouldn't insert. + array('foo', ' ', 'foo:', ''), // Space as value: should insert but retrieve empty string. + array('foo', '', 'foo:', ''), // Empty string as value: should insert but retrieve empty string. + array('foo', '0', 'foo:0', '0'), // String zero as value: should insert. + array('foo', 0, 'foo:0', '0'), // Numeric zero as value: should insert. + ); + + foreach ($cases as $index => $case) { + $after_insert = search_expression_insert($base_expression, $case[0], $case[1]); + if (empty($case[2])) { + $this->assertEqual($after_insert, $base_expression, "Empty insert does not change expression in case $index"); + } + else { + $this->assertEqual($after_insert, $base_expression . ' ' . $case[2], "Insert added correct expression for case $index"); + } + + $retrieved = search_expression_extract($after_insert, $case[0]); + if (!isset($case[3])) { + $this->assertFalse(isset($retrieved), "Empty retrieval results in unset value in case $index"); + } + else { + $this->assertEqual($retrieved, $case[3], "Value is retrieved for case $index"); + } + + $after_clear = search_expression_insert($after_insert, $case[0]); + $this->assertEqual(trim($after_clear), $base_expression, "After clearing, base expression is restored for case $index"); + + $cleared = search_expression_extract($after_clear, $case[0]); + $this->assertFalse(isset($cleared), "After clearing, value could not be retrieved for case $index"); + } + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..179b825f3359864ffcb267b40ebf8762d07570f3 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchKeywordsConditionsTest.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchKeywordsConditionsTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests keywords and conditions. + */ +class SearchKeywordsConditionsTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Keywords and conditions', + 'description' => 'Verify the search pulls in keywords and extra conditions.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('comment', 'search_extra_type')); + // Create searching user. + $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); + // Login with sufficient privileges. + $this->drupalLogin($this->searching_user); + // Test with all search modules enabled. + variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type')); + menu_router_rebuild(); + } + + /** + * Verify the kewords are captured and conditions respected. + */ + function testSearchKeyswordsConditions() { + // No keys, not conditions - no results. + $this->drupalGet('search/dummy_path'); + $this->assertNoText('Dummy search snippet to display'); + // With keys - get results. + $keys = 'bike shed ' . $this->randomName(); + $this->drupalGet("search/dummy_path/{$keys}"); + $this->assertText("Dummy search snippet to display. Keywords: {$keys}"); + $keys = 'blue drop ' . $this->randomName(); + $this->drupalGet("search/dummy_path", array('query' => array('keys' => $keys))); + $this->assertText("Dummy search snippet to display. Keywords: {$keys}"); + // Add some conditions and keys. + $keys = 'moving drop ' . $this->randomName(); + $this->drupalGet("search/dummy_path/bike", array('query' => array('search_conditions' => $keys))); + $this->assertText("Dummy search snippet to display."); + $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE)); + // Add some conditions and no keys. + $keys = 'drop kick ' . $this->randomName(); + $this->drupalGet("search/dummy_path", array('query' => array('search_conditions' => $keys))); + $this->assertText("Dummy search snippet to display."); + $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE)); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchLanguageTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..68a1d9a83cc53cfab1b209ff17136ad9b8d64e63 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchLanguageTest.php @@ -0,0 +1,68 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchLanguageTest. + */ + +namespace Drupal\search\Tests; + +/** + * Test node search with multiple languages. + */ +class SearchLanguageTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Search language selection', + 'description' => 'Tests advanced search with different languages enabled.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('locale')); + + // Create and login user. + $test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search', 'administer nodes', 'administer languages', 'access administration pages')); + $this->drupalLogin($test_user); + } + + function testLanguages() { + // Check that there are initially no languages displayed. + $this->drupalGet('search/node'); + $this->assertNoText(t('Languages'), t('No languages to choose from.')); + + // Add predefined language. + $edit = array('predefined_langcode' => 'fr'); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + + // Now we should have languages displayed. + $this->drupalGet('search/node'); + $this->assertText(t('Languages'), t('Languages displayed to choose from.')); + $this->assertText(t('English'), t('English is a possible choice.')); + $this->assertText(t('French'), t('French is a possible choice.')); + + // Ensure selecting no language does not make the query different. + $this->drupalPost('search/node', array(), t('Advanced search')); + $this->assertEqual($this->getUrl(), url('search/node/', array('absolute' => TRUE)), t('Correct page redirection, no language filtering.')); + + // Pick French and ensure it is selected. + $edit = array('language[fr]' => TRUE); + $this->drupalPost('search/node', $edit, t('Advanced search')); + $this->assertFieldByXPath('//input[@name="keys"]', 'language:fr', t('Language filter added to query.')); + + // Change the default language and delete English. + $path = 'admin/config/regional/language'; + $this->drupalGet($path); + $this->assertFieldChecked('edit-site-default-en', t('English is the default language.')); + $edit = array('site_default' => 'fr'); + $this->drupalPost(NULL, $edit, t('Save configuration')); + $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.')); + $this->drupalPost('admin/config/regional/language/delete/en', array(), t('Delete')); + + // Check that there are again no languages displayed. + $this->drupalGet('search/node'); + $this->assertNoText(t('Languages'), t('No languages to choose from.')); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e222341606a5e69f20cb6fecfd37f4f917e0286f --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchMatchTest.php @@ -0,0 +1,236 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchMatchTest. + */ + +namespace Drupal\search\Tests; + +// The search index can contain different types of content. Typically the type +// is 'node'. Here we test with _test_ and _test2_ as the type. +const SEARCH_TYPE = '_test_'; +const SEARCH_TYPE_2 = '_test2_'; +const SEARCH_TYPE_JPN = '_test3_'; + +class SearchMatchTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Search engine queries', + 'description' => 'Indexes content and queries it.', + 'group' => '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('Drupal\search\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('Drupal\search\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('Drupal\search\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'"); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php new file mode 100644 index 0000000000000000000000000000000000000000..618ed5ee6d3672adab871d570e2ff4792d41eb29 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchNodeAccessTest.php @@ -0,0 +1,51 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchNodeAccessTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests node search with node access control. + */ +class SearchNodeAccessTest extends SearchTestBase { + public $test_user; + + public static function getInfo() { + return array( + 'name' => 'Search and node access', + 'description' => 'Tests search functionality with node access control.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('node_access_test')); + node_access_rebuild(); + + // Create a test user and log in. + $this->test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search')); + $this->drupalLogin($this->test_user); + } + + /** + * Tests that search returns results with punctuation in the search phrase. + */ + function testPhraseSearchPunctuation() { + $node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => "The bunny's ears were fluffy."))))); + + // Update the search index. + module_invoke_all('update_index'); + search_update_totals(); + + // Refresh variables after the treatment. + $this->refreshVariables(); + + // Submit a phrase wrapped in double quotes to include the punctuation. + $edit = array('keys' => '"bunny\'s"'); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertText($node->title); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchNumberMatchingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchNumberMatchingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..89756c44a753ae2dfab3fd9d9a9ec27fdf27cba7 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchNumberMatchingTest.php @@ -0,0 +1,91 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchNumberMatchingTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests that numbers can be searched, with more complex matching. + */ +class SearchNumberMatchingTest extends SearchTestBase { + protected $test_user; + protected $numbers; + protected $nodes; + + public static function getInfo() { + return array( + 'name' => 'Search number matching', + 'description' => 'Check that numbers can be searched with more complex matching', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(); + + $this->test_user = $this->drupalCreateUser(array('search content', 'access content', 'administer nodes', 'access site reports')); + $this->drupalLogin($this->test_user); + + // Define a group of numbers that should all match each other -- + // numbers with internal punctuation should match each other, as well + // as numbers with and without leading zeros and leading/trailing + // . and -. + $this->numbers = array( + '123456789', + '12/34/56789', + '12.3456789', + '12-34-56789', + '123,456,789', + '-123456789', + '0123456789', + ); + + foreach ($this->numbers as $num) { + $info = array( + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $num))), + 'type' => 'page', + 'language' => LANGUAGE_NOT_SPECIFIED, + ); + $this->nodes[] = $this->drupalCreateNode($info); + } + + // Run cron to ensure the content is indexed. + $this->cronRun(); + $this->drupalGet('admin/reports/dblog'); + $this->assertText(t('Cron run completed'), 'Log shows cron run completed'); + } + + /** + * Tests that all the numbers can be searched. + */ + function testNumberSearching() { + for ($i = 0; $i < count($this->numbers); $i++) { + $node = $this->nodes[$i]; + + // Verify that the node title does not appear on the search page + // with a dummy search. + $this->drupalPost('search/node', + array('keys' => 'foo'), + t('Search')); + $this->assertNoText($node->title, $i . ': node title not shown in dummy search'); + + // Now verify that we can find node i by searching for any of the + // numbers. + for ($j = 0; $j < count($this->numbers); $j++) { + $number = $this->numbers[$j]; + // If the number is negative, remove the - sign, because - indicates + // "not keyword" when searching. + $number = ltrim($number, '-'); + + $this->drupalPost('search/node', + array('keys' => $number), + t('Search')); + $this->assertText($node->title, $i . ': node title shown (search found the node) in search for number ' . $number); + } + } + + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchNumbersTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchNumbersTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a60db7008af7b538b9fe609086047f396d700cf5 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchNumbersTest.php @@ -0,0 +1,96 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchNumbersTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests that numbers can be searched. + */ +class SearchNumbersTest extends SearchTestBase { + protected $test_user; + protected $numbers; + protected $nodes; + + public static function getInfo() { + return array( + 'name' => 'Search numbers', + 'description' => 'Check that numbers can be searched', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(); + + $this->test_user = $this->drupalCreateUser(array('search content', 'access content', 'administer nodes', 'access site reports')); + $this->drupalLogin($this->test_user); + + // Create content with various numbers in it. + // Note: 50 characters is the current limit of the search index's word + // field. + $this->numbers = array( + 'ISBN' => '978-0446365383', + 'UPC' => '036000 291452', + 'EAN bar code' => '5901234123457', + 'negative' => '-123456.7890', + 'quoted negative' => '"-123456.7890"', + 'leading zero' => '0777777777', + 'tiny' => '111', + 'small' => '22222222222222', + 'medium' => '333333333333333333333333333', + 'large' => '444444444444444444444444444444444444444', + 'gigantic' => '5555555555555555555555555555555555555555555555555', + 'over fifty characters' => '666666666666666666666666666666666666666666666666666666666666', + 'date', '01/02/2009', + 'commas', '987,654,321', + ); + + foreach ($this->numbers as $doc => $num) { + $info = array( + 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $num))), + 'type' => 'page', + 'language' => LANGUAGE_NOT_SPECIFIED, + 'title' => $doc . ' number', + ); + $this->nodes[$doc] = $this->drupalCreateNode($info); + } + + // Run cron to ensure the content is indexed. + $this->cronRun(); + $this->drupalGet('admin/reports/dblog'); + $this->assertText(t('Cron run completed'), 'Log shows cron run completed'); + } + + /** + * Tests that all the numbers can be searched. + */ + function testNumberSearching() { + $types = array_keys($this->numbers); + + foreach ($types as $type) { + $number = $this->numbers[$type]; + // If the number is negative, remove the - sign, because - indicates + // "not keyword" when searching. + $number = ltrim($number, '-'); + $node = $this->nodes[$type]; + + // Verify that the node title does not appear on the search page + // with a dummy search. + $this->drupalPost('search/node', + array('keys' => 'foo'), + t('Search')); + $this->assertNoText($node->title, $type . ': node title not shown in dummy search'); + + // Verify that the node title does appear as a link on the search page + // when searching for the number. + $this->drupalPost('search/node', + array('keys' => $number), + t('Search')); + $this->assertText($node->title, $type . ': node title shown (search found the node) in search for number ' . $number); + } + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php new file mode 100644 index 0000000000000000000000000000000000000000..590f68bc5f7df72907bdd8cb29128c889c43c62b --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchPageOverrideTest.php @@ -0,0 +1,42 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchPageOverrideTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests that hook_search_page runs. + */ +class SearchPageOverrideTest extends SearchTestBase { + public $search_user; + + public static function getInfo() { + return array( + 'name' => 'Search page override', + 'description' => 'Verify that hook_search_page can override search page display.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('search_extra_type')); + + // Login as a user that can create and search content. + $this->search_user = $this->drupalCreateUser(array('search content', 'administer search')); + $this->drupalLogin($this->search_user); + + // Enable the extra type module for searching. + variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type')); + menu_router_rebuild(); + } + + function testSearchPageHook() { + $keys = 'bike shed ' . $this->randomName(); + $this->drupalGet("search/dummy_path/{$keys}"); + $this->assertText('Dummy search snippet', 'Dummy search snippet is shown'); + $this->assertText('Test page text is here', 'Page override is working'); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchPageTextTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchPageTextTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ae3ee7982a323f4d674b3cfcd3ad9d3d94810d0b --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchPageTextTest.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchPageTextTest. + */ + +namespace Drupal\search\Tests; + +/** + * Tests the bike shed text on no results page, and text on the search page. + */ +class SearchPageTextTest extends SearchTestBase { + protected $searching_user; + + public static function getInfo() { + return array( + 'name' => 'Search page text', + 'description' => 'Tests the bike shed text on the no results page, and various other text on search pages.', + 'group' => 'Search' + ); + } + + function setUp() { + parent::setUp(); + + // Create user. + $this->searching_user = $this->drupalCreateUser(array('search content', 'access user profiles')); + } + + /** + * Tests the failed search text, and various other text on the search page. + */ + function testSearchText() { + $this->drupalLogin($this->searching_user); + $this->drupalGet('search/node'); + $this->assertText(t('Enter your keywords')); + $this->assertText(t('Search')); + $title = t('Search') . ' | Drupal'; + $this->assertTitle($title, 'Search page title is correct'); + + $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.')); + $this->assertText(t('Search')); + $this->assertTitle($title, 'Search page title is correct'); + + $edit['keys'] = $this->searching_user->name; + $this->drupalPost('search/user', $edit, t('Search')); + $this->assertText(t('Search')); + $this->assertTitle($title, 'Search page title is correct'); + + // Test that search keywords containing slashes are correctly loaded + // from the path and displayed in the search form. + $arg = $this->randomName() . '/' . $this->randomName(); + $this->drupalGet('search/node/' . $arg); + $input = $this->xpath("//input[@id='edit-keys' and @value='{$arg}']"); + $this->assertFalse(empty($input), 'Search keys with a / are correctly set as the default value in the search box.'); + + // Test a search input exceeding the limit of AND/OR combinations to test + // the Denial-of-Service protection. + $limit = variable_get('search_and_or_limit', 7); + $keys = array(); + for ($i = 0; $i < $limit + 1; $i++) { + $keys[] = $this->randomName(3); + if ($i % 2 == 0) { + $keys[] = 'OR'; + } + } + $edit['keys'] = implode(' ', $keys); + $this->drupalPost('search/node', $edit, t('Search')); + $this->assertRaw(t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', array('@count' => $limit))); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d9debc5110de292b84ecfae567d2bc4a44391a47 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchRankingTest.php @@ -0,0 +1,228 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchRankingTest. + */ + +namespace Drupal\search\Tests; + +class SearchRankingTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Search engine ranking', + 'description' => 'Indexes content and tests ranking factors.', + 'group' => 'Search', + ); + } + + function setUp() { + parent::setUp(array('statistics', 'comment')); + } + + function testRankings() { + // Login with sufficient privileges. + $this->drupalLogin($this->drupalCreateUser(array('post comments', 'skip comment 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' => 'Drupal rocks', + 'body' => array(LANGUAGE_NOT_SPECIFIED => 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_NOT_SPECIFIED][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_NOT_SPECIFIED . '][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. + // Manually calling statistics.php, simulating ajax behavior. + $nid = $nodes['views'][1]->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + for ($i = 0; $i < 5; $i ++) { + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + } + + // 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() { + $full_html_format = array( + 'format' => 'full_html', + 'name' => 'Full HTML', + ); + $full_html_format = (object) $full_html_format; + filter_format_save($full_html_format); + + // 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' => 'Simple node', + ); + foreach ($shuffled_tags as $tag) { + switch ($tag) { + case 'a': + $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => l('Drupal Rocks', 'node'), 'format' => 'full_html'))); + break; + case 'notag': + $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'Drupal Rocks'))); + break; + default: + $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 'full_html'))); + 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_NOT_SPECIFIED => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 'full_html'))); + $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('skip comment approval', 'create page content'))); + + // See testRankings() above - build a node that will rank high for sticky. + $settings = array( + 'type' => 'page', + 'title' => 'Drupal rocks', + 'body' => array(LANGUAGE_NOT_SPECIFIED => 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.'); + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchSimplifyTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchSimplifyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f316588106dd9d17f27807a54507acb922a0fef8 --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchSimplifyTest.php @@ -0,0 +1,90 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchSimplifyTest. + */ + +namespace Drupal\search\Tests; + +/** + * Test search_simplify() on every Unicode character, and some other cases. + */ +class SearchSimplifyTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'Search simplify', + 'description' => 'Check that the search_simply() function works as intended.', + 'group' => 'Search', + ); + } + + /** + * Tests that all Unicode characters simplify correctly. + */ + function testSearchSimplifyUnicode() { + // This test uses a file that was constructed so that the even lines are + // boundary characters, and the odd lines are valid word characters. (It + // was generated as a sequence of all the Unicode characters, and then the + // boundary chararacters (punctuation, spaces, etc.) were split off into + // their own lines). So the even-numbered lines should simplify to nothing, + // and the odd-numbered lines we need to split into shorter chunks and + // verify that simplification doesn't lose any characters. + $input = file_get_contents(DRUPAL_ROOT . '/core/modules/search/tests/UnicodeTest.txt'); + $basestrings = explode(chr(10), $input); + $strings = array(); + foreach ($basestrings as $key => $string) { + if ($key %2) { + // Even line - should simplify down to a space. + $simplified = search_simplify($string); + $this->assertIdentical($simplified, ' ', "Line $key is excluded from the index"); + } + else { + // Odd line, should be word characters. + // Split this into 30-character chunks, so we don't run into limits + // of truncation in search_simplify(). + $start = 0; + while ($start < drupal_strlen($string)) { + $newstr = drupal_substr($string, $start, 30); + // Special case: leading zeros are removed from numeric strings, + // and there's one string in this file that is numbers starting with + // zero, so prepend a 1 on that string. + if (preg_match('/^[0-9]+$/', $newstr)) { + $newstr = '1' . $newstr; + } + $strings[] = $newstr; + $start += 30; + } + } + } + foreach ($strings as $key => $string) { + $simplified = search_simplify($string); + $this->assertTrue(drupal_strlen($simplified) >= drupal_strlen($string), "Nothing is removed from string $key."); + } + + // Test the low-numbered ASCII control characters separately. They are not + // in the text file because they are problematic for diff, especially \0. + $string = ''; + for ($i = 0; $i < 32; $i++) { + $string .= chr($i); + } + $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]); + } + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchTestBase.php b/core/modules/search/lib/Drupal/search/Tests/SearchTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..0f1001b7a7335864ea38cde7ab3a5a8f4c95382c --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchTestBase.php @@ -0,0 +1,29 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchTestBase. + */ + +namespace Drupal\search\Tests; + +use Drupal\simpletest\WebTestBase; + +class SearchTestBase extends WebTestBase { + function setUp() { + $modules = func_get_args(); + if (isset($modules[0]) && is_array($modules[0])) { + $modules = $modules[0]; + } + $modules[] = 'node'; + $modules[] = 'search'; + $modules[] = 'dblog'; + parent::setUp($modules); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + } +} diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchTokenizerTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchTokenizerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2624abbf1e36c71f1a40dd6aa9206daebe0ced6d --- /dev/null +++ b/core/modules/search/lib/Drupal/search/Tests/SearchTokenizerTest.php @@ -0,0 +1,155 @@ +<?php + +/** + * @file + * Definition of Drupal\search\Tests\SearchTokenizerTest. + */ + +namespace Drupal\search\Tests; + +/** + * Test the CJK tokenizer. + */ +class SearchTokenizerTest extends SearchTestBase { + public static function getInfo() { + return array( + 'name' => 'CJK tokenizer', + 'description' => 'Check that CJK tokenizer works as intended.', + 'group' => '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 ''; + } +} diff --git a/core/modules/search/search.info b/core/modules/search/search.info index a5a019ece1fa4f7a73e92594393039a3749298e9..8fdbfaaf46904e8ac1fa9520faf9f46c71b2d284 100644 --- a/core/modules/search/search.info +++ b/core/modules/search/search.info @@ -3,6 +3,5 @@ description = Enables site-wide keyword searching. package = Core version = VERSION core = 8.x -files[] = search.test configure = admin/config/search/settings stylesheets[all][] = search.theme.css diff --git a/core/modules/search/search.test b/core/modules/search/search.test deleted file mode 100644 index 619055c7d4f347adc9d1147dd1a401084ef10444..0000000000000000000000000000000000000000 --- a/core/modules/search/search.test +++ /dev/null @@ -1,2054 +0,0 @@ -<?php - -/** - * @file - * Tests for search.module. - */ - -// The search index can contain different types of content. Typically the type is 'node'. -// Here we test with _test_ and _test2_ as the type. -const SEARCH_TYPE = '_test_'; -const SEARCH_TYPE_2 = '_test2_'; -const SEARCH_TYPE_JPN = '_test3_'; - -use Drupal\simpletest\WebTestBase; -use Drupal\simpletest\UnitTestBase; - -class SearchWebTestCase extends WebTestBase { - function setUp() { - $modules = func_get_args(); - if (isset($modules[0]) && is_array($modules[0])) { - $modules = $modules[0]; - } - $modules[] = 'node'; - $modules[] = 'search'; - $modules[] = 'dblog'; - parent::setUp($modules); - - // Create Basic page and Article node types. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - } - } -} - -class SearchMatchTestCase extends SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Search engine queries', - 'description' => 'Indexes content and queries it.', - 'group' => '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('Drupal\search\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('Drupal\search\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('Drupal\search\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'"); - } -} - -/** - * Tests the bike shed text on no results page, and text on the search page. - */ -class SearchPageText extends SearchWebTestCase { - protected $searching_user; - - public static function getInfo() { - return array( - 'name' => 'Search page text', - 'description' => 'Tests the bike shed text on the no results page, and various other text on search pages.', - 'group' => 'Search' - ); - } - - function setUp() { - parent::setUp(); - - // Create user. - $this->searching_user = $this->drupalCreateUser(array('search content', 'access user profiles')); - } - - /** - * Tests the failed search text, and various other text on the search page. - */ - function testSearchText() { - $this->drupalLogin($this->searching_user); - $this->drupalGet('search/node'); - $this->assertText(t('Enter your keywords')); - $this->assertText(t('Search')); - $title = t('Search') . ' | Drupal'; - $this->assertTitle($title, 'Search page title is correct'); - - $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.')); - $this->assertText(t('Search')); - $this->assertTitle($title, 'Search page title is correct'); - - $edit['keys'] = $this->searching_user->name; - $this->drupalPost('search/user', $edit, t('Search')); - $this->assertText(t('Search')); - $this->assertTitle($title, 'Search page title is correct'); - - // Test that search keywords containing slashes are correctly loaded - // from the path and displayed in the search form. - $arg = $this->randomName() . '/' . $this->randomName(); - $this->drupalGet('search/node/' . $arg); - $input = $this->xpath("//input[@id='edit-keys' and @value='{$arg}']"); - $this->assertFalse(empty($input), 'Search keys with a / are correctly set as the default value in the search box.'); - - // Test a search input exceeding the limit of AND/OR combinations to test - // the Denial-of-Service protection. - $limit = variable_get('search_and_or_limit', 7); - $keys = array(); - for ($i = 0; $i < $limit + 1; $i++) { - $keys[] = $this->randomName(3); - if ($i % 2 == 0) { - $keys[] = 'OR'; - } - } - $edit['keys'] = implode(' ', $keys); - $this->drupalPost('search/node', $edit, t('Search')); - $this->assertRaw(t('Your search used too many AND/OR expressions. Only the first @count terms were included in this search.', array('@count' => $limit))); - } -} - -class SearchAdvancedSearchForm extends SearchWebTestCase { - 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(); - // 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 doesn'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 SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Search engine ranking', - 'description' => 'Indexes content and tests ranking factors.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('statistics', 'comment')); - } - - function testRankings() { - // Login with sufficient privileges. - $this->drupalLogin($this->drupalCreateUser(array('post comments', 'skip comment 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' => 'Drupal rocks', - 'body' => array(LANGUAGE_NOT_SPECIFIED => 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_NOT_SPECIFIED][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_NOT_SPECIFIED . '][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. - // Manually calling statistics.php, simulating ajax behavior. - $nid = $nodes['views'][1]->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - for ($i = 0; $i < 5; $i ++) { - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - } - - // 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() { - $full_html_format = array( - 'format' => 'full_html', - 'name' => 'Full HTML', - ); - $full_html_format = (object) $full_html_format; - filter_format_save($full_html_format); - - // 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' => 'Simple node', - ); - foreach ($shuffled_tags as $tag) { - switch ($tag) { - case 'a': - $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => l('Drupal Rocks', 'node'), 'format' => 'full_html'))); - break; - case 'notag': - $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'Drupal Rocks'))); - break; - default: - $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 'full_html'))); - 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_NOT_SPECIFIED => array(array('value' => "<$tag>Drupal Rocks</$tag>", 'format' => 'full_html'))); - $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('skip comment approval', 'create page content'))); - - // See testRankings() above - build a node that will rank high for sticky. - $settings = array( - 'type' => 'page', - 'title' => 'Drupal rocks', - 'body' => array(LANGUAGE_NOT_SPECIFIED => 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 SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Block availability', - 'description' => 'Check if the search form block is available.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('block')); - - // 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 available. - $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 available. - $edit = array(); - $edit['blocks[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('blocks[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'); - - // Confirm that the user is redirected to the search page. - $this->assertEqual( - $this->getUrl(), - url('search/node/' . $terms['search_block_form'], array('absolute' => TRUE)), - t('Redirected to correct url.') - ); - - // Test an empty search via the block form, from the front page. - $terms = array('search_block_form' => ''); - $this->drupalPost('node', $terms, t('Search')); - $this->assertText('Please enter some keywords'); - - // Confirm that the user is redirected to the search page, when form is submitted empty. - $this->assertEqual( - $this->getUrl(), - url('search/node/', array('absolute' => TRUE)), - t('Redirected to correct url.') - ); - } -} - -/** - * Tests that searching for a phrase gets the correct page count. - */ -class SearchExactTestCase extends SearchWebTestCase { - 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', - ); - } - - /** - * 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_NOT_SPECIFIED => array(array('value' => 'love pizza'))); - $this->drupalCreateNode($settings); - } - // Create nodes containing keywords. - for ($i = 0; $i <= 17; $i++) { - $settings['body'] = array(LANGUAGE_NOT_SPECIFIED => 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 SearchWebTestCase { - protected $profile = 'standard'; - - 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(array('comment')); - - // Create and log in an administrative user having access to the Full HTML - // text format. - $full_html_format = filter_format_load('full_html'); - $permissions = array( - 'administer filters', - filter_permission_name($full_html_format), - 'administer permissions', - 'create page content', - 'skip comment 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 = 'filtered_html'; - $edit = array( - 'filters[filter_html_escape][status]' => TRUE, - ); - $this->drupalPost('admin/config/content/formats/' . $filtered_html_format_id, $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_NOT_SPECIFIED . '][0][value]'] = '<h1>' . $comment_body . '</h1>'; - $full_html_format_id = 'full_html'; - $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][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_NOT_SPECIFIED . '][0][value]']), t('HTML in comment body is not escaped.')); - - // Hide comments. - $this->drupalLogin($this->admin_user); - $node->comment = 0; - $node->save(); - - // 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_NOT_SPECIFIED . '][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); - } - - /** - * Verify that 'add new comment' does not appear in search results or index. - */ - function testAddNewComment() { - // Create a node with a short body. - $settings = array( - 'type' => 'article', - 'title' => 'short title', - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => 'short body text'))), - ); - - $user = $this->drupalCreateUser(array('search content', 'create article content', 'access content')); - $this->drupalLogin($user); - - $node = $this->drupalCreateNode($settings); - // Verify that if you view the node on its own page, 'add new comment' - // is there. - $this->drupalGet('node/' . $node->nid); - $this->assertText(t('Add new comment'), t('Add new comment appears on node page')); - - // Run cron to index this page. - $this->drupalLogout(); - $this->cronRun(); - - // Search for 'comment'. Should be no results. - $this->drupalLogin($user); - $this->drupalPost('search/node', array('keys' => 'comment'), t('Search')); - $this->assertText(t('Your search yielded no results'), t('No results searching for the word comment')); - - // Search for the node title. Should be found, and 'Add new comment' should - // not be part of the search snippet. - $this->drupalPost('search/node', array('keys' => 'short'), t('Search')); - $this->assertText($node->title, t('Search for keyword worked')); - $this->assertNoText(t('Add new comment'), t('Add new comment does not appear on search results page')); - } - -} - -/** - * Tests search_expression_insert() and search_expression_extract(). - * - * @see http://drupal.org/node/419388 (issue) - */ -class SearchExpressionInsertExtractTestCase extends UnitTestBase { - public static function getInfo() { - return array( - 'name' => 'Search expression insert/extract', - 'description' => 'Tests the functions search_expression_insert() and search_expression_extract()', - 'group' => 'Search', - ); - } - - function setUp() { - drupal_load('module', 'search'); - parent::setUp(); - } - - /** - * Tests search_expression_insert() and search_expression_extract(). - */ - function testInsertExtract() { - $base_expression = "mykeyword"; - // Build an array of option, value, what should be in the expression, what - // should be retrieved from expression. - $cases = array( - array('foo', 'bar', 'foo:bar', 'bar'), // Normal case. - array('foo', NULL, '', NULL), // Empty value: shouldn't insert. - array('foo', ' ', 'foo:', ''), // Space as value: should insert but retrieve empty string. - array('foo', '', 'foo:', ''), // Empty string as value: should insert but retrieve empty string. - array('foo', '0', 'foo:0', '0'), // String zero as value: should insert. - array('foo', 0, 'foo:0', '0'), // Numeric zero as value: should insert. - ); - - foreach ($cases as $index => $case) { - $after_insert = search_expression_insert($base_expression, $case[0], $case[1]); - if (empty($case[2])) { - $this->assertEqual($after_insert, $base_expression, "Empty insert does not change expression in case $index"); - } - else { - $this->assertEqual($after_insert, $base_expression . ' ' . $case[2], "Insert added correct expression for case $index"); - } - - $retrieved = search_expression_extract($after_insert, $case[0]); - if (!isset($case[3])) { - $this->assertFalse(isset($retrieved), "Empty retrieval results in unset value in case $index"); - } - else { - $this->assertEqual($retrieved, $case[3], "Value is retrieved for case $index"); - } - - $after_clear = search_expression_insert($after_insert, $case[0]); - $this->assertEqual(trim($after_clear), $base_expression, "After clearing, base expression is restored for case $index"); - - $cleared = search_expression_extract($after_clear, $case[0]); - $this->assertFalse(isset($cleared), "After clearing, value could not be retrieved for case $index"); - } - } -} - -/** - * 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 SearchWebTestCase { - // Requires node types, comment config, filter formats. - protected $profile = 'standard'; - - 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(array('comment')); - - // Create searching user. - $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); - - // Create initial nodes. - $node_params = array('type' => 'article', 'body' => array(LANGUAGE_NOT_SPECIFIED => 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_NOT_SPECIFIED . '][0][value]'] = $this->randomName(); - $filtered_html_format_id = 'filtered_html'; - $edit_comment['comment_body[' . LANGUAGE_NOT_SPECIFIED . '][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 SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Search simplify', - 'description' => 'Check that the search_simply() function works as intended.', - 'group' => 'Search', - ); - } - - /** - * Tests that all Unicode characters simplify correctly. - */ - function testSearchSimplifyUnicode() { - // This test uses a file that was constructed so that the even lines are - // boundary characters, and the odd lines are valid word characters. (It - // was generated as a sequence of all the Unicode characters, and then the - // boundary chararacters (punctuation, spaces, etc.) were split off into - // their own lines). So the even-numbered lines should simplify to nothing, - // and the odd-numbered lines we need to split into shorter chunks and - // verify that simplification doesn't lose any characters. - $input = file_get_contents(DRUPAL_ROOT . '/core/modules/search/tests/UnicodeTest.txt'); - $basestrings = explode(chr(10), $input); - $strings = array(); - foreach ($basestrings as $key => $string) { - if ($key %2) { - // Even line - should simplify down to a space. - $simplified = search_simplify($string); - $this->assertIdentical($simplified, ' ', "Line $key is excluded from the index"); - } - else { - // Odd line, should be word characters. - // Split this into 30-character chunks, so we don't run into limits - // of truncation in search_simplify(). - $start = 0; - while ($start < drupal_strlen($string)) { - $newstr = drupal_substr($string, $start, 30); - // Special case: leading zeros are removed from numeric strings, - // and there's one string in this file that is numbers starting with - // zero, so prepend a 1 on that string. - if (preg_match('/^[0-9]+$/', $newstr)) { - $newstr = '1' . $newstr; - } - $strings[] = $newstr; - $start += 30; - } - } - } - foreach ($strings as $key => $string) { - $simplified = search_simplify($string); - $this->assertTrue(drupal_strlen($simplified) >= drupal_strlen($string), "Nothing is removed from string $key."); - } - - // Test the low-numbered ASCII control characters separately. They are not - // in the text file because they are problematic for diff, especially \0. - $string = ''; - for ($i = 0; $i < 32; $i++) { - $string .= chr($i); - } - $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]); - } - } -} - - -/** - * Tests keywords and conditions. - */ -class SearchKeywordsConditions extends SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Keywords and conditions', - 'description' => 'Verify the search pulls in keywords and extra conditions.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('comment', 'search_extra_type')); - // Create searching user. - $this->searching_user = $this->drupalCreateUser(array('search content', 'access content', 'access comments', 'skip comment approval')); - // Login with sufficient privileges. - $this->drupalLogin($this->searching_user); - // Test with all search modules enabled. - variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type')); - menu_router_rebuild(); - } - - /** - * Verify the kewords are captured and conditions respected. - */ - function testSearchKeyswordsConditions() { - // No keys, not conditions - no results. - $this->drupalGet('search/dummy_path'); - $this->assertNoText('Dummy search snippet to display'); - // With keys - get results. - $keys = 'bike shed ' . $this->randomName(); - $this->drupalGet("search/dummy_path/{$keys}"); - $this->assertText("Dummy search snippet to display. Keywords: {$keys}"); - $keys = 'blue drop ' . $this->randomName(); - $this->drupalGet("search/dummy_path", array('query' => array('keys' => $keys))); - $this->assertText("Dummy search snippet to display. Keywords: {$keys}"); - // Add some conditions and keys. - $keys = 'moving drop ' . $this->randomName(); - $this->drupalGet("search/dummy_path/bike", array('query' => array('search_conditions' => $keys))); - $this->assertText("Dummy search snippet to display."); - $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE)); - // Add some conditions and no keys. - $keys = 'drop kick ' . $this->randomName(); - $this->drupalGet("search/dummy_path", array('query' => array('search_conditions' => $keys))); - $this->assertText("Dummy search snippet to display."); - $this->assertRaw(print_r(array('search_conditions' => $keys), TRUE)); - } -} - -/** - * Tests that numbers can be searched. - */ -class SearchNumbersTestCase extends SearchWebTestCase { - protected $test_user; - protected $numbers; - protected $nodes; - - public static function getInfo() { - return array( - 'name' => 'Search numbers', - 'description' => 'Check that numbers can be searched', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(); - - $this->test_user = $this->drupalCreateUser(array('search content', 'access content', 'administer nodes', 'access site reports')); - $this->drupalLogin($this->test_user); - - // Create content with various numbers in it. - // Note: 50 characters is the current limit of the search index's word - // field. - $this->numbers = array( - 'ISBN' => '978-0446365383', - 'UPC' => '036000 291452', - 'EAN bar code' => '5901234123457', - 'negative' => '-123456.7890', - 'quoted negative' => '"-123456.7890"', - 'leading zero' => '0777777777', - 'tiny' => '111', - 'small' => '22222222222222', - 'medium' => '333333333333333333333333333', - 'large' => '444444444444444444444444444444444444444', - 'gigantic' => '5555555555555555555555555555555555555555555555555', - 'over fifty characters' => '666666666666666666666666666666666666666666666666666666666666', - 'date', '01/02/2009', - 'commas', '987,654,321', - ); - - foreach ($this->numbers as $doc => $num) { - $info = array( - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $num))), - 'type' => 'page', - 'language' => LANGUAGE_NOT_SPECIFIED, - 'title' => $doc . ' number', - ); - $this->nodes[$doc] = $this->drupalCreateNode($info); - } - - // Run cron to ensure the content is indexed. - $this->cronRun(); - $this->drupalGet('admin/reports/dblog'); - $this->assertText(t('Cron run completed'), 'Log shows cron run completed'); - } - - /** - * Tests that all the numbers can be searched. - */ - function testNumberSearching() { - $types = array_keys($this->numbers); - - foreach ($types as $type) { - $number = $this->numbers[$type]; - // If the number is negative, remove the - sign, because - indicates - // "not keyword" when searching. - $number = ltrim($number, '-'); - $node = $this->nodes[$type]; - - // Verify that the node title does not appear on the search page - // with a dummy search. - $this->drupalPost('search/node', - array('keys' => 'foo'), - t('Search')); - $this->assertNoText($node->title, $type . ': node title not shown in dummy search'); - - // Verify that the node title does appear as a link on the search page - // when searching for the number. - $this->drupalPost('search/node', - array('keys' => $number), - t('Search')); - $this->assertText($node->title, $type . ': node title shown (search found the node) in search for number ' . $number); - } - } -} - -/** - * Tests that numbers can be searched, with more complex matching. - */ -class SearchNumberMatchingTestCase extends SearchWebTestCase { - protected $test_user; - protected $numbers; - protected $nodes; - - public static function getInfo() { - return array( - 'name' => 'Search number matching', - 'description' => 'Check that numbers can be searched with more complex matching', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(); - - $this->test_user = $this->drupalCreateUser(array('search content', 'access content', 'administer nodes', 'access site reports')); - $this->drupalLogin($this->test_user); - - // Define a group of numbers that should all match each other -- - // numbers with internal punctuation should match each other, as well - // as numbers with and without leading zeros and leading/trailing - // . and -. - $this->numbers = array( - '123456789', - '12/34/56789', - '12.3456789', - '12-34-56789', - '123,456,789', - '-123456789', - '0123456789', - ); - - foreach ($this->numbers as $num) { - $info = array( - 'body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $num))), - 'type' => 'page', - 'language' => LANGUAGE_NOT_SPECIFIED, - ); - $this->nodes[] = $this->drupalCreateNode($info); - } - - // Run cron to ensure the content is indexed. - $this->cronRun(); - $this->drupalGet('admin/reports/dblog'); - $this->assertText(t('Cron run completed'), 'Log shows cron run completed'); - } - - /** - * Tests that all the numbers can be searched. - */ - function testNumberSearching() { - for ($i = 0; $i < count($this->numbers); $i++) { - $node = $this->nodes[$i]; - - // Verify that the node title does not appear on the search page - // with a dummy search. - $this->drupalPost('search/node', - array('keys' => 'foo'), - t('Search')); - $this->assertNoText($node->title, $i . ': node title not shown in dummy search'); - - // Now verify that we can find node i by searching for any of the - // numbers. - for ($j = 0; $j < count($this->numbers); $j++) { - $number = $this->numbers[$j]; - // If the number is negative, remove the - sign, because - indicates - // "not keyword" when searching. - $number = ltrim($number, '-'); - - $this->drupalPost('search/node', - array('keys' => $number), - t('Search')); - $this->assertText($node->title, $i . ': node title shown (search found the node) in search for number ' . $number); - } - } - - } -} - -/** - * Test config page. - */ -class SearchConfigSettingsForm extends SearchWebTestCase { - 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(array('block', '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_NOT_SPECIFIED; - $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['blocks[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.')); - - // Test that the form saves with the default values. - $this->drupalPost('admin/config/search/settings', array(), t('Save configuration')); - $this->assertText(t('The configuration options have been saved.'), 'Form saves with the default values.'); - - // Test that the form does not save with an invalid word length. - $edit = array( - 'minimum_word_size' => $this->randomName(3), - ); - $this->drupalPost('admin/config/search/settings', $edit, t('Save configuration')); - $this->assertNoText(t('The configuration options have been saved.'), 'Form does not save with an invalid word length.'); - } - - /** - * Verify that you can disable individual search modules. - */ - function testSearchModuleDisabling() { - // Array of search modules 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 UnitTestBase { - public static function getInfo() { - return array( - 'name' => 'Search excerpt extraction', - 'description' => 'Tests that the search_excerpt() function works.', - 'group' => 'Search', - ); - } - - function setUp() { - drupal_load('module', 'search'); - parent::setUp(); - } - - /** - * 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'); - - // The node body that will produce this rendered $text is: - // 123456789 HTMLTest +123456789+‘ +‘ +‘ +‘ +12345678 +‘ +‘ +‘ ‘ - $text = "<div class=\"field field-name-body field-type-text-with-summary field-label-hidden\"><div class=\"field-items\"><div class=\"field-item even\" property=\"content:encoded\"><p>123456789 HTMLTest +123456789+‘ +‘ +‘ +‘ +12345678 +‘ +‘ +‘ ‘</p>\n</div></div></div> "; - $result = search_excerpt('HTMLTest', $text); - $this->assertFalse(empty($result), 'Rendered Multi-byte HTML encodings are not corrupted in search excerpts'); - } - - /** - * Tests search_excerpt() with search keywords matching simplified words. - * - * Excerpting should handle keywords that are matched only after going through - * search_simplify(). This test passes keywords that match simplified words - * and compares them with strings that contain the original unsimplified word. - */ - function testSearchExcerptSimplified() { - $lorem1 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae arcu at leo cursus laoreet. Curabitur dui tortor, adipiscing malesuada tempor in, bibendum ac diam. Cras non tellus a libero pellentesque condimentum. What is a Drupalism? Suspendisse ac lacus libero. Ut non est vel nisl faucibus interdum nec sed leo. Pellentesque sem risus, vulputate eu semper eget, auctor in libero.'; - $lorem2 = 'Ut fermentum est vitae metus convallis scelerisque. Phasellus pellentesque rhoncus tellus, eu dignissim purus posuere id. Quisque eu fringilla ligula. Morbi ullamcorper, lorem et mattis egestas, tortor neque pretium velit, eget eleifend odio turpis eu purus. Donec vitae metus quis leo pretium tincidunt a pulvinar sem. Morbi adipiscing laoreet mauris vel placerat. Nullam elementum, nisl sit amet scelerisque malesuada, dolor nunc hendrerit quam, eu ultrices erat est in orci.'; - - // Make some text with some keywords that will get simplified. - $text = $lorem1 . ' Number: 123456.7890 Hyphenated: one-two abc,def ' . $lorem2; - // Note: The search_excerpt() function adds some extra spaces -- not - // important for HTML formatting. Remove these for comparison. - $result = preg_replace('| +|', ' ', search_excerpt('123456.7890', $text)); - $this->assertTrue(strpos($result, 'Number: <strong>123456.7890</strong>') !== FALSE, 'Numeric keyword is highlighted with exact match'); - - $result = preg_replace('| +|', ' ', search_excerpt('1234567890', $text)); - $this->assertTrue(strpos($result, 'Number: <strong>123456.7890</strong>') !== FALSE, 'Numeric keyword is highlighted with simplified match'); - - $result = preg_replace('| +|', ' ', search_excerpt('Number 1234567890', $text)); - $this->assertTrue(strpos($result, '<strong>Number</strong>: <strong>123456.7890</strong>') !== FALSE, 'Punctuated and numeric keyword is highlighted with simplified match'); - - $result = preg_replace('| +|', ' ', search_excerpt('"Number 1234567890"', $text)); - $this->assertTrue(strpos($result, '<strong>Number: 123456.7890</strong>') !== FALSE, 'Phrase with punctuated and numeric keyword is highlighted with simplified match'); - - $result = preg_replace('| +|', ' ', search_excerpt('"Hyphenated onetwo"', $text)); - $this->assertTrue(strpos($result, '<strong>Hyphenated: one-two</strong>') !== FALSE, 'Phrase with punctuated and hyphenated keyword is highlighted with simplified match'); - - $result = preg_replace('| +|', ' ', search_excerpt('"abc def"', $text)); - $this->assertTrue(strpos($result, '<strong>abc,def</strong>') !== FALSE, 'Phrase with keyword simplified into two separate words is highlighted with simplified match'); - - // Test phrases with characters which are being truncated. - $result = preg_replace('| +|', ' ', search_excerpt('"ipsum _"', $text)); - $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part containing "_" is ignored.'); - - $result = preg_replace('| +|', ' ', search_excerpt('"ipsum 0000"', $text)); - $this->assertTrue(strpos($result, '<strong>ipsum </strong>') !== FALSE, 'Only valid part of the phrase is highlighted and invalid part "0000" is ignored.'); - - // Test combination of the valid keyword and keyword containing only - // characters which are being truncated during simplification. - $result = preg_replace('| +|', ' ', search_excerpt('ipsum _', $text)); - $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid keyword is highlighted and invalid keyword "_" is ignored.'); - - $result = preg_replace('| +|', ' ', search_excerpt('ipsum 0000', $text)); - $this->assertTrue(strpos($result, '<strong>ipsum</strong>') !== FALSE, 'Only valid keyword is highlighted and invalid keyword "0000" is ignored.'); - } -} - -/** - * Test the CJK tokenizer. - */ -class SearchTokenizerTestCase extends SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'CJK tokenizer', - 'description' => 'Check that CJK tokenizer works as intended.', - 'group' => '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 ''; - } -} - -/** - * Tests that we can embed a form in search results and submit it. - */ -class SearchEmbedForm extends SearchWebTestCase { - /** - * Node used for testing. - */ - public $node; - - /** - * Count of how many times the form has been submitted. - */ - public $submit_count = 0; - - public static function getInfo() { - return array( - 'name' => 'Embedded forms', - 'description' => 'Verifies that a form embedded in search results works', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('search_embedded_form')); - - // Create a user and a node, and update the search index. - $test_user = $this->drupalCreateUser(array('access content', 'search content', 'administer nodes')); - $this->drupalLogin($test_user); - - $this->node = $this->drupalCreateNode(); - - node_update_index(); - search_update_totals(); - - // Set up a dummy initial count of times the form has been submitted. - $this->submit_count = 12; - variable_set('search_embedded_form_submitted', $this->submit_count); - $this->refreshVariables(); - } - - /** - * Tests that the embedded form appears and can be submitted. - */ - function testEmbeddedForm() { - // First verify we can submit the form from the module's page. - $this->drupalPost('search_embedded_form', - array('name' => 'John'), - t('Send away')); - $this->assertText(t('Test form was submitted'), 'Form message appears'); - $count = variable_get('search_embedded_form_submitted', 0); - $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct'); - $this->submit_count = $count; - - // Now verify that we can see and submit the form from the search results. - $this->drupalGet('search/node/' . $this->node->title); - $this->assertText(t('Your name'), 'Form is visible'); - $this->drupalPost('search/node/' . $this->node->title, - array('name' => 'John'), - t('Send away')); - $this->assertText(t('Test form was submitted'), 'Form message appears'); - $count = variable_get('search_embedded_form_submitted', 0); - $this->assertEqual($this->submit_count + 1, $count, 'Form submission count is correct'); - $this->submit_count = $count; - - // Now verify that if we submit the search form, it doesn't count as - // our form being submitted. - $this->drupalPost('search', - array('keys' => 'foo'), - t('Search')); - $this->assertNoText(t('Test form was submitted'), 'Form message does not appear'); - $count = variable_get('search_embedded_form_submitted', 0); - $this->assertEqual($this->submit_count, $count, 'Form submission count is correct'); - $this->submit_count = $count; - } -} - -/** - * Tests that hook_search_page runs. - */ -class SearchPageOverride extends SearchWebTestCase { - public $search_user; - - public static function getInfo() { - return array( - 'name' => 'Search page override', - 'description' => 'Verify that hook_search_page can override search page display.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('search_extra_type')); - - // Login as a user that can create and search content. - $this->search_user = $this->drupalCreateUser(array('search content', 'administer search')); - $this->drupalLogin($this->search_user); - - // Enable the extra type module for searching. - variable_set('search_active_modules', array('node' => 'node', 'user' => 'user', 'search_extra_type' => 'search_extra_type')); - menu_router_rebuild(); - } - - function testSearchPageHook() { - $keys = 'bike shed ' . $this->randomName(); - $this->drupalGet("search/dummy_path/{$keys}"); - $this->assertText('Dummy search snippet', 'Dummy search snippet is shown'); - $this->assertText('Test page text is here', 'Page override is working'); - } -} - -/** - * Test node search with multiple languages. - */ -class SearchLanguageTestCase extends SearchWebTestCase { - public static function getInfo() { - return array( - 'name' => 'Search language selection', - 'description' => 'Tests advanced search with different languages enabled.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('locale')); - - // Create and login user. - $test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search', 'administer nodes', 'administer languages', 'access administration pages')); - $this->drupalLogin($test_user); - } - - function testLanguages() { - // Check that there are initially no languages displayed. - $this->drupalGet('search/node'); - $this->assertNoText(t('Languages'), t('No languages to choose from.')); - - // Add predefined language. - $edit = array('predefined_langcode' => 'fr'); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - - // Now we should have languages displayed. - $this->drupalGet('search/node'); - $this->assertText(t('Languages'), t('Languages displayed to choose from.')); - $this->assertText(t('English'), t('English is a possible choice.')); - $this->assertText(t('French'), t('French is a possible choice.')); - - // Ensure selecting no language does not make the query different. - $this->drupalPost('search/node', array(), t('Advanced search')); - $this->assertEqual($this->getUrl(), url('search/node/', array('absolute' => TRUE)), t('Correct page redirection, no language filtering.')); - - // Pick French and ensure it is selected. - $edit = array('language[fr]' => TRUE); - $this->drupalPost('search/node', $edit, t('Advanced search')); - $this->assertFieldByXPath('//input[@name="keys"]', 'language:fr', t('Language filter added to query.')); - - // Change the default language and delete English. - $path = 'admin/config/regional/language'; - $this->drupalGet($path); - $this->assertFieldChecked('edit-site-default-en', t('English is the default language.')); - $edit = array('site_default' => 'fr'); - $this->drupalPost(NULL, $edit, t('Save configuration')); - $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.')); - $this->drupalPost('admin/config/regional/language/delete/en', array(), t('Delete')); - - // Check that there are again no languages displayed. - $this->drupalGet('search/node'); - $this->assertNoText(t('Languages'), t('No languages to choose from.')); - } -} - -/** - * Tests node search with node access control. - */ -class SearchNodeAccessTest extends SearchWebTestCase { - public $test_user; - - public static function getInfo() { - return array( - 'name' => 'Search and node access', - 'description' => 'Tests search functionality with node access control.', - 'group' => 'Search', - ); - } - - function setUp() { - parent::setUp(array('node_access_test')); - node_access_rebuild(); - - // Create a test user and log in. - $this->test_user = $this->drupalCreateUser(array('access content', 'search content', 'use advanced search')); - $this->drupalLogin($this->test_user); - } - - /** - * Tests that search returns results with punctuation in the search phrase. - */ - function testPhraseSearchPunctuation() { - $node = $this->drupalCreateNode(array('body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => "The bunny's ears were fluffy."))))); - - // Update the search index. - module_invoke_all('update_index'); - search_update_totals(); - - // Refresh variables after the treatment. - $this->refreshVariables(); - - // Submit a phrase wrapped in double quotes to include the punctuation. - $edit = array('keys' => '"bunny\'s"'); - $this->drupalPost('search/node', $edit, t('Search')); - $this->assertText($node->title); - } -} diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..278b1e0c23cd288b294f8599a6d0e74e710153dc --- /dev/null +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php @@ -0,0 +1,137 @@ +<?php + +/** + * @file + * Definition of Drupal\shortcut\Tests\ShortcutLinksTest. + */ + +namespace Drupal\shortcut\Tests; + +/** + * Defines shortcut links test cases. + */ +class ShortcutLinksTest extends ShortcutTestBase { + + public static function getInfo() { + return array( + 'name' => 'Shortcut link functionality', + 'description' => 'Create, view, edit, delete, and change shortcut links.', + 'group' => 'Shortcut', + ); + } + + /** + * Tests that creating a shortcut works properly. + */ + function testShortcutLinkAdd() { + $set = $this->set; + + // Create an alias for the node so we can test aliases. + $path = array( + 'source' => 'node/' . $this->node->nid, + 'alias' => $this->randomName(8), + ); + path_save($path); + + // Create some paths to test. + $test_cases = array( + array('path' => 'admin'), + array('path' => 'admin/config/system/site-information'), + array('path' => "node/{$this->node->nid}/edit"), + array('path' => $path['alias']), + ); + + // Check that each new shortcut links where it should. + foreach ($test_cases as $test) { + $title = $this->randomName(10); + $form_data = array( + 'shortcut_link[link_title]' => $title, + 'shortcut_link[link_path]' => $test['path'], + ); + $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/add-link', $form_data, t('Save')); + $this->assertResponse(200); + $saved_set = shortcut_set_load($set->set_name); + $paths = $this->getShortcutInformation($saved_set, 'link_path'); + $this->assertTrue(in_array(drupal_get_normal_path($test['path']), $paths), 'Shortcut created: '. $test['path']); + $this->assertLink($title, 0, 'Shortcut link found on the page.'); + } + } + + /** + * Tests that the "add to shortcut" link changes to "remove shortcut". + */ + function testShortcutQuickLink() { + theme_enable(array('seven')); + variable_set('admin_theme', 'seven'); + variable_set('node_admin_theme', TRUE); + $this->drupalGet($this->set->links[0]['link_path']); + $this->assertRaw(t('Remove from %title shortcuts', array('%title' => $this->set->title)), '"Add to shortcuts" link properly switched to "Remove from shortcuts".'); + } + + /** + * Tests that shortcut links can be renamed. + */ + function testShortcutLinkRename() { + $set = $this->set; + + // Attempt to rename shortcut link. + $new_link_name = $this->randomName(10); + + $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $set->links[0]['link_path']), t('Save')); + $saved_set = shortcut_set_load($set->set_name); + $titles = $this->getShortcutInformation($saved_set, 'link_title'); + $this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name); + $this->assertLink($new_link_name, 0, 'Renamed shortcut link appears on the page.'); + } + + /** + * Tests that changing the path of a shortcut link works. + */ + function testShortcutLinkChangePath() { + $set = $this->set; + + // Tests changing a shortcut path. + $new_link_path = 'admin/config'; + + $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $set->links[0]['link_title'], 'shortcut_link[link_path]' => $new_link_path), t('Save')); + $saved_set = shortcut_set_load($set->set_name); + $paths = $this->getShortcutInformation($saved_set, 'link_path'); + $this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path); + $this->assertLinkByHref($new_link_path, 0, 'Shortcut with new path appears on the page.'); + } + + /** + * Tests deleting a shortcut link. + */ + function testShortcutLinkDelete() { + $set = $this->set; + + $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'] . '/delete', array(), 'Delete'); + $saved_set = shortcut_set_load($set->set_name); + $mlids = $this->getShortcutInformation($saved_set, 'mlid'); + $this->assertFalse(in_array($set->links[0]['mlid'], $mlids), 'Successfully deleted a shortcut.'); + } + + /** + * Tests that the add shortcut link is not displayed for 404/403 errors. + * + * Tests that the "Add to shortcuts" link is not displayed on a page not + * found or a page the user does not have access to. + */ + function testNoShortcutLink() { + // Change to a theme that displays shortcuts. + variable_set('theme_default', 'seven'); + + $this->drupalGet('page-that-does-not-exist'); + $this->assertNoRaw('add-shortcut', t('Add to shortcuts link was not shown on a page not found.')); + + // The user does not have access to this path. + $this->drupalGet('admin/modules'); + $this->assertNoRaw('add-shortcut', t('Add to shortcuts link was not shown on a page the user does not have access to.')); + + // Verify that the testing mechanism works by verifying the shortcut + // link appears on admin/content/node. + $this->drupalGet('admin/content/node'); + $this->assertRaw('add-shortcut', t('Add to shortcuts link was shown on a page the user does have access to.')); + } +} diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutSetsTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutSetsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8c1d9b4616d0a31cb7ca73b309ee9c794401a471 --- /dev/null +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutSetsTest.php @@ -0,0 +1,156 @@ +<?php + +/** + * @file + * Definition of Drupal\shortcut\Tests\ShortcutSetsTest. + */ + +namespace Drupal\shortcut\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Defines shortcut set test cases. + */ +class ShortcutSetsTest extends ShortcutTestBase { + + public static function getInfo() { + return array( + 'name' => 'Shortcut set functionality', + 'description' => 'Create, view, edit, delete, and change shortcut sets.', + 'group' => 'Shortcut', + ); + } + + /** + * Tests creating a shortcut set. + */ + function testShortcutSetAdd() { + $new_set = $this->generateShortcutSet($this->randomName(10)); + $sets = shortcut_sets(); + $this->assertTrue(isset($sets[$new_set->set_name]), 'Successfully created a shortcut set.'); + $this->drupalGet('user/' . $this->admin_user->uid . '/shortcuts'); + $this->assertText($new_set->title, 'Generated shortcut set was listed as a choice on the user account page.'); + } + + /** + * Tests switching a user's own shortcut set. + */ + function testShortcutSetSwitchOwn() { + $new_set = $this->generateShortcutSet($this->randomName(10)); + + // Attempt to switch the default shortcut set to the newly created shortcut + // set. + $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', array('set' => $new_set->set_name), t('Change set')); + $this->assertResponse(200); + $current_set = shortcut_current_displayed_set($this->admin_user); + $this->assertTrue($new_set->set_name == $current_set->set_name, 'Successfully switched own shortcut set.'); + } + + /** + * Tests switching another user's shortcut set. + */ + function testShortcutSetAssign() { + $new_set = $this->generateShortcutSet($this->randomName(10)); + + shortcut_set_assign_user($new_set, $this->shortcut_user); + $current_set = shortcut_current_displayed_set($this->shortcut_user); + $this->assertTrue($new_set->set_name == $current_set->set_name, "Successfully switched another user's shortcut set."); + } + + /** + * Tests switching a user's shortcut set and creating one at the same time. + */ + function testShortcutSetSwitchCreate() { + $edit = array( + 'set' => 'new', + 'new' => $this->randomName(10), + ); + $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); + $current_set = shortcut_current_displayed_set($this->admin_user); + $this->assertNotEqual($current_set->set_name, $this->set->set_name, 'A shortcut set can be switched to at the same time as it is created.'); + $this->assertEqual($current_set->title, $edit['new'], 'The new set is correctly assigned to the user.'); + } + + /** + * Tests switching a user's shortcut set without providing a new set name. + */ + function testShortcutSetSwitchNoSetName() { + $edit = array('set' => 'new'); + $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); + $this->assertText(t('The new set name is required.')); + $current_set = shortcut_current_displayed_set($this->admin_user); + $this->assertEqual($current_set->set_name, $this->set->set_name, 'Attempting to switch to a new shortcut set without providing a set name does not succeed.'); + } + + /** + * Tests that shortcut_set_save() correctly updates existing links. + */ + function testShortcutSetSave() { + $set = $this->set; + $old_mlids = $this->getShortcutInformation($set, 'mlid'); + + $set->links[] = $this->generateShortcutLink('admin', $this->randomName(10)); + shortcut_set_save($set); + $saved_set = shortcut_set_load($set->set_name); + + $new_mlids = $this->getShortcutInformation($saved_set, 'mlid'); + $this->assertTrue(count(array_intersect($old_mlids, $new_mlids)) == count($old_mlids), 'shortcut_set_save() did not inadvertently change existing mlids.'); + } + + /** + * Tests renaming a shortcut set. + */ + function testShortcutSetRename() { + $set = $this->set; + + $new_title = $this->randomName(10); + $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/edit', array('title' => $new_title), t('Save')); + $set = shortcut_set_load($set->set_name); + $this->assertTrue($set->title == $new_title, 'Shortcut set has been successfully renamed.'); + } + + /** + * Tests renaming a shortcut set to the same name as another set. + */ + function testShortcutSetRenameAlreadyExists() { + $set = $this->generateShortcutSet($this->randomName(10)); + $existing_title = $this->set->title; + $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/edit', array('title' => $existing_title), t('Save')); + $this->assertRaw(t('The shortcut set %name already exists. Choose another name.', array('%name' => $existing_title))); + $set = shortcut_set_load($set->set_name); + $this->assertNotEqual($set->title, $existing_title, t('The shortcut set %title cannot be renamed to %new-title because a shortcut set with that title already exists.', array('%title' => $set->title, '%new-title' => $existing_title))); + } + + /** + * Tests unassigning a shortcut set. + */ + function testShortcutSetUnassign() { + $new_set = $this->generateShortcutSet($this->randomName(10)); + + shortcut_set_assign_user($new_set, $this->shortcut_user); + shortcut_set_unassign_user($this->shortcut_user); + $current_set = shortcut_current_displayed_set($this->shortcut_user); + $default_set = shortcut_default_set($this->shortcut_user); + $this->assertTrue($current_set->set_name == $default_set->set_name, "Successfully unassigned another user's shortcut set."); + } + + /** + * Tests deleting a shortcut set. + */ + function testShortcutSetDelete() { + $new_set = $this->generateShortcutSet($this->randomName(10)); + + $this->drupalPost('admin/config/user-interface/shortcut/' . $new_set->set_name . '/delete', array(), t('Delete')); + $sets = shortcut_sets(); + $this->assertFalse(isset($sets[$new_set->set_name]), 'Successfully deleted a shortcut set.'); + } + + /** + * Tests deleting the default shortcut set. + */ + function testShortcutSetDeleteDefault() { + $this->drupalGet('admin/config/user-interface/shortcut/' . SHORTCUT_DEFAULT_SET_NAME . '/delete'); + $this->assertResponse(403); + } +} diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..830bbc114a0eb0926a3a5a36c54078b5e2b5a85e --- /dev/null +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php @@ -0,0 +1,109 @@ +<?php + +/** + * @file + * Definition of Drupal\shortcut\Tests\ShortcutTestBase. + */ + +namespace Drupal\shortcut\Tests; + +use Drupal\simpletest\WebTestBase; +use stdClass; + +/** + * Defines base class for shortcut test cases. + */ +class ShortcutTestBase extends WebTestBase { + + /** + * User with permission to administer shortcuts. + */ + protected $admin_user; + + /** + * User with permission to use shortcuts, but not administer them. + */ + protected $shortcut_user; + + /** + * Generic node used for testing. + */ + protected $node; + + /** + * Site-wide default shortcut set. + */ + protected $set; + + function setUp() { + parent::setUp('toolbar', 'shortcut'); + + // Create Basic page and Article node types. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + } + + // Create users. + $this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'view the administration theme', 'create article content', 'create page content', 'access content overview')); + $this->shortcut_user = $this->drupalCreateUser(array('customize shortcut links', 'switch shortcut sets')); + + // Create a node. + $this->node = $this->drupalCreateNode(array('type' => 'article')); + + // Log in as admin and grab the default shortcut set. + $this->drupalLogin($this->admin_user); + $this->set = shortcut_set_load(SHORTCUT_DEFAULT_SET_NAME); + shortcut_set_assign_user($this->set, $this->admin_user); + } + + /** + * Creates a generic shortcut set. + */ + function generateShortcutSet($title = '', $default_links = TRUE) { + $set = new stdClass(); + $set->title = empty($title) ? $this->randomName(10) : $title; + if ($default_links) { + $set->links = array(); + $set->links[] = $this->generateShortcutLink('node/add'); + $set->links[] = $this->generateShortcutLink('admin/content'); + } + shortcut_set_save($set); + + return $set; + } + + /** + * Creates a generic shortcut link. + */ + function generateShortcutLink($path, $title = '') { + $link = array( + 'link_path' => $path, + 'link_title' => !empty($title) ? $title : $this->randomName(10), + ); + + return $link; + } + + /** + * Extracts information from shortcut set links. + * + * @param object $set + * The shortcut set object to extract information from. + * @param string $key + * The array key indicating what information to extract from each link: + * - 'link_path': Extract link paths. + * - 'link_title': Extract link titles. + * - 'mlid': Extract the menu link item ID numbers. + * + * @return array + * Array of the requested information from each link. + */ + function getShortcutInformation($set, $key) { + $info = array(); + foreach ($set->links as $link) { + $info[] = $link[$key]; + } + return $info; + } +} diff --git a/core/modules/shortcut/shortcut.info b/core/modules/shortcut/shortcut.info index 0030605d3f0d3a67a7b065f990c6e7ce40f85e47..5ed5f2dd83ae92f29aa83284955f3325ef2b0252 100644 --- a/core/modules/shortcut/shortcut.info +++ b/core/modules/shortcut/shortcut.info @@ -3,5 +3,4 @@ description = Allows users to manage customizable lists of shortcut links. package = Core version = VERSION core = 8.x -files[] = shortcut.test configure = admin/config/user-interface/shortcut diff --git a/core/modules/shortcut/shortcut.test b/core/modules/shortcut/shortcut.test deleted file mode 100644 index 815b7326d535ff32b5e84afda39037b306bbd14d..0000000000000000000000000000000000000000 --- a/core/modules/shortcut/shortcut.test +++ /dev/null @@ -1,381 +0,0 @@ -<?php - -/** - * @file - * Tests for shortcut.module. - */ - -use Drupal\simpletest\WebTestBase; - -/** - * Defines base class for shortcut test cases. - */ -class ShortcutTestCase extends WebTestBase { - - /** - * User with permission to administer shortcuts. - */ - protected $admin_user; - - /** - * User with permission to use shortcuts, but not administer them. - */ - protected $shortcut_user; - - /** - * Generic node used for testing. - */ - protected $node; - - /** - * Site-wide default shortcut set. - */ - protected $set; - - function setUp() { - parent::setUp('toolbar', 'shortcut'); - - // Create Basic page and Article node types. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - } - - // Create users. - $this->admin_user = $this->drupalCreateUser(array('access toolbar', 'administer shortcuts', 'view the administration theme', 'create article content', 'create page content', 'access content overview')); - $this->shortcut_user = $this->drupalCreateUser(array('customize shortcut links', 'switch shortcut sets')); - - // Create a node. - $this->node = $this->drupalCreateNode(array('type' => 'article')); - - // Log in as admin and grab the default shortcut set. - $this->drupalLogin($this->admin_user); - $this->set = shortcut_set_load(SHORTCUT_DEFAULT_SET_NAME); - shortcut_set_assign_user($this->set, $this->admin_user); - } - - /** - * Creates a generic shortcut set. - */ - function generateShortcutSet($title = '', $default_links = TRUE) { - $set = new stdClass(); - $set->title = empty($title) ? $this->randomName(10) : $title; - if ($default_links) { - $set->links = array(); - $set->links[] = $this->generateShortcutLink('node/add'); - $set->links[] = $this->generateShortcutLink('admin/content'); - } - shortcut_set_save($set); - - return $set; - } - - /** - * Creates a generic shortcut link. - */ - function generateShortcutLink($path, $title = '') { - $link = array( - 'link_path' => $path, - 'link_title' => !empty($title) ? $title : $this->randomName(10), - ); - - return $link; - } - - /** - * Extracts information from shortcut set links. - * - * @param object $set - * The shortcut set object to extract information from. - * @param string $key - * The array key indicating what information to extract from each link: - * - 'link_path': Extract link paths. - * - 'link_title': Extract link titles. - * - 'mlid': Extract the menu link item ID numbers. - * - * @return array - * Array of the requested information from each link. - */ - function getShortcutInformation($set, $key) { - $info = array(); - foreach ($set->links as $link) { - $info[] = $link[$key]; - } - return $info; - } -} - -/** - * Defines shortcut links test cases. - */ -class ShortcutLinksTestCase extends ShortcutTestCase { - - public static function getInfo() { - return array( - 'name' => 'Shortcut link functionality', - 'description' => 'Create, view, edit, delete, and change shortcut links.', - 'group' => 'Shortcut', - ); - } - - /** - * Tests that creating a shortcut works properly. - */ - function testShortcutLinkAdd() { - $set = $this->set; - - // Create an alias for the node so we can test aliases. - $path = array( - 'source' => 'node/' . $this->node->nid, - 'alias' => $this->randomName(8), - ); - path_save($path); - - // Create some paths to test. - $test_cases = array( - array('path' => 'admin'), - array('path' => 'admin/config/system/site-information'), - array('path' => "node/{$this->node->nid}/edit"), - array('path' => $path['alias']), - ); - - // Check that each new shortcut links where it should. - foreach ($test_cases as $test) { - $title = $this->randomName(10); - $form_data = array( - 'shortcut_link[link_title]' => $title, - 'shortcut_link[link_path]' => $test['path'], - ); - $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/add-link', $form_data, t('Save')); - $this->assertResponse(200); - $saved_set = shortcut_set_load($set->set_name); - $paths = $this->getShortcutInformation($saved_set, 'link_path'); - $this->assertTrue(in_array(drupal_get_normal_path($test['path']), $paths), 'Shortcut created: '. $test['path']); - $this->assertLink($title, 0, 'Shortcut link found on the page.'); - } - } - - /** - * Tests that the "add to shortcut" link changes to "remove shortcut". - */ - function testShortcutQuickLink() { - theme_enable(array('seven')); - variable_set('admin_theme', 'seven'); - variable_set('node_admin_theme', TRUE); - $this->drupalGet($this->set->links[0]['link_path']); - $this->assertRaw(t('Remove from %title shortcuts', array('%title' => $this->set->title)), '"Add to shortcuts" link properly switched to "Remove from shortcuts".'); - } - - /** - * Tests that shortcut links can be renamed. - */ - function testShortcutLinkRename() { - $set = $this->set; - - // Attempt to rename shortcut link. - $new_link_name = $this->randomName(10); - - $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $set->links[0]['link_path']), t('Save')); - $saved_set = shortcut_set_load($set->set_name); - $titles = $this->getShortcutInformation($saved_set, 'link_title'); - $this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name); - $this->assertLink($new_link_name, 0, 'Renamed shortcut link appears on the page.'); - } - - /** - * Tests that changing the path of a shortcut link works. - */ - function testShortcutLinkChangePath() { - $set = $this->set; - - // Tests changing a shortcut path. - $new_link_path = 'admin/config'; - - $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'], array('shortcut_link[link_title]' => $set->links[0]['link_title'], 'shortcut_link[link_path]' => $new_link_path), t('Save')); - $saved_set = shortcut_set_load($set->set_name); - $paths = $this->getShortcutInformation($saved_set, 'link_path'); - $this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path); - $this->assertLinkByHref($new_link_path, 0, 'Shortcut with new path appears on the page.'); - } - - /** - * Tests deleting a shortcut link. - */ - function testShortcutLinkDelete() { - $set = $this->set; - - $this->drupalPost('admin/config/user-interface/shortcut/link/' . $set->links[0]['mlid'] . '/delete', array(), 'Delete'); - $saved_set = shortcut_set_load($set->set_name); - $mlids = $this->getShortcutInformation($saved_set, 'mlid'); - $this->assertFalse(in_array($set->links[0]['mlid'], $mlids), 'Successfully deleted a shortcut.'); - } - - /** - * Tests that the add shortcut link is not displayed for 404/403 errors. - * - * Tests that the "Add to shortcuts" link is not displayed on a page not - * found or a page the user does not have access to. - */ - function testNoShortcutLink() { - // Change to a theme that displays shortcuts. - variable_set('theme_default', 'seven'); - - $this->drupalGet('page-that-does-not-exist'); - $this->assertNoRaw('add-shortcut', t('Add to shortcuts link was not shown on a page not found.')); - - // The user does not have access to this path. - $this->drupalGet('admin/modules'); - $this->assertNoRaw('add-shortcut', t('Add to shortcuts link was not shown on a page the user does not have access to.')); - - // Verify that the testing mechanism works by verifying the shortcut - // link appears on admin/content/node. - $this->drupalGet('admin/content/node'); - $this->assertRaw('add-shortcut', t('Add to shortcuts link was shown on a page the user does have access to.')); - } -} - -/** - * Defines shortcut set test cases. - */ -class ShortcutSetsTestCase extends ShortcutTestCase { - - public static function getInfo() { - return array( - 'name' => 'Shortcut set functionality', - 'description' => 'Create, view, edit, delete, and change shortcut sets.', - 'group' => 'Shortcut', - ); - } - - /** - * Tests creating a shortcut set. - */ - function testShortcutSetAdd() { - $new_set = $this->generateShortcutSet($this->randomName(10)); - $sets = shortcut_sets(); - $this->assertTrue(isset($sets[$new_set->set_name]), 'Successfully created a shortcut set.'); - $this->drupalGet('user/' . $this->admin_user->uid . '/shortcuts'); - $this->assertText($new_set->title, 'Generated shortcut set was listed as a choice on the user account page.'); - } - - /** - * Tests switching a user's own shortcut set. - */ - function testShortcutSetSwitchOwn() { - $new_set = $this->generateShortcutSet($this->randomName(10)); - - // Attempt to switch the default shortcut set to the newly created shortcut - // set. - $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', array('set' => $new_set->set_name), t('Change set')); - $this->assertResponse(200); - $current_set = shortcut_current_displayed_set($this->admin_user); - $this->assertTrue($new_set->set_name == $current_set->set_name, 'Successfully switched own shortcut set.'); - } - - /** - * Tests switching another user's shortcut set. - */ - function testShortcutSetAssign() { - $new_set = $this->generateShortcutSet($this->randomName(10)); - - shortcut_set_assign_user($new_set, $this->shortcut_user); - $current_set = shortcut_current_displayed_set($this->shortcut_user); - $this->assertTrue($new_set->set_name == $current_set->set_name, "Successfully switched another user's shortcut set."); - } - - /** - * Tests switching a user's shortcut set and creating one at the same time. - */ - function testShortcutSetSwitchCreate() { - $edit = array( - 'set' => 'new', - 'new' => $this->randomName(10), - ); - $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); - $current_set = shortcut_current_displayed_set($this->admin_user); - $this->assertNotEqual($current_set->set_name, $this->set->set_name, 'A shortcut set can be switched to at the same time as it is created.'); - $this->assertEqual($current_set->title, $edit['new'], 'The new set is correctly assigned to the user.'); - } - - /** - * Tests switching a user's shortcut set without providing a new set name. - */ - function testShortcutSetSwitchNoSetName() { - $edit = array('set' => 'new'); - $this->drupalPost('user/' . $this->admin_user->uid . '/shortcuts', $edit, t('Change set')); - $this->assertText(t('The new set name is required.')); - $current_set = shortcut_current_displayed_set($this->admin_user); - $this->assertEqual($current_set->set_name, $this->set->set_name, 'Attempting to switch to a new shortcut set without providing a set name does not succeed.'); - } - - /** - * Tests that shortcut_set_save() correctly updates existing links. - */ - function testShortcutSetSave() { - $set = $this->set; - $old_mlids = $this->getShortcutInformation($set, 'mlid'); - - $set->links[] = $this->generateShortcutLink('admin', $this->randomName(10)); - shortcut_set_save($set); - $saved_set = shortcut_set_load($set->set_name); - - $new_mlids = $this->getShortcutInformation($saved_set, 'mlid'); - $this->assertTrue(count(array_intersect($old_mlids, $new_mlids)) == count($old_mlids), 'shortcut_set_save() did not inadvertently change existing mlids.'); - } - - /** - * Tests renaming a shortcut set. - */ - function testShortcutSetRename() { - $set = $this->set; - - $new_title = $this->randomName(10); - $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/edit', array('title' => $new_title), t('Save')); - $set = shortcut_set_load($set->set_name); - $this->assertTrue($set->title == $new_title, 'Shortcut set has been successfully renamed.'); - } - - /** - * Tests renaming a shortcut set to the same name as another set. - */ - function testShortcutSetRenameAlreadyExists() { - $set = $this->generateShortcutSet($this->randomName(10)); - $existing_title = $this->set->title; - $this->drupalPost('admin/config/user-interface/shortcut/' . $set->set_name . '/edit', array('title' => $existing_title), t('Save')); - $this->assertRaw(t('The shortcut set %name already exists. Choose another name.', array('%name' => $existing_title))); - $set = shortcut_set_load($set->set_name); - $this->assertNotEqual($set->title, $existing_title, t('The shortcut set %title cannot be renamed to %new-title because a shortcut set with that title already exists.', array('%title' => $set->title, '%new-title' => $existing_title))); - } - - /** - * Tests unassigning a shortcut set. - */ - function testShortcutSetUnassign() { - $new_set = $this->generateShortcutSet($this->randomName(10)); - - shortcut_set_assign_user($new_set, $this->shortcut_user); - shortcut_set_unassign_user($this->shortcut_user); - $current_set = shortcut_current_displayed_set($this->shortcut_user); - $default_set = shortcut_default_set($this->shortcut_user); - $this->assertTrue($current_set->set_name == $default_set->set_name, "Successfully unassigned another user's shortcut set."); - } - - /** - * Tests deleting a shortcut set. - */ - function testShortcutSetDelete() { - $new_set = $this->generateShortcutSet($this->randomName(10)); - - $this->drupalPost('admin/config/user-interface/shortcut/' . $new_set->set_name . '/delete', array(), t('Delete')); - $sets = shortcut_sets(); - $this->assertFalse(isset($sets[$new_set->set_name]), 'Successfully deleted a shortcut set.'); - } - - /** - * Tests deleting the default shortcut set. - */ - function testShortcutSetDeleteDefault() { - $this->drupalGet('admin/config/user-interface/shortcut/' . SHORTCUT_DEFAULT_SET_NAME . '/delete'); - $this->assertResponse(403); - } -} diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 536bb96d4887c560f5305e671a43642401113ee3..14e218af8c6acb5c95ce9aa627f03ba040e7540e 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -857,9 +857,14 @@ protected function tearDown() { file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10)); // Remove all prefixed tables. - $tables = db_find_tables($this->databasePrefix . '%'); + $connection_info = Database::getConnectionInfo('default'); + $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%'); + if (empty($tables)) { + $this->fail('Failed to find test tables to drop.'); + } + $prefix_length = strlen($connection_info['default']['prefix']['default']); foreach ($tables as $table) { - if (db_drop_table(substr($table, strlen($this->databasePrefix)))) { + if (db_drop_table(substr($table, $prefix_length))) { unset($tables[$table]); } } diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsAdminTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..86b446aa6355d0ff9782c11f7f1df558d76d78aa --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsAdminTest.php @@ -0,0 +1,197 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsAdminTest. + */ + +namespace Drupal\statistics\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests the statistics administration screen. + */ +class StatisticsAdminTest extends WebTestBase { + + /** + * A user that has permission to administer and access statistics. + * + * @var object|FALSE + * + * A fully loaded user object, or FALSE if user creation failed. + */ + protected $privileged_user; + + /** + * A page node for which to check access statistics. + * + * @var object + */ + protected $test_node; + + public static function getInfo() { + return array( + 'name' => 'Test statistics admin.', + 'description' => 'Tests the statistics admin.', + 'group' => 'Statistics' + ); + } + + function setUp() { + parent::setUp(array('node', 'statistics')); + + // Create Basic page node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + } + $this->privileged_user = $this->drupalCreateUser(array('access statistics', 'administer statistics', 'view post access counter', 'create page content')); + $this->drupalLogin($this->privileged_user); + $this->test_node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->privileged_user->uid)); + } + + /** + * Verifies that the statistics settings page works. + */ + function testStatisticsSettings() { + $this->assertFalse(variable_get('statistics_enable_access_log', 0), t('Access log is disabled by default.')); + $this->assertFalse(variable_get('statistics_count_content_views', 0), t('Count content view log is disabled by default.')); + + $this->drupalGet('admin/reports/pages'); + $this->assertRaw(t('No statistics available.'), t('Verifying text shown when no statistics is available.')); + + // Enable access log and counter on content view. + $edit['statistics_enable_access_log'] = 1; + $edit['statistics_count_content_views'] = 1; + $this->drupalPost('admin/config/system/statistics', $edit, t('Save configuration')); + $this->assertTrue(variable_get('statistics_enable_access_log'), t('Access log is enabled.')); + $this->assertTrue(variable_get('statistics_count_content_views'), t('Count content view log is enabled.')); + + // Hit the node. + $this->drupalGet('node/' . $this->test_node->nid); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $this->test_node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + + $this->drupalGet('admin/reports/pages'); + $this->assertText('node/1', t('Test node found.')); + + // Hit the node again (the counter is incremented after the hit, so + // "1 view" will actually be shown when the node is hit the second time). + $this->drupalGet('node/' . $this->test_node->nid); + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->assertText('1 view', t('Node is viewed once.')); + + $this->drupalGet('node/' . $this->test_node->nid); + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->assertText('2 views', t('Node is viewed 2 times.')); + } + + /** + * Tests that when a node is deleted, the node counter is deleted too. + */ + function testDeleteNode() { + variable_set('statistics_count_content_views', 1); + + $this->drupalGet('node/' . $this->test_node->nid); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $this->test_node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + + $result = db_select('node_counter', 'n') + ->fields('n', array('nid')) + ->condition('n.nid', $this->test_node->nid) + ->execute() + ->fetchAssoc(); + $this->assertEqual($result['nid'], $this->test_node->nid, 'Verifying that the node counter is incremented.'); + + node_delete($this->test_node->nid); + + $result = db_select('node_counter', 'n') + ->fields('n', array('nid')) + ->condition('n.nid', $this->test_node->nid) + ->execute() + ->fetchAssoc(); + $this->assertFalse($result, 'Verifying that the node counter is deleted.'); + } + + /** + * Tests that accesslog reflects when a user is deleted. + */ + function testDeleteUser() { + variable_set('statistics_enable_access_log', 1); + + variable_set('user_cancel_method', 'user_cancel_delete'); + $this->drupalLogout($this->privileged_user); + $account = $this->drupalCreateUser(array('access content', 'cancel account')); + $this->drupalLogin($account); + $this->drupalGet('node/' . $this->test_node->nid); + + $account = user_load($account->uid, TRUE); + + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + + $timestamp = time(); + $this->drupalPost(NULL, NULL, t('Cancel account')); + // Confirm account cancellation request. + $mails = $this->drupalGetMails(); + $mail = end($mails); + preg_match('@http.+?(user/\d+/cancel/confirm/\d+/[^\s]+)@', $mail['body'], $matches); + $path = $matches[1]; + $this->drupalGet($path); + $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); + + $this->drupalGet('admin/reports/visitors'); + $this->assertNoText($account->name, t('Did not find user in visitor statistics.')); + } + + /** + * Tests that cron clears day counts and expired access logs. + */ + function testExpiredLogs() { + variable_set('statistics_enable_access_log', 1); + variable_set('statistics_count_content_views', 1); + variable_set('statistics_day_timestamp', 8640000); + variable_set('statistics_flush_accesslog_timer', 1); + + $this->drupalGet('node/' . $this->test_node->nid); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $this->test_node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->drupalGet('node/' . $this->test_node->nid); + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->assertText('1 view', t('Node is viewed once.')); + + $this->drupalGet('admin/reports/pages'); + $this->assertText('node/' . $this->test_node->nid, t('Hit URL found.')); + + // statistics_cron will subtract the statistics_flush_accesslog_timer + // variable from REQUEST_TIME in the delete query, so wait two secs here to + // make sure the access log will be flushed for the node just hit. + sleep(2); + $this->cronRun(); + + $this->drupalGet('admin/reports/pages'); + $this->assertNoText('node/' . $this->test_node->nid, t('No hit URL found.')); + + $result = db_select('node_counter', 'nc') + ->fields('nc', array('daycount')) + ->condition('nid', $this->test_node->nid, '=') + ->execute() + ->fetchField(); + $this->assertFalse($result, t('Daycounter is zero.')); + } +} diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsBlockVisitorsTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsBlockVisitorsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c8786ce7a5cbadb0ba30b8dd44195db2d9f11145 --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsBlockVisitorsTest.php @@ -0,0 +1,58 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsBlockVisitorsTest. + */ + +namespace Drupal\statistics\Tests; + +/** + * Tests that the visitor blocking functionality works. + */ +class StatisticsBlockVisitorsTest extends StatisticsTestBase { + public static function getInfo() { + return array( + 'name' => 'Top visitor blocking', + 'description' => 'Tests blocking of IP addresses via the top visitors report.', + 'group' => 'Statistics' + ); + } + + /** + * Blocks an IP address via the top visitors report and then unblocks it. + */ + function testIPAddressBlocking() { + // IP address for testing. + $test_ip_address = '192.168.1.1'; + + // Verify the IP address from accesslog appears on the top visitors page + // and that a 'block IP address' link is displayed. + $this->drupalLogin($this->blocking_user); + $this->drupalGet('admin/reports/visitors'); + $this->assertText($test_ip_address, t('IP address found.')); + $this->assertText(t('block IP address'), t('Block IP link displayed')); + + // Block the IP address. + $this->clickLink('block IP address'); + $this->assertText(t('IP address blocking'), t('IP blocking page displayed.')); + $edit = array(); + $edit['ip'] = $test_ip_address; + $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); + $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); + $this->assertNotEqual($ip, FALSE, t('IP address found in database')); + $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.')); + + // Verify that the block/unblock link on the top visitors page has been + // altered. + $this->drupalGet('admin/reports/visitors'); + $this->assertText(t('unblock IP address'), t('Unblock IP address link displayed')); + + // Unblock the IP address. + $this->clickLink('unblock IP address'); + $this->assertRaw(t('Are you sure you want to delete %ip?', array('%ip' => $test_ip_address)), t('IP address deletion confirmation found.')); + $edit = array(); + $this->drupalPost('admin/config/people/ip-blocking/delete/1', NULL, t('Delete')); + $this->assertRaw(t('The IP address %ip was deleted.', array('%ip' => $test_ip_address)), t('IP address deleted.')); + } +} diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsLoggingTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsLoggingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e0a25f907e97cf5d48e368d39de3e143a6f665dc --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsLoggingTest.php @@ -0,0 +1,130 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsLoggingTest. + */ + +namespace Drupal\statistics\Tests; + +use Drupal\simpletest\WebTestBase; +use PDO; + +/** + * Tests that logging via statistics_exit() works for all pages. + * + * We subclass WebTestBase rather than StatisticsTestBase, because we + * want to test requests from an anonymous user. + */ +class StatisticsLoggingTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Statistics logging tests', + 'description' => 'Tests request logging for cached and uncached pages.', + 'group' => 'Statistics' + ); + } + + function setUp() { + parent::setUp(array('statistics', 'block')); + + // Create Basic page node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + } + + $this->auth_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); + + // Ensure we have a node page to access. + $this->node = $this->drupalCreateNode(array('title' => $this->randomName(255), 'uid' => $this->auth_user->uid)); + + // Enable page caching. + $config = config('system.performance'); + $config->set('cache', 1); + $config->save(); + + // Enable access logging. + variable_set('statistics_enable_access_log', 1); + variable_set('statistics_count_content_views', 1); + + // Clear the logs. + db_truncate('accesslog'); + db_truncate('node_counter'); + } + + /** + * Verifies request logging for cached and uncached pages. + */ + function testLogging() { + $path = 'node/' . $this->node->nid; + $expected = array( + 'title' => $this->node->title, + 'path' => $path, + ); + + // Verify logging of an uncached page. + $this->drupalGet($path); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $this->node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Testing an uncached page.')); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 1, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[0], $expected), $expected); + $node_counter = statistics_get($this->node->nid); + $this->assertIdentical($node_counter['totalcount'], '1'); + + // Verify logging of a cached page. + $this->drupalGet($path); + // Manually calling statistics.php, simulating ajax behavior. + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Testing a cached page.')); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 2, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[1], $expected), $expected); + $node_counter = statistics_get($this->node->nid); + $this->assertIdentical($node_counter['totalcount'], '2'); + + // Test logging from authenticated users + $this->drupalLogin($this->auth_user); + $this->drupalGet($path); + // Manually calling statistics.php, simulating ajax behavior. + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + // Check the 6th item since login and account pages are also logged + $this->assertTrue(is_array($log) && count($log) == 6, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[5], $expected), $expected); + $node_counter = statistics_get($this->node->nid); + $this->assertIdentical($node_counter['totalcount'], '3'); + + // Visit edit page to generate a title greater than 255. + $path = 'node/' . $this->node->nid . '/edit'; + $expected = array( + 'title' => truncate_utf8(t('Edit Basic page') . ' ' . $this->node->title, 255), + 'path' => $path, + ); + $this->drupalGet($path); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 7, t('Page request was logged.')); + $this->assertEqual(array_intersect_key($log[6], $expected), $expected); + + // Create a path longer than 255 characters. Drupal's .htaccess file + // instructs Apache to test paths against the file system before routing to + // index.php. Many file systems restrict file names to 255 characters + // (http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits), and + // Apache returns a 403 when testing longer file names, but the total path + // length is not restricted. + $long_path = $this->randomName(127) . '/' . $this->randomName(128); + + // Test that the long path is properly truncated when logged. + $this->drupalGet($long_path); + $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); + $this->assertTrue(is_array($log) && count($log) == 8, 'Page request was logged for a path over 255 characters.'); + $this->assertEqual($log[7]['path'], truncate_utf8($long_path, 255)); + + } +} diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7c0007ab237b2176a51d9e61739abb9adda535c8 --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php @@ -0,0 +1,105 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsReportsTest. + */ + +namespace Drupal\statistics\Tests; + +/** + * Tests that report pages render properly, and that access logging works. + */ +class StatisticsReportsTest extends StatisticsTestBase { + public static function getInfo() { + return array( + 'name' => 'Statistics reports tests', + 'description' => 'Tests display of statistics report pages and access logging.', + 'group' => 'Statistics' + ); + } + + /** + * Verifies that 'Recent hits' renders properly and displays the added hit. + */ + function testRecentHits() { + $this->drupalGet('admin/reports/hits'); + $this->assertText('test', t('Hit title found.')); + $this->assertText('node/1', t('Hit URL found.')); + $this->assertText('Anonymous', t('Hit user found.')); + } + + /** + * Verifies that 'Top pages' renders properly and displays the added hit. + */ + function testTopPages() { + $this->drupalGet('admin/reports/pages'); + $this->assertText('test', t('Hit title found.')); + $this->assertText('node/1', t('Hit URL found.')); + } + + /** + * Verifies that 'Top referrers' renders properly and displays the added hit. + */ + function testTopReferrers() { + $this->drupalGet('admin/reports/referrers'); + $this->assertText('http://example.com', t('Hit referrer found.')); + } + + /** + * Verifies that 'Details' page renders properly and displays the added hit. + */ + function testDetails() { + $this->drupalGet('admin/reports/access/1'); + $this->assertText('test', t('Hit title found.')); + $this->assertText('node/1', t('Hit URL found.')); + $this->assertText('Anonymous', t('Hit user found.')); + } + + /** + * Verifies that access logging is working and is reported correctly. + */ + function testAccessLogging() { + $this->drupalGet('admin/reports/referrers'); + $this->drupalGet('admin/reports/hits'); + $this->assertText('Top referrers in the past 3 days', t('Hit title found.')); + $this->assertText('admin/reports/referrers', t('Hit URL found.')); + } + + /** + * Tests the "popular content" block. + */ + function testPopularContentBlock() { + // Visit a node to have something show up in the block. + $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->blocking_user->uid)); + $this->drupalGet('node/' . $node->nid); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + + // Configure and save the block. + $block = block_load('statistics', 'popular'); + $block->theme = variable_get('theme_default', 'stark'); + $block->status = 1; + $block->pages = ''; + $block->region = 'sidebar_first'; + $block->cache = -1; + $block->visibility = 0; + $edit = array('statistics_block_top_day_num' => 3, 'statistics_block_top_all_num' => 3, 'statistics_block_top_last_num' => 3); + module_invoke('statistics', 'block_save', 'popular', $edit); + drupal_write_record('block', $block); + + // Get some page and check if the block is displayed. + $this->drupalGet('user'); + $this->assertText('Popular content', t('Found the popular content block.')); + $this->assertText("Today's", t('Found today\'s popular content.')); + $this->assertText('All time', t('Found the alll time popular content.')); + $this->assertText('Last viewed', t('Found the last viewed popular content.')); + + $this->assertRaw(l($node->title, 'node/' . $node->nid), t('Found link to visited node.')); + } +} diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTestBase.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..11831558d463dfedd8090d75a87939c7f3e8f116 --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTestBase.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsTestBase. + */ + +namespace Drupal\statistics\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Defines a base class for testing the Statistics module. + */ +class StatisticsTestBase extends WebTestBase { + + function setUp() { + parent::setUp(array('node', 'block', 'statistics')); + + // Create Basic page node type. + if ($this->profile != 'standard') { + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + } + + // Create user. + $this->blocking_user = $this->drupalCreateUser(array( + 'access administration pages', + 'access site reports', + 'access statistics', + 'block IP addresses', + 'administer blocks', + 'administer statistics', + 'administer users', + )); + $this->drupalLogin($this->blocking_user); + + // Enable access logging. + variable_set('statistics_enable_access_log', 1); + variable_set('statistics_count_content_views', 1); + + // Insert dummy access by anonymous user into access log. + db_insert('accesslog') + ->fields(array( + 'title' => 'test', + 'path' => 'node/1', + 'url' => 'http://example.com', + 'hostname' => '192.168.1.1', + 'uid' => 0, + 'sid' => 10, + 'timer' => 10, + 'timestamp' => REQUEST_TIME, + )) + ->execute(); + } +} diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTokenReplaceTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTokenReplaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..304561138c8611e91b7724457edfc6eb832b5154 --- /dev/null +++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsTokenReplaceTest.php @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Definition of Drupal\statistics\Tests\StatisticsTokenReplaceTest. + */ + +namespace Drupal\statistics\Tests; + +/** + * Tests statistics token replacement in strings. + */ +class StatisticsTokenReplaceTest extends StatisticsTestBase { + public static function getInfo() { + return array( + 'name' => 'Statistics token replacement', + 'description' => 'Generates text using placeholders for dummy content to check statistics token replacement.', + 'group' => 'Statistics', + ); + } + + /** + * Creates a node, then tests the statistics tokens generated from it. + */ + function testStatisticsTokenReplacement() { + global $language_interface; + + // Create user and node. + $user = $this->drupalCreateUser(array('create page content')); + $this->drupalLogin($user); + $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $user->uid)); + + // Hit the node. + $this->drupalGet('node/' . $node->nid); + // Manually calling statistics.php, simulating ajax behavior. + $nid = $node->nid; + $post = http_build_query(array('nid' => $nid)); + $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); + global $base_url; + $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; + drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); + $statistics = statistics_get($node->nid); + + // Generate and test tokens. + $tests = array(); + $tests['[node:total-count]'] = 1; + $tests['[node:day-count]'] = 1; + $tests['[node:last-view]'] = format_date($statistics['timestamp']); + $tests['[node:last-view:short]'] = format_date($statistics['timestamp'], 'short'); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Statistics token %token replaced.', array('%token' => $input))); + } + } +} diff --git a/core/modules/statistics/statistics.info b/core/modules/statistics/statistics.info index e7add6033adfb2eff6643688f39cc0c1b6752ae8..4b18b8a0dfe3cba6fe54bdd768569296dc43e7ca 100644 --- a/core/modules/statistics/statistics.info +++ b/core/modules/statistics/statistics.info @@ -3,5 +3,4 @@ description = Logs access statistics for your site. package = Core version = VERSION core = 8.x -files[] = statistics.test configure = admin/config/system/statistics diff --git a/core/modules/statistics/statistics.test b/core/modules/statistics/statistics.test deleted file mode 100644 index bb28b01cb3dba278569a6f4406f6f3a52996f488..0000000000000000000000000000000000000000 --- a/core/modules/statistics/statistics.test +++ /dev/null @@ -1,557 +0,0 @@ -<?php - -/** - * @file - * Tests for the Statistics module. - */ - -use Drupal\simpletest\WebTestBase; - -/** - * Defines a base class for testing the Statistics module. - */ -class StatisticsTestCase extends WebTestBase { - - function setUp() { - parent::setUp(array('node', 'block', 'statistics')); - - // Create Basic page node type. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - } - - // Create user. - $this->blocking_user = $this->drupalCreateUser(array( - 'access administration pages', - 'access site reports', - 'access statistics', - 'block IP addresses', - 'administer blocks', - 'administer statistics', - 'administer users', - )); - $this->drupalLogin($this->blocking_user); - - // Enable access logging. - variable_set('statistics_enable_access_log', 1); - variable_set('statistics_count_content_views', 1); - - // Insert dummy access by anonymous user into access log. - db_insert('accesslog') - ->fields(array( - 'title' => 'test', - 'path' => 'node/1', - 'url' => 'http://example.com', - 'hostname' => '192.168.1.1', - 'uid' => 0, - 'sid' => 10, - 'timer' => 10, - 'timestamp' => REQUEST_TIME, - )) - ->execute(); - } -} - -/** - * Tests that logging via statistics_exit() works for all pages. - * - * We subclass WebTestBase rather than StatisticsTestCase, because we - * want to test requests from an anonymous user. - */ -class StatisticsLoggingTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Statistics logging tests', - 'description' => 'Tests request logging for cached and uncached pages.', - 'group' => 'Statistics' - ); - } - - function setUp() { - parent::setUp(array('statistics', 'block')); - - // Create Basic page node type. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - } - - $this->auth_user = $this->drupalCreateUser(array('access content', 'create page content', 'edit own page content')); - - // Ensure we have a node page to access. - $this->node = $this->drupalCreateNode(array('title' => $this->randomName(255), 'uid' => $this->auth_user->uid)); - - // Enable page caching. - $config = config('system.performance'); - $config->set('cache', 1); - $config->save(); - - // Enable access logging. - variable_set('statistics_enable_access_log', 1); - variable_set('statistics_count_content_views', 1); - - // Clear the logs. - db_truncate('accesslog'); - db_truncate('node_counter'); - } - - /** - * Verifies request logging for cached and uncached pages. - */ - function testLogging() { - $path = 'node/' . $this->node->nid; - $expected = array( - 'title' => $this->node->title, - 'path' => $path, - ); - - // Verify logging of an uncached page. - $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', t('Testing an uncached page.')); - $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); - $this->assertTrue(is_array($log) && count($log) == 1, t('Page request was logged.')); - $this->assertEqual(array_intersect_key($log[0], $expected), $expected); - $node_counter = statistics_get($this->node->nid); - $this->assertIdentical($node_counter['totalcount'], '1'); - - // Verify logging of a cached page. - $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->assertIdentical($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', t('Testing a cached page.')); - $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); - $this->assertTrue(is_array($log) && count($log) == 2, t('Page request was logged.')); - $this->assertEqual(array_intersect_key($log[1], $expected), $expected); - $node_counter = statistics_get($this->node->nid); - $this->assertIdentical($node_counter['totalcount'], '2'); - - // Test logging from authenticated users - $this->drupalLogin($this->auth_user); - $this->drupalGet($path); - // Manually calling statistics.php, simulating ajax behavior. - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); - // Check the 6th item since login and account pages are also logged - $this->assertTrue(is_array($log) && count($log) == 6, t('Page request was logged.')); - $this->assertEqual(array_intersect_key($log[5], $expected), $expected); - $node_counter = statistics_get($this->node->nid); - $this->assertIdentical($node_counter['totalcount'], '3'); - - // Visit edit page to generate a title greater than 255. - $path = 'node/' . $this->node->nid . '/edit'; - $expected = array( - 'title' => truncate_utf8(t('Edit Basic page') . ' ' . $this->node->title, 255), - 'path' => $path, - ); - $this->drupalGet($path); - $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); - $this->assertTrue(is_array($log) && count($log) == 7, t('Page request was logged.')); - $this->assertEqual(array_intersect_key($log[6], $expected), $expected); - - // Create a path longer than 255 characters. Drupal's .htaccess file - // instructs Apache to test paths against the file system before routing to - // index.php. Many file systems restrict file names to 255 characters - // (http://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits), and - // Apache returns a 403 when testing longer file names, but the total path - // length is not restricted. - $long_path = $this->randomName(127) . '/' . $this->randomName(128); - - // Test that the long path is properly truncated when logged. - $this->drupalGet($long_path); - $log = db_query('SELECT * FROM {accesslog}')->fetchAll(PDO::FETCH_ASSOC); - $this->assertTrue(is_array($log) && count($log) == 8, 'Page request was logged for a path over 255 characters.'); - $this->assertEqual($log[7]['path'], truncate_utf8($long_path, 255)); - - } -} - -/** - * Tests that report pages render properly, and that access logging works. - */ -class StatisticsReportsTestCase extends StatisticsTestCase { - public static function getInfo() { - return array( - 'name' => 'Statistics reports tests', - 'description' => 'Tests display of statistics report pages and access logging.', - 'group' => 'Statistics' - ); - } - - /** - * Verifies that 'Recent hits' renders properly and displays the added hit. - */ - function testRecentHits() { - $this->drupalGet('admin/reports/hits'); - $this->assertText('test', t('Hit title found.')); - $this->assertText('node/1', t('Hit URL found.')); - $this->assertText('Anonymous', t('Hit user found.')); - } - - /** - * Verifies that 'Top pages' renders properly and displays the added hit. - */ - function testTopPages() { - $this->drupalGet('admin/reports/pages'); - $this->assertText('test', t('Hit title found.')); - $this->assertText('node/1', t('Hit URL found.')); - } - - /** - * Verifies that 'Top referrers' renders properly and displays the added hit. - */ - function testTopReferrers() { - $this->drupalGet('admin/reports/referrers'); - $this->assertText('http://example.com', t('Hit referrer found.')); - } - - /** - * Verifies that 'Details' page renders properly and displays the added hit. - */ - function testDetails() { - $this->drupalGet('admin/reports/access/1'); - $this->assertText('test', t('Hit title found.')); - $this->assertText('node/1', t('Hit URL found.')); - $this->assertText('Anonymous', t('Hit user found.')); - } - - /** - * Verifies that access logging is working and is reported correctly. - */ - function testAccessLogging() { - $this->drupalGet('admin/reports/referrers'); - $this->drupalGet('admin/reports/hits'); - $this->assertText('Top referrers in the past 3 days', t('Hit title found.')); - $this->assertText('admin/reports/referrers', t('Hit URL found.')); - } - - /** - * Tests the "popular content" block. - */ - function testPopularContentBlock() { - // Visit a node to have something show up in the block. - $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->blocking_user->uid)); - $this->drupalGet('node/' . $node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - - // Configure and save the block. - $block = block_load('statistics', 'popular'); - $block->theme = variable_get('theme_default', 'stark'); - $block->status = 1; - $block->pages = ''; - $block->region = 'sidebar_first'; - $block->cache = -1; - $block->visibility = 0; - $edit = array('statistics_block_top_day_num' => 3, 'statistics_block_top_all_num' => 3, 'statistics_block_top_last_num' => 3); - module_invoke('statistics', 'block_save', 'popular', $edit); - drupal_write_record('block', $block); - - // Get some page and check if the block is displayed. - $this->drupalGet('user'); - $this->assertText('Popular content', t('Found the popular content block.')); - $this->assertText("Today's", t('Found today\'s popular content.')); - $this->assertText('All time', t('Found the alll time popular content.')); - $this->assertText('Last viewed', t('Found the last viewed popular content.')); - - $this->assertRaw(l($node->title, 'node/' . $node->nid), t('Found link to visited node.')); - } -} - -/** - * Tests that the visitor blocking functionality works. - */ -class StatisticsBlockVisitorsTestCase extends StatisticsTestCase { - public static function getInfo() { - return array( - 'name' => 'Top visitor blocking', - 'description' => 'Tests blocking of IP addresses via the top visitors report.', - 'group' => 'Statistics' - ); - } - - /** - * Blocks an IP address via the top visitors report and then unblocks it. - */ - function testIPAddressBlocking() { - // IP address for testing. - $test_ip_address = '192.168.1.1'; - - // Verify the IP address from accesslog appears on the top visitors page - // and that a 'block IP address' link is displayed. - $this->drupalLogin($this->blocking_user); - $this->drupalGet('admin/reports/visitors'); - $this->assertText($test_ip_address, t('IP address found.')); - $this->assertText(t('block IP address'), t('Block IP link displayed')); - - // Block the IP address. - $this->clickLink('block IP address'); - $this->assertText(t('IP address blocking'), t('IP blocking page displayed.')); - $edit = array(); - $edit['ip'] = $test_ip_address; - $this->drupalPost('admin/config/people/ip-blocking', $edit, t('Add')); - $ip = db_query("SELECT iid from {blocked_ips} WHERE ip = :ip", array(':ip' => $edit['ip']))->fetchField(); - $this->assertNotEqual($ip, FALSE, t('IP address found in database')); - $this->assertRaw(t('The IP address %ip has been blocked.', array('%ip' => $edit['ip'])), t('IP address was blocked.')); - - // Verify that the block/unblock link on the top visitors page has been - // altered. - $this->drupalGet('admin/reports/visitors'); - $this->assertText(t('unblock IP address'), t('Unblock IP address link displayed')); - - // Unblock the IP address. - $this->clickLink('unblock IP address'); - $this->assertRaw(t('Are you sure you want to delete %ip?', array('%ip' => $test_ip_address)), t('IP address deletion confirmation found.')); - $edit = array(); - $this->drupalPost('admin/config/people/ip-blocking/delete/1', NULL, t('Delete')); - $this->assertRaw(t('The IP address %ip was deleted.', array('%ip' => $test_ip_address)), t('IP address deleted.')); - } -} - -/** - * Tests the statistics administration screen. - */ -class StatisticsAdminTestCase extends WebTestBase { - - /** - * A user that has permission to administer and access statistics. - * - * @var object|FALSE - * - * A fully loaded user object, or FALSE if user creation failed. - */ - protected $privileged_user; - - /** - * A page node for which to check access statistics. - * - * @var object - */ - protected $test_node; - - public static function getInfo() { - return array( - 'name' => 'Test statistics admin.', - 'description' => 'Tests the statistics admin.', - 'group' => 'Statistics' - ); - } - - function setUp() { - parent::setUp(array('node', 'statistics')); - - // Create Basic page node type. - if ($this->profile != 'standard') { - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - } - $this->privileged_user = $this->drupalCreateUser(array('access statistics', 'administer statistics', 'view post access counter', 'create page content')); - $this->drupalLogin($this->privileged_user); - $this->test_node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->privileged_user->uid)); - } - - /** - * Verifies that the statistics settings page works. - */ - function testStatisticsSettings() { - $this->assertFalse(variable_get('statistics_enable_access_log', 0), t('Access log is disabled by default.')); - $this->assertFalse(variable_get('statistics_count_content_views', 0), t('Count content view log is disabled by default.')); - - $this->drupalGet('admin/reports/pages'); - $this->assertRaw(t('No statistics available.'), t('Verifying text shown when no statistics is available.')); - - // Enable access log and counter on content view. - $edit['statistics_enable_access_log'] = 1; - $edit['statistics_count_content_views'] = 1; - $this->drupalPost('admin/config/system/statistics', $edit, t('Save configuration')); - $this->assertTrue(variable_get('statistics_enable_access_log'), t('Access log is enabled.')); - $this->assertTrue(variable_get('statistics_count_content_views'), t('Count content view log is enabled.')); - - // Hit the node. - $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - - $this->drupalGet('admin/reports/pages'); - $this->assertText('node/1', t('Test node found.')); - - // Hit the node again (the counter is incremented after the hit, so - // "1 view" will actually be shown when the node is hit the second time). - $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->assertText('1 view', t('Node is viewed once.')); - - $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->assertText('2 views', t('Node is viewed 2 times.')); - } - - /** - * Tests that when a node is deleted, the node counter is deleted too. - */ - function testDeleteNode() { - variable_set('statistics_count_content_views', 1); - - $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - - $result = db_select('node_counter', 'n') - ->fields('n', array('nid')) - ->condition('n.nid', $this->test_node->nid) - ->execute() - ->fetchAssoc(); - $this->assertEqual($result['nid'], $this->test_node->nid, 'Verifying that the node counter is incremented.'); - - node_delete($this->test_node->nid); - - $result = db_select('node_counter', 'n') - ->fields('n', array('nid')) - ->condition('n.nid', $this->test_node->nid) - ->execute() - ->fetchAssoc(); - $this->assertFalse($result, 'Verifying that the node counter is deleted.'); - } - - /** - * Tests that accesslog reflects when a user is deleted. - */ - function testDeleteUser() { - variable_set('statistics_enable_access_log', 1); - - variable_set('user_cancel_method', 'user_cancel_delete'); - $this->drupalLogout($this->privileged_user); - $account = $this->drupalCreateUser(array('access content', 'cancel account')); - $this->drupalLogin($account); - $this->drupalGet('node/' . $this->test_node->nid); - - $account = user_load($account->uid, TRUE); - - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - - $timestamp = time(); - $this->drupalPost(NULL, NULL, t('Cancel account')); - // Confirm account cancellation request. - $mails = $this->drupalGetMails(); - $mail = end($mails); - preg_match('@http.+?(user/\d+/cancel/confirm/\d+/[^\s]+)@', $mail['body'], $matches); - $path = $matches[1]; - $this->drupalGet($path); - $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); - - $this->drupalGet('admin/reports/visitors'); - $this->assertNoText($account->name, t('Did not find user in visitor statistics.')); - } - - /** - * Tests that cron clears day counts and expired access logs. - */ - function testExpiredLogs() { - variable_set('statistics_enable_access_log', 1); - variable_set('statistics_count_content_views', 1); - variable_set('statistics_day_timestamp', 8640000); - variable_set('statistics_flush_accesslog_timer', 1); - - $this->drupalGet('node/' . $this->test_node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $this->test_node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->drupalGet('node/' . $this->test_node->nid); - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $this->assertText('1 view', t('Node is viewed once.')); - - $this->drupalGet('admin/reports/pages'); - $this->assertText('node/' . $this->test_node->nid, t('Hit URL found.')); - - // statistics_cron will subtract the statistics_flush_accesslog_timer - // variable from REQUEST_TIME in the delete query, so wait two secs here to - // make sure the access log will be flushed for the node just hit. - sleep(2); - $this->cronRun(); - - $this->drupalGet('admin/reports/pages'); - $this->assertNoText('node/' . $this->test_node->nid, t('No hit URL found.')); - - $result = db_select('node_counter', 'nc') - ->fields('nc', array('daycount')) - ->condition('nid', $this->test_node->nid, '=') - ->execute() - ->fetchField(); - $this->assertFalse($result, t('Daycounter is zero.')); - } -} - -/** - * Tests statistics token replacement in strings. - */ -class StatisticsTokenReplaceTestCase extends StatisticsTestCase { - public static function getInfo() { - return array( - 'name' => 'Statistics token replacement', - 'description' => 'Generates text using placeholders for dummy content to check statistics token replacement.', - 'group' => 'Statistics', - ); - } - - /** - * Creates a node, then tests the statistics tokens generated from it. - */ - function testStatisticsTokenReplacement() { - global $language_interface; - - // Create user and node. - $user = $this->drupalCreateUser(array('create page content')); - $this->drupalLogin($user); - $node = $this->drupalCreateNode(array('type' => 'page', 'uid' => $user->uid)); - - // Hit the node. - $this->drupalGet('node/' . $node->nid); - // Manually calling statistics.php, simulating ajax behavior. - $nid = $node->nid; - $post = http_build_query(array('nid' => $nid)); - $headers = array('Content-Type' => 'application/x-www-form-urlencoded'); - global $base_url; - $stats_path = $base_url . '/' . drupal_get_path('module', 'statistics'). '/statistics.php'; - drupal_http_request($stats_path, array('method' => 'POST', 'data' => $post, 'headers' => $headers, 'timeout' => 10000)); - $statistics = statistics_get($node->nid); - - // Generate and test tokens. - $tests = array(); - $tests['[node:total-count]'] = 1; - $tests['[node:day-count]'] = 1; - $tests['[node:last-view]'] = format_date($statistics['timestamp']); - $tests['[node:last-view:short]'] = format_date($statistics['timestamp'], 'short'); - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('node' => $node), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Statistics token %token replaced.', array('%token' => $input))); - } - } -} diff --git a/core/modules/syslog/syslog.test b/core/modules/syslog/lib/Drupal/syslog/Tests/SyslogTest.php similarity index 90% rename from core/modules/syslog/syslog.test rename to core/modules/syslog/lib/Drupal/syslog/Tests/SyslogTest.php index 49da077d1ea67e393c1d57d993a20a97fcf520bd..dd719bcbf1cf829e853506ccf2ffa68fa4f71747 100644 --- a/core/modules/syslog/syslog.test +++ b/core/modules/syslog/lib/Drupal/syslog/Tests/SyslogTest.php @@ -2,15 +2,17 @@ /** * @file - * Tests for syslog.module. + * Definition of Drupal\syslog\Tests\SyslogTest. */ +namespace Drupal\syslog\Tests; + use Drupal\simpletest\WebTestBase; /** * Tests the Syslog module functionality. */ -class SyslogTestCase extends WebTestBase { +class SyslogTest extends WebTestBase { public static function getInfo() { return array( 'name' => 'Syslog functionality', diff --git a/core/modules/syslog/syslog.info b/core/modules/syslog/syslog.info index 4024bfd503528f84ed4df8bde15738dd0f9ca40b..e3f541fc7b3550c1e720009df1bbd94fb2217f3b 100644 --- a/core/modules/syslog/syslog.info +++ b/core/modules/syslog/syslog.info @@ -3,4 +3,3 @@ description = Logs and records system events to syslog. package = Core version = VERSION core = 8.x -files[] = syslog.test diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test index b50daad66006399f762844f831b3c047d28190e5..15286b6b880695451b459ecee5f0ce5e61173e5e 100644 --- a/core/modules/system/tests/common.test +++ b/core/modules/system/tests/common.test @@ -1180,6 +1180,40 @@ class CommonJavaScriptTestCase extends WebTestBase { $this->assertTrue(array_key_exists('http://example.com/script.js', $javascript), t('Added an external JavaScript file.')); } + /** + * Tests adding external JavaScript Files with the async attribute. + */ + function testAsyncAttribute() { + $default_query_string = variable_get('css_js_query_string', '0'); + + drupal_add_js('http://example.com/script.js', array('async' => TRUE)); + drupal_add_js('core/misc/collapse.js', array('async' => TRUE)); + $javascript = drupal_get_js(); + + $expected_1 = '<script type="text/javascript" src="http://example.com/script.js?' . $default_query_string . '" async="async"></script>'; + $expected_2 = '<script type="text/javascript" src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" async="async"></script>'; + + $this->assertTrue(strpos($javascript, $expected_1) > 0, t('Rendered external JavaScript with correct async attribute.')); + $this->assertTrue(strpos($javascript, $expected_2) > 0, t('Rendered internal JavaScript with correct async attribute.')); + } + + /** + * Tests adding external JavaScript Files with the defer attribute. + */ + function testDeferAttribute() { + $default_query_string = variable_get('css_js_query_string', '0'); + + drupal_add_js('http://example.com/script.js', array('defer' => TRUE)); + drupal_add_js('core/misc/collapse.js', array('defer' => TRUE)); + $javascript = drupal_get_js(); + + $expected_1 = '<script type="text/javascript" src="http://example.com/script.js?' . $default_query_string . '" defer="defer"></script>'; + $expected_2 = '<script type="text/javascript" src="' . file_create_url('core/misc/collapse.js') . '?' . $default_query_string . '" defer="defer"></script>'; + + $this->assertTrue(strpos($javascript, $expected_1) > 0, t('Rendered external JavaScript with correct defer attribute.')); + $this->assertTrue(strpos($javascript, $expected_2) > 0, t('Rendered internal JavaScript with correct defer attribute.')); + } + /** * Test drupal_get_js() for JavaScript settings. */ diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php new file mode 100644 index 0000000000000000000000000000000000000000..91141cd478f7bdbbad2ca3d90e2d8349602ce339 --- /dev/null +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateContribTest.php @@ -0,0 +1,373 @@ +<?php + +/** + * @file + * Definition of Drupal\update\Tests\UpdateContribTest. + */ + +namespace Drupal\update\Tests; + +class UpdateContribTest extends UpdateTestBase { + + public static function getInfo() { + return array( + 'name' => 'Update contrib functionality', + 'description' => 'Tests how the update module handles contributed modules and themes in a series of functional tests using mock XML data.', + 'group' => 'Update', + ); + } + + function setUp() { + parent::setUp('update_test', 'update', 'aaa_update_test', 'bbb_update_test', 'ccc_update_test'); + $admin_user = $this->drupalCreateUser(array('administer site configuration')); + $this->drupalLogin($admin_user); + } + + /** + * Tests when there is no available release data for a contrib module. + */ + function testNoReleasesAvailable() { + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'aaa_update_test' => array( + 'project' => 'aaa_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + $this->refreshUpdateStatus(array('drupal' => '0', 'aaa_update_test' => 'no-releases')); + $this->drupalGet('admin/reports/updates'); + // Cannot use $this->standardTests() because we need to check for the + // 'No available releases found' string. + $this->assertRaw('<h3>' . t('Drupal core') . '</h3>'); + $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal')); + $this->assertText(t('Up to date')); + $this->assertRaw('<h3>' . t('Modules') . '</h3>'); + $this->assertNoText(t('Update available')); + $this->assertText(t('No available releases found')); + $this->assertNoRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test')); + } + + /** + * Test the basic functionality of a contrib module on the status report. + */ + function testUpdateContribBasic() { + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'aaa_update_test' => array( + 'project' => 'aaa_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + $this->refreshUpdateStatus( + array( + 'drupal' => '0', + 'aaa_update_test' => '1_0', + ) + ); + $this->standardTests(); + $this->assertText(t('Up to date')); + $this->assertRaw('<h3>' . t('Modules') . '</h3>'); + $this->assertNoText(t('Update available')); + $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); + } + + /** + * Test that contrib projects are ordered by project name. + * + * If a project contains multiple modules, we want to make sure that the + * available updates report is sorted by the parent project names, not by + * the names of the modules included in each project. In this test case, we + * have 2 contrib projects, "BBB Update test" and "CCC Update test". + * However, we have a module called "aaa_update_test" that's part of the + * "CCC Update test" project. We need to make sure that we see the "BBB" + * project before the "CCC" project, even though "CCC" includes a module + * that's processed first if you sort alphabetically by module name (which + * is the order we see things inside system_rebuild_module_data() for example). + */ + function testUpdateContribOrder() { + // We want core to be version 7.0. + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + // All the rest should be visible as contrib modules at version 8.x-1.0. + + // aaa_update_test needs to be part of the "CCC Update test" project, + // which would throw off the report if we weren't properly sorting by + // the project names. + 'aaa_update_test' => array( + 'project' => 'ccc_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + + // This should be its own project, and listed first on the report. + 'bbb_update_test' => array( + 'project' => 'bbb_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + + // This will contain both aaa_update_test and ccc_update_test, and + // should come after the bbb_update_test project. + 'ccc_update_test' => array( + 'project' => 'ccc_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + $this->refreshUpdateStatus(array('drupal' => '0', '#all' => '1_0')); + $this->standardTests(); + // We're expecting the report to say all projects are up to date. + $this->assertText(t('Up to date')); + $this->assertNoText(t('Update available')); + // We want to see all 3 module names listed, since they'll show up either + // as project names or as modules under the "Includes" listing. + $this->assertText(t('AAA Update test')); + $this->assertText(t('BBB Update test')); + $this->assertText(t('CCC Update test')); + // We want aaa_update_test included in the ccc_update_test project, not as + // its own project on the report. + $this->assertNoRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project does not appear.')); + // The other two should be listed as projects. + $this->assertRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), t('Link to bbb_update_test project appears.')); + $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), t('Link to bbb_update_test project appears.')); + + // We want to make sure we see the BBB project before the CCC project. + // Instead of just searching for 'BBB Update test' or something, we want + // to use the full markup that starts the project entry itself, so that + // we're really testing that the project listings are in the right order. + $bbb_project_link = '<div class="project"><a href="http://example.com/project/bbb_update_test">BBB Update test</a>'; + $ccc_project_link = '<div class="project"><a href="http://example.com/project/ccc_update_test">CCC Update test</a>'; + $this->assertTrue(strpos($this->drupalGetContent(), $bbb_project_link) < strpos($this->drupalGetContent(), $ccc_project_link), "'BBB Update test' project is listed before the 'CCC Update test' project"); + } + + /** + * Test that subthemes are notified about security updates for base themes. + */ + function testUpdateBaseThemeSecurityUpdate() { + // Only enable the subtheme, not the base theme. + db_update('system') + ->fields(array('status' => 1)) + ->condition('type', 'theme') + ->condition('name', 'update_test_subtheme') + ->execute(); + + // Define the initial state for core and the subtheme. + $system_info = array( + // We want core to be version 7.0. + '#all' => array( + 'version' => '7.0', + ), + // Show the update_test_basetheme + 'update_test_basetheme' => array( + 'project' => 'update_test_basetheme', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + // Show the update_test_subtheme + 'update_test_subtheme' => array( + 'project' => 'update_test_subtheme', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + $xml_mapping = array( + 'drupal' => '0', + 'update_test_subtheme' => '1_0', + 'update_test_basetheme' => '1_1-sec', + ); + $this->refreshUpdateStatus($xml_mapping); + $this->assertText(t('Security update required!')); + $this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), t('Link to the Update test base theme project appears.')); + } + + /** + * Test that disabled themes are only shown when desired. + */ + function testUpdateShowDisabledThemes() { + // Make sure all the update_test_* themes are disabled. + db_update('system') + ->fields(array('status' => 0)) + ->condition('type', 'theme') + ->condition('name', 'update_test_%', 'LIKE') + ->execute(); + + // Define the initial state for core and the test contrib themes. + $system_info = array( + // We want core to be version 7.0. + '#all' => array( + 'version' => '7.0', + ), + // The update_test_basetheme should be visible and up to date. + 'update_test_basetheme' => array( + 'project' => 'update_test_basetheme', + 'version' => '8.x-1.1', + 'hidden' => FALSE, + ), + // The update_test_subtheme should be visible and up to date. + 'update_test_subtheme' => array( + 'project' => 'update_test_subtheme', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + // When there are contributed modules in the site's file system, the + // total number of attempts made in the test may exceed the default value + // of update_max_fetch_attempts. Therefore this variable is set very high + // to avoid test failures in those cases. + variable_set('update_max_fetch_attempts', 99999); + variable_set('update_test_system_info', $system_info); + $xml_mapping = array( + 'drupal' => '0', + 'update_test_subtheme' => '1_0', + 'update_test_basetheme' => '1_1-sec', + ); + $base_theme_project_link = l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'); + $sub_theme_project_link = l(t('Update test subtheme'), 'http://example.com/project/update_test_subtheme'); + foreach (array(TRUE, FALSE) as $check_disabled) { + variable_set('update_check_disabled', $check_disabled); + $this->refreshUpdateStatus($xml_mapping); + // In neither case should we see the "Themes" heading for enabled themes. + $this->assertNoText(t('Themes')); + if ($check_disabled) { + $this->assertText(t('Disabled themes')); + $this->assertRaw($base_theme_project_link, t('Link to the Update test base theme project appears.')); + $this->assertRaw($sub_theme_project_link, t('Link to the Update test subtheme project appears.')); + } + else { + $this->assertNoText(t('Disabled themes')); + $this->assertNoRaw($base_theme_project_link, t('Link to the Update test base theme project does not appear.')); + $this->assertNoRaw($sub_theme_project_link, t('Link to the Update test subtheme project does not appear.')); + } + } + } + + /** + * Make sure that if we fetch from a broken URL, sane things happen. + */ + function testUpdateBrokenFetchURL() { + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'aaa_update_test' => array( + 'project' => 'aaa_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + 'bbb_update_test' => array( + 'project' => 'bbb_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + 'ccc_update_test' => array( + 'project' => 'ccc_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + + $xml_mapping = array( + 'drupal' => '0', + 'aaa_update_test' => '1_0', + 'bbb_update_test' => 'does-not-exist', + 'ccc_update_test' => '1_0', + ); + $this->refreshUpdateStatus($xml_mapping); + + $this->assertText(t('Up to date')); + // We're expecting the report to say most projects are up to date, so we + // hope that 'Up to date' is not unique. + $this->assertNoUniqueText(t('Up to date')); + // It should say we failed to get data, not that we're missing an update. + $this->assertNoText(t('Update available')); + + // We need to check that this string is found as part of a project row, + // not just in the "Failed to get available update data for ..." message + // at the top of the page. + $this->assertRaw('<div class="version-status">' . t('Failed to get available update data')); + + // We should see the output messages from fetching manually. + $this->assertUniqueText(t('Checked available update data for 3 projects.')); + $this->assertUniqueText(t('Failed to get available update data for one project.')); + + // The other two should be listed as projects. + $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); + $this->assertNoRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), t('Link to bbb_update_test project does not appear.')); + $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), t('Link to bbb_update_test project appears.')); + } + + /** + * Check that hook_update_status_alter() works to change a status. + * + * We provide the same external data as if aaa_update_test 8.x-1.0 were + * installed and that was the latest release. Then we use + * hook_update_status_alter() to try to mark this as missing a security + * update, then assert if we see the appropriate warnings on the right + * pages. + */ + function testHookUpdateStatusAlter() { + variable_set('allow_authorize_operations', TRUE); + $update_admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer software updates')); + $this->drupalLogin($update_admin_user); + + $system_info = array( + '#all' => array( + 'version' => '7.0', + ), + 'aaa_update_test' => array( + 'project' => 'aaa_update_test', + 'version' => '8.x-1.0', + 'hidden' => FALSE, + ), + ); + variable_set('update_test_system_info', $system_info); + $update_status = array( + 'aaa_update_test' => array( + 'status' => UPDATE_NOT_SECURE, + ), + ); + variable_set('update_test_update_status', $update_status); + $this->refreshUpdateStatus( + array( + 'drupal' => '0', + 'aaa_update_test' => '1_0', + ) + ); + $this->drupalGet('admin/reports/updates'); + $this->assertRaw('<h3>' . t('Modules') . '</h3>'); + $this->assertText(t('Security update required!')); + $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); + + // Visit the reports page again without the altering and make sure the + // status is back to normal. + variable_set('update_test_update_status', array()); + $this->drupalGet('admin/reports/updates'); + $this->assertRaw('<h3>' . t('Modules') . '</h3>'); + $this->assertNoText(t('Security update required!')); + $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); + + // Turn the altering back on and visit the Update manager UI. + variable_set('update_test_update_status', $update_status); + $this->drupalGet('admin/modules/update'); + $this->assertText(t('Security update')); + + // Turn the altering back off and visit the Update manager UI. + variable_set('update_test_update_status', array()); + $this->drupalGet('admin/modules/update'); + $this->assertNoText(t('Security update')); + } + +} diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f4b9989dbc94ac21c50c1db4e6c2b07e5622df9c --- /dev/null +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreTest.php @@ -0,0 +1,218 @@ +<?php + +/** + * @file + * Definition of Drupal\update\Tests\UpdateCoreTest. + */ + +namespace Drupal\update\Tests; + +class UpdateCoreTest extends UpdateTestBase { + + public static function getInfo() { + return array( + 'name' => 'Update core functionality', + 'description' => 'Tests the update module through a series of functional tests using mock XML data.', + 'group' => 'Update', + ); + } + + function setUp() { + parent::setUp('update_test', 'update'); + $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer modules')); + $this->drupalLogin($admin_user); + } + + /** + * Tests the update module when no updates are available. + */ + function testNoUpdatesAvailable() { + $this->setSystemInfo7_0(); + $this->refreshUpdateStatus(array('drupal' => '0')); + $this->standardTests(); + $this->assertText(t('Up to date')); + $this->assertNoText(t('Update available')); + $this->assertNoText(t('Security update required!')); + } + + /** + * Tests the update module when one normal update ("7.1") is available. + */ + function testNormalUpdateAvailable() { + $this->setSystemInfo7_0(); + $this->refreshUpdateStatus(array('drupal' => '1')); + $this->standardTests(); + $this->assertNoText(t('Up to date')); + $this->assertText(t('Update available')); + $this->assertNoText(t('Security update required!')); + $this->assertRaw(l('7.1', 'http://example.com/drupal-7-1-release'), t('Link to release appears.')); + $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-1.tar.gz'), t('Link to download appears.')); + $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-1-release'), t('Link to release notes appears.')); + } + + /** + * Tests the update module when a security update ("7.2") is available. + */ + function testSecurityUpdateAvailable() { + $this->setSystemInfo7_0(); + $this->refreshUpdateStatus(array('drupal' => '2-sec')); + $this->standardTests(); + $this->assertNoText(t('Up to date')); + $this->assertNoText(t('Update available')); + $this->assertText(t('Security update required!')); + $this->assertRaw(l('7.2', 'http://example.com/drupal-7-2-release'), t('Link to release appears.')); + $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-2.tar.gz'), t('Link to download appears.')); + $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-2-release'), t('Link to release notes appears.')); + } + + /** + * Ensure proper results where there are date mismatches among modules. + */ + function testDatestampMismatch() { + $system_info = array( + '#all' => array( + // We need to think we're running a -dev snapshot to see dates. + 'version' => '7.0-dev', + 'datestamp' => time(), + ), + 'block' => array( + // This is 2001-09-09 01:46:40 GMT, so test for "2001-Sep-". + 'datestamp' => '1000000000', + ), + ); + variable_set('update_test_system_info', $system_info); + $this->refreshUpdateStatus(array('drupal' => 'dev')); + $this->assertNoText(t('2001-Sep-')); + $this->assertText(t('Up to date')); + $this->assertNoText(t('Update available')); + $this->assertNoText(t('Security update required!')); + } + + /** + * Check that running cron updates the list of available updates. + */ + function testModulePageRunCron() { + $this->setSystemInfo7_0(); + variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); + variable_set('update_test_xml_map', array('drupal' => '0')); + + $this->cronRun(); + $this->drupalGet('admin/modules'); + $this->assertNoText(t('No update information available.')); + } + + /** + * Check the messages at admin/modules when the site is up to date. + */ + function testModulePageUpToDate() { + $this->setSystemInfo7_0(); + // Instead of using refreshUpdateStatus(), set these manually. + variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); + variable_set('update_test_xml_map', array('drupal' => '0')); + + $this->drupalGet('admin/reports/updates'); + $this->clickLink(t('Check manually')); + $this->assertText(t('Checked available update data for one project.')); + $this->drupalGet('admin/modules'); + $this->assertNoText(t('There are updates available for your version of Drupal.')); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + } + + /** + * Check the messages at admin/modules when missing an update. + */ + function testModulePageRegularUpdate() { + $this->setSystemInfo7_0(); + // Instead of using refreshUpdateStatus(), set these manually. + variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); + variable_set('update_test_xml_map', array('drupal' => '1')); + + $this->drupalGet('admin/reports/updates'); + $this->clickLink(t('Check manually')); + $this->assertText(t('Checked available update data for one project.')); + $this->drupalGet('admin/modules'); + $this->assertText(t('There are updates available for your version of Drupal.')); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + } + + /** + * Check the messages at admin/modules when missing a security update. + */ + function testModulePageSecurityUpdate() { + $this->setSystemInfo7_0(); + // Instead of using refreshUpdateStatus(), set these manually. + variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); + variable_set('update_test_xml_map', array('drupal' => '2-sec')); + + $this->drupalGet('admin/reports/updates'); + $this->clickLink(t('Check manually')); + $this->assertText(t('Checked available update data for one project.')); + $this->drupalGet('admin/modules'); + $this->assertNoText(t('There are updates available for your version of Drupal.')); + $this->assertText(t('There is a security update available for your version of Drupal.')); + + // Make sure admin/appearance warns you you're missing a security update. + $this->drupalGet('admin/appearance'); + $this->assertNoText(t('There are updates available for your version of Drupal.')); + $this->assertText(t('There is a security update available for your version of Drupal.')); + + // Make sure duplicate messages don't appear on Update status pages. + $this->drupalGet('admin/reports/status'); + // We're expecting "There is a security update..." inside the status report + // itself, but the drupal_set_message() appears as an li so we can prefix + // with that and search for the raw HTML. + $this->assertNoRaw('<li>' . t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/reports/updates'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/reports/updates/settings'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + } + + /** + * Tests the update module when the update server returns 503 (Service unavailable) errors. + */ + function testServiceUnavailable() { + $this->refreshUpdateStatus(array(), '503-error'); + // Ensure that no "Warning: SimpleXMLElement..." parse errors are found. + $this->assertNoText('SimpleXMLElement'); + $this->assertUniqueText(t('Failed to get available update data for one project.')); + } + + /** + * Tests that exactly one fetch task per project is created and not more. + */ + function testFetchTasks() { + $projecta = array( + 'name' => 'aaa_update_test', + ); + $projectb = array( + 'name' => 'bbb_update_test', + ); + $queue = queue('update_fetch_tasks'); + $this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty'); + update_create_fetch_task($projecta); + $this->assertEqual($queue->numberOfItems(), 1, 'Queue contains one item'); + update_create_fetch_task($projectb); + $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); + // Try to add project a again. + update_create_fetch_task($projecta); + $this->assertEqual($queue->numberOfItems(), 2, 'Queue still contains two items'); + + // Clear cache and try again. + _update_cache_clear(); + drupal_static_reset('_update_create_fetch_task'); + update_create_fetch_task($projecta); + $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); + } + + protected function setSystemInfo7_0() { + $setting = array( + '#all' => array( + 'version' => '7.0', + ), + ); + variable_set('update_test_system_info', $setting); + } +} diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4648526514633df6eea10ff9a738715293d03f3a --- /dev/null +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateCoreUnitTest.php @@ -0,0 +1,67 @@ +<?php + +/** + * @file + * Definition of Drupal\update\Tests\UpdateCoreUnitTest. + */ + +namespace Drupal\update\Tests; + +use Drupal\simpletest\UnitTestBase; + +class UpdateCoreUnitTest extends UnitTestBase { + + public static function getInfo() { + return array( + 'name' => "Unit tests", + 'description' => 'Test update funcionality unrelated to the database.', + 'group' => 'Update', + ); + } + + function setUp() { + parent::setUp('update'); + module_load_include('inc', 'update', 'update.fetch'); + } + + /** + * Tests _update_build_fetch_url according to issue 1481156 + */ + function testUpdateBuildFetchUrl() { + //first test that we didn't break the trivial case + $project['name'] = 'update_test'; + $project['project_type'] = ''; + $project['info']['version'] = ''; + $project['info']['project status url'] = 'http://www.example.com'; + $project['includes'] = array('module1' => 'Module 1', 'module2' => 'Module 2'); + $site_key = ''; + $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; + $url = _update_build_fetch_url($project, $site_key); + $this->assertEqual($url, $expected, "'$url' when no site_key provided should be '$expected'."); + + //For disabled projects it shouldn't add the site key either. + $site_key = 'site_key'; + $project['project_type'] = 'disabled'; + $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; + $url = _update_build_fetch_url($project, $site_key); + $this->assertEqual($url, $expected, "'$url' should be '$expected' for disabled projects."); + + //for enabled projects, adding the site key + $project['project_type'] = ''; + $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; + $expected .= '?site_key=site_key'; + $expected .= '&list=' . rawurlencode('module1,module2'); + $url = _update_build_fetch_url($project, $site_key); + $this->assertEqual($url, $expected, "When site_key provided, '$url' should be '$expected'."); + + // http://drupal.org/node/1481156 test incorrect logic when url contains + // a question mark. + $project['info']['project status url'] = 'http://www.example.com/?project='; + $expected = 'http://www.example.com/?project=/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; + $expected .= '&site_key=site_key'; + $expected .= '&list=' . rawurlencode('module1,module2'); + $url = _update_build_fetch_url($project, $site_key); + $this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'."); + + } +} diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php b/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php new file mode 100644 index 0000000000000000000000000000000000000000..7399d3c52acd9c9c6a636037e6ca316d457bd8d2 --- /dev/null +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateTestBase.php @@ -0,0 +1,56 @@ +<?php + +/** + * @file + * Definition of Drupal\update\Tests\UpdateTestBase. + * + * This file contains tests for the update module. The overarching methodology + * of these tests is we need to compare a given state of installed modules and + * themes (e.g. version, project grouping, timestamps, etc) vs. a current + * state of what the release history XML files we fetch say is available. We + * have dummy XML files (in the 'tests' subdirectory) that describe various + * scenarios of what's available for different test projects, and we have + * dummy .info file data (specified via hook_system_info_alter() in the + * update_test helper module) describing what's currently installed. Each + * test case defines a set of projects to install, their current state (via + * the 'update_test_system_info' variable) and the desired available update + * data (via the 'update_test_xml_map' variable), and then performs a series + * of assertions that the report matches our expectations given the specific + * initial state and availability scenario. + */ + +namespace Drupal\update\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Base class to define some shared functions used by all update tests. + */ +class UpdateTestBase extends WebTestBase { + /** + * Refresh the update status based on the desired available update scenario. + * + * @param $xml_map + * Array that maps project names to availability scenarios to fetch. + * The key '#all' is used if a project-specific mapping is not defined. + * + * @see update_test_mock_page() + */ + protected function refreshUpdateStatus($xml_map, $url = 'update-test') { + // Tell update module to fetch from the URL provided by update_test module. + variable_set('update_fetch_url', url($url, array('absolute' => TRUE))); + // Save the map for update_test_mock_page() to use. + variable_set('update_test_xml_map', $xml_map); + // Manually check the update status. + $this->drupalGet('admin/reports/updates/check'); + } + + /** + * Run a series of assertions that are applicable for all update statuses. + */ + protected function standardTests() { + $this->assertRaw('<h3>' . t('Drupal core') . '</h3>'); + $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal'), t('Link to the Drupal project appears.')); + $this->assertNoText(t('No available releases found')); + } +} diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fae57db0b80e70a274ccf3ae0b411cff127a9134 --- /dev/null +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php @@ -0,0 +1,104 @@ +<?php + +/** + * @file + * Definition of Drupal\update\Tests\UpdateUploadTest. + */ + +namespace Drupal\update\Tests; + +class UpdateUploadTest extends UpdateTestBase { + public static function getInfo() { + return array( + 'name' => 'Upload and extract module functionality', + 'description' => 'Tests the update module\'s upload and extraction functionality.', + 'group' => 'Update', + ); + } + + public function setUp() { + parent::setUp('update', 'update_test'); + variable_set('allow_authorize_operations', TRUE); + $admin_user = $this->drupalCreateUser(array('administer software updates', 'administer site configuration')); + $this->drupalLogin($admin_user); + } + + /** + * Tests upload and extraction of a module. + */ + public function testUploadModule() { + // Images are not valid archives, so get one and try to install it. We + // need an extra variable to store the result of drupalGetTestFiles() + // since reset() takes an argument by reference and passing in a constant + // emits a notice in strict mode. + $imageTestFiles = $this->drupalGetTestFiles('image'); + $invalidArchiveFile = reset($imageTestFiles); + $edit = array( + 'files[project_upload]' => $invalidArchiveFile->uri, + ); + // This also checks that the correct archive extensions are allowed. + $this->drupalPost('admin/modules/install', $edit, t('Install')); + $this->assertText(t('Only files with the following extensions are allowed: @archive_extensions.', array('@archive_extensions' => archiver_get_extensions())),'Only valid archives can be uploaded.'); + + // Check to ensure an existing module can't be reinstalled. Also checks that + // the archive was extracted since we can't know if the module is already + // installed until after extraction. + $validArchiveFile = drupal_get_path('module', 'update') . '/tests/aaa_update_test.tar.gz'; + $edit = array( + 'files[project_upload]' => $validArchiveFile, + ); + $this->drupalPost('admin/modules/install', $edit, t('Install')); + $this->assertText(t('@module_name is already installed.', array('@module_name' => 'AAA Update test')), 'Existing module was extracted and not reinstalled.'); + } + + /** + * Ensure that archiver extensions are properly merged in the UI. + */ + function testFileNameExtensionMerging() { + $this->drupalGet('admin/modules/install'); + // Make sure the bogus extension supported by update_test.module is there. + $this->assertPattern('/file extensions are supported:.*update-test-extension/', t("Found 'update-test-extension' extension")); + // Make sure it didn't clobber the first option from core. + $this->assertPattern('/file extensions are supported:.*tar/', t("Found 'tar' extension")); + } + + /** + * Check the messages on Update manager pages when missing a security update. + */ + function testUpdateManagerCoreSecurityUpdateMessages() { + $setting = array( + '#all' => array( + 'version' => '7.0', + ), + ); + variable_set('update_test_system_info', $setting); + variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); + variable_set('update_test_xml_map', array('drupal' => '2-sec')); + // Initialize the update status. + $this->drupalGet('admin/reports/updates'); + + // Now, make sure none of the Update manager pages have duplicate messages + // about core missing a security update. + + $this->drupalGet('admin/modules/install'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/modules/update'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/appearance/install'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/appearance/update'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/reports/updates/install'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/reports/updates/update'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + + $this->drupalGet('admin/update/ready'); + $this->assertNoText(t('There is a security update available for your version of Drupal.')); + } +} diff --git a/core/modules/update/update.info b/core/modules/update/update.info index cdad3003e7b0b0768bb560704e9abbc3ba9872dd..0dfda0a5c41ef36c316a568ab417e3dfd4f1b35b 100644 --- a/core/modules/update/update.info +++ b/core/modules/update/update.info @@ -3,5 +3,4 @@ description = Checks for available updates, and can securely install or update m version = VERSION package = Core core = 8.x -files[] = update.test configure = admin/reports/updates/settings diff --git a/core/modules/update/update.test b/core/modules/update/update.test deleted file mode 100644 index 0e100576ac4de01493e00433be1684d11a968218..0000000000000000000000000000000000000000 --- a/core/modules/update/update.test +++ /dev/null @@ -1,786 +0,0 @@ -<?php - -/** - * @file - * Tests for update.module. - * - * This file contains tests for the update module. The overarching methodology - * of these tests is we need to compare a given state of installed modules and - * themes (e.g. version, project grouping, timestamps, etc) vs. a current - * state of what the release history XML files we fetch say is available. We - * have dummy XML files (in the 'tests' subdirectory) that describe various - * scenarios of what's available for different test projects, and we have - * dummy .info file data (specified via hook_system_info_alter() in the - * update_test helper module) describing what's currently installed. Each - * test case defines a set of projects to install, their current state (via - * the 'update_test_system_info' variable) and the desired available update - * data (via the 'update_test_xml_map' variable), and then performs a series - * of assertions that the report matches our expectations given the specific - * initial state and availability scenario. - */ - -use Drupal\simpletest\WebTestBase; -use Drupal\simpletest\UnitTestBase; - -/** - * Base class to define some shared functions used by all update tests. - */ -class UpdateTestHelper extends WebTestBase { - /** - * Refresh the update status based on the desired available update scenario. - * - * @param $xml_map - * Array that maps project names to availability scenarios to fetch. - * The key '#all' is used if a project-specific mapping is not defined. - * - * @see update_test_mock_page() - */ - protected function refreshUpdateStatus($xml_map, $url = 'update-test') { - // Tell update module to fetch from the URL provided by update_test module. - variable_set('update_fetch_url', url($url, array('absolute' => TRUE))); - // Save the map for update_test_mock_page() to use. - variable_set('update_test_xml_map', $xml_map); - // Manually check the update status. - $this->drupalGet('admin/reports/updates/check'); - } - - /** - * Run a series of assertions that are applicable for all update statuses. - */ - protected function standardTests() { - $this->assertRaw('<h3>' . t('Drupal core') . '</h3>'); - $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal'), t('Link to the Drupal project appears.')); - $this->assertNoText(t('No available releases found')); - } - -} - -class UpdateCoreTestCase extends UpdateTestHelper { - - public static function getInfo() { - return array( - 'name' => 'Update core functionality', - 'description' => 'Tests the update module through a series of functional tests using mock XML data.', - 'group' => 'Update', - ); - } - - function setUp() { - parent::setUp('update_test', 'update'); - $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer modules')); - $this->drupalLogin($admin_user); - } - - /** - * Tests the update module when no updates are available. - */ - function testNoUpdatesAvailable() { - $this->setSystemInfo7_0(); - $this->refreshUpdateStatus(array('drupal' => '0')); - $this->standardTests(); - $this->assertText(t('Up to date')); - $this->assertNoText(t('Update available')); - $this->assertNoText(t('Security update required!')); - } - - /** - * Tests the update module when one normal update ("7.1") is available. - */ - function testNormalUpdateAvailable() { - $this->setSystemInfo7_0(); - $this->refreshUpdateStatus(array('drupal' => '1')); - $this->standardTests(); - $this->assertNoText(t('Up to date')); - $this->assertText(t('Update available')); - $this->assertNoText(t('Security update required!')); - $this->assertRaw(l('7.1', 'http://example.com/drupal-7-1-release'), t('Link to release appears.')); - $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-1.tar.gz'), t('Link to download appears.')); - $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-1-release'), t('Link to release notes appears.')); - } - - /** - * Tests the update module when a security update ("7.2") is available. - */ - function testSecurityUpdateAvailable() { - $this->setSystemInfo7_0(); - $this->refreshUpdateStatus(array('drupal' => '2-sec')); - $this->standardTests(); - $this->assertNoText(t('Up to date')); - $this->assertNoText(t('Update available')); - $this->assertText(t('Security update required!')); - $this->assertRaw(l('7.2', 'http://example.com/drupal-7-2-release'), t('Link to release appears.')); - $this->assertRaw(l(t('Download'), 'http://example.com/drupal-7-2.tar.gz'), t('Link to download appears.')); - $this->assertRaw(l(t('Release notes'), 'http://example.com/drupal-7-2-release'), t('Link to release notes appears.')); - } - - /** - * Ensure proper results where there are date mismatches among modules. - */ - function testDatestampMismatch() { - $system_info = array( - '#all' => array( - // We need to think we're running a -dev snapshot to see dates. - 'version' => '7.0-dev', - 'datestamp' => time(), - ), - 'block' => array( - // This is 2001-09-09 01:46:40 GMT, so test for "2001-Sep-". - 'datestamp' => '1000000000', - ), - ); - variable_set('update_test_system_info', $system_info); - $this->refreshUpdateStatus(array('drupal' => 'dev')); - $this->assertNoText(t('2001-Sep-')); - $this->assertText(t('Up to date')); - $this->assertNoText(t('Update available')); - $this->assertNoText(t('Security update required!')); - } - - /** - * Check that running cron updates the list of available updates. - */ - function testModulePageRunCron() { - $this->setSystemInfo7_0(); - variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); - variable_set('update_test_xml_map', array('drupal' => '0')); - - $this->cronRun(); - $this->drupalGet('admin/modules'); - $this->assertNoText(t('No update information available.')); - } - - /** - * Check the messages at admin/modules when the site is up to date. - */ - function testModulePageUpToDate() { - $this->setSystemInfo7_0(); - // Instead of using refreshUpdateStatus(), set these manually. - variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); - variable_set('update_test_xml_map', array('drupal' => '0')); - - $this->drupalGet('admin/reports/updates'); - $this->clickLink(t('Check manually')); - $this->assertText(t('Checked available update data for one project.')); - $this->drupalGet('admin/modules'); - $this->assertNoText(t('There are updates available for your version of Drupal.')); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - } - - /** - * Check the messages at admin/modules when missing an update. - */ - function testModulePageRegularUpdate() { - $this->setSystemInfo7_0(); - // Instead of using refreshUpdateStatus(), set these manually. - variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); - variable_set('update_test_xml_map', array('drupal' => '1')); - - $this->drupalGet('admin/reports/updates'); - $this->clickLink(t('Check manually')); - $this->assertText(t('Checked available update data for one project.')); - $this->drupalGet('admin/modules'); - $this->assertText(t('There are updates available for your version of Drupal.')); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - } - - /** - * Check the messages at admin/modules when missing a security update. - */ - function testModulePageSecurityUpdate() { - $this->setSystemInfo7_0(); - // Instead of using refreshUpdateStatus(), set these manually. - variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); - variable_set('update_test_xml_map', array('drupal' => '2-sec')); - - $this->drupalGet('admin/reports/updates'); - $this->clickLink(t('Check manually')); - $this->assertText(t('Checked available update data for one project.')); - $this->drupalGet('admin/modules'); - $this->assertNoText(t('There are updates available for your version of Drupal.')); - $this->assertText(t('There is a security update available for your version of Drupal.')); - - // Make sure admin/appearance warns you you're missing a security update. - $this->drupalGet('admin/appearance'); - $this->assertNoText(t('There are updates available for your version of Drupal.')); - $this->assertText(t('There is a security update available for your version of Drupal.')); - - // Make sure duplicate messages don't appear on Update status pages. - $this->drupalGet('admin/reports/status'); - // We're expecting "There is a security update..." inside the status report - // itself, but the drupal_set_message() appears as an li so we can prefix - // with that and search for the raw HTML. - $this->assertNoRaw('<li>' . t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/reports/updates'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/reports/updates/settings'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - } - - /** - * Tests the update module when the update server returns 503 (Service unavailable) errors. - */ - function testServiceUnavailable() { - $this->refreshUpdateStatus(array(), '503-error'); - // Ensure that no "Warning: SimpleXMLElement..." parse errors are found. - $this->assertNoText('SimpleXMLElement'); - $this->assertUniqueText(t('Failed to get available update data for one project.')); - } - - /** - * Tests that exactly one fetch task per project is created and not more. - */ - function testFetchTasks() { - $projecta = array( - 'name' => 'aaa_update_test', - ); - $projectb = array( - 'name' => 'bbb_update_test', - ); - $queue = queue('update_fetch_tasks'); - $this->assertEqual($queue->numberOfItems(), 0, 'Queue is empty'); - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 1, 'Queue contains one item'); - update_create_fetch_task($projectb); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); - // Try to add project a again. - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue still contains two items'); - - // Clear cache and try again. - _update_cache_clear(); - drupal_static_reset('_update_create_fetch_task'); - update_create_fetch_task($projecta); - $this->assertEqual($queue->numberOfItems(), 2, 'Queue contains two items'); - } - - protected function setSystemInfo7_0() { - $setting = array( - '#all' => array( - 'version' => '7.0', - ), - ); - variable_set('update_test_system_info', $setting); - } - -} - -class UpdateTestContribCase extends UpdateTestHelper { - - public static function getInfo() { - return array( - 'name' => 'Update contrib functionality', - 'description' => 'Tests how the update module handles contributed modules and themes in a series of functional tests using mock XML data.', - 'group' => 'Update', - ); - } - - function setUp() { - parent::setUp('update_test', 'update', 'aaa_update_test', 'bbb_update_test', 'ccc_update_test'); - $admin_user = $this->drupalCreateUser(array('administer site configuration')); - $this->drupalLogin($admin_user); - } - - /** - * Tests when there is no available release data for a contrib module. - */ - function testNoReleasesAvailable() { - $system_info = array( - '#all' => array( - 'version' => '7.0', - ), - 'aaa_update_test' => array( - 'project' => 'aaa_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - $this->refreshUpdateStatus(array('drupal' => '0', 'aaa_update_test' => 'no-releases')); - $this->drupalGet('admin/reports/updates'); - // Cannot use $this->standardTests() because we need to check for the - // 'No available releases found' string. - $this->assertRaw('<h3>' . t('Drupal core') . '</h3>'); - $this->assertRaw(l(t('Drupal'), 'http://example.com/project/drupal')); - $this->assertText(t('Up to date')); - $this->assertRaw('<h3>' . t('Modules') . '</h3>'); - $this->assertNoText(t('Update available')); - $this->assertText(t('No available releases found')); - $this->assertNoRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test')); - } - - /** - * Test the basic functionality of a contrib module on the status report. - */ - function testUpdateContribBasic() { - $system_info = array( - '#all' => array( - 'version' => '7.0', - ), - 'aaa_update_test' => array( - 'project' => 'aaa_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - $this->refreshUpdateStatus( - array( - 'drupal' => '0', - 'aaa_update_test' => '1_0', - ) - ); - $this->standardTests(); - $this->assertText(t('Up to date')); - $this->assertRaw('<h3>' . t('Modules') . '</h3>'); - $this->assertNoText(t('Update available')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); - } - - /** - * Test that contrib projects are ordered by project name. - * - * If a project contains multiple modules, we want to make sure that the - * available updates report is sorted by the parent project names, not by - * the names of the modules included in each project. In this test case, we - * have 2 contrib projects, "BBB Update test" and "CCC Update test". - * However, we have a module called "aaa_update_test" that's part of the - * "CCC Update test" project. We need to make sure that we see the "BBB" - * project before the "CCC" project, even though "CCC" includes a module - * that's processed first if you sort alphabetically by module name (which - * is the order we see things inside system_rebuild_module_data() for example). - */ - function testUpdateContribOrder() { - // We want core to be version 7.0. - $system_info = array( - '#all' => array( - 'version' => '7.0', - ), - // All the rest should be visible as contrib modules at version 8.x-1.0. - - // aaa_update_test needs to be part of the "CCC Update test" project, - // which would throw off the report if we weren't properly sorting by - // the project names. - 'aaa_update_test' => array( - 'project' => 'ccc_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - - // This should be its own project, and listed first on the report. - 'bbb_update_test' => array( - 'project' => 'bbb_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - - // This will contain both aaa_update_test and ccc_update_test, and - // should come after the bbb_update_test project. - 'ccc_update_test' => array( - 'project' => 'ccc_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - $this->refreshUpdateStatus(array('drupal' => '0', '#all' => '1_0')); - $this->standardTests(); - // We're expecting the report to say all projects are up to date. - $this->assertText(t('Up to date')); - $this->assertNoText(t('Update available')); - // We want to see all 3 module names listed, since they'll show up either - // as project names or as modules under the "Includes" listing. - $this->assertText(t('AAA Update test')); - $this->assertText(t('BBB Update test')); - $this->assertText(t('CCC Update test')); - // We want aaa_update_test included in the ccc_update_test project, not as - // its own project on the report. - $this->assertNoRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project does not appear.')); - // The other two should be listed as projects. - $this->assertRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), t('Link to bbb_update_test project appears.')); - $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), t('Link to bbb_update_test project appears.')); - - // We want to make sure we see the BBB project before the CCC project. - // Instead of just searching for 'BBB Update test' or something, we want - // to use the full markup that starts the project entry itself, so that - // we're really testing that the project listings are in the right order. - $bbb_project_link = '<div class="project"><a href="http://example.com/project/bbb_update_test">BBB Update test</a>'; - $ccc_project_link = '<div class="project"><a href="http://example.com/project/ccc_update_test">CCC Update test</a>'; - $this->assertTrue(strpos($this->drupalGetContent(), $bbb_project_link) < strpos($this->drupalGetContent(), $ccc_project_link), "'BBB Update test' project is listed before the 'CCC Update test' project"); - } - - /** - * Test that subthemes are notified about security updates for base themes. - */ - function testUpdateBaseThemeSecurityUpdate() { - // Only enable the subtheme, not the base theme. - db_update('system') - ->fields(array('status' => 1)) - ->condition('type', 'theme') - ->condition('name', 'update_test_subtheme') - ->execute(); - - // Define the initial state for core and the subtheme. - $system_info = array( - // We want core to be version 7.0. - '#all' => array( - 'version' => '7.0', - ), - // Show the update_test_basetheme - 'update_test_basetheme' => array( - 'project' => 'update_test_basetheme', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - // Show the update_test_subtheme - 'update_test_subtheme' => array( - 'project' => 'update_test_subtheme', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - $xml_mapping = array( - 'drupal' => '0', - 'update_test_subtheme' => '1_0', - 'update_test_basetheme' => '1_1-sec', - ); - $this->refreshUpdateStatus($xml_mapping); - $this->assertText(t('Security update required!')); - $this->assertRaw(l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'), t('Link to the Update test base theme project appears.')); - } - - /** - * Test that disabled themes are only shown when desired. - */ - function testUpdateShowDisabledThemes() { - // Make sure all the update_test_* themes are disabled. - db_update('system') - ->fields(array('status' => 0)) - ->condition('type', 'theme') - ->condition('name', 'update_test_%', 'LIKE') - ->execute(); - - // Define the initial state for core and the test contrib themes. - $system_info = array( - // We want core to be version 7.0. - '#all' => array( - 'version' => '7.0', - ), - // The update_test_basetheme should be visible and up to date. - 'update_test_basetheme' => array( - 'project' => 'update_test_basetheme', - 'version' => '8.x-1.1', - 'hidden' => FALSE, - ), - // The update_test_subtheme should be visible and up to date. - 'update_test_subtheme' => array( - 'project' => 'update_test_subtheme', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - // When there are contributed modules in the site's file system, the - // total number of attempts made in the test may exceed the default value - // of update_max_fetch_attempts. Therefore this variable is set very high - // to avoid test failures in those cases. - variable_set('update_max_fetch_attempts', 99999); - variable_set('update_test_system_info', $system_info); - $xml_mapping = array( - 'drupal' => '0', - 'update_test_subtheme' => '1_0', - 'update_test_basetheme' => '1_1-sec', - ); - $base_theme_project_link = l(t('Update test base theme'), 'http://example.com/project/update_test_basetheme'); - $sub_theme_project_link = l(t('Update test subtheme'), 'http://example.com/project/update_test_subtheme'); - foreach (array(TRUE, FALSE) as $check_disabled) { - variable_set('update_check_disabled', $check_disabled); - $this->refreshUpdateStatus($xml_mapping); - // In neither case should we see the "Themes" heading for enabled themes. - $this->assertNoText(t('Themes')); - if ($check_disabled) { - $this->assertText(t('Disabled themes')); - $this->assertRaw($base_theme_project_link, t('Link to the Update test base theme project appears.')); - $this->assertRaw($sub_theme_project_link, t('Link to the Update test subtheme project appears.')); - } - else { - $this->assertNoText(t('Disabled themes')); - $this->assertNoRaw($base_theme_project_link, t('Link to the Update test base theme project does not appear.')); - $this->assertNoRaw($sub_theme_project_link, t('Link to the Update test subtheme project does not appear.')); - } - } - } - - /** - * Make sure that if we fetch from a broken URL, sane things happen. - */ - function testUpdateBrokenFetchURL() { - $system_info = array( - '#all' => array( - 'version' => '7.0', - ), - 'aaa_update_test' => array( - 'project' => 'aaa_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - 'bbb_update_test' => array( - 'project' => 'bbb_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - 'ccc_update_test' => array( - 'project' => 'ccc_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - - $xml_mapping = array( - 'drupal' => '0', - 'aaa_update_test' => '1_0', - 'bbb_update_test' => 'does-not-exist', - 'ccc_update_test' => '1_0', - ); - $this->refreshUpdateStatus($xml_mapping); - - $this->assertText(t('Up to date')); - // We're expecting the report to say most projects are up to date, so we - // hope that 'Up to date' is not unique. - $this->assertNoUniqueText(t('Up to date')); - // It should say we failed to get data, not that we're missing an update. - $this->assertNoText(t('Update available')); - - // We need to check that this string is found as part of a project row, - // not just in the "Failed to get available update data for ..." message - // at the top of the page. - $this->assertRaw('<div class="version-status">' . t('Failed to get available update data')); - - // We should see the output messages from fetching manually. - $this->assertUniqueText(t('Checked available update data for 3 projects.')); - $this->assertUniqueText(t('Failed to get available update data for one project.')); - - // The other two should be listed as projects. - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); - $this->assertNoRaw(l(t('BBB Update test'), 'http://example.com/project/bbb_update_test'), t('Link to bbb_update_test project does not appear.')); - $this->assertRaw(l(t('CCC Update test'), 'http://example.com/project/ccc_update_test'), t('Link to bbb_update_test project appears.')); - } - - /** - * Check that hook_update_status_alter() works to change a status. - * - * We provide the same external data as if aaa_update_test 8.x-1.0 were - * installed and that was the latest release. Then we use - * hook_update_status_alter() to try to mark this as missing a security - * update, then assert if we see the appropriate warnings on the right - * pages. - */ - function testHookUpdateStatusAlter() { - variable_set('allow_authorize_operations', TRUE); - $update_admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer software updates')); - $this->drupalLogin($update_admin_user); - - $system_info = array( - '#all' => array( - 'version' => '7.0', - ), - 'aaa_update_test' => array( - 'project' => 'aaa_update_test', - 'version' => '8.x-1.0', - 'hidden' => FALSE, - ), - ); - variable_set('update_test_system_info', $system_info); - $update_status = array( - 'aaa_update_test' => array( - 'status' => UPDATE_NOT_SECURE, - ), - ); - variable_set('update_test_update_status', $update_status); - $this->refreshUpdateStatus( - array( - 'drupal' => '0', - 'aaa_update_test' => '1_0', - ) - ); - $this->drupalGet('admin/reports/updates'); - $this->assertRaw('<h3>' . t('Modules') . '</h3>'); - $this->assertText(t('Security update required!')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); - - // Visit the reports page again without the altering and make sure the - // status is back to normal. - variable_set('update_test_update_status', array()); - $this->drupalGet('admin/reports/updates'); - $this->assertRaw('<h3>' . t('Modules') . '</h3>'); - $this->assertNoText(t('Security update required!')); - $this->assertRaw(l(t('AAA Update test'), 'http://example.com/project/aaa_update_test'), t('Link to aaa_update_test project appears.')); - - // Turn the altering back on and visit the Update manager UI. - variable_set('update_test_update_status', $update_status); - $this->drupalGet('admin/modules/update'); - $this->assertText(t('Security update')); - - // Turn the altering back off and visit the Update manager UI. - variable_set('update_test_update_status', array()); - $this->drupalGet('admin/modules/update'); - $this->assertNoText(t('Security update')); - } - -} - -class UpdateTestUploadCase extends UpdateTestHelper { - public static function getInfo() { - return array( - 'name' => 'Upload and extract module functionality', - 'description' => 'Tests the update module\'s upload and extraction functionality.', - 'group' => 'Update', - ); - } - - public function setUp() { - parent::setUp('update', 'update_test'); - variable_set('allow_authorize_operations', TRUE); - $admin_user = $this->drupalCreateUser(array('administer software updates', 'administer site configuration')); - $this->drupalLogin($admin_user); - } - - /** - * Tests upload and extraction of a module. - */ - public function testUploadModule() { - // Images are not valid archives, so get one and try to install it. We - // need an extra variable to store the result of drupalGetTestFiles() - // since reset() takes an argument by reference and passing in a constant - // emits a notice in strict mode. - $imageTestFiles = $this->drupalGetTestFiles('image'); - $invalidArchiveFile = reset($imageTestFiles); - $edit = array( - 'files[project_upload]' => $invalidArchiveFile->uri, - ); - // This also checks that the correct archive extensions are allowed. - $this->drupalPost('admin/modules/install', $edit, t('Install')); - $this->assertText(t('Only files with the following extensions are allowed: @archive_extensions.', array('@archive_extensions' => archiver_get_extensions())),'Only valid archives can be uploaded.'); - - // Check to ensure an existing module can't be reinstalled. Also checks that - // the archive was extracted since we can't know if the module is already - // installed until after extraction. - $validArchiveFile = drupal_get_path('module', 'update') . '/tests/aaa_update_test.tar.gz'; - $edit = array( - 'files[project_upload]' => $validArchiveFile, - ); - $this->drupalPost('admin/modules/install', $edit, t('Install')); - $this->assertText(t('@module_name is already installed.', array('@module_name' => 'AAA Update test')), 'Existing module was extracted and not reinstalled.'); - } - - /** - * Ensure that archiver extensions are properly merged in the UI. - */ - function testFileNameExtensionMerging() { - $this->drupalGet('admin/modules/install'); - // Make sure the bogus extension supported by update_test.module is there. - $this->assertPattern('/file extensions are supported:.*update-test-extension/', t("Found 'update-test-extension' extension")); - // Make sure it didn't clobber the first option from core. - $this->assertPattern('/file extensions are supported:.*tar/', t("Found 'tar' extension")); - } - - /** - * Check the messages on Update manager pages when missing a security update. - */ - function testUpdateManagerCoreSecurityUpdateMessages() { - $setting = array( - '#all' => array( - 'version' => '7.0', - ), - ); - variable_set('update_test_system_info', $setting); - variable_set('update_fetch_url', url('update-test', array('absolute' => TRUE))); - variable_set('update_test_xml_map', array('drupal' => '2-sec')); - // Initialize the update status. - $this->drupalGet('admin/reports/updates'); - - // Now, make sure none of the Update manager pages have duplicate messages - // about core missing a security update. - - $this->drupalGet('admin/modules/install'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/modules/update'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/appearance/install'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/appearance/update'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/reports/updates/install'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/reports/updates/update'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - - $this->drupalGet('admin/update/ready'); - $this->assertNoText(t('There is a security update available for your version of Drupal.')); - } - -} - -class UpdateCoreUnitTestCase extends UnitTestBase { - - public static function getInfo() { - return array( - 'name' => "Unit tests", - 'description' => 'Test update funcionality unrelated to the database.', - 'group' => 'Update', - ); - } - - function setUp() { - parent::setUp('update'); - module_load_include('inc', 'update', 'update.fetch'); - } - - /** - * Tests _update_build_fetch_url according to issue 1481156 - */ - function testUpdateBuildFetchUrl() { - //first test that we didn't break the trivial case - $project['name'] = 'update_test'; - $project['project_type'] = ''; - $project['info']['version'] = ''; - $project['info']['project status url'] = 'http://www.example.com'; - $project['includes'] = array('module1' => 'Module 1', 'module2' => 'Module 2'); - $site_key = ''; - $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; - $url = _update_build_fetch_url($project, $site_key); - $this->assertEqual($url, $expected, "'$url' when no site_key provided should be '$expected'."); - - //For disabled projects it shouldn't add the site key either. - $site_key = 'site_key'; - $project['project_type'] = 'disabled'; - $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; - $url = _update_build_fetch_url($project, $site_key); - $this->assertEqual($url, $expected, "'$url' should be '$expected' for disabled projects."); - - //for enabled projects, adding the site key - $project['project_type'] = ''; - $expected = 'http://www.example.com/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; - $expected .= '?site_key=site_key'; - $expected .= '&list=' . rawurlencode('module1,module2'); - $url = _update_build_fetch_url($project, $site_key); - $this->assertEqual($url, $expected, "When site_key provided, '$url' should be '$expected'."); - - // http://drupal.org/node/1481156 test incorrect logic when url contains - // a question mark. - $project['info']['project status url'] = 'http://www.example.com/?project='; - $expected = 'http://www.example.com/?project=/' . $project['name'] . '/' . DRUPAL_CORE_COMPATIBILITY; - $expected .= '&site_key=site_key'; - $expected .= '&list=' . rawurlencode('module1,module2'); - $url = _update_build_fetch_url($project, $site_key); - $this->assertEqual($url, $expected, "When ? is present, '$url' should be '$expected'."); - - } -} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php new file mode 100644 index 0000000000000000000000000000000000000000..31a6c022cbc39ce1fb69c48799eab564f02f2890 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserAccountLinksTests.php @@ -0,0 +1,59 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserAccountLinksTests. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user-links in secondary menu. + */ +class UserAccountLinksTests extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User account links', + 'description' => 'Test user-account links.', + 'group' => 'User' + ); + } + + /** + * Tests the secondary menu. + */ + function testSecondaryMenu() { + // Create a regular user. + $user = $this->drupalCreateUser(array()); + + // Log in and get the homepage. + $this->drupalLogin($user); + $this->drupalGet('<front>'); + + // For a logged-in user, expect the secondary menu to have links for "My + // account" and "Log out". + $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array( + ':menu_id' => 'secondary-menu', + ':href' => 'user', + ':text' => 'My account', + )); + $this->assertEqual(count($link), 1, 'My account link is in secondary menu.'); + + $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array( + ':menu_id' => 'secondary-menu', + ':href' => 'user/logout', + ':text' => 'Log out', + )); + $this->assertEqual(count($link), 1, 'Log out link is in secondary menu.'); + + // Log out and get the homepage. + $this->drupalLogout(); + $this->drupalGet('<front>'); + + // For a logged-out user, expect no secondary links. + $element = $this->xpath('//ul[@id=:menu_id]', array(':menu_id' => 'secondary-menu')); + $this->assertEqual(count($element), 0, 'No secondary-menu for logged-out users.'); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9c9aa60bfe5bf9d23a588c7bc8263b01e6a23367 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserAdminTest.php @@ -0,0 +1,96 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserAdminTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +class UserAdminTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User administration', + 'description' => 'Test user administration page functionality.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp(array('taxonomy')); + } + + /** + * Registers a user and deletes it. + */ + function testUserAdmin() { + + $user_a = $this->drupalCreateUser(array()); + $user_b = $this->drupalCreateUser(array('administer taxonomy')); + $user_c = $this->drupalCreateUser(array('administer taxonomy')); + + // Create admin user to delete registered user. + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + $this->drupalGet('admin/people'); + $this->assertText($user_a->name, t('Found user A on admin users page')); + $this->assertText($user_b->name, t('Found user B on admin users page')); + $this->assertText($user_c->name, t('Found user C on admin users page')); + $this->assertText($admin_user->name, t('Found Admin user on admin users page')); + + // Test for existence of edit link in table. + $link = l(t('edit'), "user/$user_a->uid/edit", array('query' => array('destination' => 'admin/people'))); + $this->assertRaw($link, t('Found user A edit link on admin users page')); + + // Filter the users by permission 'administer taxonomy'. + $edit = array(); + $edit['permission'] = 'administer taxonomy'; + $this->drupalPost('admin/people', $edit, t('Filter')); + + // Check if the correct users show up. + $this->assertNoText($user_a->name, t('User A not on filtered by perm admin users page')); + $this->assertText($user_b->name, t('Found user B on filtered by perm admin users page')); + $this->assertText($user_c->name, t('Found user C on filtered by perm admin users page')); + + // Filter the users by role. Grab the system-generated role name for User C. + $edit['role'] = max(array_flip($user_c->roles)); + $this->drupalPost('admin/people', $edit, t('Refine')); + + // Check if the correct users show up when filtered by role. + $this->assertNoText($user_a->name, t('User A not on filtered by role on admin users page')); + $this->assertNoText($user_b->name, t('User B not on filtered by role on admin users page')); + $this->assertText($user_c->name, t('User C on filtered by role on admin users page')); + + // Test blocking of a user. + $account = user_load($user_c->uid); + $this->assertEqual($account->status, 1, 'User C not blocked'); + $edit = array(); + $edit['operation'] = 'block'; + $edit['accounts[' . $account->uid . ']'] = TRUE; + $this->drupalPost('admin/people', $edit, t('Update')); + $account = user_load($user_c->uid, TRUE); + $this->assertEqual($account->status, 0, 'User C blocked'); + + // Test unblocking of a user from /admin/people page and sending of activation mail + $editunblock = array(); + $editunblock['operation'] = 'unblock'; + $editunblock['accounts[' . $account->uid . ']'] = TRUE; + $this->drupalPost('admin/people', $editunblock, t('Update')); + $account = user_load($user_c->uid, TRUE); + $this->assertEqual($account->status, 1, 'User C unblocked'); + $this->assertMail("to", $account->mail, "Activation mail sent to user C"); + + // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail + $user_d = $this->drupalCreateUser(array()); + $account1 = user_load($user_d->uid, TRUE); + $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => 0), t('Save')); + $account1 = user_load($user_d->uid, TRUE); + $this->assertEqual($account1->status, 0, 'User D blocked'); + $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => TRUE), t('Save')); + $account1 = user_load($user_d->uid, TRUE); + $this->assertEqual($account1->status, 1, 'User D unblocked'); + $this->assertMail("to", $account1->mail, "Activation mail sent to user D"); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAuthmapAssignmentTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAuthmapAssignmentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..332c5636d98b271eb631d235914dbb6ea26764c9 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserAuthmapAssignmentTest.php @@ -0,0 +1,69 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserAuthmapAssignmentTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Unit test for authmap assignment. + */ +class UserAuthmapAssignmentTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => t('Authmap assignment'), + 'description' => t('Tests that users can be assigned and unassigned authmaps.'), + 'group' => t('User') + ); + } + + /** + * Test authmap assignment and retrieval. + */ + function testAuthmapAssignment() { + $account = $this->drupalCreateUser(); + + // Assign authmaps to the user. + $authmaps = array( + 'authname_poll' => 'external username one', + 'authname_book' => 'external username two', + ); + user_set_authmaps($account, $authmaps); + + // Test for expected authmaps. + $expected_authmaps = array( + 'external username one' => array( + 'poll' => 'external username one', + ), + 'external username two' => array( + 'book' => 'external username two', + ), + ); + foreach ($expected_authmaps as $authname => $expected_output) { + $this->assertIdentical(user_get_authmaps($authname), $expected_output, t('Authmap for authname %authname was set correctly.', array('%authname' => $authname))); + } + + // Remove authmap for module poll, add authmap for module blog. + $authmaps = array( + 'authname_poll' => NULL, + 'authname_blog' => 'external username three', + ); + user_set_authmaps($account, $authmaps); + + // Assert that external username one does not have authmaps. + $remove_username = 'external username one'; + unset($expected_authmaps[$remove_username]); + $this->assertFalse(user_get_authmaps($remove_username), t('Authmap for %authname was removed.', array('%authname' => $remove_username))); + + // Assert that a new authmap was created for external username three, and + // existing authmaps for external username two were unchanged. + $expected_authmaps['external username three'] = array('blog' => 'external username three'); + foreach ($expected_authmaps as $authname => $expected_output) { + $this->assertIdentical(user_get_authmaps($authname), $expected_output, t('Authmap for authname %authname was set correctly.', array('%authname' => $authname))); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d5f1d8afafd02372ebaab105d156228eb65514ac --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserAutocompleteTest.php @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserAutocompleteTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user autocompletion. + */ +class UserAutocompleteTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User autocompletion', + 'description' => 'Test user autocompletion functionality.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp(); + + // Set up two users with different permissions to test access. + $this->unprivileged_user = $this->drupalCreateUser(); + $this->privileged_user = $this->drupalCreateUser(array('access user profiles')); + } + + /** + * Tests access to user autocompletion and verify the correct results. + */ + function testUserAutocomplete() { + // Check access from unprivileged user, should be denied. + $this->drupalLogin($this->unprivileged_user); + $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]); + $this->assertResponse(403, t('Autocompletion access denied to user without permission.')); + + // Check access from privileged user. + $this->drupalLogout(); + $this->drupalLogin($this->privileged_user); + $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]); + $this->assertResponse(200, t('Autocompletion access allowed.')); + + // Using first letter of the user's name, make sure the user's full name is in the results. + $this->assertRaw($this->unprivileged_user->name, t('User name found in autocompletion results.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php b/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php new file mode 100644 index 0000000000000000000000000000000000000000..8e69750a3ac9deef412651f92f700b0a9371917e --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php @@ -0,0 +1,125 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserBlocksTests. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user blocks. + */ +class UserBlocksTests extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User blocks', + 'description' => 'Test user blocks.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp(array('block')); + + // Enable user login block. + db_merge('block') + ->key(array( + 'module' => 'user', + 'delta' => 'login', + 'theme' => variable_get('theme_default', 'stark'), + )) + ->fields(array( + 'status' => 1, + 'weight' => 0, + 'region' => 'sidebar_first', + 'pages' => '', + 'cache' => -1, + )) + ->execute(); + } + + /** + * Test the user login block. + */ + function testUserLoginBlock() { + // Create a user with some permission that anonymous users lack. + $user = $this->drupalCreateUser(array('administer permissions')); + + // Log in using the block. + $edit = array(); + $edit['name'] = $user->name; + $edit['pass'] = $user->pass_raw; + $this->drupalPost('admin/people/permissions', $edit, t('Log in')); + $this->assertNoText(t('User login'), t('Logged in.')); + + // Check that we are still on the same page. + $this->assertEqual(url('admin/people/permissions', array('absolute' => TRUE)), $this->getUrl(), t('Still on the same page after login for access denied page')); + + // Now, log out and repeat with a non-403 page. + $this->drupalLogout(); + $this->drupalPost('filter/tips', $edit, t('Log in')); + $this->assertNoText(t('User login'), t('Logged in.')); + $this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', t('Still on the same page after login for allowed page')); + + // Check that the user login block is not vulnerable to information + // disclosure to third party sites. + $this->drupalLogout(); + $this->drupalPost('http://example.com/', $edit, t('Log in'), array('external' => FALSE)); + // Check that we remain on the site after login. + $this->assertEqual(url('user/' . $user->uid, array('absolute' => TRUE)), $this->getUrl(), t('Redirected to user profile page after login from the frontpage')); + } + + /** + * Test the Who's Online block. + */ + function testWhosOnlineBlock() { + // Generate users and make sure there are no current user sessions. + $user1 = $this->drupalCreateUser(array()); + $user2 = $this->drupalCreateUser(array()); + $user3 = $this->drupalCreateUser(array()); + $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions}")->fetchField(), 0, t('Sessions table is empty.')); + + // Insert a user with two sessions. + $this->insertSession(array('uid' => $user1->uid)); + $this->insertSession(array('uid' => $user1->uid)); + $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid", array(':uid' => $user1->uid))->fetchField(), 2, t('Duplicate user session has been inserted.')); + + // Insert a user with only one session. + $this->insertSession(array('uid' => $user2->uid, 'timestamp' => REQUEST_TIME + 1)); + + // Insert an inactive logged-in user who should not be seen in the block. + $this->insertSession(array('uid' => $user3->uid, 'timestamp' => (REQUEST_TIME - variable_get('user_block_seconds_online', 900) - 1))); + + // Insert two anonymous user sessions. + $this->insertSession(); + $this->insertSession(); + + // Test block output. + $block = user_block_view('online'); + $this->drupalSetContent($block['content']); + $this->assertRaw(t('2 users'), t('Correct number of online users (2 users).')); + $this->assertText($user1->name, t('Active user 1 found in online list.')); + $this->assertText($user2->name, t('Active user 2 found in online list.')); + $this->assertNoText($user3->name, t("Inactive user not found in online list.")); + $this->assertTrue(strpos($this->drupalGetContent(), $user1->name) > strpos($this->drupalGetContent(), $user2->name), t('Online users are ordered correctly.')); + } + + /** + * Insert a user session into the {sessions} table. This function is used + * since we cannot log in more than one user at the same time in tests. + */ + private function insertSession(array $fields = array()) { + $fields += array( + 'uid' => 0, + 'sid' => drupal_hash_base64(uniqid(mt_rand(), TRUE)), + 'timestamp' => REQUEST_TIME, + ); + db_insert('sessions') + ->fields($fields) + ->execute(); + $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid AND sid = :sid AND timestamp = :timestamp", array(':uid' => $fields['uid'], ':sid' => $fields['sid'], ':timestamp' => $fields['timestamp']))->fetchField(), 1, t('Session record inserted.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4e6a0d50664ead9df7e84c2637b6570a1d9afbb0 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserCancelTest.php @@ -0,0 +1,439 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserCancelTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test cancelling a user. + */ +class UserCancelTest extends WebTestBase { + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Cancel account', + 'description' => 'Ensure that account cancellation methods work as expected.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('comment'); + } + + /** + * Attempt to cancel account without permission. + */ + function testUserCancelWithoutPermission() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + + // Create a user. + $account = $this->drupalCreateUser(array()); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + + // Create a node. + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + + // Attempt to cancel account. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->assertNoRaw(t('Cancel account'), t('No cancel account button displayed.')); + + // Attempt bogus account cancellation request confirmation. + $timestamp = $account->login; + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->assertResponse(403, t('Bogus cancelling request rejected.')); + $account = user_load($account->uid); + $this->assertTrue($account->status == 1, t('User account was not canceled.')); + + // Confirm user's content has not been altered. + $test_node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), t('Node of the user has not been altered.')); + } + + /** + * Tests that user account for uid 1 cannot be cancelled. + * + * This should never be possible, or the site owner would become unable to + * administer the site. + */ + function testUserCancelUid1() { + // Update uid 1's name and password to we know it. + $password = user_password(); + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); + $account = array( + 'name' => 'user1', + 'pass' => user_hash_password(trim($password)), + ); + // We cannot use $account->save() here, because this would result in the + // password being hashed again. + db_update('users') + ->fields($account) + ->condition('uid', 1) + ->execute(); + + // Reload and log in uid 1. + $user1 = user_load(1, TRUE); + $user1->pass_raw = $password; + + // Try to cancel uid 1's account with a different user. + $this->admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($this->admin_user); + $edit = array( + 'operation' => 'cancel', + 'accounts[1]' => TRUE, + ); + $this->drupalPost('admin/people', $edit, t('Update')); + + // Verify that uid 1's account was not cancelled. + $user1 = user_load(1, TRUE); + $this->assertEqual($user1->status, 1, t('User #1 still exists and is not blocked.')); + } + + /** + * Attempt invalid account cancellations. + */ + function testUserCancelInvalid() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + + // Create a user. + $account = $this->drupalCreateUser(array('cancel account')); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + + // Create a node. + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + + // Attempt to cancel account. + $this->drupalPost('user/' . $account->uid . '/edit', NULL, t('Cancel account')); + + // Confirm account cancellation. + $timestamp = time(); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + + // Attempt bogus account cancellation request confirmation. + $bogus_timestamp = $timestamp + 60; + $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Bogus cancelling request rejected.')); + $account = user_load($account->uid); + $this->assertTrue($account->status == 1, t('User account was not canceled.')); + + // Attempt expired account cancellation request confirmation. + $bogus_timestamp = $timestamp - 86400 - 60; + $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Expired cancel account request rejected.')); + $accounts = user_load_multiple(array($account->uid), array('status' => 1)); + $this->assertTrue(reset($accounts), t('User account was not canceled.')); + + // Confirm user's content has not been altered. + $test_node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), t('Node of the user has not been altered.')); + } + + /** + * Disable account and keep all content. + */ + function testUserBlock() { + variable_set('user_cancel_method', 'user_cancel_block'); + + // Create a user. + $web_user = $this->drupalCreateUser(array('cancel account')); + $this->drupalLogin($web_user); + + // Load real user object. + $account = user_load($web_user->uid, TRUE); + + // Attempt to cancel account. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); + $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your user name.'), t('Informs that all content will be remain as is.')); + $this->assertNoText(t('Select the method to cancel the account above.'), t('Does not allow user to select account cancellation method.')); + + // Confirm account cancellation. + $timestamp = time(); + + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + + // Confirm account cancellation request. + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $account = user_load($account->uid, TRUE); + $this->assertTrue($account->status == 0, t('User has been blocked.')); + + // Confirm user is logged out. + $this->assertNoText($account->name, t('Logged out.')); + } + + /** + * Disable account and unpublish all content. + */ + function testUserBlockUnpublish() { + variable_set('user_cancel_method', 'user_cancel_block_unpublish'); + + // Create a user. + $account = $this->drupalCreateUser(array('cancel account')); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + + // Create a node with two revisions. + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + $settings = get_object_vars($node); + $settings['revision'] = 1; + $node = $this->drupalCreateNode($settings); + + // Attempt to cancel account. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); + $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators.'), t('Informs that all content will be unpublished.')); + + // Confirm account cancellation. + $timestamp = time(); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + + // Confirm account cancellation request. + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $account = user_load($account->uid, TRUE); + $this->assertTrue($account->status == 0, t('User has been blocked.')); + + // Confirm user's content has been unpublished. + $test_node = node_load($node->nid, NULL, TRUE); + $this->assertTrue($test_node->status == 0, t('Node of the user has been unpublished.')); + $test_node = node_load($node->nid, $node->vid, TRUE); + $this->assertTrue($test_node->status == 0, t('Node revision of the user has been unpublished.')); + + // Confirm user is logged out. + $this->assertNoText($account->name, t('Logged out.')); + } + + /** + * Delete account and anonymize all content. + */ + function testUserAnonymize() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + + // Create a user. + $account = $this->drupalCreateUser(array('cancel account')); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + + // Create a simple node. + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + + // Create a node with two revisions, the initial one belonging to the + // cancelling user. + $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); + $revision = $revision_node->vid; + $settings = get_object_vars($revision_node); + $settings['revision'] = 1; + $settings['uid'] = 1; // Set new/current revision to someone else. + $revision_node = $this->drupalCreateNode($settings); + + // Attempt to cancel account. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); + $this->assertRaw(t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')))), t('Informs that all content will be attributed to anonymous account.')); + + // Confirm account cancellation. + $timestamp = time(); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + + // Confirm account cancellation request. + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); + + // Confirm that user's content has been attributed to anonymous user. + $test_node = node_load($node->nid, NULL, TRUE); + $this->assertTrue(($test_node->uid == 0 && $test_node->status == 1), t('Node of the user has been attributed to anonymous user.')); + $test_node = node_load($revision_node->nid, $revision, TRUE); + $this->assertTrue(($test_node->revision_uid == 0 && $test_node->status == 1), t('Node revision of the user has been attributed to anonymous user.')); + $test_node = node_load($revision_node->nid, NULL, TRUE); + $this->assertTrue(($test_node->uid != 0 && $test_node->status == 1), t("Current revision of the user's node was not attributed to anonymous user.")); + + // Confirm that user is logged out. + $this->assertNoText($account->name, t('Logged out.')); + } + + /** + * Delete account and remove all content. + */ + function testUserDelete() { + variable_set('user_cancel_method', 'user_cancel_delete'); + + // Create a user. + $account = $this->drupalCreateUser(array('cancel account', 'post comments', 'skip comment approval')); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + + // Create a simple node. + $node = $this->drupalCreateNode(array('uid' => $account->uid)); + + // Create comment. + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + + $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); + $this->drupalPost(NULL, array(), t('Save')); + $this->assertText(t('Your comment has been posted.')); + $comments = comment_load_multiple(FALSE, array('subject' => $edit['subject'])); + $comment = reset($comments); + $this->assertTrue($comment->cid, t('Comment found.')); + + // Create a node with two revisions, the initial one belonging to the + // cancelling user. + $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); + $revision = $revision_node->vid; + $settings = get_object_vars($revision_node); + $settings['revision'] = 1; + $settings['uid'] = 1; // Set new/current revision to someone else. + $revision_node = $this->drupalCreateNode($settings); + + // Attempt to cancel account. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); + $this->assertText(t('Your account will be removed and all account information deleted. All of your content will also be deleted.'), t('Informs that all content will be deleted.')); + + // Confirm account cancellation. + $timestamp = time(); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + + // Confirm account cancellation request. + $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); + $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); + + // Confirm that user's content has been deleted. + $this->assertFalse(node_load($node->nid, NULL, TRUE), t('Node of the user has been deleted.')); + $this->assertFalse(node_load($node->nid, $revision, TRUE), t('Node revision of the user has been deleted.')); + $this->assertTrue(node_load($revision_node->nid, NULL, TRUE), t("Current revision of the user's node was not deleted.")); + $this->assertFalse(comment_load($comment->cid), t('Comment of the user has been deleted.')); + + // Confirm that user is logged out. + $this->assertNoText($account->name, t('Logged out.')); + } + + /** + * Create an administrative user and delete another user. + */ + function testUserCancelByAdmin() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + + // Create a regular user. + $account = $this->drupalCreateUser(array()); + + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + + // Delete regular user. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertRaw(t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)), t('Confirmation form to cancel account displayed.')); + $this->assertText(t('Select the method to cancel the account above.'), t('Allows to select account cancellation method.')); + + // Confirm deletion. + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), t('User deleted.')); + $this->assertFalse(user_load($account->uid), t('User is not found in the database.')); + } + + /** + * Tests deletion of a user account without an e-mail address. + */ + function testUserWithoutEmailCancelByAdmin() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + + // Create a regular user. + $account = $this->drupalCreateUser(array()); + // This user has no e-mail address. + $account->mail = ''; + $account->save(); + + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + + // Delete regular user without e-mail address. + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertRaw(t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)), t('Confirmation form to cancel account displayed.')); + $this->assertText(t('Select the method to cancel the account above.'), t('Allows to select account cancellation method.')); + + // Confirm deletion. + $this->drupalPost(NULL, NULL, t('Cancel account')); + $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), t('User deleted.')); + $this->assertFalse(user_load($account->uid), t('User is not found in the database.')); + } + + /** + * Create an administrative user and mass-delete other users. + */ + function testMassUserCancelByAdmin() { + variable_set('user_cancel_method', 'user_cancel_reassign'); + // Enable account cancellation notification. + variable_set('user_mail_status_canceled_notify', TRUE); + + // Create administrative user. + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + + // Create some users. + $users = array(); + for ($i = 0; $i < 3; $i++) { + $account = $this->drupalCreateUser(array()); + $users[$account->uid] = $account; + } + + // Cancel user accounts, including own one. + $edit = array(); + $edit['operation'] = 'cancel'; + foreach ($users as $uid => $account) { + $edit['accounts[' . $uid . ']'] = TRUE; + } + $edit['accounts[' . $admin_user->uid . ']'] = TRUE; + // Also try to cancel uid 1. + $edit['accounts[1]'] = TRUE; + $this->drupalPost('admin/people', $edit, t('Update')); + $this->assertText(t('Are you sure you want to cancel these user accounts?'), t('Confirmation form to cancel accounts displayed.')); + $this->assertText(t('When cancelling these accounts'), t('Allows to select account cancellation method.')); + $this->assertText(t('Require e-mail confirmation to cancel account.'), t('Allows to send confirmation mail.')); + $this->assertText(t('Notify user when account is canceled.'), t('Allows to send notification mail.')); + + // Confirm deletion. + $this->drupalPost(NULL, NULL, t('Cancel accounts')); + $status = TRUE; + foreach ($users as $account) { + $status = $status && (strpos($this->content, t('%name has been deleted.', array('%name' => $account->name))) !== FALSE); + $status = $status && !user_load($account->uid, TRUE); + } + $this->assertTrue($status, t('Users deleted and not found in the database.')); + + // Ensure that admin account was not cancelled. + $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); + $admin_user = user_load($admin_user->uid); + $this->assertTrue($admin_user->status == 1, t('Administrative user is found in the database and enabled.')); + + // Verify that uid 1's account was not cancelled. + $user1 = user_load(1, TRUE); + $this->assertEqual($user1->status, 1, t('User #1 still exists and is not blocked.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserCreateTest.php b/core/modules/user/lib/Drupal/user/Tests/UserCreateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cfa725443ec8dbff47fa7b6ed1216fbffae147ee --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserCreateTest.php @@ -0,0 +1,56 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserCreateTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test the create user administration page. + */ +class UserCreateTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User create', + 'description' => 'Test the create user administration page.', + 'group' => 'User', + ); + } + + /** + * Create a user through the administration interface and ensure that it + * displays in the user list. + */ + protected function testUserAdd() { + $user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($user); + + foreach (array(FALSE, TRUE) as $notify) { + $edit = array( + 'name' => $this->randomName(), + 'mail' => $this->randomName() . '@example.com', + 'pass[pass1]' => $pass = $this->randomString(), + 'pass[pass2]' => $pass, + 'notify' => $notify, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + + if ($notify) { + $this->assertText(t('A welcome message with further instructions has been e-mailed to the new user @name.', array('@name' => $edit['name'])), 'User created'); + $this->assertEqual(count($this->drupalGetMails()), 1, 'Notification e-mail sent'); + } + else { + $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created'); + $this->assertEqual(count($this->drupalGetMails()), 0, 'Notification e-mail not sent'); + } + + $this->drupalGet('admin/people'); + $this->assertText($edit['name'], 'User found in list of users'); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserEditTest.php b/core/modules/user/lib/Drupal/user/Tests/UserEditTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6bf60bc4a1426b3ca463077cfca335d8c2607e1e --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserEditTest.php @@ -0,0 +1,102 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserEditTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests the user edit form. + */ +class UserEditTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User edit', + 'description' => 'Test user edit page.', + 'group' => 'User', + ); + } + + /** + * Test user edit page. + */ + function testUserEdit() { + // Test user edit functionality with user pictures disabled. + variable_set('user_pictures', 0); + $user1 = $this->drupalCreateUser(array('change own username')); + $user2 = $this->drupalCreateUser(array()); + $this->drupalLogin($user1); + + // Test that error message appears when attempting to use a non-unique user name. + $edit['name'] = $user2->name; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); + + // Repeat the test with user pictures enabled, which modifies the form. + variable_set('user_pictures', 1); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); + + // Check that filling out a single password field does not validate. + $edit = array(); + $edit['pass[pass1]'] = ''; + $edit['pass[pass2]'] = $this->randomName(); + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); + + $edit['pass[pass1]'] = $this->randomName(); + $edit['pass[pass2]'] = ''; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); + + // Test that the error message appears when attempting to change the mail or + // pass without the current password. + $edit = array(); + $edit['mail'] = $this->randomName() . '@new.example.com'; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('E-mail address')))); + + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + // Test that the user must enter current password before changing passwords. + $edit = array(); + $edit['pass[pass1]'] = $new_pass = $this->randomName(); + $edit['pass[pass2]'] = $new_pass; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('Password')))); + + // Try again with the current password. + $edit['current_pass'] = $user1->pass_raw; + $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); + $this->assertRaw(t("The changes have been saved.")); + + // Make sure the user can log in with their new password. + $this->drupalLogout(); + $user1->pass_raw = $new_pass; + $this->drupalLogin($user1); + $this->drupalLogout(); + } + + /** + * Tests editing of a user account without an e-mail address. + */ + function testUserWithoutEmailEdit() { + // Test that an admin can edit users without an e-mail address. + $admin = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin); + // Create a regular user. + $user1 = $this->drupalCreateUser(array()); + // This user has no e-mail address. + $user1->mail = ''; + $user1->save(); + $this->drupalPost("user/$user1->uid/edit", array('mail' => ''), t('Save')); + $this->assertRaw(t("The changes have been saved.")); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserEditedOwnAccountTest.php b/core/modules/user/lib/Drupal/user/Tests/UserEditedOwnAccountTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d7d3bb27f691c71c045f2d63625cf86b79fc4a74 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserEditedOwnAccountTest.php @@ -0,0 +1,46 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserEditedOwnAccountTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/* + * Test that a user, having editing their own account, can still log in. + */ +class UserEditedOwnAccountTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User edited own account', + 'description' => 'Test user edited own account can still log in.', + 'group' => 'User', + ); + } + + function testUserEditedOwnAccount() { + // Change account setting 'Who can register accounts?' to Administrators + // only. + variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); + + // Create a new user account and log in. + $account = $this->drupalCreateUser(array('change own username')); + $this->drupalLogin($account); + + // Change own username. + $edit = array(); + $edit['name'] = $this->randomName(); + $this->drupalPost('user/' . $account->uid . '/edit', $edit, t('Save')); + + // Log out. + $this->drupalLogout(); + + // Set the new name on the user account and attempt to log back in. + $account->name = $edit['name']; + $this->drupalLogin($account); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserEntityCallbacksTest.php b/core/modules/user/lib/Drupal/user/Tests/UserEntityCallbacksTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a8fd35b3b11560f97eb1ae7f4d1ad1fe2d82aec1 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserEntityCallbacksTest.php @@ -0,0 +1,50 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserEntityCallbacksTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user entity callbacks. + */ +class UserEntityCallbacksTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User entity callback tests', + 'description' => 'Tests specific parts of the user entity like the URI callback and the label callback.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp('user'); + + $this->account = $this->drupalCreateUser(); + $this->anonymous = drupal_anonymous_user(); + } + + /** + * Test label callback. + */ + function testLabelCallback() { + $this->assertEqual(entity_label('user', $this->account), $this->account->name, t('The username should be used as label')); + + // Setup a random anonymous name to be sure the name is used. + $name = $this->randomName(); + variable_set('anonymous', $name); + $this->assertEqual(entity_label('user', $this->anonymous), $name, t('The variable anonymous should be used for name of uid 0')); + } + + /** + * Test URI callback. + */ + function testUriCallback() { + $uri = entity_uri('user', $this->account); + $this->assertEqual('user/' . $this->account->uid, $uri['path'], t('Correct user URI.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserLanguageCreationTest.php b/core/modules/user/lib/Drupal/user/Tests/UserLanguageCreationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..dce7d0b0a0f1602c9d7f15d799086dc32e5ee966 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserLanguageCreationTest.php @@ -0,0 +1,114 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserLanguageCreationTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Functional test for language handling during user creation. + */ +class UserLanguageCreationTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User language creation', + 'description' => 'Tests whether proper language is stored for new users and access to language selector.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp(array('user', 'language')); + variable_set('user_register', USER_REGISTER_VISITORS); + } + + /** + * Functional test for language handling during user creation. + */ + function testLocalUserCreation() { + // User to add and remove language and create new users. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users')); + $this->drupalLogin($admin_user); + + // Add predefined language. + $langcode = 'fr'; + $edit = array( + 'predefined_langcode' => 'fr', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); + $this->assertText('French', t('Language added successfully.')); + $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); + + // Set language negotiation. + $edit = array( + 'language_interface[enabled][language-url]' => TRUE, + ); + $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + $this->assertText(t('Language negotiation configuration saved.'), t('Set language negotiation.')); + + // Check if the language selector is available on admin/people/create and + // set to the currently active language. + $this->drupalGet($langcode . '/admin/people/create'); + $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Global language set in the language selector.')); + + // Create a user with the admin/people/create form and check if the correct + // language is set. + $username = $this->randomName(10); + $edit = array( + 'name' => $username, + 'mail' => $this->randomName(4) . '@example.com', + 'pass[pass1]' => $username, + 'pass[pass2]' => $username, + ); + + $this->drupalPost($langcode . '/admin/people/create', $edit, t('Create new account')); + + $user = user_load_by_name($username); + $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.')); + $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.')); + + // Register a new user and check if the language selector is hidden. + $this->drupalLogout(); + + $this->drupalGet($langcode . '/user/register'); + $this->assertNoFieldByName('language[fr]', t('Language selector is not accessible.')); + + $username = $this->randomName(10); + $edit = array( + 'name' => $username, + 'mail' => $this->randomName(4) . '@example.com', + ); + + $this->drupalPost($langcode . '/user/register', $edit, t('Create new account')); + + $user = user_load_by_name($username); + $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.')); + $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.')); + + // Test if the admin can use the language selector and if the + // correct language is was saved. + $user_edit = $langcode . '/user/' . $user->uid . '/edit'; + + $this->drupalLogin($admin_user); + $this->drupalGet($user_edit); + $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.')); + + // Set pass_raw so we can login the new user. + $user->pass_raw = $this->randomName(10); + $edit = array( + 'pass[pass1]' => $user->pass_raw, + 'pass[pass2]' => $user->pass_raw, + ); + + $this->drupalPost($user_edit, $edit, t('Save')); + + $this->drupalLogin($user); + $this->drupalGet($user_edit); + $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserLanguageTest.php b/core/modules/user/lib/Drupal/user/Tests/UserLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..41084c3cd24e55c0e9b3f8c1ce2b6b8d6cace9e7 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserLanguageTest.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserLanguageTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Functional tests for a user's ability to change their default language. + */ +class UserLanguageTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User language settings', + 'description' => "Tests user's ability to change their default language.", + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp(array('user', 'language')); + } + + /** + * Test if user can change their default language. + */ + function testUserLanguageConfiguration() { + global $base_url; + + // User to add and remove language. + $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); + // User to change their default language. + $web_user = $this->drupalCreateUser(); + + // Add custom language. + $this->drupalLogin($admin_user); + // Code for the language. + $langcode = 'xx'; + // The English name for the language. + $name = $this->randomName(16); + $edit = array( + 'predefined_langcode' => 'custom', + 'langcode' => $langcode, + 'name' => $name, + 'direction' => '0', + ); + $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); + $this->drupalLogout(); + + // Login as normal user and edit account settings. + $this->drupalLogin($web_user); + $path = 'user/' . $web_user->uid . '/edit'; + $this->drupalGet($path); + // Ensure language settings fieldset is available. + $this->assertText(t('Language'), t('Language selector available.')); + // Ensure custom language is present. + $this->assertText($name, t('Language present on form.')); + // Switch to our custom language. + $edit = array( + 'preferred_langcode' => $langcode, + ); + $this->drupalPost($path, $edit, t('Save')); + // Ensure form was submitted successfully. + $this->assertText(t('The changes have been saved.'), t('Changes were saved.')); + // Check if language was changed. + $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-preferred-langcode-' . $langcode)); + $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.')); + + $this->drupalLogout(); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php b/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b5e8882948fe1d297392c2b93e7e7208085bf7aa --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserLoginTest.php @@ -0,0 +1,153 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserLoginTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Functional tests for user logins, including rate limiting of login attempts. + */ +class UserLoginTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User login', + 'description' => 'Ensure that login works as expected.', + 'group' => 'User', + ); + } + + /** + * Test the global login flood control. + */ + function testGlobalLoginFloodControl() { + // Set the global login limit. + variable_set('user_failed_login_ip_limit', 10); + // Set a high per-user limit out so that it is not relevant in the test. + variable_set('user_failed_login_user_limit', 4000); + + $user1 = $this->drupalCreateUser(array()); + $incorrect_user1 = clone $user1; + $incorrect_user1->pass_raw .= 'incorrect'; + + // Try 2 failed logins. + for ($i = 0; $i < 2; $i++) { + $this->assertFailedLogin($incorrect_user1); + } + + // A successful login will not reset the IP-based flood control count. + $this->drupalLogin($user1); + $this->drupalLogout(); + + // Try 8 more failed logins, they should not trigger the flood control + // mechanism. + for ($i = 0; $i < 8; $i++) { + $this->assertFailedLogin($incorrect_user1); + } + + // The next login trial should result in an IP-based flood error message. + $this->assertFailedLogin($incorrect_user1, 'ip'); + + // A login with the correct password should also result in a flood error + // message. + $this->assertFailedLogin($user1, 'ip'); + } + + /** + * Test the per-user login flood control. + */ + function testPerUserLoginFloodControl() { + // Set a high global limit out so that it is not relevant in the test. + variable_set('user_failed_login_ip_limit', 4000); + // Set the per-user login limit. + variable_set('user_failed_login_user_limit', 3); + + $user1 = $this->drupalCreateUser(array()); + $incorrect_user1 = clone $user1; + $incorrect_user1->pass_raw .= 'incorrect'; + + $user2 = $this->drupalCreateUser(array()); + + // Try 2 failed logins. + for ($i = 0; $i < 2; $i++) { + $this->assertFailedLogin($incorrect_user1); + } + + // A successful login will reset the per-user flood control count. + $this->drupalLogin($user1); + $this->drupalLogout(); + + // Try 3 failed logins for user 1, they will not trigger flood control. + for ($i = 0; $i < 3; $i++) { + $this->assertFailedLogin($incorrect_user1); + } + + // Try one successful attempt for user 2, it should not trigger any + // flood control. + $this->drupalLogin($user2); + $this->drupalLogout(); + + // Try one more attempt for user 1, it should be rejected, even if the + // correct password has been used. + $this->assertFailedLogin($user1, 'user'); + } + + /** + * Test that user password is re-hashed upon login after changing $count_log2. + */ + function testPasswordRehashOnLogin() { + // Load password hashing API. + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); + // Set initial $count_log2 to the default, DRUPAL_HASH_COUNT. + variable_set('password_count_log2', DRUPAL_HASH_COUNT); + // Create a new user and authenticate. + $account = $this->drupalCreateUser(array()); + $password = $account->pass_raw; + $this->drupalLogin($account); + $this->drupalLogout(); + // Load the stored user. The password hash should reflect $count_log2. + $account = user_load($account->uid); + $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT); + // Change $count_log2 and log in again. + variable_set('password_count_log2', DRUPAL_HASH_COUNT + 1); + $account->pass_raw = $password; + $this->drupalLogin($account); + // Load the stored user, which should have a different password hash now. + $account = user_load($account->uid, TRUE); + $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT + 1); + } + + /** + * Make an unsuccessful login attempt. + * + * @param $account + * A user object with name and pass_raw attributes for the login attempt. + * @param $flood_trigger + * Whether or not to expect that the flood control mechanism will be + * triggered. + */ + function assertFailedLogin($account, $flood_trigger = NULL) { + $edit = array( + 'name' => $account->name, + 'pass' => $account->pass_raw, + ); + $this->drupalPost('user', $edit, t('Log in')); + $this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, t('Password value attribute is blank.')); + if (isset($flood_trigger)) { + if ($flood_trigger == 'user') { + $this->assertRaw(format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password')))); + } + else { + // No uid, so the limit is IP-based. + $this->assertRaw(t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password')))); + } + } + else { + $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?')); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPasswordResetTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPasswordResetTest.php new file mode 100644 index 0000000000000000000000000000000000000000..575409f892e3533340ad3392c655c07091fe1057 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserPasswordResetTest.php @@ -0,0 +1,62 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserPasswordResetTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests resetting a user password. + */ +class UserPasswordResetTest extends WebTestBase { + protected $profile = 'standard'; + + public static function getInfo() { + return array( + 'name' => 'Reset password', + 'description' => 'Ensure that password reset methods work as expected.', + 'group' => 'User', + ); + } + + /** + * Tests password reset functionality. + */ + function testUserPasswordReset() { + // Create a user. + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + $this->drupalLogout(); + // Attempt to reset password. + $edit = array('name' => $account->name); + $this->drupalPost('user/password', $edit, t('E-mail new password')); + // Confirm the password reset. + $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); + } + + /** + * Attempts login using an expired password reset link. + */ + function testUserPasswordResetExpired() { + // Set password reset timeout variable to 43200 seconds = 12 hours. + $timeout = 43200; + variable_set('user_password_reset_timeout', $timeout); + + // Create a user. + $account = $this->drupalCreateUser(); + $this->drupalLogin($account); + // Load real user object. + $account = user_load($account->uid, TRUE); + $this->drupalLogout(); + + // To attempt an expired password reset, create a password reset link as if + // its request time was 60 seconds older than the allowed limit of timeout. + $bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60; + $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); + $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPermissionsTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPermissionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2a0224821e7b2ca3b4b847ee8482bb34c257fd25 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserPermissionsTest.php @@ -0,0 +1,108 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserPermissionsTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +class UserPermissionsTest extends WebTestBase { + protected $admin_user; + protected $rid; + + public static function getInfo() { + return array( + 'name' => 'Role permissions', + 'description' => 'Verify that role permissions can be added and removed via the permissions page.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp(); + + $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'access user profiles', 'administer site configuration', 'administer modules', 'administer users')); + + // Find the new role ID - it must be the maximum. + $all_rids = array_keys($this->admin_user->roles); + sort($all_rids); + $this->rid = array_pop($all_rids); + } + + /** + * Change user permissions and check user_access(). + */ + function testUserPermissionChanges() { + $this->drupalLogin($this->admin_user); + $rid = $this->rid; + $account = $this->admin_user; + + // Add a permission. + $this->assertFalse(user_access('administer nodes', $account), t('User does not have "administer nodes" permission.')); + $edit = array(); + $edit[$rid . '[administer nodes]'] = TRUE; + $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); + $this->assertText(t('The changes have been saved.'), t('Successful save message displayed.')); + drupal_static_reset('user_access'); + drupal_static_reset('user_role_permissions'); + $this->assertTrue(user_access('administer nodes', $account), t('User now has "administer nodes" permission.')); + + // Remove a permission. + $this->assertTrue(user_access('access user profiles', $account), t('User has "access user profiles" permission.')); + $edit = array(); + $edit[$rid . '[access user profiles]'] = FALSE; + $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); + $this->assertText(t('The changes have been saved.'), t('Successful save message displayed.')); + drupal_static_reset('user_access'); + drupal_static_reset('user_role_permissions'); + $this->assertFalse(user_access('access user profiles', $account), t('User no longer has "access user profiles" permission.')); + } + + /** + * Test assigning of permissions for the administrator role. + */ + function testAdministratorRole() { + $this->drupalLogin($this->admin_user); + $this->drupalGet('admin/config/people/accounts'); + + // Set the user's role to be the administrator role. + $edit = array(); + $edit['user_admin_role'] = $this->rid; + $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); + + // Enable aggregator module and ensure the 'administer news feeds' + // permission is assigned by default. + $edit = array(); + $edit['modules[Core][aggregator][enable]'] = TRUE; + $this->drupalPost('admin/modules', $edit, t('Save configuration')); + $this->assertTrue(user_access('administer news feeds', $this->admin_user), t('The permission was automatically assigned to the administrator role')); + } + + /** + * Verify proper permission changes by user_role_change_permissions(). + */ + function testUserRoleChangePermissions() { + $rid = $this->rid; + $account = $this->admin_user; + + // Verify current permissions. + $this->assertFalse(user_access('administer nodes', $account), t('User does not have "administer nodes" permission.')); + $this->assertTrue(user_access('access user profiles', $account), t('User has "access user profiles" permission.')); + $this->assertTrue(user_access('administer site configuration', $account), t('User has "administer site configuration" permission.')); + + // Change permissions. + $permissions = array( + 'administer nodes' => 1, + 'access user profiles' => 0, + ); + user_role_change_permissions($rid, $permissions); + + // Verify proper permission changes. + $this->assertTrue(user_access('administer nodes', $account), t('User now has "administer nodes" permission.')); + $this->assertFalse(user_access('access user profiles', $account), t('User no longer has "access user profiles" permission.')); + $this->assertTrue(user_access('administer site configuration', $account), t('User still has "administer site configuration" permission.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e57cc42d1bffceb898adb2535e917c57974a4b0b --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php @@ -0,0 +1,318 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserPictureTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +class UserPictureTest extends WebTestBase { + protected $user; + protected $_directory_test; + + public static function getInfo() { + return array( + 'name' => 'Upload user picture', + 'description' => 'Assure that dimension check, extension check and image scaling work as designed.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp(array('image')); + // Enable user pictures. + variable_set('user_pictures', 1); + + // Configure default user picture settings. + variable_set('user_picture_dimensions', '1024x1024'); + variable_set('user_picture_file_size', '800'); + variable_set('user_picture_style', 'thumbnail'); + + $this->user = $this->drupalCreateUser(); + + // Test if directories specified in settings exist in filesystem. + $file_dir = 'public://'; + $file_check = file_prepare_directory($file_dir, FILE_CREATE_DIRECTORY); + // TODO: Test public and private methods? + + $picture_dir = variable_get('user_picture_path', 'pictures'); + $picture_path = $file_dir . $picture_dir; + + $pic_check = file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY); + $this->_directory_test = is_writable($picture_path); + $this->assertTrue($this->_directory_test, "The directory $picture_path doesn't exist or is not writable. Further tests won't be made."); + } + + function testNoPicture() { + $this->drupalLogin($this->user); + + // Try to upload a file that is not an image for the user picture. + $not_an_image = current($this->drupalGetTestFiles('html')); + $this->saveUserPicture($not_an_image); + $this->assertRaw(t('Only JPEG, PNG and GIF images are allowed.'), t('Non-image files are not accepted.')); + } + + /** + * Do the test: + * GD Toolkit is installed + * Picture has invalid dimension + * + * results: The image should be uploaded because ImageGDToolkit resizes the picture + */ + function testWithGDinvalidDimension() { + if ($this->_directory_test && image_get_toolkit()) { + $this->drupalLogin($this->user); + + $image = current($this->drupalGetTestFiles('image')); + $info = image_get_info($image->uri); + + // Set new variables: invalid dimensions, valid filesize (0 = no limit). + $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10); + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', 0); + + $pic_path = $this->saveUserPicture($image); + // Check that the image was resized and is being displayed on the + // user's profile page. + $text = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $test_dim)); + $this->assertRaw($text, t('Image was resized.')); + $alt = t("@user's picture", array('@user' => user_format_name($this->user))); + $style = variable_get('user_picture_style', ''); + $this->assertRaw(image_style_url($style, $pic_path), t("Image is displayed in user's edit page")); + + // Check if file is located in proper directory. + $this->assertTrue(is_file($pic_path), t("File is located in proper directory")); + } + } + + /** + * Do the test: + * GD Toolkit is installed + * Picture has invalid size + * + * results: The image should be uploaded because ImageGDToolkit resizes the picture + */ + function testWithGDinvalidSize() { + if ($this->_directory_test && image_get_toolkit()) { + $this->drupalLogin($this->user); + + // Images are sorted first by size then by name. We need an image + // bigger than 1 KB so we'll grab the last one. + $files = $this->drupalGetTestFiles('image'); + $image = end($files); + $info = image_get_info($image->uri); + + // Set new variables: valid dimensions, invalid filesize. + $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); + $test_size = 1; + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', $test_size); + + $pic_path = $this->saveUserPicture($image); + + // Test that the upload failed and that the correct reason was cited. + $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); + $this->assertRaw($text, t('Upload failed.')); + $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024))); + $this->assertRaw($text, t('File size cited as reason for failure.')); + + // Check if file is not uploaded. + $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); + } + } + + /** + * Do the test: + * GD Toolkit is not installed + * Picture has invalid size + * + * results: The image shouldn't be uploaded + */ + function testWithoutGDinvalidDimension() { + if ($this->_directory_test && !image_get_toolkit()) { + $this->drupalLogin($this->user); + + $image = current($this->drupalGetTestFiles('image')); + $info = image_get_info($image->uri); + + // Set new variables: invalid dimensions, valid filesize (0 = no limit). + $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10); + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', 0); + + $pic_path = $this->saveUserPicture($image); + + // Test that the upload failed and that the correct reason was cited. + $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); + $this->assertRaw($text, t('Upload failed.')); + $text = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $test_dim)); + $this->assertRaw($text, t('Checking response on invalid image (dimensions).')); + + // Check if file is not uploaded. + $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); + } + } + + /** + * Do the test: + * GD Toolkit is not installed + * Picture has invalid size + * + * results: The image shouldn't be uploaded + */ + function testWithoutGDinvalidSize() { + if ($this->_directory_test && !image_get_toolkit()) { + $this->drupalLogin($this->user); + + $image = current($this->drupalGetTestFiles('image')); + $info = image_get_info($image->uri); + + // Set new variables: valid dimensions, invalid filesize. + $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); + $test_size = 1; + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', $test_size); + + $pic_path = $this->saveUserPicture($image); + + // Test that the upload failed and that the correct reason was cited. + $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); + $this->assertRaw($text, t('Upload failed.')); + $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024))); + $this->assertRaw($text, t('File size cited as reason for failure.')); + + // Check if file is not uploaded. + $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); + } + } + + /** + * Do the test: + * Picture is valid (proper size and dimension) + * + * results: The image should be uploaded + */ + function testPictureIsValid() { + if ($this->_directory_test) { + $this->drupalLogin($this->user); + + $image = current($this->drupalGetTestFiles('image')); + $info = image_get_info($image->uri); + + // Set new variables: valid dimensions, valid filesize (0 = no limit). + $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', 0); + + $pic_path = $this->saveUserPicture($image); + + // Check if image is displayed in user's profile page. + $this->drupalGet('user'); + $this->assertRaw(file_uri_target($pic_path), t("Image is displayed in user's profile page")); + + // Check if file is located in proper directory. + $this->assertTrue(is_file($pic_path), t('File is located in proper directory')); + + // Set new picture dimensions. + $test_dim = ($info['width'] + 5) . 'x' . ($info['height'] + 5); + variable_set('user_picture_dimensions', $test_dim); + + $pic_path2 = $this->saveUserPicture($image); + $this->assertNotEqual($pic_path, $pic_path2, t('Filename of second picture is different.')); + } + } + + /** + * Test HTTP schema working with user pictures. + */ + function testExternalPicture() { + $this->drupalLogin($this->user); + // Set the default picture to an URI with a HTTP schema. + $images = $this->drupalGetTestFiles('image'); + $image = $images[0]; + $pic_path = file_create_url($image->uri); + variable_set('user_picture_default', $pic_path); + + // Check if image is displayed in user's profile page. + $this->drupalGet('user'); + + // Get the user picture image via xpath. + $elements = $this->xpath('//div[@class="user-picture"]/img'); + $this->assertEqual(count($elements), 1, t("There is exactly one user picture on the user's profile page")); + $this->assertEqual($pic_path, (string) $elements[0]['src'], t("User picture source is correct: " . $pic_path . " " . print_r($elements, TRUE))); + } + + /** + * Tests deletion of user pictures. + */ + function testDeletePicture() { + $this->drupalLogin($this->user); + + $image = current($this->drupalGetTestFiles('image')); + $info = image_get_info($image->uri); + + // Set new variables: valid dimensions, valid filesize (0 = no limit). + $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); + variable_set('user_picture_dimensions', $test_dim); + variable_set('user_picture_file_size', 0); + + // Save a new picture. + $edit = array('files[picture_upload]' => drupal_realpath($image->uri)); + $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); + + // Load actual user data from database. + $account = user_load($this->user->uid, TRUE); + $pic_path = !empty($account->picture) ? $account->picture->uri : NULL; + + // Check if image is displayed in user's profile page. + $this->drupalGet('user'); + $this->assertRaw(file_uri_target($pic_path), "Image is displayed in user's profile page"); + + // Check if file is located in proper directory. + $this->assertTrue(is_file($pic_path), 'File is located in proper directory'); + + $edit = array('picture_delete' => 1); + $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); + + // Load actual user data from database. + $account1 = user_load($this->user->uid, TRUE); + $this->assertFalse($account1->picture, 'User object has no picture'); + + $file = file_load($account->picture->fid); + $this->assertFalse($file, 'File is removed from database'); + + // Clear out PHP's file stat cache so we see the current value. + clearstatcache(); + $this->assertFalse(is_file($pic_path), 'File is removed from file system'); + } + + function saveUserPicture($image) { + $edit = array('files[picture_upload]' => drupal_realpath($image->uri)); + $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); + + // Load actual user data from database. + $account = user_load($this->user->uid, TRUE); + return !empty($account->picture) ? $account->picture->uri : NULL; + } + + /** + * Tests the admin form validates user picture settings. + */ + function testUserPictureAdminFormValidation() { + $this->drupalLogin($this->drupalCreateUser(array('administer users'))); + + // The default values are valid. + $this->drupalPost('admin/config/people/accounts', array(), t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), 'The default values are valid.'); + + // The form does not save with an invalid file size. + $edit = array( + 'user_picture_file_size' => $this->randomName(), + ); + $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); + $this->assertNoText(t('The configuration options have been saved.'), 'The form does not save with an invalid file size.'); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserRegistrationTest.php b/core/modules/user/lib/Drupal/user/Tests/UserRegistrationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..56ad9a0bd177f88f8b6467b62439d60ae6a93717 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserRegistrationTest.php @@ -0,0 +1,263 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserRegistrationTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +class UserRegistrationTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User registration', + 'description' => 'Test registration of user under different configurations.', + 'group' => 'User' + ); + } + + function setUp() { + parent::setUp('field_test'); + } + + function testRegistrationWithEmailVerification() { + // Require e-mail verification. + variable_set('user_email_verification', TRUE); + + // Set registration to administrator only. + variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); + $this->drupalGet('user/register'); + $this->assertResponse(403, t('Registration page is inaccessible when only administrators can create accounts.')); + + // Allow registration by site visitors without administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS); + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + $this->drupalPost('user/register', $edit, t('Create new account')); + $this->assertText(t('A welcome message with further instructions has been sent to your e-mail address.'), t('User registered successfully.')); + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertTrue($new_user->status, t('New account is active after registration.')); + + // Allow registration by site visitors, but require administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + $this->drupalPost('user/register', $edit, t('Create new account')); + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertFalse($new_user->status, t('New account is blocked until approved by an administrator.')); + } + + function testRegistrationWithoutEmailVerification() { + // Don't require e-mail verification. + variable_set('user_email_verification', FALSE); + + // Allow registration by site visitors without administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS); + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + + // Try entering a mismatching password. + $edit['pass[pass1]'] = '99999.0'; + $edit['pass[pass2]'] = '99999'; + $this->drupalPost('user/register', $edit, t('Create new account')); + $this->assertText(t('The specified passwords do not match.'), t('Typing mismatched passwords displays an error message.')); + + // Enter a correct password. + $edit['pass[pass1]'] = $new_pass = $this->randomName(); + $edit['pass[pass2]'] = $new_pass; + $this->drupalPost('user/register', $edit, t('Create new account')); + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertText(t('Registration successful. You are now logged in.'), t('Users are logged in after registering.')); + $this->drupalLogout(); + + // Allow registration by site visitors, but require administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + $edit['pass[pass1]'] = $pass = $this->randomName(); + $edit['pass[pass2]'] = $pass; + $this->drupalPost('user/register', $edit, t('Create new account')); + $this->assertText(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.'), t('Users are notified of pending approval')); + + // Try to login before administrator approval. + $auth = array( + 'name' => $name, + 'pass' => $pass, + ); + $this->drupalPost('user/login', $auth, t('Log in')); + $this->assertText(t('The username @name has not been activated or is blocked.', array('@name' => $name)), t('User cannot login yet.')); + + // Activate the new account. + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $admin_user = $this->drupalCreateUser(array('administer users')); + $this->drupalLogin($admin_user); + $edit = array( + 'status' => 1, + ); + $this->drupalPost('user/' . $new_user->uid . '/edit', $edit, t('Save')); + $this->drupalLogout(); + + // Login after administrator approval. + $this->drupalPost('user/login', $auth, t('Log in')); + $this->assertText(t('Member for'), t('User can log in after administrator approval.')); + } + + function testRegistrationEmailDuplicates() { + // Don't require e-mail verification. + variable_set('user_email_verification', FALSE); + + // Allow registration by site visitors without administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS); + + // Set up a user to check for duplicates. + $duplicate_user = $this->drupalCreateUser(); + + $edit = array(); + $edit['name'] = $this->randomName(); + $edit['mail'] = $duplicate_user->mail; + + // Attempt to create a new account using an existing e-mail address. + $this->drupalPost('user/register', $edit, t('Create new account')); + $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), t('Supplying an exact duplicate email address displays an error message')); + + // Attempt to bypass duplicate email registration validation by adding spaces. + $edit['mail'] = ' ' . $duplicate_user->mail . ' '; + + $this->drupalPost('user/register', $edit, t('Create new account')); + $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), t('Supplying a duplicate email address with added whitespace displays an error message')); + } + + function testRegistrationDefaultValues() { + // Allow registration by site visitors without administrator approval. + variable_set('user_register', USER_REGISTER_VISITORS); + + // Don't require e-mail verification. + variable_set('user_email_verification', FALSE); + + // Set the default timezone to Brussels. + variable_set('configurable_timezones', 1); + variable_set('date_default_timezone', 'Europe/Brussels'); + + // Check that the account information fieldset's options are not displayed + // is a fieldset if there is not more than one fieldset in the form. + $this->drupalGet('user/register'); + $this->assertNoRaw('<fieldset id="edit-account"><legend>Account information</legend>', t('Account settings fieldset was hidden.')); + + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + $edit['pass[pass1]'] = $new_pass = $this->randomName(); + $edit['pass[pass2]'] = $new_pass; + $this->drupalPost(NULL, $edit, t('Create new account')); + + // Check user fields. + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertEqual($new_user->name, $name, t('Username matches.')); + $this->assertEqual($new_user->mail, $mail, t('E-mail address matches.')); + $this->assertEqual($new_user->theme, '', t('Correct theme field.')); + $this->assertEqual($new_user->signature, '', t('Correct signature field.')); + $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.')); + $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, t('Correct status field.')); + $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.')); + $this->assertEqual($new_user->langcode, language_default()->langcode, t('Correct language field.')); + $this->assertEqual($new_user->preferred_langcode, language_default()->langcode, t('Correct preferred language field.')); + $this->assertEqual($new_user->picture, 0, t('Correct picture field.')); + $this->assertEqual($new_user->init, $mail, t('Correct init field.')); + } + + /** + * Tests Field API fields on user registration forms. + */ + function testRegistrationWithUserFields() { + // Create a field, and an instance on 'user' entity type. + $field = array( + 'type' => 'test_field', + 'field_name' => 'test_user_field', + 'cardinality' => 1, + ); + field_create_field($field); + $instance = array( + 'field_name' => 'test_user_field', + 'entity_type' => 'user', + 'label' => 'Some user field', + 'bundle' => 'user', + 'required' => TRUE, + 'settings' => array('user_register_form' => FALSE), + ); + field_create_instance($instance); + + // Check that the field does not appear on the registration form. + $this->drupalGet('user/register'); + $this->assertNoText($instance['label'], t('The field does not appear on user registration form')); + + // Have the field appear on the registration form. + $instance['settings']['user_register_form'] = TRUE; + field_update_instance($instance); + $this->drupalGet('user/register'); + $this->assertText($instance['label'], t('The field appears on user registration form')); + + // Check that validation errors are correctly reported. + $edit = array(); + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + // Missing input in required field. + $edit['test_user_field[und][0][value]'] = ''; + $this->drupalPost(NULL, $edit, t('Create new account')); + $this->assertRaw(t('@name field is required.', array('@name' => $instance['label'])), t('Field validation error was correctly reported.')); + // Invalid input. + $edit['test_user_field[und][0][value]'] = '-1'; + $this->drupalPost(NULL, $edit, t('Create new account')); + $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $instance['label'])), t('Field validation error was correctly reported.')); + + // Submit with valid data. + $value = rand(1, 255); + $edit['test_user_field[und][0][value]'] = $value; + $this->drupalPost(NULL, $edit, t('Create new account')); + // Check user fields. + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][0]['value'], $value, t('The field value was correclty saved.')); + + // Check that the 'add more' button works. + $field['cardinality'] = FIELD_CARDINALITY_UNLIMITED; + field_update_field($field); + foreach (array('js', 'nojs') as $js) { + $this->drupalGet('user/register'); + // Add two inputs. + $value = rand(1, 255); + $edit = array(); + $edit['test_user_field[und][0][value]'] = $value; + if ($js == 'js') { + $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more'); + $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more'); + } + else { + $this->drupalPost(NULL, $edit, t('Add another item')); + $this->drupalPost(NULL, $edit, t('Add another item')); + } + // Submit with three values. + $edit['test_user_field[und][1][value]'] = $value + 1; + $edit['test_user_field[und][2][value]'] = $value + 2; + $edit['name'] = $name = $this->randomName(); + $edit['mail'] = $mail = $edit['name'] . '@example.com'; + $this->drupalPost(NULL, $edit, t('Create new account')); + // Check user fields. + $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); + $new_user = reset($accounts); + $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][0]['value'], $value, t('@js : The field value was correclty saved.', array('@js' => $js))); + $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][1]['value'], $value + 1, t('@js : The field value was correclty saved.', array('@js' => $js))); + $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][2]['value'], $value + 2, t('@js : The field value was correclty saved.', array('@js' => $js))); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserRoleAdminTest.php b/core/modules/user/lib/Drupal/user/Tests/UserRoleAdminTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4798fc56d4139478831a9c45c315e1dc59ecff45 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserRoleAdminTest.php @@ -0,0 +1,95 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserRoleAdminTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test case to test adding, editing and deleting roles. + */ +class UserRoleAdminTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User role administration', + 'description' => 'Test adding, editing and deleting user roles and changing role weights.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp(); + $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users')); + } + + /** + * Test adding, renaming and deleting roles. + */ + function testRoleAdministration() { + $this->drupalLogin($this->admin_user); + + // Test adding a role. (In doing so, we use a role name that happens to + // correspond to an integer, to test that the role administration pages + // correctly distinguish between role names and IDs.) + $role_name = '123'; + $edit = array('name' => $role_name); + $this->drupalPost('admin/people/permissions/roles', $edit, t('Add role')); + $this->assertText(t('The role has been added.'), t('The role has been added.')); + $role = user_role_load_by_name($role_name); + $this->assertTrue(is_object($role), t('The role was successfully retrieved from the database.')); + + // Try adding a duplicate role. + $this->drupalPost(NULL, $edit, t('Add role')); + $this->assertRaw(t('The role name %name already exists. Choose another role name.', array('%name' => $role_name)), t('Duplicate role warning displayed.')); + + // Test renaming a role. + $old_name = $role_name; + $role_name = '456'; + $edit = array('name' => $role_name); + $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", $edit, t('Save role')); + $this->assertText(t('The role has been renamed.'), t('The role has been renamed.')); + $this->assertFalse(user_role_load_by_name($old_name), t('The role can no longer be retrieved from the database using its old name.')); + $this->assertTrue(is_object(user_role_load_by_name($role_name)), t('The role can be retrieved from the database using its new name.')); + + // Test deleting a role. + $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role')); + $this->drupalPost(NULL, NULL, t('Delete')); + $this->assertText(t('The role has been deleted.'), t('The role has been deleted')); + $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", t('Role edit link removed.')); + $this->assertFalse(user_role_load_by_name($role_name), t('A deleted role can no longer be loaded.')); + + // Make sure that the system-defined roles cannot be edited via the user + // interface. + $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_ANONYMOUS_RID); + $this->assertResponse(403, t('Access denied when trying to edit the built-in anonymous role.')); + $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_AUTHENTICATED_RID); + $this->assertResponse(403, t('Access denied when trying to edit the built-in authenticated role.')); + } + + /** + * Test user role weight change operation. + */ + function testRoleWeightChange() { + $this->drupalLogin($this->admin_user); + + // Pick up a random role and get its weight. + $rid = array_rand(user_roles()); + $role = user_role_load($rid); + $old_weight = $role->weight; + + // Change the role weight and submit the form. + $edit = array('roles['. $rid .'][weight]' => $old_weight + 1); + $this->drupalPost('admin/people/permissions/roles', $edit, t('Save order')); + $this->assertText(t('The role settings have been updated.'), t('The role settings form submitted successfully.')); + + // Retrieve the saved role and compare its weight. + $role = user_role_load($rid); + $new_weight = $role->weight; + $this->assertTrue(($old_weight + 1) == $new_weight, t('Role weight updated successfully.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserRolesAssignmentTest.php b/core/modules/user/lib/Drupal/user/Tests/UserRolesAssignmentTest.php new file mode 100644 index 0000000000000000000000000000000000000000..eaabdd2e6721a1ded4becb0c12a8ed7f33cd5335 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserRolesAssignmentTest.php @@ -0,0 +1,103 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserRolesAssignmentTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test role assignment. + */ +class UserRolesAssignmentTest extends WebTestBase { + protected $admin_user; + + public static function getInfo() { + return array( + 'name' => t('Role assignment'), + 'description' => t('Tests that users can be assigned and unassigned roles.'), + 'group' => t('User') + ); + } + + function setUp() { + parent::setUp(); + $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users')); + $this->drupalLogin($this->admin_user); + } + + /** + * Tests that a user can be assigned a role and that the role can be removed + * again. + */ + function testAssignAndRemoveRole() { + $rid = $this->drupalCreateRole(array('administer content types')); + $account = $this->drupalCreateUser(); + + // Assign the role to the user. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => $rid), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); + $this->userLoadAndCheckRoleAssigned($account, $rid); + + // Remove the role from the user. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); + $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); + } + + /** + * Tests that when creating a user the role can be assigned. And that it can + * be removed again. + */ + function testCreateUserWithRole() { + $rid = $this->drupalCreateRole(array('administer content types')); + // Create a new user and add the role at the same time. + $edit = array( + 'name' => $this->randomName(), + 'mail' => $this->randomName() . '@example.com', + 'pass[pass1]' => $pass = $this->randomString(), + 'pass[pass2]' => $pass, + "roles[$rid]" => $rid, + ); + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertText(t('Created a new user account for !name.', array('!name' => $edit['name']))); + // Get the newly added user. + $account = user_load_by_name($edit['name']); + + $this->drupalGet('user/' . $account->uid . '/edit'); + $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); + $this->userLoadAndCheckRoleAssigned($account, $rid); + + // Remove the role again. + $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); + $this->assertText(t('The changes have been saved.')); + $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); + $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); + } + + /** + * Check role on user object. + * + * @param object $account + * The user account to check. + * @param string $rid + * The role ID to search for. + * @param bool $is_assigned + * (optional) Whether to assert that $rid exists (TRUE) or not (FALSE). + * Defaults to TRUE. + */ + private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) { + $account = user_load($account->uid, TRUE); + if ($is_assigned) { + $this->assertTrue(array_key_exists($rid, $account->roles), t('The role is present in the user object.')); + } + else { + $this->assertFalse(array_key_exists($rid, $account->roles), t('The role is not present in the user object.')); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserSaveTest.php b/core/modules/user/lib/Drupal/user/Tests/UserSaveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..655651fac3b49c726c6ca95a58b08c16a839e298 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserSaveTest.php @@ -0,0 +1,52 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserSaveTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests user_save() behavior. + */ +class UserSaveTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User save test', + 'description' => 'Test account saving for arbitrary new uid.', + 'group' => 'User', + ); + } + + /** + * Test creating a user with arbitrary uid. + */ + function testUserImport() { + // User ID must be a number that is not in the database. + $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField(); + $test_uid = $max_uid + mt_rand(1000, 1000000); + $test_name = $this->randomName(); + + // Create the base user, based on drupalCreateUser(). + $user = entity_create('user', array( + 'name' => $test_name, + 'uid' => $test_uid, + 'mail' => $test_name . '@example.com', + 'pass' => user_password(), + 'status' => 1, + )); + $user->enforceIsNew(); + $user->save(); + + // Test if created user exists. + $user_by_uid = user_load($test_uid); + $this->assertTrue($user_by_uid, t('Loading user by uid.')); + + $user_by_name = user_load_by_name($test_name); + $this->assertTrue($user_by_name, t('Loading user by name.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserSearchTest.php b/core/modules/user/lib/Drupal/user/Tests/UserSearchTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7b427687bd68757058a03bdb457f9f66fc7b539b --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserSearchTest.php @@ -0,0 +1,45 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserSearchTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user search. + */ +class UserSearchTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User search', + 'description' => 'Testing that only user with the right permission can see the email address in the user search.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp(array('search')); + } + + function testUserSearch() { + $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search')); + $this->drupalLogin($user1); + $keys = $user1->mail; + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertNoText($keys); + $this->drupalLogout(); + + $user2 = $this->drupalCreateUser(array('administer users', 'access user profiles', 'search content', 'use advanced search')); + $this->drupalLogin($user2); + $keys = $user2->mail; + $edit = array('keys' => $keys); + $this->drupalPost('search/user/', $edit, t('Search')); + $this->assertText($keys); + $this->drupalLogout(); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php new file mode 100644 index 0000000000000000000000000000000000000000..19aa8c957d210662134770569ba69930afca662e --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserSignatureTest.php @@ -0,0 +1,114 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserSignatureTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test case for user signatures. + */ +class UserSignatureTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User signatures', + 'description' => 'Test user signatures.', + 'group' => 'User', + ); + } + + function setUp() { + parent::setUp('comment'); + + // Enable user signatures. + variable_set('user_signatures', 1); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + // Prefetch and create text formats. + $this->plain_text_format = filter_format_load('plain_text'); + + $filtered_html_format = array( + 'format' => 'filtered_html', + 'name' => 'Filtered HTML', + ); + $this->filtered_html_format = (object) $filtered_html_format; + filter_format_save($this->filtered_html_format); + + $full_html_format = array( + 'format' => 'full_html', + 'name' => 'Full HTML', + ); + $this->full_html_format = (object) $full_html_format; + filter_format_save($this->full_html_format); + + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array(filter_permission_name($this->filtered_html_format))); + $this->checkPermissions(array(), TRUE); + + // Create regular and administrative users. + $this->web_user = $this->drupalCreateUser(array('post comments')); + + $admin_permissions = array('administer comments'); + foreach (filter_formats() as $format) { + if ($permission = filter_permission_name($format)) { + $admin_permissions[] = $permission; + } + } + $this->admin_user = $this->drupalCreateUser($admin_permissions); + } + + /** + * Test that a user can change their signature format and that it is respected + * upon display. + */ + function testUserSignature() { + // Create a new node with comments on. + $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); + + // Verify that user signature field is not displayed on registration form. + $this->drupalGet('user/register'); + $this->assertNoText(t('Signature')); + + // Log in as a regular user and create a signature. + $this->drupalLogin($this->web_user); + $signature_text = "<h1>" . $this->randomName() . "</h1>"; + $edit = array( + 'signature[value]' => $signature_text, + 'signature[format]' => $this->plain_text_format->format, + ); + $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); + + // Verify that values were stored. + $this->assertFieldByName('signature[value]', $edit['signature[value]'], 'Submitted signature text found.'); + $this->assertFieldByName('signature[format]', $edit['signature[format]'], 'Submitted signature format found.'); + + // Create a comment. + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); + $this->drupalPost(NULL, array(), t('Save')); + + // Get the comment ID. (This technique is the same one used in the Comment + // module's CommentTestBase test case.) + preg_match('/#comment-([0-9]+)/', $this->getURL(), $match); + $comment_id = $match[1]; + + // Log in as an administrator and edit the comment to use Full HTML, so + // that the comment text itself is not filtered at all. + $this->drupalLogin($this->admin_user); + $edit['comment_body[' . $langcode . '][0][format]'] = $this->full_html_format->format; + $this->drupalPost('comment/' . $comment_id . '/edit', $edit, t('Save')); + + // Assert that the signature did not make it through unfiltered. + $this->drupalGet('node/' . $node->nid); + $this->assertNoRaw($signature_text, 'Unfiltered signature text not found.'); + $this->assertRaw(check_markup($signature_text, $this->plain_text_format->format), 'Filtered signature text found.'); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserTimeZoneTest.php b/core/modules/user/lib/Drupal/user/Tests/UserTimeZoneTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3ff4dafb1a8b296a544841e4826e42a908341ef0 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserTimeZoneTest.php @@ -0,0 +1,70 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserTimeZoneTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests for user-configurable time zones. + */ +class UserTimeZoneTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User time zones', + 'description' => 'Set a user time zone and verify that dates are displayed in local time.', + 'group' => 'User', + ); + } + + /** + * Tests the display of dates and time when user-configurable time zones are set. + */ + function testUserTimeZone() { + // Setup date/time settings for Los Angeles time. + variable_set('date_default_timezone', 'America/Los_Angeles'); + variable_set('configurable_timezones', 1); + variable_set('date_format_medium', 'Y-m-d H:i T'); + + // Create a user account and login. + $web_user = $this->drupalCreateUser(); + $this->drupalLogin($web_user); + + // Create some nodes with different authored-on dates. + // Two dates in PST (winter time): + $date1 = '2007-03-09 21:00:00 -0800'; + $date2 = '2007-03-11 01:00:00 -0800'; + // One date in PDT (summer time): + $date3 = '2007-03-20 21:00:00 -0700'; + $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article')); + $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article')); + $node3 = $this->drupalCreateNode(array('created' => strtotime($date3), 'type' => 'article')); + + // Confirm date format and time zone. + $this->drupalGet("node/$node1->nid"); + $this->assertText('2007-03-09 21:00 PST', t('Date should be PST.')); + $this->drupalGet("node/$node2->nid"); + $this->assertText('2007-03-11 01:00 PST', t('Date should be PST.')); + $this->drupalGet("node/$node3->nid"); + $this->assertText('2007-03-20 21:00 PDT', t('Date should be PDT.')); + + // Change user time zone to Santiago time. + $edit = array(); + $edit['mail'] = $web_user->mail; + $edit['timezone'] = 'America/Santiago'; + $this->drupalPost("user/$web_user->uid/edit", $edit, t('Save')); + $this->assertText(t('The changes have been saved.'), t('Time zone changed to Santiago time.')); + + // Confirm date format and time zone. + $this->drupalGet("node/$node1->nid"); + $this->assertText('2007-03-10 02:00 CLST', t('Date should be Chile summer time; five hours ahead of PST.')); + $this->drupalGet("node/$node2->nid"); + $this->assertText('2007-03-11 05:00 CLT', t('Date should be Chile time; four hours ahead of PST')); + $this->drupalGet("node/$node3->nid"); + $this->assertText('2007-03-21 00:00 CLT', t('Date should be Chile time; three hours ahead of PDT.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d6466cdb4df0bea2b704166538773fa74e91dd90 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserTokenReplaceTest.php @@ -0,0 +1,75 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserTokenReplaceTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Test user token replacement in strings. + */ +class UserTokenReplaceTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'User token replacement', + 'description' => 'Generates text using placeholders for dummy content to check user token replacement.', + 'group' => 'User', + ); + } + + /** + * Creates a user, then tests the tokens generated from it. + */ + function testUserTokenReplacement() { + global $language_interface; + $url_options = array( + 'absolute' => TRUE, + 'language' => $language_interface, + ); + + // Create two users and log them in one after another. + $user1 = $this->drupalCreateUser(array()); + $user2 = $this->drupalCreateUser(array()); + $this->drupalLogin($user1); + $this->drupalLogout(); + $this->drupalLogin($user2); + + $account = user_load($user1->uid); + $global_account = user_load($GLOBALS['user']->uid); + + // Generate and test sanitized tokens. + $tests = array(); + $tests['[user:uid]'] = $account->uid; + $tests['[user:name]'] = check_plain(user_format_name($account)); + $tests['[user:mail]'] = check_plain($account->mail); + $tests['[user:url]'] = url("user/$account->uid", $url_options); + $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options); + $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language_interface->langcode); + $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language_interface->langcode); + $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language_interface->langcode); + $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language_interface->langcode); + $tests['[current-user:name]'] = check_plain(user_format_name($global_account)); + + // Test to make sure that we generated something for each token. + $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('user' => $account), array('language' => $language_interface)); + $this->assertEqual($output, $expected, t('Sanitized user token %token replaced.', array('%token' => $input))); + } + + // Generate and test unsanitized tokens. + $tests['[user:name]'] = user_format_name($account); + $tests['[user:mail]'] = $account->mail; + $tests['[current-user:name]'] = user_format_name($global_account); + + foreach ($tests as $input => $expected) { + $output = token_replace($input, array('user' => $account), array('language' => $language_interface, 'sanitize' => FALSE)); + $this->assertEqual($output, $expected, t('Unsanitized user token %token replaced.', array('%token' => $input))); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserValidateCurrentPassCustomFormTest.php b/core/modules/user/lib/Drupal/user/Tests/UserValidateCurrentPassCustomFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7b553d27a36832fcd25789a4589e6d500a8f83e7 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserValidateCurrentPassCustomFormTest.php @@ -0,0 +1,55 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserValidateCurrentPassCustomFormTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +/** + * Tests user_validate_current_pass on a custom form. + */ +class UserValidateCurrentPassCustomFormTest extends WebTestBase { + + public static function getInfo() { + return array( + 'name' => 'User validate current pass custom form', + 'description' => 'Test that user_validate_current_pass is usable on a custom form.', + 'group' => 'User', + ); + } + + /** + * User with permission to view content. + */ + protected $accessUser; + + /** + * User permission to administer users. + */ + protected $adminUser; + + function setUp() { + parent::setUp('user_form_test'); + // Create two users + $this->accessUser = $this->drupalCreateUser(array('access content')); + $this->adminUser = $this->drupalCreateUser(array('administer users')); + } + + /** + * Tests that user_validate_current_pass can be reused on a custom form. + */ + function testUserValidateCurrentPassCustomForm() { + $this->drupalLogin($this->adminUser); + + // Submit the custom form with the admin user using the access user's password. + $edit = array(); + $edit['user_form_test_field'] = $this->accessUser->name; + $edit['current_pass'] = $this->accessUser->pass_raw; + $this->drupalPost('user_form_test_current_password/' . $this->accessUser->uid, $edit, t('Test')); + $this->assertText(t('The password has been validated and the form submitted successfully.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php b/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8801b1d8aabf6d859a28da2a35e5f453eb50ff24 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Tests/UserValidationTest.php @@ -0,0 +1,47 @@ +<?php + +/** + * @file + * Definition of Drupal\user\Tests\UserValidationTest. + */ + +namespace Drupal\user\Tests; + +use Drupal\simpletest\WebTestBase; + +class UserValidationTest extends WebTestBase { + public static function getInfo() { + return array( + 'name' => 'Username/e-mail validation', + 'description' => 'Verify that username/email validity checks behave as designed.', + 'group' => 'User' + ); + } + + // Username validation. + function testUsernames() { + $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'), + 'foo' => array('Valid username', 'assertNull'), + 'FOO' => array('Valid username', 'assertNull'), + 'Foo O\'Bar' => array('Valid username', 'assertNull'), + 'foo@bar' => array('Valid username', 'assertNull'), + 'foo@example.com' => array('Valid username', 'assertNull'), + 'foo@-example.com' => array('Valid username', 'assertNull'), // invalid domains are allowed in usernames + 'þòøÇߪř€' => array('Valid username', 'assertNull'), + 'ᚠᛇᚻ᛫ᛒᛦᚦ' => array('Valid UTF8 username', 'assertNull'), // runes + ' foo' => array('Invalid username that starts with a space', 'assertNotNull'), + 'foo ' => array('Invalid username that ends with a space', 'assertNotNull'), + 'foo bar' => array('Invalid username that contains 2 spaces \' \'', 'assertNotNull'), + '' => array('Invalid empty username', 'assertNotNull'), + 'foo/' => array('Invalid username containing invalid chars', 'assertNotNull'), + 'foo' . chr(0) . 'bar' => array('Invalid username containing chr(0)', 'assertNotNull'), // NULL + 'foo' . chr(13) . 'bar' => array('Invalid username containing chr(13)', 'assertNotNull'), // CR + str_repeat('x', USERNAME_MAX_LENGTH + 1) => array('Invalid excessively long username', 'assertNotNull'), + ); + foreach ($test_cases as $name => $test_case) { + list($description, $test) = $test_case; + $result = user_validate_name($name); + $this->$test($result, $description . ' (' . $name . ')'); + } + } +} diff --git a/core/modules/user/user.info b/core/modules/user/user.info index 8dad5a361695cf71c081d28098a7e238f6640031..7e497e6ddcbe3ab9d4ee44b9398c772d75274827 100644 --- a/core/modules/user/user.info +++ b/core/modules/user/user.info @@ -3,7 +3,6 @@ description = Manages the user registration and login system. package = Core version = VERSION core = 8.x -files[] = user.test required = TRUE configure = admin/config/people stylesheets[all][] = user.css diff --git a/core/modules/user/user.test b/core/modules/user/user.test deleted file mode 100644 index f8a1d3a6daa897f01a0c84a05b1997d1901e16d5..0000000000000000000000000000000000000000 --- a/core/modules/user/user.test +++ /dev/null @@ -1,2593 +0,0 @@ -<?php - -/** - * @file - * Tests for user.module. - */ - -use Drupal\simpletest\WebTestBase; - -class UserRegistrationTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User registration', - 'description' => 'Test registration of user under different configurations.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp('field_test'); - } - - function testRegistrationWithEmailVerification() { - // Require e-mail verification. - variable_set('user_email_verification', TRUE); - - // Set registration to administrator only. - variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); - $this->drupalGet('user/register'); - $this->assertResponse(403, t('Registration page is inaccessible when only administrators can create accounts.')); - - // Allow registration by site visitors without administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS); - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('A welcome message with further instructions has been sent to your e-mail address.'), t('User registered successfully.')); - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertTrue($new_user->status, t('New account is active after registration.')); - - // Allow registration by site visitors, but require administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $this->drupalPost('user/register', $edit, t('Create new account')); - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertFalse($new_user->status, t('New account is blocked until approved by an administrator.')); - } - - function testRegistrationWithoutEmailVerification() { - // Don't require e-mail verification. - variable_set('user_email_verification', FALSE); - - // Allow registration by site visitors without administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS); - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - - // Try entering a mismatching password. - $edit['pass[pass1]'] = '99999.0'; - $edit['pass[pass2]'] = '99999'; - $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('The specified passwords do not match.'), t('Typing mismatched passwords displays an error message.')); - - // Enter a correct password. - $edit['pass[pass1]'] = $new_pass = $this->randomName(); - $edit['pass[pass2]'] = $new_pass; - $this->drupalPost('user/register', $edit, t('Create new account')); - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertText(t('Registration successful. You are now logged in.'), t('Users are logged in after registering.')); - $this->drupalLogout(); - - // Allow registration by site visitors, but require administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL); - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $edit['pass[pass1]'] = $pass = $this->randomName(); - $edit['pass[pass2]'] = $pass; - $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.'), t('Users are notified of pending approval')); - - // Try to login before administrator approval. - $auth = array( - 'name' => $name, - 'pass' => $pass, - ); - $this->drupalPost('user/login', $auth, t('Log in')); - $this->assertText(t('The username @name has not been activated or is blocked.', array('@name' => $name)), t('User cannot login yet.')); - - // Activate the new account. - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - $edit = array( - 'status' => 1, - ); - $this->drupalPost('user/' . $new_user->uid . '/edit', $edit, t('Save')); - $this->drupalLogout(); - - // Login after administrator approval. - $this->drupalPost('user/login', $auth, t('Log in')); - $this->assertText(t('Member for'), t('User can log in after administrator approval.')); - } - - function testRegistrationEmailDuplicates() { - // Don't require e-mail verification. - variable_set('user_email_verification', FALSE); - - // Allow registration by site visitors without administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS); - - // Set up a user to check for duplicates. - $duplicate_user = $this->drupalCreateUser(); - - $edit = array(); - $edit['name'] = $this->randomName(); - $edit['mail'] = $duplicate_user->mail; - - // Attempt to create a new account using an existing e-mail address. - $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), t('Supplying an exact duplicate email address displays an error message')); - - // Attempt to bypass duplicate email registration validation by adding spaces. - $edit['mail'] = ' ' . $duplicate_user->mail . ' '; - - $this->drupalPost('user/register', $edit, t('Create new account')); - $this->assertText(t('The e-mail address @email is already registered.', array('@email' => $duplicate_user->mail)), t('Supplying a duplicate email address with added whitespace displays an error message')); - } - - function testRegistrationDefaultValues() { - // Allow registration by site visitors without administrator approval. - variable_set('user_register', USER_REGISTER_VISITORS); - - // Don't require e-mail verification. - variable_set('user_email_verification', FALSE); - - // Set the default timezone to Brussels. - variable_set('configurable_timezones', 1); - variable_set('date_default_timezone', 'Europe/Brussels'); - - // Check that the account information fieldset's options are not displayed - // is a fieldset if there is not more than one fieldset in the form. - $this->drupalGet('user/register'); - $this->assertNoRaw('<fieldset id="edit-account"><legend>Account information</legend>', t('Account settings fieldset was hidden.')); - - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $edit['pass[pass1]'] = $new_pass = $this->randomName(); - $edit['pass[pass2]'] = $new_pass; - $this->drupalPost(NULL, $edit, t('Create new account')); - - // Check user fields. - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertEqual($new_user->name, $name, t('Username matches.')); - $this->assertEqual($new_user->mail, $mail, t('E-mail address matches.')); - $this->assertEqual($new_user->theme, '', t('Correct theme field.')); - $this->assertEqual($new_user->signature, '', t('Correct signature field.')); - $this->assertTrue(($new_user->created > REQUEST_TIME - 20 ), t('Correct creation time.')); - $this->assertEqual($new_user->status, variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) == USER_REGISTER_VISITORS ? 1 : 0, t('Correct status field.')); - $this->assertEqual($new_user->timezone, variable_get('date_default_timezone'), t('Correct time zone field.')); - $this->assertEqual($new_user->langcode, language_default()->langcode, t('Correct language field.')); - $this->assertEqual($new_user->preferred_langcode, language_default()->langcode, t('Correct preferred language field.')); - $this->assertEqual($new_user->picture, 0, t('Correct picture field.')); - $this->assertEqual($new_user->init, $mail, t('Correct init field.')); - } - - /** - * Tests Field API fields on user registration forms. - */ - function testRegistrationWithUserFields() { - // Create a field, and an instance on 'user' entity type. - $field = array( - 'type' => 'test_field', - 'field_name' => 'test_user_field', - 'cardinality' => 1, - ); - field_create_field($field); - $instance = array( - 'field_name' => 'test_user_field', - 'entity_type' => 'user', - 'label' => 'Some user field', - 'bundle' => 'user', - 'required' => TRUE, - 'settings' => array('user_register_form' => FALSE), - ); - field_create_instance($instance); - - // Check that the field does not appear on the registration form. - $this->drupalGet('user/register'); - $this->assertNoText($instance['label'], t('The field does not appear on user registration form')); - - // Have the field appear on the registration form. - $instance['settings']['user_register_form'] = TRUE; - field_update_instance($instance); - $this->drupalGet('user/register'); - $this->assertText($instance['label'], t('The field appears on user registration form')); - - // Check that validation errors are correctly reported. - $edit = array(); - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - // Missing input in required field. - $edit['test_user_field[und][0][value]'] = ''; - $this->drupalPost(NULL, $edit, t('Create new account')); - $this->assertRaw(t('@name field is required.', array('@name' => $instance['label'])), t('Field validation error was correctly reported.')); - // Invalid input. - $edit['test_user_field[und][0][value]'] = '-1'; - $this->drupalPost(NULL, $edit, t('Create new account')); - $this->assertRaw(t('%name does not accept the value -1.', array('%name' => $instance['label'])), t('Field validation error was correctly reported.')); - - // Submit with valid data. - $value = rand(1, 255); - $edit['test_user_field[und][0][value]'] = $value; - $this->drupalPost(NULL, $edit, t('Create new account')); - // Check user fields. - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][0]['value'], $value, t('The field value was correclty saved.')); - - // Check that the 'add more' button works. - $field['cardinality'] = FIELD_CARDINALITY_UNLIMITED; - field_update_field($field); - foreach (array('js', 'nojs') as $js) { - $this->drupalGet('user/register'); - // Add two inputs. - $value = rand(1, 255); - $edit = array(); - $edit['test_user_field[und][0][value]'] = $value; - if ($js == 'js') { - $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more'); - $this->drupalPostAJAX(NULL, $edit, 'test_user_field_add_more'); - } - else { - $this->drupalPost(NULL, $edit, t('Add another item')); - $this->drupalPost(NULL, $edit, t('Add another item')); - } - // Submit with three values. - $edit['test_user_field[und][1][value]'] = $value + 1; - $edit['test_user_field[und][2][value]'] = $value + 2; - $edit['name'] = $name = $this->randomName(); - $edit['mail'] = $mail = $edit['name'] . '@example.com'; - $this->drupalPost(NULL, $edit, t('Create new account')); - // Check user fields. - $accounts = user_load_multiple(array(), array('name' => $name, 'mail' => $mail)); - $new_user = reset($accounts); - $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][0]['value'], $value, t('@js : The field value was correclty saved.', array('@js' => $js))); - $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][1]['value'], $value + 1, t('@js : The field value was correclty saved.', array('@js' => $js))); - $this->assertEqual($new_user->test_user_field[LANGUAGE_NOT_SPECIFIED][2]['value'], $value + 2, t('@js : The field value was correclty saved.', array('@js' => $js))); - } - } -} - -class UserValidationTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'Username/e-mail validation', - 'description' => 'Verify that username/email validity checks behave as designed.', - 'group' => 'User' - ); - } - - // Username validation. - function testUsernames() { - $test_cases = array( // '<username>' => array('<description>', 'assert<testName>'), - 'foo' => array('Valid username', 'assertNull'), - 'FOO' => array('Valid username', 'assertNull'), - 'Foo O\'Bar' => array('Valid username', 'assertNull'), - 'foo@bar' => array('Valid username', 'assertNull'), - 'foo@example.com' => array('Valid username', 'assertNull'), - 'foo@-example.com' => array('Valid username', 'assertNull'), // invalid domains are allowed in usernames - 'þòøÇߪř€' => array('Valid username', 'assertNull'), - 'ᚠᛇᚻ᛫ᛒᛦᚦ' => array('Valid UTF8 username', 'assertNull'), // runes - ' foo' => array('Invalid username that starts with a space', 'assertNotNull'), - 'foo ' => array('Invalid username that ends with a space', 'assertNotNull'), - 'foo bar' => array('Invalid username that contains 2 spaces \' \'', 'assertNotNull'), - '' => array('Invalid empty username', 'assertNotNull'), - 'foo/' => array('Invalid username containing invalid chars', 'assertNotNull'), - 'foo' . chr(0) . 'bar' => array('Invalid username containing chr(0)', 'assertNotNull'), // NULL - 'foo' . chr(13) . 'bar' => array('Invalid username containing chr(13)', 'assertNotNull'), // CR - str_repeat('x', USERNAME_MAX_LENGTH + 1) => array('Invalid excessively long username', 'assertNotNull'), - ); - foreach ($test_cases as $name => $test_case) { - list($description, $test) = $test_case; - $result = user_validate_name($name); - $this->$test($result, $description . ' (' . $name . ')'); - } - } -} - -/** - * Functional tests for user logins, including rate limiting of login attempts. - */ -class UserLoginTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User login', - 'description' => 'Ensure that login works as expected.', - 'group' => 'User', - ); - } - - /** - * Test the global login flood control. - */ - function testGlobalLoginFloodControl() { - // Set the global login limit. - variable_set('user_failed_login_ip_limit', 10); - // Set a high per-user limit out so that it is not relevant in the test. - variable_set('user_failed_login_user_limit', 4000); - - $user1 = $this->drupalCreateUser(array()); - $incorrect_user1 = clone $user1; - $incorrect_user1->pass_raw .= 'incorrect'; - - // Try 2 failed logins. - for ($i = 0; $i < 2; $i++) { - $this->assertFailedLogin($incorrect_user1); - } - - // A successful login will not reset the IP-based flood control count. - $this->drupalLogin($user1); - $this->drupalLogout(); - - // Try 8 more failed logins, they should not trigger the flood control - // mechanism. - for ($i = 0; $i < 8; $i++) { - $this->assertFailedLogin($incorrect_user1); - } - - // The next login trial should result in an IP-based flood error message. - $this->assertFailedLogin($incorrect_user1, 'ip'); - - // A login with the correct password should also result in a flood error - // message. - $this->assertFailedLogin($user1, 'ip'); - } - - /** - * Test the per-user login flood control. - */ - function testPerUserLoginFloodControl() { - // Set a high global limit out so that it is not relevant in the test. - variable_set('user_failed_login_ip_limit', 4000); - // Set the per-user login limit. - variable_set('user_failed_login_user_limit', 3); - - $user1 = $this->drupalCreateUser(array()); - $incorrect_user1 = clone $user1; - $incorrect_user1->pass_raw .= 'incorrect'; - - $user2 = $this->drupalCreateUser(array()); - - // Try 2 failed logins. - for ($i = 0; $i < 2; $i++) { - $this->assertFailedLogin($incorrect_user1); - } - - // A successful login will reset the per-user flood control count. - $this->drupalLogin($user1); - $this->drupalLogout(); - - // Try 3 failed logins for user 1, they will not trigger flood control. - for ($i = 0; $i < 3; $i++) { - $this->assertFailedLogin($incorrect_user1); - } - - // Try one successful attempt for user 2, it should not trigger any - // flood control. - $this->drupalLogin($user2); - $this->drupalLogout(); - - // Try one more attempt for user 1, it should be rejected, even if the - // correct password has been used. - $this->assertFailedLogin($user1, 'user'); - } - - /** - * Test that user password is re-hashed upon login after changing $count_log2. - */ - function testPasswordRehashOnLogin() { - // Load password hashing API. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - // Set initial $count_log2 to the default, DRUPAL_HASH_COUNT. - variable_set('password_count_log2', DRUPAL_HASH_COUNT); - // Create a new user and authenticate. - $account = $this->drupalCreateUser(array()); - $password = $account->pass_raw; - $this->drupalLogin($account); - $this->drupalLogout(); - // Load the stored user. The password hash should reflect $count_log2. - $account = user_load($account->uid); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT); - // Change $count_log2 and log in again. - variable_set('password_count_log2', DRUPAL_HASH_COUNT + 1); - $account->pass_raw = $password; - $this->drupalLogin($account); - // Load the stored user, which should have a different password hash now. - $account = user_load($account->uid, TRUE); - $this->assertIdentical(_password_get_count_log2($account->pass), DRUPAL_HASH_COUNT + 1); - } - - /** - * Make an unsuccessful login attempt. - * - * @param $account - * A user object with name and pass_raw attributes for the login attempt. - * @param $flood_trigger - * Whether or not to expect that the flood control mechanism will be - * triggered. - */ - function assertFailedLogin($account, $flood_trigger = NULL) { - $edit = array( - 'name' => $account->name, - 'pass' => $account->pass_raw, - ); - $this->drupalPost('user', $edit, t('Log in')); - $this->assertNoFieldByXPath("//input[@name='pass' and @value!='']", NULL, t('Password value attribute is blank.')); - if (isset($flood_trigger)) { - if ($flood_trigger == 'user') { - $this->assertRaw(format_plural(variable_get('user_failed_login_user_limit', 5), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password')))); - } - else { - // No uid, so the limit is IP-based. - $this->assertRaw(t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password')))); - } - } - else { - $this->assertText(t('Sorry, unrecognized username or password. Have you forgotten your password?')); - } - } -} - -/** - * Tests resetting a user password. - */ -class UserPasswordResetTestCase extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Reset password', - 'description' => 'Ensure that password reset methods work as expected.', - 'group' => 'User', - ); - } - - /** - * Tests password reset functionality. - */ - function testUserPasswordReset() { - // Create a user. - $account = $this->drupalCreateUser(); - $this->drupalLogin($account); - $this->drupalLogout(); - // Attempt to reset password. - $edit = array('name' => $account->name); - $this->drupalPost('user/password', $edit, t('E-mail new password')); - // Confirm the password reset. - $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.'); - } - - /** - * Attempts login using an expired password reset link. - */ - function testUserPasswordResetExpired() { - // Set password reset timeout variable to 43200 seconds = 12 hours. - $timeout = 43200; - variable_set('user_password_reset_timeout', $timeout); - - // Create a user. - $account = $this->drupalCreateUser(); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - $this->drupalLogout(); - - // To attempt an expired password reset, create a password reset link as if - // its request time was 60 seconds older than the allowed limit of timeout. - $bogus_timestamp = REQUEST_TIME - variable_get('user_password_reset_timeout', 86400) - 60; - $this->drupalGet("user/reset/$account->uid/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); - $this->assertText(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'Expired password reset request rejected.'); - } -} - -/** - * Test cancelling a user. - */ -class UserCancelTestCase extends WebTestBase { - protected $profile = 'standard'; - - public static function getInfo() { - return array( - 'name' => 'Cancel account', - 'description' => 'Ensure that account cancellation methods work as expected.', - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp('comment'); - } - - /** - * Attempt to cancel account without permission. - */ - function testUserCancelWithoutPermission() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - - // Create a user. - $account = $this->drupalCreateUser(array()); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - - // Create a node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); - - // Attempt to cancel account. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->assertNoRaw(t('Cancel account'), t('No cancel account button displayed.')); - - // Attempt bogus account cancellation request confirmation. - $timestamp = $account->login; - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $this->assertResponse(403, t('Bogus cancelling request rejected.')); - $account = user_load($account->uid); - $this->assertTrue($account->status == 1, t('User account was not canceled.')); - - // Confirm user's content has not been altered. - $test_node = node_load($node->nid, NULL, TRUE); - $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), t('Node of the user has not been altered.')); - } - - /** - * Tests that user account for uid 1 cannot be cancelled. - * - * This should never be possible, or the site owner would become unable to - * administer the site. - */ - function testUserCancelUid1() { - // Update uid 1's name and password to we know it. - $password = user_password(); - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $account = array( - 'name' => 'user1', - 'pass' => user_hash_password(trim($password)), - ); - // We cannot use $account->save() here, because this would result in the - // password being hashed again. - db_update('users') - ->fields($account) - ->condition('uid', 1) - ->execute(); - - // Reload and log in uid 1. - $user1 = user_load(1, TRUE); - $user1->pass_raw = $password; - - // Try to cancel uid 1's account with a different user. - $this->admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($this->admin_user); - $edit = array( - 'operation' => 'cancel', - 'accounts[1]' => TRUE, - ); - $this->drupalPost('admin/people', $edit, t('Update')); - - // Verify that uid 1's account was not cancelled. - $user1 = user_load(1, TRUE); - $this->assertEqual($user1->status, 1, t('User #1 still exists and is not blocked.')); - } - - /** - * Attempt invalid account cancellations. - */ - function testUserCancelInvalid() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - - // Create a user. - $account = $this->drupalCreateUser(array('cancel account')); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - - // Create a node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); - - // Attempt to cancel account. - $this->drupalPost('user/' . $account->uid . '/edit', NULL, t('Cancel account')); - - // Confirm account cancellation. - $timestamp = time(); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - - // Attempt bogus account cancellation request confirmation. - $bogus_timestamp = $timestamp + 60; - $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); - $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Bogus cancelling request rejected.')); - $account = user_load($account->uid); - $this->assertTrue($account->status == 1, t('User account was not canceled.')); - - // Attempt expired account cancellation request confirmation. - $bogus_timestamp = $timestamp - 86400 - 60; - $this->drupalGet("user/$account->uid/cancel/confirm/$bogus_timestamp/" . user_pass_rehash($account->pass, $bogus_timestamp, $account->login)); - $this->assertText(t('You have tried to use an account cancellation link that has expired. Please request a new one using the form below.'), t('Expired cancel account request rejected.')); - $accounts = user_load_multiple(array($account->uid), array('status' => 1)); - $this->assertTrue(reset($accounts), t('User account was not canceled.')); - - // Confirm user's content has not been altered. - $test_node = node_load($node->nid, NULL, TRUE); - $this->assertTrue(($test_node->uid == $account->uid && $test_node->status == 1), t('Node of the user has not been altered.')); - } - - /** - * Disable account and keep all content. - */ - function testUserBlock() { - variable_set('user_cancel_method', 'user_cancel_block'); - - // Create a user. - $web_user = $this->drupalCreateUser(array('cancel account')); - $this->drupalLogin($web_user); - - // Load real user object. - $account = user_load($web_user->uid, TRUE); - - // Attempt to cancel account. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); - $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will remain attributed to your user name.'), t('Informs that all content will be remain as is.')); - $this->assertNoText(t('Select the method to cancel the account above.'), t('Does not allow user to select account cancellation method.')); - - // Confirm account cancellation. - $timestamp = time(); - - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - - // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $account = user_load($account->uid, TRUE); - $this->assertTrue($account->status == 0, t('User has been blocked.')); - - // Confirm user is logged out. - $this->assertNoText($account->name, t('Logged out.')); - } - - /** - * Disable account and unpublish all content. - */ - function testUserBlockUnpublish() { - variable_set('user_cancel_method', 'user_cancel_block_unpublish'); - - // Create a user. - $account = $this->drupalCreateUser(array('cancel account')); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - - // Create a node with two revisions. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); - $settings = get_object_vars($node); - $settings['revision'] = 1; - $node = $this->drupalCreateNode($settings); - - // Attempt to cancel account. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); - $this->assertText(t('Your account will be blocked and you will no longer be able to log in. All of your content will be hidden from everyone but administrators.'), t('Informs that all content will be unpublished.')); - - // Confirm account cancellation. - $timestamp = time(); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - - // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $account = user_load($account->uid, TRUE); - $this->assertTrue($account->status == 0, t('User has been blocked.')); - - // Confirm user's content has been unpublished. - $test_node = node_load($node->nid, NULL, TRUE); - $this->assertTrue($test_node->status == 0, t('Node of the user has been unpublished.')); - $test_node = node_load($node->nid, $node->vid, TRUE); - $this->assertTrue($test_node->status == 0, t('Node revision of the user has been unpublished.')); - - // Confirm user is logged out. - $this->assertNoText($account->name, t('Logged out.')); - } - - /** - * Delete account and anonymize all content. - */ - function testUserAnonymize() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - - // Create a user. - $account = $this->drupalCreateUser(array('cancel account')); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - - // Create a simple node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); - - // Create a node with two revisions, the initial one belonging to the - // cancelling user. - $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); - $revision = $revision_node->vid; - $settings = get_object_vars($revision_node); - $settings['revision'] = 1; - $settings['uid'] = 1; // Set new/current revision to someone else. - $revision_node = $this->drupalCreateNode($settings); - - // Attempt to cancel account. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); - $this->assertRaw(t('Your account will be removed and all account information deleted. All of your content will be assigned to the %anonymous-name user.', array('%anonymous-name' => variable_get('anonymous', t('Anonymous')))), t('Informs that all content will be attributed to anonymous account.')); - - // Confirm account cancellation. - $timestamp = time(); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - - // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); - - // Confirm that user's content has been attributed to anonymous user. - $test_node = node_load($node->nid, NULL, TRUE); - $this->assertTrue(($test_node->uid == 0 && $test_node->status == 1), t('Node of the user has been attributed to anonymous user.')); - $test_node = node_load($revision_node->nid, $revision, TRUE); - $this->assertTrue(($test_node->revision_uid == 0 && $test_node->status == 1), t('Node revision of the user has been attributed to anonymous user.')); - $test_node = node_load($revision_node->nid, NULL, TRUE); - $this->assertTrue(($test_node->uid != 0 && $test_node->status == 1), t("Current revision of the user's node was not attributed to anonymous user.")); - - // Confirm that user is logged out. - $this->assertNoText($account->name, t('Logged out.')); - } - - /** - * Delete account and remove all content. - */ - function testUserDelete() { - variable_set('user_cancel_method', 'user_cancel_delete'); - - // Create a user. - $account = $this->drupalCreateUser(array('cancel account', 'post comments', 'skip comment approval')); - $this->drupalLogin($account); - // Load real user object. - $account = user_load($account->uid, TRUE); - - // Create a simple node. - $node = $this->drupalCreateNode(array('uid' => $account->uid)); - - // Create comment. - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit = array(); - $edit['subject'] = $this->randomName(8); - $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - - $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); - $this->drupalPost(NULL, array(), t('Save')); - $this->assertText(t('Your comment has been posted.')); - $comments = comment_load_multiple(FALSE, array('subject' => $edit['subject'])); - $comment = reset($comments); - $this->assertTrue($comment->cid, t('Comment found.')); - - // Create a node with two revisions, the initial one belonging to the - // cancelling user. - $revision_node = $this->drupalCreateNode(array('uid' => $account->uid)); - $revision = $revision_node->vid; - $settings = get_object_vars($revision_node); - $settings['revision'] = 1; - $settings['uid'] = 1; // Set new/current revision to someone else. - $revision_node = $this->drupalCreateNode($settings); - - // Attempt to cancel account. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('Are you sure you want to cancel your account?'), t('Confirmation form to cancel account displayed.')); - $this->assertText(t('Your account will be removed and all account information deleted. All of your content will also be deleted.'), t('Informs that all content will be deleted.')); - - // Confirm account cancellation. - $timestamp = time(); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - - // Confirm account cancellation request. - $this->drupalGet("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login)); - $this->assertFalse(user_load($account->uid, TRUE), t('User is not found in the database.')); - - // Confirm that user's content has been deleted. - $this->assertFalse(node_load($node->nid, NULL, TRUE), t('Node of the user has been deleted.')); - $this->assertFalse(node_load($node->nid, $revision, TRUE), t('Node revision of the user has been deleted.')); - $this->assertTrue(node_load($revision_node->nid, NULL, TRUE), t("Current revision of the user's node was not deleted.")); - $this->assertFalse(comment_load($comment->cid), t('Comment of the user has been deleted.')); - - // Confirm that user is logged out. - $this->assertNoText($account->name, t('Logged out.')); - } - - /** - * Create an administrative user and delete another user. - */ - function testUserCancelByAdmin() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - - // Create a regular user. - $account = $this->drupalCreateUser(array()); - - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - - // Delete regular user. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertRaw(t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)), t('Confirmation form to cancel account displayed.')); - $this->assertText(t('Select the method to cancel the account above.'), t('Allows to select account cancellation method.')); - - // Confirm deletion. - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), t('User deleted.')); - $this->assertFalse(user_load($account->uid), t('User is not found in the database.')); - } - - /** - * Tests deletion of a user account without an e-mail address. - */ - function testUserWithoutEmailCancelByAdmin() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - - // Create a regular user. - $account = $this->drupalCreateUser(array()); - // This user has no e-mail address. - $account->mail = ''; - $account->save(); - - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - - // Delete regular user without e-mail address. - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertRaw(t('Are you sure you want to cancel the account %name?', array('%name' => $account->name)), t('Confirmation form to cancel account displayed.')); - $this->assertText(t('Select the method to cancel the account above.'), t('Allows to select account cancellation method.')); - - // Confirm deletion. - $this->drupalPost(NULL, NULL, t('Cancel account')); - $this->assertRaw(t('%name has been deleted.', array('%name' => $account->name)), t('User deleted.')); - $this->assertFalse(user_load($account->uid), t('User is not found in the database.')); - } - - /** - * Create an administrative user and mass-delete other users. - */ - function testMassUserCancelByAdmin() { - variable_set('user_cancel_method', 'user_cancel_reassign'); - // Enable account cancellation notification. - variable_set('user_mail_status_canceled_notify', TRUE); - - // Create administrative user. - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - - // Create some users. - $users = array(); - for ($i = 0; $i < 3; $i++) { - $account = $this->drupalCreateUser(array()); - $users[$account->uid] = $account; - } - - // Cancel user accounts, including own one. - $edit = array(); - $edit['operation'] = 'cancel'; - foreach ($users as $uid => $account) { - $edit['accounts[' . $uid . ']'] = TRUE; - } - $edit['accounts[' . $admin_user->uid . ']'] = TRUE; - // Also try to cancel uid 1. - $edit['accounts[1]'] = TRUE; - $this->drupalPost('admin/people', $edit, t('Update')); - $this->assertText(t('Are you sure you want to cancel these user accounts?'), t('Confirmation form to cancel accounts displayed.')); - $this->assertText(t('When cancelling these accounts'), t('Allows to select account cancellation method.')); - $this->assertText(t('Require e-mail confirmation to cancel account.'), t('Allows to send confirmation mail.')); - $this->assertText(t('Notify user when account is canceled.'), t('Allows to send notification mail.')); - - // Confirm deletion. - $this->drupalPost(NULL, NULL, t('Cancel accounts')); - $status = TRUE; - foreach ($users as $account) { - $status = $status && (strpos($this->content, t('%name has been deleted.', array('%name' => $account->name))) !== FALSE); - $status = $status && !user_load($account->uid, TRUE); - } - $this->assertTrue($status, t('Users deleted and not found in the database.')); - - // Ensure that admin account was not cancelled. - $this->assertText(t('A confirmation request to cancel your account has been sent to your e-mail address.'), t('Account cancellation request mailed message displayed.')); - $admin_user = user_load($admin_user->uid); - $this->assertTrue($admin_user->status == 1, t('Administrative user is found in the database and enabled.')); - - // Verify that uid 1's account was not cancelled. - $user1 = user_load(1, TRUE); - $this->assertEqual($user1->status, 1, t('User #1 still exists and is not blocked.')); - } -} - -class UserPictureTestCase extends WebTestBase { - protected $user; - protected $_directory_test; - - public static function getInfo() { - return array( - 'name' => 'Upload user picture', - 'description' => 'Assure that dimension check, extension check and image scaling work as designed.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp(array('image')); - // Enable user pictures. - variable_set('user_pictures', 1); - - // Configure default user picture settings. - variable_set('user_picture_dimensions', '1024x1024'); - variable_set('user_picture_file_size', '800'); - variable_set('user_picture_style', 'thumbnail'); - - $this->user = $this->drupalCreateUser(); - - // Test if directories specified in settings exist in filesystem. - $file_dir = 'public://'; - $file_check = file_prepare_directory($file_dir, FILE_CREATE_DIRECTORY); - // TODO: Test public and private methods? - - $picture_dir = variable_get('user_picture_path', 'pictures'); - $picture_path = $file_dir . $picture_dir; - - $pic_check = file_prepare_directory($picture_path, FILE_CREATE_DIRECTORY); - $this->_directory_test = is_writable($picture_path); - $this->assertTrue($this->_directory_test, "The directory $picture_path doesn't exist or is not writable. Further tests won't be made."); - } - - function testNoPicture() { - $this->drupalLogin($this->user); - - // Try to upload a file that is not an image for the user picture. - $not_an_image = current($this->drupalGetTestFiles('html')); - $this->saveUserPicture($not_an_image); - $this->assertRaw(t('Only JPEG, PNG and GIF images are allowed.'), t('Non-image files are not accepted.')); - } - - /** - * Do the test: - * GD Toolkit is installed - * Picture has invalid dimension - * - * results: The image should be uploaded because ImageGDToolkit resizes the picture - */ - function testWithGDinvalidDimension() { - if ($this->_directory_test && image_get_toolkit()) { - $this->drupalLogin($this->user); - - $image = current($this->drupalGetTestFiles('image')); - $info = image_get_info($image->uri); - - // Set new variables: invalid dimensions, valid filesize (0 = no limit). - $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10); - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', 0); - - $pic_path = $this->saveUserPicture($image); - // Check that the image was resized and is being displayed on the - // user's profile page. - $text = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $test_dim)); - $this->assertRaw($text, t('Image was resized.')); - $alt = t("@user's picture", array('@user' => user_format_name($this->user))); - $style = variable_get('user_picture_style', ''); - $this->assertRaw(image_style_url($style, $pic_path), t("Image is displayed in user's edit page")); - - // Check if file is located in proper directory. - $this->assertTrue(is_file($pic_path), t("File is located in proper directory")); - } - } - - /** - * Do the test: - * GD Toolkit is installed - * Picture has invalid size - * - * results: The image should be uploaded because ImageGDToolkit resizes the picture - */ - function testWithGDinvalidSize() { - if ($this->_directory_test && image_get_toolkit()) { - $this->drupalLogin($this->user); - - // Images are sorted first by size then by name. We need an image - // bigger than 1 KB so we'll grab the last one. - $files = $this->drupalGetTestFiles('image'); - $image = end($files); - $info = image_get_info($image->uri); - - // Set new variables: valid dimensions, invalid filesize. - $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); - $test_size = 1; - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', $test_size); - - $pic_path = $this->saveUserPicture($image); - - // Test that the upload failed and that the correct reason was cited. - $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); - $this->assertRaw($text, t('Upload failed.')); - $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024))); - $this->assertRaw($text, t('File size cited as reason for failure.')); - - // Check if file is not uploaded. - $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); - } - } - - /** - * Do the test: - * GD Toolkit is not installed - * Picture has invalid size - * - * results: The image shouldn't be uploaded - */ - function testWithoutGDinvalidDimension() { - if ($this->_directory_test && !image_get_toolkit()) { - $this->drupalLogin($this->user); - - $image = current($this->drupalGetTestFiles('image')); - $info = image_get_info($image->uri); - - // Set new variables: invalid dimensions, valid filesize (0 = no limit). - $test_dim = ($info['width'] - 10) . 'x' . ($info['height'] - 10); - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', 0); - - $pic_path = $this->saveUserPicture($image); - - // Test that the upload failed and that the correct reason was cited. - $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); - $this->assertRaw($text, t('Upload failed.')); - $text = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $test_dim)); - $this->assertRaw($text, t('Checking response on invalid image (dimensions).')); - - // Check if file is not uploaded. - $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); - } - } - - /** - * Do the test: - * GD Toolkit is not installed - * Picture has invalid size - * - * results: The image shouldn't be uploaded - */ - function testWithoutGDinvalidSize() { - if ($this->_directory_test && !image_get_toolkit()) { - $this->drupalLogin($this->user); - - $image = current($this->drupalGetTestFiles('image')); - $info = image_get_info($image->uri); - - // Set new variables: valid dimensions, invalid filesize. - $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); - $test_size = 1; - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', $test_size); - - $pic_path = $this->saveUserPicture($image); - - // Test that the upload failed and that the correct reason was cited. - $text = t('The specified file %filename could not be uploaded.', array('%filename' => $image->filename)); - $this->assertRaw($text, t('Upload failed.')); - $text = t('The file is %filesize exceeding the maximum file size of %maxsize.', array('%filesize' => format_size(filesize($image->uri)), '%maxsize' => format_size($test_size * 1024))); - $this->assertRaw($text, t('File size cited as reason for failure.')); - - // Check if file is not uploaded. - $this->assertFalse(is_file($pic_path), t('File was not uploaded.')); - } - } - - /** - * Do the test: - * Picture is valid (proper size and dimension) - * - * results: The image should be uploaded - */ - function testPictureIsValid() { - if ($this->_directory_test) { - $this->drupalLogin($this->user); - - $image = current($this->drupalGetTestFiles('image')); - $info = image_get_info($image->uri); - - // Set new variables: valid dimensions, valid filesize (0 = no limit). - $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', 0); - - $pic_path = $this->saveUserPicture($image); - - // Check if image is displayed in user's profile page. - $this->drupalGet('user'); - $this->assertRaw(file_uri_target($pic_path), t("Image is displayed in user's profile page")); - - // Check if file is located in proper directory. - $this->assertTrue(is_file($pic_path), t('File is located in proper directory')); - - // Set new picture dimensions. - $test_dim = ($info['width'] + 5) . 'x' . ($info['height'] + 5); - variable_set('user_picture_dimensions', $test_dim); - - $pic_path2 = $this->saveUserPicture($image); - $this->assertNotEqual($pic_path, $pic_path2, t('Filename of second picture is different.')); - } - } - - /** - * Test HTTP schema working with user pictures. - */ - function testExternalPicture() { - $this->drupalLogin($this->user); - // Set the default picture to an URI with a HTTP schema. - $images = $this->drupalGetTestFiles('image'); - $image = $images[0]; - $pic_path = file_create_url($image->uri); - variable_set('user_picture_default', $pic_path); - - // Check if image is displayed in user's profile page. - $this->drupalGet('user'); - - // Get the user picture image via xpath. - $elements = $this->xpath('//div[@class="user-picture"]/img'); - $this->assertEqual(count($elements), 1, t("There is exactly one user picture on the user's profile page")); - $this->assertEqual($pic_path, (string) $elements[0]['src'], t("User picture source is correct: " . $pic_path . " " . print_r($elements, TRUE))); - } - - /** - * Tests deletion of user pictures. - */ - function testDeletePicture() { - $this->drupalLogin($this->user); - - $image = current($this->drupalGetTestFiles('image')); - $info = image_get_info($image->uri); - - // Set new variables: valid dimensions, valid filesize (0 = no limit). - $test_dim = ($info['width'] + 10) . 'x' . ($info['height'] + 10); - variable_set('user_picture_dimensions', $test_dim); - variable_set('user_picture_file_size', 0); - - // Save a new picture. - $edit = array('files[picture_upload]' => drupal_realpath($image->uri)); - $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); - - // Load actual user data from database. - $account = user_load($this->user->uid, TRUE); - $pic_path = !empty($account->picture) ? $account->picture->uri : NULL; - - // Check if image is displayed in user's profile page. - $this->drupalGet('user'); - $this->assertRaw(file_uri_target($pic_path), "Image is displayed in user's profile page"); - - // Check if file is located in proper directory. - $this->assertTrue(is_file($pic_path), 'File is located in proper directory'); - - $edit = array('picture_delete' => 1); - $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); - - // Load actual user data from database. - $account1 = user_load($this->user->uid, TRUE); - $this->assertFalse($account1->picture, 'User object has no picture'); - - $file = file_load($account->picture->fid); - $this->assertFalse($file, 'File is removed from database'); - - // Clear out PHP's file stat cache so we see the current value. - clearstatcache(); - $this->assertFalse(is_file($pic_path), 'File is removed from file system'); - } - - function saveUserPicture($image) { - $edit = array('files[picture_upload]' => drupal_realpath($image->uri)); - $this->drupalPost('user/' . $this->user->uid . '/edit', $edit, t('Save')); - - // Load actual user data from database. - $account = user_load($this->user->uid, TRUE); - return !empty($account->picture) ? $account->picture->uri : NULL; - } - - /** - * Tests the admin form validates user picture settings. - */ - function testUserPictureAdminFormValidation() { - $this->drupalLogin($this->drupalCreateUser(array('administer users'))); - - // The default values are valid. - $this->drupalPost('admin/config/people/accounts', array(), t('Save configuration')); - $this->assertText(t('The configuration options have been saved.'), 'The default values are valid.'); - - // The form does not save with an invalid file size. - $edit = array( - 'user_picture_file_size' => $this->randomName(), - ); - $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); - $this->assertNoText(t('The configuration options have been saved.'), 'The form does not save with an invalid file size.'); - } -} - - -class UserPermissionsTestCase extends WebTestBase { - protected $admin_user; - protected $rid; - - public static function getInfo() { - return array( - 'name' => 'Role permissions', - 'description' => 'Verify that role permissions can be added and removed via the permissions page.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp(); - - $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'access user profiles', 'administer site configuration', 'administer modules', 'administer users')); - - // Find the new role ID - it must be the maximum. - $all_rids = array_keys($this->admin_user->roles); - sort($all_rids); - $this->rid = array_pop($all_rids); - } - - /** - * Change user permissions and check user_access(). - */ - function testUserPermissionChanges() { - $this->drupalLogin($this->admin_user); - $rid = $this->rid; - $account = $this->admin_user; - - // Add a permission. - $this->assertFalse(user_access('administer nodes', $account), t('User does not have "administer nodes" permission.')); - $edit = array(); - $edit[$rid . '[administer nodes]'] = TRUE; - $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t('Successful save message displayed.')); - drupal_static_reset('user_access'); - drupal_static_reset('user_role_permissions'); - $this->assertTrue(user_access('administer nodes', $account), t('User now has "administer nodes" permission.')); - - // Remove a permission. - $this->assertTrue(user_access('access user profiles', $account), t('User has "access user profiles" permission.')); - $edit = array(); - $edit[$rid . '[access user profiles]'] = FALSE; - $this->drupalPost('admin/people/permissions', $edit, t('Save permissions')); - $this->assertText(t('The changes have been saved.'), t('Successful save message displayed.')); - drupal_static_reset('user_access'); - drupal_static_reset('user_role_permissions'); - $this->assertFalse(user_access('access user profiles', $account), t('User no longer has "access user profiles" permission.')); - } - - /** - * Test assigning of permissions for the administrator role. - */ - function testAdministratorRole() { - $this->drupalLogin($this->admin_user); - $this->drupalGet('admin/config/people/accounts'); - - // Set the user's role to be the administrator role. - $edit = array(); - $edit['user_admin_role'] = $this->rid; - $this->drupalPost('admin/config/people/accounts', $edit, t('Save configuration')); - - // Enable aggregator module and ensure the 'administer news feeds' - // permission is assigned by default. - $edit = array(); - $edit['modules[Core][aggregator][enable]'] = TRUE; - $this->drupalPost('admin/modules', $edit, t('Save configuration')); - $this->assertTrue(user_access('administer news feeds', $this->admin_user), t('The permission was automatically assigned to the administrator role')); - } - - /** - * Verify proper permission changes by user_role_change_permissions(). - */ - function testUserRoleChangePermissions() { - $rid = $this->rid; - $account = $this->admin_user; - - // Verify current permissions. - $this->assertFalse(user_access('administer nodes', $account), t('User does not have "administer nodes" permission.')); - $this->assertTrue(user_access('access user profiles', $account), t('User has "access user profiles" permission.')); - $this->assertTrue(user_access('administer site configuration', $account), t('User has "administer site configuration" permission.')); - - // Change permissions. - $permissions = array( - 'administer nodes' => 1, - 'access user profiles' => 0, - ); - user_role_change_permissions($rid, $permissions); - - // Verify proper permission changes. - $this->assertTrue(user_access('administer nodes', $account), t('User now has "administer nodes" permission.')); - $this->assertFalse(user_access('access user profiles', $account), t('User no longer has "access user profiles" permission.')); - $this->assertTrue(user_access('administer site configuration', $account), t('User still has "administer site configuration" permission.')); - } -} - -class UserAdminTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User administration', - 'description' => 'Test user administration page functionality.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp(array('taxonomy')); - } - - /** - * Registers a user and deletes it. - */ - function testUserAdmin() { - - $user_a = $this->drupalCreateUser(array()); - $user_b = $this->drupalCreateUser(array('administer taxonomy')); - $user_c = $this->drupalCreateUser(array('administer taxonomy')); - - // Create admin user to delete registered user. - $admin_user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin_user); - $this->drupalGet('admin/people'); - $this->assertText($user_a->name, t('Found user A on admin users page')); - $this->assertText($user_b->name, t('Found user B on admin users page')); - $this->assertText($user_c->name, t('Found user C on admin users page')); - $this->assertText($admin_user->name, t('Found Admin user on admin users page')); - - // Test for existence of edit link in table. - $link = l(t('edit'), "user/$user_a->uid/edit", array('query' => array('destination' => 'admin/people'))); - $this->assertRaw($link, t('Found user A edit link on admin users page')); - - // Filter the users by permission 'administer taxonomy'. - $edit = array(); - $edit['permission'] = 'administer taxonomy'; - $this->drupalPost('admin/people', $edit, t('Filter')); - - // Check if the correct users show up. - $this->assertNoText($user_a->name, t('User A not on filtered by perm admin users page')); - $this->assertText($user_b->name, t('Found user B on filtered by perm admin users page')); - $this->assertText($user_c->name, t('Found user C on filtered by perm admin users page')); - - // Filter the users by role. Grab the system-generated role name for User C. - $edit['role'] = max(array_flip($user_c->roles)); - $this->drupalPost('admin/people', $edit, t('Refine')); - - // Check if the correct users show up when filtered by role. - $this->assertNoText($user_a->name, t('User A not on filtered by role on admin users page')); - $this->assertNoText($user_b->name, t('User B not on filtered by role on admin users page')); - $this->assertText($user_c->name, t('User C on filtered by role on admin users page')); - - // Test blocking of a user. - $account = user_load($user_c->uid); - $this->assertEqual($account->status, 1, 'User C not blocked'); - $edit = array(); - $edit['operation'] = 'block'; - $edit['accounts[' . $account->uid . ']'] = TRUE; - $this->drupalPost('admin/people', $edit, t('Update')); - $account = user_load($user_c->uid, TRUE); - $this->assertEqual($account->status, 0, 'User C blocked'); - - // Test unblocking of a user from /admin/people page and sending of activation mail - $editunblock = array(); - $editunblock['operation'] = 'unblock'; - $editunblock['accounts[' . $account->uid . ']'] = TRUE; - $this->drupalPost('admin/people', $editunblock, t('Update')); - $account = user_load($user_c->uid, TRUE); - $this->assertEqual($account->status, 1, 'User C unblocked'); - $this->assertMail("to", $account->mail, "Activation mail sent to user C"); - - // Test blocking and unblocking another user from /user/[uid]/edit form and sending of activation mail - $user_d = $this->drupalCreateUser(array()); - $account1 = user_load($user_d->uid, TRUE); - $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => 0), t('Save')); - $account1 = user_load($user_d->uid, TRUE); - $this->assertEqual($account1->status, 0, 'User D blocked'); - $this->drupalPost('user/' . $account1->uid . '/edit', array('status' => TRUE), t('Save')); - $account1 = user_load($user_d->uid, TRUE); - $this->assertEqual($account1->status, 1, 'User D unblocked'); - $this->assertMail("to", $account1->mail, "Activation mail sent to user D"); - } -} - -/** - * Tests for user-configurable time zones. - */ -class UserTimeZoneFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User time zones', - 'description' => 'Set a user time zone and verify that dates are displayed in local time.', - 'group' => 'User', - ); - } - - /** - * Tests the display of dates and time when user-configurable time zones are set. - */ - function testUserTimeZone() { - // Setup date/time settings for Los Angeles time. - variable_set('date_default_timezone', 'America/Los_Angeles'); - variable_set('configurable_timezones', 1); - variable_set('date_format_medium', 'Y-m-d H:i T'); - - // Create a user account and login. - $web_user = $this->drupalCreateUser(); - $this->drupalLogin($web_user); - - // Create some nodes with different authored-on dates. - // Two dates in PST (winter time): - $date1 = '2007-03-09 21:00:00 -0800'; - $date2 = '2007-03-11 01:00:00 -0800'; - // One date in PDT (summer time): - $date3 = '2007-03-20 21:00:00 -0700'; - $node1 = $this->drupalCreateNode(array('created' => strtotime($date1), 'type' => 'article')); - $node2 = $this->drupalCreateNode(array('created' => strtotime($date2), 'type' => 'article')); - $node3 = $this->drupalCreateNode(array('created' => strtotime($date3), 'type' => 'article')); - - // Confirm date format and time zone. - $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-09 21:00 PST', t('Date should be PST.')); - $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 01:00 PST', t('Date should be PST.')); - $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-20 21:00 PDT', t('Date should be PDT.')); - - // Change user time zone to Santiago time. - $edit = array(); - $edit['mail'] = $web_user->mail; - $edit['timezone'] = 'America/Santiago'; - $this->drupalPost("user/$web_user->uid/edit", $edit, t('Save')); - $this->assertText(t('The changes have been saved.'), t('Time zone changed to Santiago time.')); - - // Confirm date format and time zone. - $this->drupalGet("node/$node1->nid"); - $this->assertText('2007-03-10 02:00 CLST', t('Date should be Chile summer time; five hours ahead of PST.')); - $this->drupalGet("node/$node2->nid"); - $this->assertText('2007-03-11 05:00 CLT', t('Date should be Chile time; four hours ahead of PST')); - $this->drupalGet("node/$node3->nid"); - $this->assertText('2007-03-21 00:00 CLT', t('Date should be Chile time; three hours ahead of PDT.')); - } -} - -/** - * Test user autocompletion. - */ -class UserAutocompleteTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User autocompletion', - 'description' => 'Test user autocompletion functionality.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp(); - - // Set up two users with different permissions to test access. - $this->unprivileged_user = $this->drupalCreateUser(); - $this->privileged_user = $this->drupalCreateUser(array('access user profiles')); - } - - /** - * Tests access to user autocompletion and verify the correct results. - */ - function testUserAutocomplete() { - // Check access from unprivileged user, should be denied. - $this->drupalLogin($this->unprivileged_user); - $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]); - $this->assertResponse(403, t('Autocompletion access denied to user without permission.')); - - // Check access from privileged user. - $this->drupalLogout(); - $this->drupalLogin($this->privileged_user); - $this->drupalGet('user/autocomplete/' . $this->unprivileged_user->name[0]); - $this->assertResponse(200, t('Autocompletion access allowed.')); - - // Using first letter of the user's name, make sure the user's full name is in the results. - $this->assertRaw($this->unprivileged_user->name, t('User name found in autocompletion results.')); - } -} - - -/** - * Test user-links in secondary menu. - */ -class UserAccountLinksUnitTests extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User account links', - 'description' => 'Test user-account links.', - 'group' => 'User' - ); - } - - /** - * Tests the secondary menu. - */ - function testSecondaryMenu() { - // Create a regular user. - $user = $this->drupalCreateUser(array()); - - // Log in and get the homepage. - $this->drupalLogin($user); - $this->drupalGet('<front>'); - - // For a logged-in user, expect the secondary menu to have links for "My - // account" and "Log out". - $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_id' => 'secondary-menu', - ':href' => 'user', - ':text' => 'My account', - )); - $this->assertEqual(count($link), 1, 'My account link is in secondary menu.'); - - $link = $this->xpath('//ul[@id=:menu_id]/li/a[contains(@href, :href) and text()=:text]', array( - ':menu_id' => 'secondary-menu', - ':href' => 'user/logout', - ':text' => 'Log out', - )); - $this->assertEqual(count($link), 1, 'Log out link is in secondary menu.'); - - // Log out and get the homepage. - $this->drupalLogout(); - $this->drupalGet('<front>'); - - // For a logged-out user, expect no secondary links. - $element = $this->xpath('//ul[@id=:menu_id]', array(':menu_id' => 'secondary-menu')); - $this->assertEqual(count($element), 0, 'No secondary-menu for logged-out users.'); - } -} - -/** - * Test user blocks. - */ -class UserBlocksUnitTests extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User blocks', - 'description' => 'Test user blocks.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp(array('block')); - - // Enable user login block. - db_merge('block') - ->key(array( - 'module' => 'user', - 'delta' => 'login', - 'theme' => variable_get('theme_default', 'stark'), - )) - ->fields(array( - 'status' => 1, - 'weight' => 0, - 'region' => 'sidebar_first', - 'pages' => '', - 'cache' => -1, - )) - ->execute(); - } - - /** - * Test the user login block. - */ - function testUserLoginBlock() { - // Create a user with some permission that anonymous users lack. - $user = $this->drupalCreateUser(array('administer permissions')); - - // Log in using the block. - $edit = array(); - $edit['name'] = $user->name; - $edit['pass'] = $user->pass_raw; - $this->drupalPost('admin/people/permissions', $edit, t('Log in')); - $this->assertNoText(t('User login'), t('Logged in.')); - - // Check that we are still on the same page. - $this->assertEqual(url('admin/people/permissions', array('absolute' => TRUE)), $this->getUrl(), t('Still on the same page after login for access denied page')); - - // Now, log out and repeat with a non-403 page. - $this->drupalLogout(); - $this->drupalPost('filter/tips', $edit, t('Log in')); - $this->assertNoText(t('User login'), t('Logged in.')); - $this->assertPattern('!<title.*?' . t('Compose tips') . '.*?</title>!', t('Still on the same page after login for allowed page')); - - // Check that the user login block is not vulnerable to information - // disclosure to third party sites. - $this->drupalLogout(); - $this->drupalPost('http://example.com/', $edit, t('Log in'), array('external' => FALSE)); - // Check that we remain on the site after login. - $this->assertEqual(url('user/' . $user->uid, array('absolute' => TRUE)), $this->getUrl(), t('Redirected to user profile page after login from the frontpage')); - } - - /** - * Test the Who's Online block. - */ - function testWhosOnlineBlock() { - // Generate users and make sure there are no current user sessions. - $user1 = $this->drupalCreateUser(array()); - $user2 = $this->drupalCreateUser(array()); - $user3 = $this->drupalCreateUser(array()); - $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions}")->fetchField(), 0, t('Sessions table is empty.')); - - // Insert a user with two sessions. - $this->insertSession(array('uid' => $user1->uid)); - $this->insertSession(array('uid' => $user1->uid)); - $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid", array(':uid' => $user1->uid))->fetchField(), 2, t('Duplicate user session has been inserted.')); - - // Insert a user with only one session. - $this->insertSession(array('uid' => $user2->uid, 'timestamp' => REQUEST_TIME + 1)); - - // Insert an inactive logged-in user who should not be seen in the block. - $this->insertSession(array('uid' => $user3->uid, 'timestamp' => (REQUEST_TIME - variable_get('user_block_seconds_online', 900) - 1))); - - // Insert two anonymous user sessions. - $this->insertSession(); - $this->insertSession(); - - // Test block output. - $block = user_block_view('online'); - $this->drupalSetContent($block['content']); - $this->assertRaw(t('2 users'), t('Correct number of online users (2 users).')); - $this->assertText($user1->name, t('Active user 1 found in online list.')); - $this->assertText($user2->name, t('Active user 2 found in online list.')); - $this->assertNoText($user3->name, t("Inactive user not found in online list.")); - $this->assertTrue(strpos($this->drupalGetContent(), $user1->name) > strpos($this->drupalGetContent(), $user2->name), t('Online users are ordered correctly.')); - } - - /** - * Insert a user session into the {sessions} table. This function is used - * since we cannot log in more than one user at the same time in tests. - */ - private function insertSession(array $fields = array()) { - $fields += array( - 'uid' => 0, - 'sid' => drupal_hash_base64(uniqid(mt_rand(), TRUE)), - 'timestamp' => REQUEST_TIME, - ); - db_insert('sessions') - ->fields($fields) - ->execute(); - $this->assertEqual(db_query("SELECT COUNT(*) FROM {sessions} WHERE uid = :uid AND sid = :sid AND timestamp = :timestamp", array(':uid' => $fields['uid'], ':sid' => $fields['sid'], ':timestamp' => $fields['timestamp']))->fetchField(), 1, t('Session record inserted.')); - } -} - -/** - * Tests user_save() behavior. - */ -class UserSaveTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User save test', - 'description' => 'Test account saving for arbitrary new uid.', - 'group' => 'User', - ); - } - - /** - * Test creating a user with arbitrary uid. - */ - function testUserImport() { - // User ID must be a number that is not in the database. - $max_uid = db_query('SELECT MAX(uid) FROM {users}')->fetchField(); - $test_uid = $max_uid + mt_rand(1000, 1000000); - $test_name = $this->randomName(); - - // Create the base user, based on drupalCreateUser(). - $user = entity_create('user', array( - 'name' => $test_name, - 'uid' => $test_uid, - 'mail' => $test_name . '@example.com', - 'pass' => user_password(), - 'status' => 1, - )); - $user->enforceIsNew(); - $user->save(); - - // Test if created user exists. - $user_by_uid = user_load($test_uid); - $this->assertTrue($user_by_uid, t('Loading user by uid.')); - - $user_by_name = user_load_by_name($test_name); - $this->assertTrue($user_by_name, t('Loading user by name.')); - } -} - -/** - * Test the create user administration page. - */ -class UserCreateTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User create', - 'description' => 'Test the create user administration page.', - 'group' => 'User', - ); - } - - /** - * Create a user through the administration interface and ensure that it - * displays in the user list. - */ - protected function testUserAdd() { - $user = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($user); - - foreach (array(FALSE, TRUE) as $notify) { - $edit = array( - 'name' => $this->randomName(), - 'mail' => $this->randomName() . '@example.com', - 'pass[pass1]' => $pass = $this->randomString(), - 'pass[pass2]' => $pass, - 'notify' => $notify, - ); - $this->drupalPost('admin/people/create', $edit, t('Create new account')); - - if ($notify) { - $this->assertText(t('A welcome message with further instructions has been e-mailed to the new user @name.', array('@name' => $edit['name'])), 'User created'); - $this->assertEqual(count($this->drupalGetMails()), 1, 'Notification e-mail sent'); - } - else { - $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created'); - $this->assertEqual(count($this->drupalGetMails()), 0, 'Notification e-mail not sent'); - } - - $this->drupalGet('admin/people'); - $this->assertText($edit['name'], 'User found in list of users'); - } - } -} - -/** - * Tests the user edit form. - */ -class UserEditTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User edit', - 'description' => 'Test user edit page.', - 'group' => 'User', - ); - } - - /** - * Test user edit page. - */ - function testUserEdit() { - // Test user edit functionality with user pictures disabled. - variable_set('user_pictures', 0); - $user1 = $this->drupalCreateUser(array('change own username')); - $user2 = $this->drupalCreateUser(array()); - $this->drupalLogin($user1); - - // Test that error message appears when attempting to use a non-unique user name. - $edit['name'] = $user2->name; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); - - // Repeat the test with user pictures enabled, which modifies the form. - variable_set('user_pictures', 1); - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t('The name %name is already taken.', array('%name' => $edit['name']))); - - // Check that filling out a single password field does not validate. - $edit = array(); - $edit['pass[pass1]'] = ''; - $edit['pass[pass2]'] = $this->randomName(); - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); - - $edit['pass[pass1]'] = $this->randomName(); - $edit['pass[pass2]'] = ''; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertText(t("The specified passwords do not match."), t('Typing mismatched passwords displays an error message.')); - - // Test that the error message appears when attempting to change the mail or - // pass without the current password. - $edit = array(); - $edit['mail'] = $this->randomName() . '@new.example.com'; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('E-mail address')))); - - $edit['current_pass'] = $user1->pass_raw; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t("The changes have been saved.")); - - // Test that the user must enter current password before changing passwords. - $edit = array(); - $edit['pass[pass1]'] = $new_pass = $this->randomName(); - $edit['pass[pass2]'] = $new_pass; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => t('Password')))); - - // Try again with the current password. - $edit['current_pass'] = $user1->pass_raw; - $this->drupalPost("user/$user1->uid/edit", $edit, t('Save')); - $this->assertRaw(t("The changes have been saved.")); - - // Make sure the user can log in with their new password. - $this->drupalLogout(); - $user1->pass_raw = $new_pass; - $this->drupalLogin($user1); - $this->drupalLogout(); - } - - /** - * Tests editing of a user account without an e-mail address. - */ - function testUserWithoutEmailEdit() { - // Test that an admin can edit users without an e-mail address. - $admin = $this->drupalCreateUser(array('administer users')); - $this->drupalLogin($admin); - // Create a regular user. - $user1 = $this->drupalCreateUser(array()); - // This user has no e-mail address. - $user1->mail = ''; - $user1->save(); - $this->drupalPost("user/$user1->uid/edit", array('mail' => ''), t('Save')); - $this->assertRaw(t("The changes have been saved.")); - } -} - -/** - * Test case for user signatures. - */ -class UserSignatureTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User signatures', - 'description' => 'Test user signatures.', - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp('comment'); - - // Enable user signatures. - variable_set('user_signatures', 1); - - // Create Basic page node type. - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - // Prefetch and create text formats. - $this->plain_text_format = filter_format_load('plain_text'); - - $filtered_html_format = array( - 'format' => 'filtered_html', - 'name' => 'Filtered HTML', - ); - $this->filtered_html_format = (object) $filtered_html_format; - filter_format_save($this->filtered_html_format); - - $full_html_format = array( - 'format' => 'full_html', - 'name' => 'Full HTML', - ); - $this->full_html_format = (object) $full_html_format; - filter_format_save($this->full_html_format); - - user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array(filter_permission_name($this->filtered_html_format))); - $this->checkPermissions(array(), TRUE); - - // Create regular and administrative users. - $this->web_user = $this->drupalCreateUser(array('post comments')); - - $admin_permissions = array('administer comments'); - foreach (filter_formats() as $format) { - if ($permission = filter_permission_name($format)) { - $admin_permissions[] = $permission; - } - } - $this->admin_user = $this->drupalCreateUser($admin_permissions); - } - - /** - * Test that a user can change their signature format and that it is respected - * upon display. - */ - function testUserSignature() { - // Create a new node with comments on. - $node = $this->drupalCreateNode(array('comment' => COMMENT_NODE_OPEN)); - - // Verify that user signature field is not displayed on registration form. - $this->drupalGet('user/register'); - $this->assertNoText(t('Signature')); - - // Log in as a regular user and create a signature. - $this->drupalLogin($this->web_user); - $signature_text = "<h1>" . $this->randomName() . "</h1>"; - $edit = array( - 'signature[value]' => $signature_text, - 'signature[format]' => $this->plain_text_format->format, - ); - $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); - - // Verify that values were stored. - $this->assertFieldByName('signature[value]', $edit['signature[value]'], 'Submitted signature text found.'); - $this->assertFieldByName('signature[format]', $edit['signature[format]'], 'Submitted signature format found.'); - - // Create a comment. - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit = array(); - $edit['subject'] = $this->randomName(8); - $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); - $this->drupalPost('comment/reply/' . $node->nid, $edit, t('Preview')); - $this->drupalPost(NULL, array(), t('Save')); - - // Get the comment ID. (This technique is the same one used in the Comment - // module's CommentTestBase test case.) - preg_match('/#comment-([0-9]+)/', $this->getURL(), $match); - $comment_id = $match[1]; - - // Log in as an administrator and edit the comment to use Full HTML, so - // that the comment text itself is not filtered at all. - $this->drupalLogin($this->admin_user); - $edit['comment_body[' . $langcode . '][0][format]'] = $this->full_html_format->format; - $this->drupalPost('comment/' . $comment_id . '/edit', $edit, t('Save')); - - // Assert that the signature did not make it through unfiltered. - $this->drupalGet('node/' . $node->nid); - $this->assertNoRaw($signature_text, 'Unfiltered signature text not found.'); - $this->assertRaw(check_markup($signature_text, $this->plain_text_format->format), 'Filtered signature text found.'); - } -} - -/* - * Test that a user, having editing their own account, can still log in. - */ -class UserEditedOwnAccountTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User edited own account', - 'description' => 'Test user edited own account can still log in.', - 'group' => 'User', - ); - } - - function testUserEditedOwnAccount() { - // Change account setting 'Who can register accounts?' to Administrators - // only. - variable_set('user_register', USER_REGISTER_ADMINISTRATORS_ONLY); - - // Create a new user account and log in. - $account = $this->drupalCreateUser(array('change own username')); - $this->drupalLogin($account); - - // Change own username. - $edit = array(); - $edit['name'] = $this->randomName(); - $this->drupalPost('user/' . $account->uid . '/edit', $edit, t('Save')); - - // Log out. - $this->drupalLogout(); - - // Set the new name on the user account and attempt to log back in. - $account->name = $edit['name']; - $this->drupalLogin($account); - } -} - -/** - * Test case to test adding, editing and deleting roles. - */ -class UserRoleAdminTestCase extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User role administration', - 'description' => 'Test adding, editing and deleting user roles and changing role weights.', - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp(); - $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users')); - } - - /** - * Test adding, renaming and deleting roles. - */ - function testRoleAdministration() { - $this->drupalLogin($this->admin_user); - - // Test adding a role. (In doing so, we use a role name that happens to - // correspond to an integer, to test that the role administration pages - // correctly distinguish between role names and IDs.) - $role_name = '123'; - $edit = array('name' => $role_name); - $this->drupalPost('admin/people/permissions/roles', $edit, t('Add role')); - $this->assertText(t('The role has been added.'), t('The role has been added.')); - $role = user_role_load_by_name($role_name); - $this->assertTrue(is_object($role), t('The role was successfully retrieved from the database.')); - - // Try adding a duplicate role. - $this->drupalPost(NULL, $edit, t('Add role')); - $this->assertRaw(t('The role name %name already exists. Choose another role name.', array('%name' => $role_name)), t('Duplicate role warning displayed.')); - - // Test renaming a role. - $old_name = $role_name; - $role_name = '456'; - $edit = array('name' => $role_name); - $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", $edit, t('Save role')); - $this->assertText(t('The role has been renamed.'), t('The role has been renamed.')); - $this->assertFalse(user_role_load_by_name($old_name), t('The role can no longer be retrieved from the database using its old name.')); - $this->assertTrue(is_object(user_role_load_by_name($role_name)), t('The role can be retrieved from the database using its new name.')); - - // Test deleting a role. - $this->drupalPost("admin/people/permissions/roles/edit/{$role->rid}", NULL, t('Delete role')); - $this->drupalPost(NULL, NULL, t('Delete')); - $this->assertText(t('The role has been deleted.'), t('The role has been deleted')); - $this->assertNoLinkByHref("admin/people/permissions/roles/edit/{$role->rid}", t('Role edit link removed.')); - $this->assertFalse(user_role_load_by_name($role_name), t('A deleted role can no longer be loaded.')); - - // Make sure that the system-defined roles cannot be edited via the user - // interface. - $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_ANONYMOUS_RID); - $this->assertResponse(403, t('Access denied when trying to edit the built-in anonymous role.')); - $this->drupalGet('admin/people/permissions/roles/edit/' . DRUPAL_AUTHENTICATED_RID); - $this->assertResponse(403, t('Access denied when trying to edit the built-in authenticated role.')); - } - - /** - * Test user role weight change operation. - */ - function testRoleWeightChange() { - $this->drupalLogin($this->admin_user); - - // Pick up a random role and get its weight. - $rid = array_rand(user_roles()); - $role = user_role_load($rid); - $old_weight = $role->weight; - - // Change the role weight and submit the form. - $edit = array('roles['. $rid .'][weight]' => $old_weight + 1); - $this->drupalPost('admin/people/permissions/roles', $edit, t('Save order')); - $this->assertText(t('The role settings have been updated.'), t('The role settings form submitted successfully.')); - - // Retrieve the saved role and compare its weight. - $role = user_role_load($rid); - $new_weight = $role->weight; - $this->assertTrue(($old_weight + 1) == $new_weight, t('Role weight updated successfully.')); - } -} - -/** - * Test user token replacement in strings. - */ -class UserTokenReplaceTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User token replacement', - 'description' => 'Generates text using placeholders for dummy content to check user token replacement.', - 'group' => 'User', - ); - } - - /** - * Creates a user, then tests the tokens generated from it. - */ - function testUserTokenReplacement() { - global $language_interface; - $url_options = array( - 'absolute' => TRUE, - 'language' => $language_interface, - ); - - // Create two users and log them in one after another. - $user1 = $this->drupalCreateUser(array()); - $user2 = $this->drupalCreateUser(array()); - $this->drupalLogin($user1); - $this->drupalLogout(); - $this->drupalLogin($user2); - - $account = user_load($user1->uid); - $global_account = user_load($GLOBALS['user']->uid); - - // Generate and test sanitized tokens. - $tests = array(); - $tests['[user:uid]'] = $account->uid; - $tests['[user:name]'] = check_plain(user_format_name($account)); - $tests['[user:mail]'] = check_plain($account->mail); - $tests['[user:url]'] = url("user/$account->uid", $url_options); - $tests['[user:edit-url]'] = url("user/$account->uid/edit", $url_options); - $tests['[user:last-login]'] = format_date($account->login, 'medium', '', NULL, $language_interface->langcode); - $tests['[user:last-login:short]'] = format_date($account->login, 'short', '', NULL, $language_interface->langcode); - $tests['[user:created]'] = format_date($account->created, 'medium', '', NULL, $language_interface->langcode); - $tests['[user:created:short]'] = format_date($account->created, 'short', '', NULL, $language_interface->langcode); - $tests['[current-user:name]'] = check_plain(user_format_name($global_account)); - - // Test to make sure that we generated something for each token. - $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.')); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('user' => $account), array('language' => $language_interface)); - $this->assertEqual($output, $expected, t('Sanitized user token %token replaced.', array('%token' => $input))); - } - - // Generate and test unsanitized tokens. - $tests['[user:name]'] = user_format_name($account); - $tests['[user:mail]'] = $account->mail; - $tests['[current-user:name]'] = user_format_name($global_account); - - foreach ($tests as $input => $expected) { - $output = token_replace($input, array('user' => $account), array('language' => $language_interface, 'sanitize' => FALSE)); - $this->assertEqual($output, $expected, t('Unsanitized user token %token replaced.', array('%token' => $input))); - } - } -} - -/** - * Test user search. - */ -class UserUserSearchTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User search', - 'description' => 'Testing that only user with the right permission can see the email address in the user search.', - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp(array('search')); - } - - function testUserSearch() { - $user1 = $this->drupalCreateUser(array('access user profiles', 'search content', 'use advanced search')); - $this->drupalLogin($user1); - $keys = $user1->mail; - $edit = array('keys' => $keys); - $this->drupalPost('search/user/', $edit, t('Search')); - $this->assertNoText($keys); - $this->drupalLogout(); - - $user2 = $this->drupalCreateUser(array('administer users', 'access user profiles', 'search content', 'use advanced search')); - $this->drupalLogin($user2); - $keys = $user2->mail; - $edit = array('keys' => $keys); - $this->drupalPost('search/user/', $edit, t('Search')); - $this->assertText($keys); - $this->drupalLogout(); - } -} - - -/** - * Test role assignment. - */ -class UserRolesAssignmentTestCase extends WebTestBase { - protected $admin_user; - - public static function getInfo() { - return array( - 'name' => t('Role assignment'), - 'description' => t('Tests that users can be assigned and unassigned roles.'), - 'group' => t('User') - ); - } - - function setUp() { - parent::setUp(); - $this->admin_user = $this->drupalCreateUser(array('administer permissions', 'administer users')); - $this->drupalLogin($this->admin_user); - } - - /** - * Tests that a user can be assigned a role and that the role can be removed - * again. - */ - function testAssignAndRemoveRole() { - $rid = $this->drupalCreateRole(array('administer content types')); - $account = $this->drupalCreateUser(); - - // Assign the role to the user. - $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => $rid), t('Save')); - $this->assertText(t('The changes have been saved.')); - $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); - $this->userLoadAndCheckRoleAssigned($account, $rid); - - // Remove the role from the user. - $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); - $this->assertText(t('The changes have been saved.')); - $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); - $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); - } - - /** - * Tests that when creating a user the role can be assigned. And that it can - * be removed again. - */ - function testCreateUserWithRole() { - $rid = $this->drupalCreateRole(array('administer content types')); - // Create a new user and add the role at the same time. - $edit = array( - 'name' => $this->randomName(), - 'mail' => $this->randomName() . '@example.com', - 'pass[pass1]' => $pass = $this->randomString(), - 'pass[pass2]' => $pass, - "roles[$rid]" => $rid, - ); - $this->drupalPost('admin/people/create', $edit, t('Create new account')); - $this->assertText(t('Created a new user account for !name.', array('!name' => $edit['name']))); - // Get the newly added user. - $account = user_load_by_name($edit['name']); - - $this->drupalGet('user/' . $account->uid . '/edit'); - $this->assertFieldChecked('edit-roles-' . $rid, t('Role is assigned.')); - $this->userLoadAndCheckRoleAssigned($account, $rid); - - // Remove the role again. - $this->drupalPost('user/' . $account->uid . '/edit', array("roles[$rid]" => FALSE), t('Save')); - $this->assertText(t('The changes have been saved.')); - $this->assertNoFieldChecked('edit-roles-' . $rid, t('Role is removed from user.')); - $this->userLoadAndCheckRoleAssigned($account, $rid, FALSE); - } - - /** - * Check role on user object. - * - * @param object $account - * The user account to check. - * @param string $rid - * The role ID to search for. - * @param bool $is_assigned - * (optional) Whether to assert that $rid exists (TRUE) or not (FALSE). - * Defaults to TRUE. - */ - private function userLoadAndCheckRoleAssigned($account, $rid, $is_assigned = TRUE) { - $account = user_load($account->uid, TRUE); - if ($is_assigned) { - $this->assertTrue(array_key_exists($rid, $account->roles), t('The role is present in the user object.')); - } - else { - $this->assertFalse(array_key_exists($rid, $account->roles), t('The role is not present in the user object.')); - } - } -} - - -/** - * Unit test for authmap assignment. - */ -class UserAuthmapAssignmentTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => t('Authmap assignment'), - 'description' => t('Tests that users can be assigned and unassigned authmaps.'), - 'group' => t('User') - ); - } - - /** - * Test authmap assignment and retrieval. - */ - function testAuthmapAssignment() { - $account = $this->drupalCreateUser(); - - // Assign authmaps to the user. - $authmaps = array( - 'authname_poll' => 'external username one', - 'authname_book' => 'external username two', - ); - user_set_authmaps($account, $authmaps); - - // Test for expected authmaps. - $expected_authmaps = array( - 'external username one' => array( - 'poll' => 'external username one', - ), - 'external username two' => array( - 'book' => 'external username two', - ), - ); - foreach ($expected_authmaps as $authname => $expected_output) { - $this->assertIdentical(user_get_authmaps($authname), $expected_output, t('Authmap for authname %authname was set correctly.', array('%authname' => $authname))); - } - - // Remove authmap for module poll, add authmap for module blog. - $authmaps = array( - 'authname_poll' => NULL, - 'authname_blog' => 'external username three', - ); - user_set_authmaps($account, $authmaps); - - // Assert that external username one does not have authmaps. - $remove_username = 'external username one'; - unset($expected_authmaps[$remove_username]); - $this->assertFalse(user_get_authmaps($remove_username), t('Authmap for %authname was removed.', array('%authname' => $remove_username))); - - // Assert that a new authmap was created for external username three, and - // existing authmaps for external username two were unchanged. - $expected_authmaps['external username three'] = array('blog' => 'external username three'); - foreach ($expected_authmaps as $authname => $expected_output) { - $this->assertIdentical(user_get_authmaps($authname), $expected_output, t('Authmap for authname %authname was set correctly.', array('%authname' => $authname))); - } - } -} - -/** - * Tests user_validate_current_pass on a custom form. - */ -class UserValidateCurrentPassCustomForm extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User validate current pass custom form', - 'description' => 'Test that user_validate_current_pass is usable on a custom form.', - 'group' => 'User', - ); - } - - /** - * User with permission to view content. - */ - protected $accessUser; - - /** - * User permission to administer users. - */ - protected $adminUser; - - function setUp() { - parent::setUp('user_form_test'); - // Create two users - $this->accessUser = $this->drupalCreateUser(array('access content')); - $this->adminUser = $this->drupalCreateUser(array('administer users')); - } - - /** - * Tests that user_validate_current_pass can be reused on a custom form. - */ - function testUserValidateCurrentPassCustomForm() { - $this->drupalLogin($this->adminUser); - - // Submit the custom form with the admin user using the access user's password. - $edit = array(); - $edit['user_form_test_field'] = $this->accessUser->name; - $edit['current_pass'] = $this->accessUser->pass_raw; - $this->drupalPost('user_form_test_current_password/' . $this->accessUser->uid, $edit, t('Test')); - $this->assertText(t('The password has been validated and the form submitted successfully.')); - } -} - -/** - * Test user entity callbacks. - */ -class UserEntityCallbacksTestCase extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User entity callback tests', - 'description' => 'Tests specific parts of the user entity like the URI callback and the label callback.', - 'group' => 'User' - ); - } - - function setUp() { - parent::setUp('user'); - - $this->account = $this->drupalCreateUser(); - $this->anonymous = drupal_anonymous_user(); - } - - /** - * Test label callback. - */ - function testLabelCallback() { - $this->assertEqual(entity_label('user', $this->account), $this->account->name, t('The username should be used as label')); - - // Setup a random anonymous name to be sure the name is used. - $name = $this->randomName(); - variable_set('anonymous', $name); - $this->assertEqual(entity_label('user', $this->anonymous), $name, t('The variable anonymous should be used for name of uid 0')); - } - - /** - * Test URI callback. - */ - function testUriCallback() { - $uri = entity_uri('user', $this->account); - $this->assertEqual('user/' . $this->account->uid, $uri['path'], t('Correct user URI.')); - } -} - -/** - * Functional tests for a user's ability to change their default language. - */ -class UserLanguageFunctionalTest extends WebTestBase { - public static function getInfo() { - return array( - 'name' => 'User language settings', - 'description' => "Tests user's ability to change their default language.", - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp(array('user', 'language')); - } - - /** - * Test if user can change their default language. - */ - function testUserLanguageConfiguration() { - global $base_url; - - // User to add and remove language. - $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages')); - // User to change their default language. - $web_user = $this->drupalCreateUser(); - - // Add custom language. - $this->drupalLogin($admin_user); - // Code for the language. - $langcode = 'xx'; - // The English name for the language. - $name = $this->randomName(16); - $edit = array( - 'predefined_langcode' => 'custom', - 'langcode' => $langcode, - 'name' => $name, - 'direction' => '0', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language')); - $this->drupalLogout(); - - // Login as normal user and edit account settings. - $this->drupalLogin($web_user); - $path = 'user/' . $web_user->uid . '/edit'; - $this->drupalGet($path); - // Ensure language settings fieldset is available. - $this->assertText(t('Language'), t('Language selector available.')); - // Ensure custom language is present. - $this->assertText($name, t('Language present on form.')); - // Switch to our custom language. - $edit = array( - 'preferred_langcode' => $langcode, - ); - $this->drupalPost($path, $edit, t('Save')); - // Ensure form was submitted successfully. - $this->assertText(t('The changes have been saved.'), t('Changes were saved.')); - // Check if language was changed. - $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-preferred-langcode-' . $langcode)); - $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.')); - - $this->drupalLogout(); - } -} - -/** - * Functional test for language handling during user creation. - */ -class UserLanguageCreationTest extends WebTestBase { - - public static function getInfo() { - return array( - 'name' => 'User language creation', - 'description' => 'Tests whether proper language is stored for new users and access to language selector.', - 'group' => 'User', - ); - } - - function setUp() { - parent::setUp(array('user', 'language')); - variable_set('user_register', USER_REGISTER_VISITORS); - } - - /** - * Functional test for language handling during user creation. - */ - function testLocalUserCreation() { - // User to add and remove language and create new users. - $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users')); - $this->drupalLogin($admin_user); - - // Add predefined language. - $langcode = 'fr'; - $edit = array( - 'predefined_langcode' => 'fr', - ); - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); - $this->assertText('French', t('Language added successfully.')); - $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.')); - - // Set language negotiation. - $edit = array( - 'language_interface[enabled][language-url]' => TRUE, - ); - $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); - $this->assertText(t('Language negotiation configuration saved.'), t('Set language negotiation.')); - - // Check if the language selector is available on admin/people/create and - // set to the currently active language. - $this->drupalGet($langcode . '/admin/people/create'); - $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Global language set in the language selector.')); - - // Create a user with the admin/people/create form and check if the correct - // language is set. - $username = $this->randomName(10); - $edit = array( - 'name' => $username, - 'mail' => $this->randomName(4) . '@example.com', - 'pass[pass1]' => $username, - 'pass[pass2]' => $username, - ); - - $this->drupalPost($langcode . '/admin/people/create', $edit, t('Create new account')); - - $user = user_load_by_name($username); - $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.')); - $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.')); - - // Register a new user and check if the language selector is hidden. - $this->drupalLogout(); - - $this->drupalGet($langcode . '/user/register'); - $this->assertNoFieldByName('language[fr]', t('Language selector is not accessible.')); - - $username = $this->randomName(10); - $edit = array( - 'name' => $username, - 'mail' => $this->randomName(4) . '@example.com', - ); - - $this->drupalPost($langcode . '/user/register', $edit, t('Create new account')); - - $user = user_load_by_name($username); - $this->assertEqual($user->preferred_langcode, $langcode, t('New user has correct preferred language set.')); - $this->assertEqual($user->langcode, $langcode, t('New user has correct profile language set.')); - - // Test if the admin can use the language selector and if the - // correct language is was saved. - $user_edit = $langcode . '/user/' . $user->uid . '/edit'; - - $this->drupalLogin($admin_user); - $this->drupalGet($user_edit); - $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.')); - - // Set pass_raw so we can login the new user. - $user->pass_raw = $this->randomName(10); - $edit = array( - 'pass[pass1]' => $user->pass_raw, - 'pass[pass2]' => $user->pass_raw, - ); - - $this->drupalPost($user_edit, $edit, t('Save')); - - $this->drupalLogin($user); - $this->drupalGet($user_edit); - $this->assertFieldChecked("edit-preferred-langcode-$langcode", t('Language selector is accessible and correct language is selected.')); - } -} diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php index d720d549cd27e6fc4bdf3a9b9f294681526a4296..79f306791279f0147e5735acf3417e834c82cd19 100755 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -528,3 +528,17 @@ * Remove the leading hash signs to disable. */ # $conf['allow_authorize_operations'] = FALSE; + +/** + * Load local development override configuration, if available. + * + * Use settings.local.php to override variables on secondary (staging, + * development, etc) installations of this site. Typically used to disable + * caching, JavaScript/CSS compression, re-routing of outgoing e-mails, and + * other things that should not happen on development and testing sites. + * + * Keep this code block at the end of this file to take full effect. + */ +# if (file_exists(DRUPAL_ROOT . '/' . $conf_path . '/settings.local.php')) { +# include DRUPAL_ROOT . '/' . $conf_path . '/settings.local.php'; +# }