From af474609e3e80db9ba1d16b9ad2eae89775f51c8 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Sun, 20 Apr 2008 18:24:07 +0000 Subject: [PATCH] - Added a test framework to Drupal along with a first batch of tests for Drupal core! This is an important milestone for the project so enable the module and check it out ... :) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Rok Žlender, Károly Négyesi, Jimmy Berry, Kevin Bridges, Charlie Gordon, Douglas Hubler, Miglius Alaburda, Andy Kirkham, Dimitri13, Kieran Lal, Moshe Weitzman, and the many other people that helped with testing over the past years and that drove this home. It all works but it is still rough around the edges (i.e. documentation is still being written, the coding style is not 100% yet, a number of tests still fail) but we spent the entire weekend working on it in Paris and made a ton of progress. The best way to help and to get up to speed, is to start writing and contributing some tests ... as well as fixing some of the failures. For those willing to help with improving the test framework, here are some next steps and issues to resolve: - How to best approach unit tests and mock functions? - How to test drupal_mail() and drupal_http_request()? - How to improve the admin UI so we have a nice progress bar? - How best to do code coverage? - See http://g.d.o/node/10099 for more ... --- CHANGELOG.txt | 2 + includes/database.inc | 8 +- includes/xmlrpc.inc.test | 117 +++ modules/aggregator/aggregator.test | 314 ++++++ modules/block/block.test | 111 +++ modules/blog/blog.test | 171 ++++ modules/blogapi/blogapi.test | 159 +++ modules/book/book.test | 154 +++ modules/comment/comment.test | 372 +++++++ modules/contact/contact.test | 266 +++++ modules/dblog/dblog.test | 370 +++++++ modules/filter/filter.test | 182 ++++ modules/forum/forum.test | 398 ++++++++ modules/help/help.module | 1 + modules/help/help.test | 87 ++ modules/locale/locale.test | 116 +++ modules/menu/menu.test | 435 +++++++++ modules/node/node.test | 403 ++++++++ modules/path/path.test | 139 +++ modules/php/php.test | 93 ++ modules/poll/poll.test | 92 ++ modules/profile/profile.test | 975 +++++++++++++++++++ modules/search/search.test | 162 +++ modules/simpletest/files/README.txt | 5 + modules/simpletest/files/html-1.txt | 1 + modules/simpletest/files/html-2.html | 1 + modules/simpletest/files/image-1.png | Bin 0 -> 64027 bytes modules/simpletest/files/image-2.jpg | Bin 0 -> 6218 bytes modules/simpletest/files/javascript-1.txt | 3 + modules/simpletest/files/javascript-2.script | 3 + modules/simpletest/files/php-1.txt | 3 + modules/simpletest/files/php-2.php | 3 + modules/simpletest/files/sql-1.txt | 1 + modules/simpletest/files/sql-2.sql | 1 + modules/simpletest/simpletest.css | 62 ++ modules/simpletest/simpletest.info | 6 + modules/simpletest/simpletest.install | 98 ++ modules/simpletest/simpletest.js | 58 ++ modules/simpletest/simpletest.module | 478 +++++++++ modules/simpletest/simpletest.php | 369 +++++++ modules/syslog/syslog.test | 41 + modules/system/system.test | 128 +++ modules/taxonomy/taxonomy.test | 394 ++++++++ modules/translation/translation.test | 151 +++ modules/trigger/trigger.test | 114 +++ modules/upload/upload.test | 550 +++++++++++ modules/user/upload.test | 522 ++++++++++ modules/user/user.test | 205 ++++ scripts/run-functional-tests.php | 58 ++ 49 files changed, 8381 insertions(+), 1 deletion(-) create mode 100644 includes/xmlrpc.inc.test create mode 100644 modules/aggregator/aggregator.test create mode 100644 modules/block/block.test create mode 100644 modules/blog/blog.test create mode 100644 modules/blogapi/blogapi.test create mode 100644 modules/book/book.test create mode 100644 modules/comment/comment.test create mode 100644 modules/contact/contact.test create mode 100644 modules/dblog/dblog.test create mode 100644 modules/filter/filter.test create mode 100644 modules/forum/forum.test create mode 100644 modules/help/help.test create mode 100644 modules/locale/locale.test create mode 100644 modules/menu/menu.test create mode 100644 modules/node/node.test create mode 100644 modules/path/path.test create mode 100644 modules/php/php.test create mode 100644 modules/poll/poll.test create mode 100644 modules/profile/profile.test create mode 100644 modules/search/search.test create mode 100644 modules/simpletest/files/README.txt create mode 100644 modules/simpletest/files/html-1.txt create mode 100644 modules/simpletest/files/html-2.html create mode 100644 modules/simpletest/files/image-1.png create mode 100644 modules/simpletest/files/image-2.jpg create mode 100644 modules/simpletest/files/javascript-1.txt create mode 100644 modules/simpletest/files/javascript-2.script create mode 100644 modules/simpletest/files/php-1.txt create mode 100644 modules/simpletest/files/php-2.php create mode 100644 modules/simpletest/files/sql-1.txt create mode 100644 modules/simpletest/files/sql-2.sql create mode 100644 modules/simpletest/simpletest.css create mode 100644 modules/simpletest/simpletest.info create mode 100644 modules/simpletest/simpletest.install create mode 100644 modules/simpletest/simpletest.js create mode 100644 modules/simpletest/simpletest.module create mode 100644 modules/simpletest/simpletest.php create mode 100644 modules/syslog/syslog.test create mode 100644 modules/system/system.test create mode 100644 modules/taxonomy/taxonomy.test create mode 100644 modules/translation/translation.test create mode 100644 modules/trigger/trigger.test create mode 100644 modules/upload/upload.test create mode 100644 modules/user/upload.test create mode 100644 modules/user/user.test create mode 100755 scripts/run-functional-tests.php diff --git a/CHANGELOG.txt b/CHANGELOG.txt index e76bb1b198a..953c7bf9d59 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -14,6 +14,8 @@ Drupal 7.0, xxxx-xx-xx (development version) * Provide descriptions for permissions on the administration page. - Search: * Made it possible to language-aware searches. +- Testing: + * Added test framework and tests. - Removed ping module: * This module has been removed from the core download. Contributed alternatives are available. diff --git a/includes/database.inc b/includes/database.inc index 145290ab08d..41116091bb7 100644 --- a/includes/database.inc +++ b/includes/database.inc @@ -122,7 +122,7 @@ function db_prefix_tables($sql) { * @return the name of the previously active database or FALSE if non was found. */ function db_set_active($name = 'default') { - global $db_url, $db_type, $active_db; + global $db_url, $db_type, $active_db, $db_prefix; static $db_conns, $active_name = FALSE; if (empty($db_url)) { @@ -150,6 +150,12 @@ function db_set_active($name = 'default') { } $db_conns[$name] = db_connect($connect_url); + // We need to pass around the simpletest database prefix in the request + // and we put that in the user_agent header. + if (preg_match("/^simpletest\d+$/", $_SERVER['HTTP_USER_AGENT'])) { + $db_prefix = $_SERVER['HTTP_USER_AGENT']; + } + } $previous_name = $active_name; diff --git a/includes/xmlrpc.inc.test b/includes/xmlrpc.inc.test new file mode 100644 index 00000000000..49bab141ad9 --- /dev/null +++ b/includes/xmlrpc.inc.test @@ -0,0 +1,117 @@ + t('XML-RPC validator1'), + 'description' => t('See !validator-link. note: simpletest_xmlrpc.module must be enabled', array('!validator-link' => l('the xmlrpc validator1 specification', 'http://www.xmlrpc.com/validator1Docs'))), + 'group' => t('XML-RPC')); + } + + function test_run_all_tests() { + if (!$this->drupalModuleEnable('simpletest_xmlrpc')) { + return FALSE; + } + $xml_url = url(NULL, array('absolute' => TRUE)) . 'xmlrpc.php'; + srand(); + mt_srand(); + + + $array_1 = array(array('curly' => mt_rand(-100,100)), + array('curly' => mt_rand(-100,100)), + array('larry' => mt_rand(-100,100)), + array('larry' => mt_rand(-100,100)), + array('moe' => mt_rand(-100,100)), + array('moe' => mt_rand(-100,100)), + array('larry' => mt_rand(-100,100))); + shuffle($array_1); + $l_res_1 = simpletest_xmlrpc_arrayOfStructsTest($array_1); + $r_res_1 = xmlrpc($xml_url, 'validator1.arrayOfStructsTest', $array_1); + $this->assertIdentical($l_res_1, $r_res_1, 'array of structs test: %s'); + + + $string_2 = 't\'&>>zf"md>yr>xlcev">>uai"np&s>>q\'&b<>"&&&'; + $l_res_2 = simpletest_xmlrpc_countTheEntities($string_2); + $r_res_2 = xmlrpc($xml_url, 'validator1.countTheEntities', $string_2); + $this->assertIdentical($l_res_2, $r_res_2, 'count the entities test: %s'); + + + $struct_3 = array('moe' => mt_rand(-100,100), 'larry' => mt_rand(-100,100), 'curly' => mt_rand(-100,100), 'homer' => mt_rand(-100,100)); + $l_res_3 = simpletest_xmlrpc_easyStructTest($struct_3); + $r_res_3 = xmlrpc($xml_url, 'validator1.easyStructTest', $struct_3); + $this->assertIdentical($l_res_3, $r_res_3, 'easy struct test: %s'); + + + $struct_4 = array('sub1' => array('bar' => 13), + 'sub2' => 14, + 'sub3' => array('foo' => 1, 'baz' => 2), + 'sub4' => array('ss' => array('sss' => array('ssss' => 'sssss')))); + $l_res_4 = simpletest_xmlrpc_echoStructTest($struct_4); + $r_res_4 = xmlrpc($xml_url, 'validator1.echoStructTest', $struct_4); + $this->assertIdentical($l_res_4, $r_res_4, 'echo struct test: %s'); + + $int_5 = mt_rand(-100,100); + $bool_5 = (($int_5 % 2) == 0); + $string_5 = $this->randomName(); + $double_5 = (double)(mt_rand(-1000,1000) / 100); + $time_5 = time(); + $base64_5 = $this->randomName(100); + $l_res_5 = simpletest_xmlrpc_manyTypesTest($int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), $base64_5); + $l_res_5[5] = $l_res_5[5]->data; /* override warpping */ + $r_res_5 = xmlrpc($xml_url, 'validator1.manyTypesTest', $int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5)); + /* Contains objects, objects are not equal */ + // See http://drupal.org/node/37766 why this currnetly fails + $this->assertEqual($l_res_5, $r_res_5, 'many types test: %s'); + + + $size = mt_rand(100,200); + $array_6 = array(); + for ($i = 0; $i < $size; $i++) { + $array_6[] = $this->randomName(mt_rand(8,12)); + } + + $l_res_6 = simpletest_xmlrpc_moderateSizeArrayCheck($array_6); + $r_res_6 = xmlrpc($xml_url, 'validator1.moderateSizeArrayCheck', $array_6); + $this->assertIdentical($l_res_6, $r_res_6, 'moderate size array check: %s'); + + + $struct_7 = array(); + for ($y = 2000; $y < 2002; $y++) { + for ($m = 3; $m < 5; $m++) { + for ($d = 1; $d < 6; $d++) { + $ys = (string)$y; + $ms = sprintf('%02d', $m); + $ds = sprintf('%02d', $d); + $struct_7[$ys][$ms][$ds]['moe'] = mt_rand(-100,100); + $struct_7[$ys][$ms][$ds]['larry'] = mt_rand(-100,100); + $struct_7[$ys][$ms][$ds]['curly'] = mt_rand(-100,100); + } + } + } + $l_res_7 = simpletest_xmlrpc_nestedStructTest($struct_7); + $r_res_7 = xmlrpc($xml_url, 'validator1.nestedStructTest', $struct_7); + $this->assertIdentical($l_res_7, $r_res_7, 'nested struct test: %s'); + + + $int_8 = mt_rand(-100,100); + $l_res_8 = simpletest_xmlrpc_simpleStructReturnTest($int_8); + $r_res_8 = xmlrpc($xml_url, 'validator1.simpleStructReturnTest', $int_8); + $this->assertIdentical($l_res_8, $r_res_8, 'nested struct test: %s'); + + /* Now test multicall */ + $x = array(); + $x[] = array('validator1.arrayOfStructsTest', $array_1); + $x[] = array('validator1.countTheEntities', $string_2); + $x[] = array('validator1.easyStructTest', $struct_3); + $x[] = array('validator1.echoStructTest', $struct_4); + $x[] = array('validator1.manyTypesTest', $int_5, $bool_5, $string_5, $double_5, xmlrpc_date($time_5), xmlrpc_base64($base64_5)); + $x[] = array('validator1.moderateSizeArrayCheck', $array_6); + $x[] = array('validator1.nestedStructTest', $struct_7); + $x[] = array('validator1.simpleStructReturnTest', $int_8); + + $a_l_res = array($l_res_1, $l_res_2, $l_res_3, $l_res_4, $l_res_5, $l_res_6, $l_res_7, $l_res_8); + $a_r_res = xmlrpc($xml_url, $x); + $this->assertEqual($a_l_res, $a_r_res, 'multicall equals result'); + } +} +?> \ No newline at end of file diff --git a/modules/aggregator/aggregator.test b/modules/aggregator/aggregator.test new file mode 100644 index 00000000000..e94c23af3f8 --- /dev/null +++ b/modules/aggregator/aggregator.test @@ -0,0 +1,314 @@ +drupalCreateUser(array('administer news feeds', 'access news feeds')); + $this->drupalLogin($web_user); + } + + /** + * Create an aggregator feed (simulate form submission on admin/content/aggregator/add/feed). + * + * @return $feed Full feed object if possible. + */ + function createFeed() { + $edit = $this->getFeedEditArray(); + $this->drupalPost('admin/content/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_fetch_object(db_query("SELECT * FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $edit['title'], $edit['url'])); + $this->assertTrue(!empty($feed), t('The feed found in database.')); + return $feed; + } + + /** + * Delete an aggregator feed. + * + * @param object $feed Feed object representing the feed. + */ + function deleteFeed($feed) { + $this->drupalPost('admin/content/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. + * + * @return array Feed array. + */ + function getFeedEditArray() { + $feed_name = $this->randomName(10, self::$prefix); + $feed_url = url(NULL, array('absolute' => TRUE)) .'rss.xml?feed='. $feed_name; + $edit = array( + 'title' => $feed_name, + 'url' => $feed_url, + 'refresh' => '900', + ); + return $edit; + } + + /** + * Update feed items (simulate click to admin/content/aggregator/update/$fid). + * + * @param object $feed Feed object representing the feed. + */ + function updateFeedItems(&$feed) { + // First, let's ensure we could get to the rss xml + $this->drupalGet('rss.xml'); + $this->assertResponse(200, t('rss.xml is reachable.')); + + // our tests are based off of rss.xml, so let's find out how many elements should be related + $feed_count = db_result(db_query_range(db_rewrite_sql('SELECT COUNT(*) FROM {node} n WHERE n.promote = 1 AND n.status = 1'), 0, variable_get('feed_default_items', 10))); + $feed_count = $feed_count > 10 ? 10 : $feed_count; + + // refresh the feed (simulated link click) + $this->drupalGet('admin/content/aggregator/update/'. $feed->fid); + + // ensure we have the right number of items + $result = db_query('SELECT iid FROM {aggregator_item} WHERE fid = %d', $feed->fid); + $items = array(); + $feed->items = array(); + while ($item = db_fetch_object($result)) { + $feed->items[] = $item->iid; + } + $feed->item_count = count($feed->items); + $this->assertEqual($feed_count, $feed->item_count, t('Total items in feed equal to the total items in database (!val1 != !val2)', array('!val1' => $feed_count, '!val2' => $feed->item_count))); + } + + /** + * Confirm item removal from a feed. + * + * @param object $feed Feed object representing the feed. + */ + function removeFeedItems($feed) { + $this->drupalPost('admin/content/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.')); + } + + /** + * Pull feed categories from aggregator_category_feed table. + * + * @param object $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 = %d', $feed->fid); + while ($category = db_fetch_object($result)) { + $feed->categories[] = $category->cid; + } + } + + /** + * Check if the feed name and url is unique. + * + * @param string $feed_name Feed name to check. + * @param string $feed_url Feed url to check. + * @return boolean Feed is unique. + */ + function uniqueFeed($feed_name, $feed_url) { + $result = db_result(db_query("SELECT count(*) FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $feed_name, $feed_url)); + return (1 == $result); + } +} + +class AddFeedTestCase extends AggregatorTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Add feed functionality'), + 'description' => t('Add feed test.'), + 'group' => t('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/content/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')); + + // Delete feed. + $this->deleteFeed($feed); + } +} + +class UpdateFeedTestCase extends AggregatorTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Update feed functionality'), + 'description' => t('Update feed test.'), + 'group' => t('Aggregator') + ); + } + + /** + * Create a feed and attempt to update it. + */ + function testUpdateFeed() { + $feed = $this->createFeed(); + + // Get new feed data array and modify newly created feed. + $edit = $this->getFeedEditArray(); + $edit['refresh'] = 1800; // Change refresh value. + $this->drupalPost('admin/content/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/content/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 { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Remove feed functionality'), + 'description' => t('Remove feed test.'), + 'group' => t('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_result(db_query("SELECT count(*) FROM {aggregator_feed} WHERE title = '%s' AND url='%s'", $feed->title, $feed->url)); + $this->assertFalse($result, t('Feed not found in database')); + } +} + +class UpdateFeedItemTestCase extends AggregatorTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Update feed item functionality'), + 'description' => t('Update feed items from a feed.'), + 'group' => t('Aggregator') + ); + } + + /** + * Test running "update items" from the 'admin/content/aggregator' page. + */ + function testUpdateFeedItem() { + // Create a feed and test updating feed items if possible. + $feed = $this->createFeed(); + if (!empty($feed)) { + $this->updateFeedItems($feed); + $this->removeFeedItems($feed); + } + + // Delete feed. + $this->deleteFeed($feed); + } +} + +class RemoveFeedItemTestCase extends AggregatorTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Remove feed item functionality'), + 'description' => t('Remove feed items from a feed.'), + 'group' => t('Aggregator') + ); + } + + /** + * Test running "remove items" from the 'admin/content/aggregator' page. + */ + function testRemoveFeedItem() { + $feed = $this->createFeed(); + + // Add and remove feed items and ensure that the count is zero. + $this->updateFeedItems($feed); + $this->removeFeedItems($feed); + $count = db_result(db_query('SELECT COUNT(*) FROM {aggregator_item} WHERE fid = %d', $feed->fid)); + $this->assertTrue($count == 0); + + // Delete feed. + $this->deleteFeed($feed); + } +} + +class CategorizeFeedItemTestCase extends AggregatorTestCase { + /** + * Implementation of getInfo(). + */ + function getInfo() { + return array( + 'name' => t('Categorize feed item functionality'), + 'description' => t('Test feed item categorization.'), + 'group' => t('Aggregator') + ); + } + + /** + * If a feed has a category, make sure that the children inherit that + * categorization. + */ + function testCategorizeFeedItem() { + // TODO: Need to add categories to the feed on creation. + $feed = $this->createFeed(); + $this->updateFeedItems($feed); + $this->getFeedCategories($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) { + $items_str = implode(', ', $feed->items); + $categorized_count = db_result(db_query('SELECT COUNT(*) FROM {aggregator_category_item} WHERE iid IN (' . $items_str . ')')); + $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/modules/block/block.test b/modules/block/block.test new file mode 100644 index 00000000000..85a95e60347 --- /dev/null +++ b/modules/block/block.test @@ -0,0 +1,111 @@ + t('Block functionality'), + 'description' => t('Add, edit and delete custom block. Configure and move a module-defined block.'), + 'group' => t('Block'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp(); + + // Create and login user + $admin_user = $this->drupalCreateUser(array('administer blocks')); + $this->drupalLogin($admin_user); + } + + /** + * Test creating custom block (i.e. box), moving it to a specific region and then deleting it. + */ + function testBox() { + // Add a new box by filling out the input form on the admin/build/block/add page. + $box = array(); + $box['info'] = $this->randomName(8); + $box['title'] = $this->randomName(8); + $box['body'] = $this->randomName(32); + $this->drupalPost('admin/build/block/add', $box, t('Save block')); + + // Confirm that the box has been created, and then query the created bid. + $this->assertText(t('The block has been created.'), t('Box successfully created.')); + $bid = db_result(db_query("SELECT bid FROM {boxes} WHERE info = '%s'", array($box['info']))); + + // Check to see if the box was created by checking that it's in the database.. + $this->assertNotNull($bid, t('Box found in database')); + + // Set the created box to a specific region. + // TODO: Implement full region checking. + $edit = array(); + $edit['block_'. $bid .'[region]'] = 'left'; + $this->drupalPost('admin/build/block', $edit, t('Save blocks')); + + // Confirm that the box was moved to the proper region. + $this->assertText(t('The block settings have been updated.'), t('Box successfully moved to left region.')); + + // Confirm that the box is being displayed. + $this->assertText(t($box['title']), t('Box successfully being displayed on the page.')); + + // Delete the created box & verify that it's been deleted and no longer appearing on the page. + $this->drupalPost('admin/build/block/delete/'. $bid, array(), t('Delete')); + $this->assertRaw(t('The block %title has been removed.', array('%title' => $box['info'])), t('Box successfully deleted.')); + $this->assertNoText(t($box['title']), t('Box no longer appears on page.')); + } + + /** + * 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'] = 'user'; + $block['delta'] = 'navigation'; + $block['title'] = $this->randomName(8); + + // Set block title to confirm that interface works and override any custom titles. + $this->drupalPost('admin/build/block/configure/'. $block['module'] .'/'. $block['delta'], array('title' => $block['title']), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); + $bid = db_result(db_query("SELECT bid FROM {blocks} WHERE module = '%s' AND delta = %d", array($block['module'], $block['delta']))); + + // Check to see if the block was created by checking that it's in the database. + $this->assertNotNull($bid, t('Block found in database')); + + // Set the created block to a specific region. + $edit = array(); + $edit[$block['module'] .'_'. $block['delta'] .'[region]'] = 'left'; + $this->drupalPost('admin/build/block', $edit, t('Save blocks')); + + // Confirm that the block was moved to the proper region. + // TODO: Implement full region checking. + $this->assertText(t('The block settings have been updated.'), t('Block successfully moved to left region.')); + + // Confirm that the block is being displayed. + $this->assertText(t($block['title']), t('Block successfully being displayed on the page.')); + + // Set the block to the disabled region. + $edit = array(); + $edit[$block['module'] .'_'. $block['delta'] .'[region]'] = '-1'; + $this->drupalPost('admin/build/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.')); + + // For convenience of developers, put the navigation block back. + $edit = array(); + $edit[$block['module'] .'_'. $block['delta'] .'[region]'] = 'left'; + $this->drupalPost('admin/build/block', $edit, t('Save blocks')); + $this->assertText(t('The block settings have been updated.'), t('Block successfully move to disabled region.')); + + $this->drupalPost('admin/build/block/configure/'. $block['module'] .'/'. $block['delta'], array('title' => 'Navigation'), t('Save block')); + $this->assertText(t('The block configuration has been saved.'), t('Block title set.')); + } +} diff --git a/modules/blog/blog.test b/modules/blog/blog.test new file mode 100644 index 00000000000..a3a6c144131 --- /dev/null +++ b/modules/blog/blog.test @@ -0,0 +1,171 @@ + t('Blog functionality'), + 'description' => t('Create, view, edit, delete, and change blog entries and verify its consistency in the database.'), + 'group' => t('Blog'), + ); + } + + /** + * Enable modules and create users with specific permissions. + */ + function setUp() { + parent::setUp('blog'); + // Create users. + $this->big_user = $this->drupalCreateUser(array('administer blocks')); + $this->own_user = $this->drupalCreateUser(array('create blog content', 'edit own blog content', 'delete own blog content')); + $this->any_user = $this->drupalCreateUser(array('create blog content', 'edit any blog content', 'delete any blog content', 'access administration pages')); + } + + /** + * Login users, create blog nodes, and test blog functionality through the admin and user interfaces. + */ + function testBlog() { + // Login the admin user. + $this->drupalLogin($this->big_user); + // Enable the recent blog block. + $edit = array(); + $edit['blog_recent[region]'] = 'right'; + $this->drupalPost('admin/build/block', $edit, t('Save blocks')); + $this->assertResponse(200); + + // Do basic tests for each user. + $this->doBasicTests($this->any_user, TRUE); + $this->doBasicTests($this->own_user, FALSE); + + // Create another blog node for the any blog user. + $node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $this->any_user->uid)); + // Verify the own blog user only has access to the blog view node. + $this->verifyBlogs($this->any_user, $node, FALSE, 403); + + // Create another blog node for the own blog user. + $node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $this->own_user->uid)); + // Login the any blog user. + $this->drupalLogin($this->any_user); + // Verify the any blog user has access to all the blog nodes. + $this->verifyBlogs($this->own_user, $node, TRUE); + } + + /** + * Run basic tests on the indicated user. + * + * @param object $user The logged in user. + * @param boolean $admin User has 'access administration pages' privilege. + */ + private function doBasicTests($user, $admin) { + // Login the user. + $this->drupalLogin($user); + // Create blog node. + $node = $this->drupalCreateNode(array('type' => 'blog', 'uid' => $user->uid)); + // Verify the user has access to all the blog nodes. + $this->verifyBlogs($user, $node, $admin); + // Verify the blog links are displayed. + $this->verifyBlogLinks($user); + } + + /** + * Verify the logged in user has the desired access to the various blog nodes. + * + * @param object $node_user The user who creates the node. + * @param object $node Node. + * @param boolean $admin User has 'access administration pages' privilege. + * @param integer $response HTTP response code. + */ + private function verifyBlogs($node_user, $node, $admin, $response = 200) { + $crumb = '›'; + $quote = '''; + + $response2 = ($admin) ? 200 : 403; + + // View blog help node. + $this->drupalGet('admin/help/blog'); + $this->assertResponse($response2); + // NOTE: The two commented asserts fail because the get returns the 'admin/help' node instead of the indicated node??? + if ($response2 == 200) { +// $this->assertTitle(t('Blog | Drupal'), t('Blog help node was displayed')); + $this->assertText(t('Blog'), t('Blog help node was displayed')); +// $this->assertText(t('Home '. $crumb .' Administer '. $crumb .' Help'), t('Breadcrumbs were displayed')); + } + + // Verify the blog block was displayed. + $this->drupalGet(''); + $this->assertResponse(200); + $this->assertText(t('Recent blog posts'), t('Blog block was displayed')); + + // View blog node. + $this->drupalGet('node/'. $node->nid); + $this->assertResponse(200); + $this->assertTitle($node->title. ' | Drupal', t('Blog node was displayed')); + $this->assertText(t('Home '. $crumb .' Blogs '. $crumb .' @name'. $quote .'s blog', array('@name' => $node_user->name)), t('Breadcrumbs were displayed')); + + // View blog edit node. + $this->drupalGet('node/'. $node->nid .'/edit'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertTitle($node->title. ' | Drupal', t('Blog edit node was displayed')); + $this->assertText(t('Home '. $crumb .' @title', array('@title' => $node->title)), t('Breadcrumbs were displayed')); + } + + if ($response == 200) { + // Edit blog node. + $edit = array(); + $edit['title'] = 'node/' . $node->nid; + $edit['body'] = $this->randomName(256); + $this->drupalPost('node/'. $node->nid .'/edit', $edit, t('Save')); + $this->assertRaw(t('Blog entry %title has been updated.', array('%title' => $edit['title'])), t('Blog node was edited')); + + // Delete blog node. + $this->drupalPost('node/'. $node->nid .'/delete', array(), t('Delete')); + $this->assertResponse($response); + $this->assertRaw(t('Blog entry %title has been deleted.', array('%title' => $edit['title'])), t('Blog node was deleted')); + } + } + + /** + * Verify the blog links are displayed to the logged in user. + * + * @param object $user The logged in user. + */ + private function verifyBlogLinks($user) { + $crumb = '›'; + + // Confirm blog entries link exists on the user page. + $this->drupalGet('user/'. $user->uid); + $this->assertResponse(200); + $this->assertText(t('View recent blog entries'), t('View recent blog entries link was displayed')); + + // Confirm the recent blog entries link links to the user's blog page. + $this->clickLink('View recent blog entries'); + $this->assertTitle(t("@name's blog | Drupal", array('@name' => $user->name)), t('View recent blog entries link target was correct')); + + // Confirm a blog page was displayed. + $this->drupalGet('blog'); + $this->assertResponse(200); + $this->assertTitle('Blogs | Drupal', t('Blog page was displayed')); + $this->assertText(t('Home'), t('Breadcrumbs were displayed')); + + // Confirm a blog page was displayed per user. + $this->drupalGet('blog/'. $user->uid); + $this->assertTitle(t("@name's blog | Drupal", array('@name' => $user->name)), t('User blog node was displayed')); + $this->assertText(t('Home '. $crumb .' Blogs'), t('Breadcrumbs were displayed')); + + // Confirm a blog feed was displayed. + $this->drupalGet('blog/feed'); + $this->assertTitle(t('Drupal blogs'), t('Blog feed was displayed')); + + // Confirm a blog feed was displayed per user. + $this->drupalGet('blog/'. $user->uid .'/feed'); + $this->assertTitle(t("@name's blog", array('@name' => $user->name)), t('User blog feed was displayed')); + } +} diff --git a/modules/blogapi/blogapi.test b/modules/blogapi/blogapi.test new file mode 100644 index 00000000000..ea6accf0b64 --- /dev/null +++ b/modules/blogapi/blogapi.test @@ -0,0 +1,159 @@ + t('Blog API functionality'), + 'description' => t('Create, edit, and delete post; upload file; and set/get categories.'), + 'group' => t('Blog API'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('blog', 'blogapi', 'taxonomy'); + } + + /** + * Create, edit, and delete post; upload file; and set/get categories. + */ + function testBlogAPI() { + // Create admin user and taxononmy for later use. + $admin_user = $this->drupalCreateUser(array('administer taxonomy')); + $this->drupalLogin($admin_user); + $vid = $this->addVocabulary('simpletest_vocab'); + $term = $this->addTerm($vid, 'simpletest_term1'); + $this->drupalLogout(); + + // Create user. + $web_user = $this->drupalCreateUser(array('create blog content', 'delete own blog content', 'edit own blog content', 'administer content with blog api')); + $this->drupalLogin($web_user); + + // Init common variables. + $local = url('xmlrpc.php', array('absolute' => TRUE)); + $appid = 'simpletest'; + + // Get user's blog. + $result = xmlrpc($local, 'blogger.getUsersBlogs', $appid, $web_user->name, $web_user->pass_raw); + $this->assertTrue($result, t('Request for user\'s blogs returned correctly.')); + + if ($result !== FALSE) { + if ($this->assertTrue(array_key_exists('blogid', $result[0]), t('Blog found.'))) { + $blog_id = $result[0]['blogid']; + } + } + + // Create post. + $content = $this->randomName(32); + $result = xmlrpc($local, 'blogger.newPost', $appid, $blog_id, $web_user->name, $web_user->pass_raw, $content, TRUE); + $this->assertTrue($result, t('Post created.')); + + $nid = $result; + + // Check recent posts. + $result = xmlrpc($local, 'blogger.getRecentPosts', $appid, $blog_id, $web_user->name, $web_user->pass_raw, 5); + $this->assertTrue($result, t('Recent post list retreived.')); + + if ($result !== FALSE && array_key_exists('title', $result[0])) { + $this->assertEqual($content, $result[0]['title'], t('Post found.')); + } + else + $this->assertTrue(false, 'Post found.'); + + // Edit post. + $content_new = $this->randomName(10); + $result = xmlrpc($local, 'blogger.editPost', $appid, $nid, $web_user->name, $web_user->pass_raw, $content_new, TRUE); + $this->assertTrue($result, t('Post successfully modified.')); + + // Upload file. + $file = current($this->drupalGetTestFiles('text')); + $file_contents = file_get_contents($file->filename); + $file = array(); + $file['name'] = $this->randomName() .'.txt'; + $file['type'] = 'text'; + $file['bits'] = xmlrpc_base64($file_contents); + $result = xmlrpc($local, 'metaWeblog.newMediaObject', $blog_id, $web_user->name, $web_user->pass_raw, $file); + $this->assertTrue($result, t('File successfully uploaded.')); + + $url = (array_key_exists('url', $result) ? $result['url'] : ''); + + // Check uploaded file. + $this->drupalGet($url); + $this->assertEqual($this->drupalGetContent(), $file_contents, t('Uploaded contents verified.')); + + // Set post categories. + $categories = array(array('categoryId' => $term)); + $result = xmlrpc($local, 'mt.setPostCategories', $nid, $web_user->name, $web_user->pass_raw, $categories); + $this->assertTrue($result, t('Post categories set.')); + + // Get post categories. + $result = xmlrpc($local, 'mt.getPostCategories', $nid, $web_user->name, $web_user->pass_raw); + $this->assertTrue($result, t('Category list successfully retreived.')); + + if ($result !== FALSE && array_key_exists('categoryId', $result[0])) { + $this->assertEqual($term, $result[0]['categoryId'], t('Category list verified.')); + } + + // Delete post. + $result = xmlrpc($local, 'blogger.deletePost', $appid, $nid, $web_user->name, $web_user->pass_raw, TRUE); + $this->assertTrue($result, t('Post successfully deleted.')); + } + + /** + * Add taxonomy vocabulary. + * + * @param string $vocab Vocabulary name. + * @return interger Vocab id. + */ + function addVocabulary($vocab) { + $edit = array(); + $edit['name'] = $vocab; + $edit['nodes[blog]'] = TRUE; + $this->drupalPost('admin/content/taxonomy/add/vocabulary', $edit, t('Save')); + $this->assertRaw(t('Created new vocabulary %vocab.', array('%vocab' => $edit['name'])), t('Taxonomy vocabulary added.')); + + $vocab_arr = taxonomy_get_vocabularies(); + $vid = NULL; + foreach ($vocab_arr as $vocab_item) { + if ($vocab_item->name == $vocab) { + $vid = $vocab_item->vid; + break; + } + } + + $this->assertNotNull($vid, t('Vocabulary found in database.')); + return $vid; + } + + /** + * Add a taxonomy term to vocabulary. + * + * @param integer $vid Vocabulary id. + * @param string $term Term name. + * @return interger Term id. + */ + function addTerm($vid, $term) { + $edit = array(); + $edit['name'] = $term; + $this->drupalPost('admin/content/taxonomy/'. $vid .'/add/term', $edit, t('Save')); + $this->assertRaw(t('Created new term %term.', array('%term' => $edit['name'])), t('Taxonomy term added.')); + + $tree = taxonomy_get_tree($vid); + $tid = NULL; + foreach ($tree as $tree_term) { + if ($tree_term->name == $term) { + $tid = $tree_term->tid; + break; + } + } + + $this->assertNotNull($tid, t('Term found in database.')); + return $tid; + } +} diff --git a/modules/book/book.test b/modules/book/book.test new file mode 100644 index 00000000000..7961bda8bd5 --- /dev/null +++ b/modules/book/book.test @@ -0,0 +1,154 @@ + t('Book functionality'), + 'description' => t('Create a book, add pages, and test book interface.'), + 'group' => t('Book'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('book'); + } + + /** + * Test book funcitonality through node interfaces. + */ + function testBook() { + // Create users. + $book_author = $this->drupalCreateUser(array('create new books', 'create book content', 'add content to books')); + $web_user = $this->drupalCreateUser(array('access printer-friendly version')); + + // Create new book. + $this->drupalLogin($book_author); + + $this->book = $this->createBookNode('new'); + $book = $this->book; + + /* + * Add page hiearchy to book. + * Book + * |- Node 0 + * |- Node 1 + * |- Node 2 + * |- Node 3 + * |- Node 4 + */ + $nodes = array(); + $nodes[] = $this->createBookNode($book->nid); // Node 0. + $nodes[] = $this->createBookNode($book->nid, $nodes[0]->book['mlid']); // Node 1. + $nodes[] = $this->createBookNode($book->nid, $nodes[0]->book['mlid']); // Node 2. + $nodes[] = $this->createBookNode($book->nid); // Node 3. + $nodes[] = $this->createBookNode($book->nid); // Node 4. + + $this->drupalLogout(); + + // Check to make sure that book pages display properly. + $this->drupalLogin($web_user); + + $this->checkBookNode($book, array($nodes[0], $nodes[3], $nodes[4]), false, false, $nodes[0]); + $this->checkBookNode($nodes[0], array($nodes[1], $nodes[2]), $book, $book, $nodes[1]); + $this->checkBookNode($nodes[1], NULL, $nodes[0], $nodes[0], $nodes[2]); + $this->checkBookNode($nodes[2], NULL, $nodes[1], $nodes[0], $nodes[3]); + $this->checkBookNode($nodes[3], NULL, $nodes[2], $book, $nodes[4]); + $this->checkBookNode($nodes[4], NULL, $nodes[3], $book, false); + } + + /** + * Checks the outline of sub-pages; previous, up, and next; and check printer friendly version. + * + * @param Node $node Node to check. + * @param array $nodes Nodes that should be in outline. + * @param Node $previous Previous link node. + * @param Node $up Up link node. + * @param Node $next Next link node. + */ + function checkBookNode($node, $nodes, $previous = false, $up = false, $next = false) { + static $number = 0; + $this->drupalGet('node/' . $node->nid); + + // Check outline structure. + if ($nodes !== NULL) { + $this->assertPattern($this->generateOutlinePattern($nodes), t('Node ' . $number . ' outline confirmed.')); + } + else { + $this->pass(t('Node ' . $number . ' doesn\'t have outline.')); + } + + // Check previous, up, and next links. + if ($previous) { + $this->assertRaw(l('‹ ' . $previous->title, 'node/' . $previous->nid, array('attributes' => array('class' => 'page-previous', 'title' => t('Go to previous page')))), t('Prevoius page link found.')); + } + if ($up) { + $this->assertRaw(l('up', 'node/' . $up->nid, array('attributes' => array('class' => 'page-up', 'title' => t('Go to parent page')))), t('Up page link found.')); + } + if ($next) { + $this->assertRaw(l($next->title . ' ›', 'node/' . $next->nid, array('attributes' => array('class' => 'page-next', 'title' => t('Go to next page')))), t('Next page link found.')); + } + + // Check printer friendly version. + $this->drupalGet('book/export/html/' . $node->nid); + $this->assertText($node->title, t('Printer friendly title found.')); + $node->body = str_replace('', '', $node->body); + $this->assertRaw(check_markup($node->body, $node->format), t('Printer friendly body found.')); + + $number++; + } + + /** + * Create a regular expression to check for the sub-nodes in the outline. + * + * @param array $nodes Nodes to check in outline. + */ + function generateOutlinePattern($nodes) { + $outline = ''; + foreach ($nodes as $node) { + $outline .= '(node\/' . $node->nid . ')(.*?)(' . $node->title . ')(.*?)'; + } + + return '/
/s'; + } + + /** + * Create book node. + * + * @param integer $book_nid Book node id or set to 'new' to create new book. + * @param integer $parent Parent book reference id. + */ + function createBookNode($book_nid, $parent = NULL) { + static $number = 0; // Used to ensure that when sorted nodes stay in same order. + + $edit = array(); + $edit['title'] = $number . ' - SimpleTest test node ' . $this->randomName(10); + $edit['body'] = 'SimpleTest test body ' . $this->randomName(32) . ' ' . $this->randomName(32); + $edit['book[bid]'] = $book_nid; + + if ($parent !== NULL) { + $this->drupalPost('node/add/book', $edit, t('Change book (update list of parents)')); + + $edit['book[plid]'] = $parent; + $this->drupalPost(NULL, $edit, t('Save')); + } + else { + $this->drupalPost('node/add/book', $edit, t('Save')); + } + + // Check to make sure the book node was created. + $node = node_load(array('title' => $edit['title'])); + $this->assertNotNull(($node === FALSE ? NULL : $node), t('Book node found in database.')); + $number++; + + return $node; + } +} diff --git a/modules/comment/comment.test b/modules/comment/comment.test new file mode 100644 index 00000000000..c309769f242 --- /dev/null +++ b/modules/comment/comment.test @@ -0,0 +1,372 @@ + t('Comment functionality'), + 'description' => t('Thoroughly test comment administration and user interfaces.'), + 'group' => t('Comment'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('comment'); + // Create users. + $this->admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer permissions')); + $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create story content')); + + $this->drupalLogin($this->web_user); + $this->node = $this->drupalCreateNode(array('type' => 'story')); + $this->assertTrue($this->node, t('Story node created.')); + $this->drupalLogout(); + } + + /** + * Test comment interface. + */ + function testCommentInterface() { + // Set comments to not have subject. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(TRUE); + $this->setCommentSubject(FALSE); + $this->drupalLogout(); + + // Post comment without subject. + $this->drupalLogin($this->web_user); + $this->drupalGet('comment/reply/'. $this->node->nid); + $this->assertNoFieldByName('subject', '', t('Subject field not found.')); + + // Set comments to have subject and preview to required. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->setCommentSubject(TRUE); + $this->setCommentPreview(TRUE); + $this->drupalLogout(); + + // Create comment that requires preview. + $this->drupalLogin($this->web_user); + $comment = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($comment), t('Comment found.')); + + // Reply to comment. + $this->drupalGet('comment/reply/'. $this->node->nid .'/'. $comment->id); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($reply, TRUE), t('Reply found.')); + + // Edit reply. + $this->drupalGet('comment/edit/'. $reply->id); + $reply = $this->postComment(NULL, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($reply, TRUE), t('Modified reply found.')); + + // Delete comment and make sure that reply is also removed. + $this->drupalLogout(); + $this->drupalLogin($this->admin_user); + $this->deleteComment($comment); + + $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.')); + } + + /** + * Test comment form on node page. + */ + function testFormOnPage() { + // 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()); + $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); + } + + /** + * Test anonymous comment functionality. + */ + function testAnonymous() { + $this->drupalLogin($this->admin_user); + // Enabled anonymous user comments. + $this->setAnonymousUserComment(TRUE, 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'); + $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.')); + + // 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, 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). + $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), FALSE, array('mail' => 'tester@simpletest.org')); + $this->assertTrue($this->commentExists($anonymous_comment3), t('Anonymous comment with contact info (required) found.')); + + // Unpublish comment. + $this->drupalLogin($this->admin_user); + $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.')); + + // Set anonymouse comments to require approval. + $this->setAnonymousUserComment(TRUE, FALSE); + $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, $subject, $body, TRUE, TRUE); // Set $contact to true so that it won't check for id and message. + $this->assertText(t('Your comment has been queued for moderation by site administrators and will be published after approval.'), t('Comment requires approval.')); + + // Get unaproved comment id. + $this->drupalLogin($this->admin_user); + $anonymous_comment4 = $this->getUnaprovedComment($subject); + $anonymous_comment4 = (object) 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.')); + + // Reset. + $this->drupalLogin($this->admin_user); + $this->setAnonymousUserComment(FALSE, FALSE); + } + + /** + * Post comment. + * + * @param object $node Node to post comment on. + * @param string $subject Comment subject. + * @param string $comment Comment body. + * @param boolean $preview Should preview be required. + * @param mixed $contact Set to NULL for no contact info, TRUE to ignore success checking, and array of values to set contact info. + */ + function postComment($node, $subject, $comment, $preview = TRUE, $contact = NULL) { + $edit = array(); + $edit['subject'] = $subject; + $edit['comment'] = $comment; + if ($contact !== NULL && is_array($contact)) { + $edit += $contact; + } + if ($node !== NULL) { + $this->drupalGet('comment/reply/'. $node->nid); + } + if ($preview) { + $this->assertNoFieldByName('op', t('Save'), t('Save button not found.')); // Preview required so no save button should be found. + $this->drupalPost(NULL, $edit, t('Preview')); + } + $this->drupalPost(NULL, $edit, t('Save')); + + $match = array(); + // Get comment ID + preg_match('/#comment-([^"]+)/', $this->getURL(), $match); + // get comment + if ($contact !== TRUE) { // If true then attempting to find error message. + $this->assertText($subject, 'Comment posted.'); + $this->assertTrue((!empty($match) && !empty($match[1])), t('Comment id found.')); + } + if (isset($match[1])) { + return (object) array('id' => $match[1], 'subject' => $subject, 'comment' => $comment); + } + } + + /** + * Checks current pag for specified comment. + * + * @param object $comment Comment object. + * @param boolean $reply The comment is a reply to another comment. + * @return boolean Comment found. + */ + function commentExists($comment, $reply = FALSE) { + if ($comment && is_object($comment)) { + $regex = '/'. ($reply ? '
(.*?)' : ''); + $regex .= 'subject .'(.*?)'; // Match subject. + $regex .= $comment->comment .'(.*?)'; // Match comment. + $regex .= '<\/div>/s'; // Dot matches newlines and ensure that match doesn't bleed outside comment div. + return preg_match($regex, $this->drupalGetContent()); + } + else { + return FALSE; + } + } + + /** + * Delete comment. + * + * @param object $comment Comment to delete. + */ + function deleteComment($comment) { + $this->drupalPost('comment/delete/'. $comment->id, array(), t('Delete')); + $this->assertText(t('The comment and all its replies have been deleted.'), t('Comment deleted.')); + } + + /** + * Set comment subject setting. + * + * @param boolean $enabled Subject value. + */ + function setCommentSubject($enabled) { + $this->setCommentSettings('comment_subject_field', ($enabled ? '1' : '0'), 'Comment subject '. ($enabled ? 'enabled' : 'disabled') .'.'); + } + + /** + * Set comment preview setting. + * + * @param boolean $required Preview value. + */ + function setCommentPreview($required) { + $this->setCommentSettings('comment_preview', ($required ? '1' : '0'), 'Comment preview '. ($required ? 'required' : 'optional') .'.'); + } + + /** + * Set comment form setting. + * + * @param boolean $enabled Form value. + */ + function setCommentForm($enabled) { + $this->setCommentSettings('comment_form_location', ($enabled ? '1' : '3'), 'Comment controls '. ($enabled ? 'enabled' : 'disabled') .'.'); + } + + /** + * Set comment anonymous level setting. + * + * @param integer $level Anonymous level. + */ + function setCommentAnonymous($level) { + $this->setCommentSettings('comment_anonymous', $level, 'Anonymous commenting set to level '. $level .'.'); + } + + /** + * Set comment setting for story content type. + * + * @param string $name Name of variable. + * @param string $vale Value of variable. + * @param string $message Status message to display. + */ + function setCommentSettings($name, $value, $message) { + variable_set($name .'_story', $value); + $this->assertTrue(TRUE, t($message)); // Display status message. + } + + /** + * Set anonymous comment setting. + * + * @param boolean $enabled Allow anonymous commenting. + * @param boolean $without_approval Allow anonymous commenting without approval. + */ + function setAnonymousUserComment($enabled, $without_approval) { + $edit = array(); + $edit['1[access comments]'] = $enabled; + $edit['1[post comments]'] = $enabled; + $edit['1[post comments without approval]'] = $without_approval; + $this->drupalPost('admin/user/permissions', $edit, t('Save permissions')); + $this->assertText(t('The changes have been saved.'), t('Anonymous user comments '. ($enabled ? 'enabled' : 'disabled') .'.')); + } + + /** + * Check for contact info. + * + * @return boolean Contact info is avialable. + */ + function commentContactInfoAvailable() { + return preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->drupalGetContent()); + } + + /** + * Perform the specified operation on the specified comment. + * + * @param object $comment Comment to perform operation on. + * @param string $operation Operation to perform. + * @param boolean $aproval Operation is found on approval page. + */ + function performCommentOperation($comment, $operation, $approval = FALSE) { + $edit = array(); + $edit['operation'] = $operation; + $edit['comments['. $comment->id .']'] = TRUE; + $this->drupalPost('admin/content/comment'. ($approval ? '/approval' : ''), $edit, t('Update')); + + if ($operation == 'delete') { + $this->drupalPost(NULL, array(), t('Delete comments')); + $this->assertText(t('The comments have been deleted.'), t('Operation "'. $operation .'" was performed on comment.')); + } + else { + $this->assertText(t('The update has been performed.'), t('Operation "'. $operation .'" was performed on comment.')); + } + } + + /** + * Get the comment id for an unaproved comment. + * + * @param string $subject Comment subject to find. + * @return integer Comment id. + */ + function getUnaprovedComment($subject) { + $this->drupalGet('admin/content/comment/approval'); + preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>('. $subject .')/', $this->drupalGetContent(), $match); + return $match[2]; + } +} diff --git a/modules/contact/contact.test b/modules/contact/contact.test new file mode 100644 index 00000000000..2372ad95b69 --- /dev/null +++ b/modules/contact/contact.test @@ -0,0 +1,266 @@ + t('Contact functionality'), + 'description' => t('Test site-wide contact form and personal contact form functionality.'), + 'group' => t('Contact'), + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUp('contact'); + } + + /** + * Test configuration options and site-wide contact form. + */ + function testSiteWideContact() { + // Create and login administative user. + $admin_user = $this->drupalCreateUser(array('administer site-wide contact form', 'administer permissions')); + $this->drupalLogin($admin_user); + + // Set settings. + $edit = array(); + $edit['contact_form_information'] = $this->randomName(100); + $edit['contact_hourly_threshold'] = 3; + $edit['contact_default_status'] = TRUE; + $this->drupalPost('admin/build/contact/settings', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.')); + + $this->reloadVariables(); + + // Delete old categories to ensure that new categories are used. + $this->deleteCategories(); + + // Add categories. + // Test invalid recipients. + $invalid_recipients = array('invalid', 'invalid@', /*'invalid@site', 'invalid@site.',*/ '@site.', '@site.com'); + foreach ($invalid_recipients as $invalid_recipient) { + $this->addCategory($this->randomName(16), $invalid_recipient, '', FALSE); + $this->assertRaw(t('%recipient is an invalid e-mail address.', array('%recipient' => $invalid_recipient)), t('Caught invalid recipient ('. $invalid_recipient .').')); + } + + // Create valid categories. + $recipients = array('simpletest@test.com', 'simpletest2@test.com', 'simpletest3@test.com'); + $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0])), '', TRUE); + $this->assertRaw(t('Category %category has been added.', array('%category' => $category)), t('Category successfully added.')); + + $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0], $recipients[1])), '', FALSE); + $this->assertRaw(t('Category %category has been added.', array('%category' => $category)), t('Category successfully added.')); + + $this->addCategory($category = $this->randomName(16), implode(',', array($recipients[0], $recipients[1], $recipients[2])), '', FALSE); + $this->assertRaw(t('Category %category has been added.', array('%category' => $category)), t('Category successfully added.')); + + // Clear food table in preparation for flood test and allow other checks to complete. + $this->assertTrue(db_query('DELETE FROM {flood}'), t('Flood table emptied.')); + + // Check to see that anonymous user cannot see contact page without permission. + $this->setPermission('anonymous user', array('access site-wide contact form' => FALSE)); + $this->drupalLogout(); + + $this->drupalGet('contact'); + $this->assertResponse(403, t('Access denied to anonymous user without permission.')); + + // Give anonymous user permission and see that page is viewable. + $this->drupalLogin($admin_user); + $this->setPermission('anonymous user', array('access site-wide contact form' => TRUE)); + $this->drupalLogout(); + + $this->drupalGet('contact'); + $this->assertResponse(200, t('Access granted to anonymous user with permission.')); + + // Check that "Additional information" is displayed on the page. + $this->assertText($edit['contact_form_information'], t('Contact information displayed.')); + + // Submit contact form with invalid values. + $categories = $this->getCategories(); + $this->submitContact('', $recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); + $this->assertText(t('Your name field is required.'), t('Name required.')); + + $this->submitContact($this->randomName(16), '', $this->randomName(16), $categories[0], $this->randomName(64)); + $this->assertText(t('Your e-mail address field is required.'), t('E-mail required.')); + + $this->submitContact($this->randomName(16), $invalid_recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); + $this->assertText(t('You must enter a valid e-mail address.'), t('Valid e-mail required.')); + + $this->submitContact($this->randomName(16), $recipients[0], '', $categories[0], $this->randomName(64)); + $this->assertText(t('Subject field is required.'), t('Subject required.')); + + $this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), $categories[0], ''); + $this->assertText(t('Message field is required.'), t('Message required.')); + + // Submit contact form with correct values and check flood interval. + for ($i = 0; $i < $edit['contact_hourly_threshold']; $i++) { + $this->submitContact($this->randomName(16), $recipients[0], $this->randomName(16), $categories[0], $this->randomName(64)); + $this->assertText(t('Your message has been sent.'), t('Message sent.')); + } + // Submit contact form one over limit. + $this->drupalGet('contact'); + $this->assertRaw(t('You cannot send more than %number messages per hour. Please try again later.', array('%number' => $edit['contact_hourly_threshold'])), t('Message threshold reached.')); + + // Delete created categories. + $this->drupalLogin($admin_user); + $this->deleteCategories(); + } + + /** + * Test personal contact form. + */ + function testPersonalContact() { + $admin_user = $this->drupalCreateUser(array('administer site-wide contact form')); + $this->drupalLogin($admin_user); + + // Enable the personal contact form. + $edit = array(); + $edit['contact_default_status'] = TRUE; + $this->drupalPost('admin/build/contact/settings', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.')); + + // Reload variables. + $this->drupalLogout(); + $this->reloadVariables(); + + // Create web users and attempt to use personal contact forms with default set to true. + $web_user1 = $this->drupalCreateUser(array()); + $web_user2 = $this->drupalCreateUser(array()); + + $this->drupalLogin($web_user1); + + $this->drupalGet('user/'. $web_user2->uid .'/contact'); + $this->assertResponse(200, t('Access to personal contact form granted.')); + + $edit = array(); + $edit['subject'] = $this->randomName(16); + $edit['message'] = $this->randomName(64); + $this->drupalPost(NULL, $edit, t('Send e-mail')); + $this->assertText(t('The message has been sent.'), t('Message sent.')); + + $this->drupalLogout(); + + $this->drupalLogin($admin_user); + + // Disable the personal contact form. + $edit = array(); + $edit['contact_default_status'] = FALSE; + $this->drupalPost('admin/build/contact/settings', $edit, t('Save configuration')); + $this->assertText(t('The configuration options have been saved.'), t('Setting successfully saved.')); + + // Reload variables. + $this->drupalLogout(); + $this->reloadVariables(); + + // Create web users and attempt to use personal contact forms with default set to false. + $web_user3 = $this->drupalCreateUser(array()); + $web_user4 = $this->drupalCreateUser(array()); + + $this->drupalLogin($web_user3); + + $this->drupalGet('user/'. $web_user4->uid .'/contact'); + $this->assertResponse(403, t('Access to personal contact form denied.')); + } + + /** + * Add a category. + * + * @param string $category Name of category. + * @param string $recipients List of recipient e-mail addresses. + * @param string $reply Auto-reply text. + * @param boolean $selected Defautly selected. + */ + function addCategory($category, $recipients, $reply, $selected) { + $edit = array(); + $edit['category'] = $category; + $edit['recipients'] = $recipients; + $edit['reply'] = $reply; + $edit['selected'] = ($selected ? '1' : '0'); + $this->drupalPost('admin/build/contact/add', $edit, t('Save')); + } + + /** + * Submit contact form. + * + * @param string $name Name. + * @param string $mail E-mail address. + * @param string $subject Subject. + * @param integer $cid Category id. + * @param string $message Message. + */ + function submitContact($name, $mail, $subject, $cid, $message) { + $edit = array(); + $edit['name'] = $name; + $edit['mail'] = $mail; + $edit['subject'] = $subject; + $edit['cid'] = $cid; + $edit['message'] = $message; + $this->drupalPost('contact', $edit, t('Send e-mail')); + } + + /** + * Delete all categories. + */ + function deleteCategories() { + $categories = $this->getCategories(); + foreach ($categories as $category) { + $category_name = db_result(db_query('SELECT category FROM {contact} WHERE cid = %d', array($category))); + $this->drupalPost('admin/build/contact/delete/'. $category, array(), t('Delete')); + $this->assertRaw(t('Category %category has been deleted.', array('%category' => $category_name)), t('Category deleted sucessfully.')); + } + } + + /** + * Get list category ids. + * + * @return array Category ids. + */ + function getCategories() { + $result = db_query('SELECT cid FROM {contact}'); + $categories = array(); + while ($category = db_result($result)) { + $categories[] = $category; + } + return $categories; + } + + /** + * Set permission. + * + * @param string $role User role to set permissions for. + * @param array $permissions Key-value array of permissions to set. + */ + function setPermission($role, $permissions) { + // Get role id (rid) for specified role. + $rid = db_result(db_query("SELECT rid FROM {role} WHERE name = '%s'", array($role))); + if ($rid === FALSE) { + $this->fail(t(' [permission] Role "'. $role .'" not found.')); + } + + // Create edit array from permission. + $edit = array(); + foreach ($permissions as $name => $value) { + $edit[$rid .'['. $name .']'] = $value; + } + + $this->drupalPost('admin/user/permissions', $edit, t('Save permissions')); + $this->assertText(t('The changes have been saved.'), t(' [permission] Saved changes.')); + } + + /** + * Reload variables table. + */ + function reloadVariables() { + global $conf; + + cache_clear_all('variables', 'cache'); + $conf = variable_init(); + $this->assertTrue($conf, t('Variables reloaded.')); + } +} diff --git a/modules/dblog/dblog.test b/modules/dblog/dblog.test new file mode 100644 index 00000000000..75faadd8262 --- /dev/null +++ b/modules/dblog/dblog.test @@ -0,0 +1,370 @@ + t('DBLog functionality'), + 'description' => t('Generate events and verify dblog entries; verify user access to log reports based on persmissions.'), + 'group' => t('DBLog'), + ); + } + + /** + * Enable modules and create users with specific permissions. + */ + function setUp() { + parent::setUp('dblog', 'blog', 'poll'); + // Create users. + $this->big_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users')); + $this->any_user = $this->drupalCreateUser(array()); + } + + /** + * Login users, create dblog events, and test dblog functionality through the admin and user interfaces. + */ + function testDBLog() { + // Login the admin user. + $this->drupalLogin($this->big_user); + + $row_limit = 100; + $this->verifyRowLimit($row_limit); + $this->verifyCron($row_limit); + $this->verifyEvents(); + $this->verifyReports(); + + // Login the regular user. + $user = $this->drupalLogin($this->any_user); + $this->verifyReports(403); + } + + /** + * Verify setting of the dblog row limit. + * + * @param integer $count Log row limit. + */ + private function verifyRowLimit($row_limit) { + // Change the dblog row limit. + $edit = array(); + $edit['dblog_row_limit'] = $row_limit; + $this->drupalPost('admin/settings/logging/dblog', $edit, t('Save configuration')); + $this->assertResponse(200); + // Reload variable cache (since the global $conf array was changed in another "process" when the settings page above was posted). + $this->reloadVariables(); + // Check row limit variable. + $current_limit = variable_get('dblog_row_limit', 1000); + $this->assertTrue($current_limit == $row_limit, t('[Cache] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit))); + // Verify dblog row limit equals specified row limit. + $current_limit = unserialize(db_result(db_query("SELECT value FROM {variable} WHERE name = '%s'", 'dblog_row_limit'))); + $this->assertTrue($current_limit == $row_limit, t('[Variable table] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit))); + } + + /** + * Verify cron applies the dblog row limit. + * + * @param integer $count Log row limit. + */ + private function verifyCron($row_limit) { + // Generate additional log entries. + $this->generateLogEntries($row_limit + 10); + // Verify dblog row count exceeds row limit. + $count = db_result(db_query('SELECT COUNT(wid) FROM {watchdog}')); + $this->assertTrue($count > $row_limit, t('Dblog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit))); + + // Run cron job. + $this->drupalGet('admin/reports/status/run-cron'); + $this->assertResponse(200); + $this->assertText(t('Cron ran successfully'), t('Cron ran successfully')); + // Verify dblog row count equals row limit plus one because cron adds a record after it runs. + $count = db_result(db_query('SELECT COUNT(wid) FROM {watchdog}')); + $this->assertTrue($count == $row_limit + 1, t('Dblog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit))); + } + + /** + * Generate dblog entries. + * + * @param integer $count Log row limit. + */ + private function generateLogEntries($count) { + global $base_root; + + // Prepare the fields to be logged + $log = array( + 'type' => 'custom', + 'message' => 'Log entry added to test the dblog row limit.', + 'variables' => array(), + 'severity' => WATCHDOG_NOTICE, + 'link' => NULL, + 'user' => $this->big_user, + 'request_uri' => $base_root . request_uri(), + 'referer' => referer_uri(), + 'ip' => ip_address(), + 'timestamp' => time(), + ); + $message = 'Log entry added to test the dblog row limit.'; + for ($i = 0; $i < $count; $i++) { + $log['message'] = $i. ' => ' .$message; + dblog_watchdog($log); + } + } + + /** + * Verify the logged in user has the desired access to the various dblog nodes. + * + * @param integer $response HTTP response code. + */ + private function verifyReports($response = 200) { + $quote = '''; + + // View dblog help node. + $this->drupalGet('admin/help/dblog'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Database logging'), t('DBLog help was displayed')); + } + + // View dblog report node. + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Recent log entries'), t('DBLog report was displayed')); + } + + // View dblog page-not-found report node. + $this->drupalGet('admin/reports/page-not-found'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Top '. $quote .'page not found'. $quote .' errors'), t('DBLog page-not-found report was displayed')); + } + + // View dblog access-denied report node. + $this->drupalGet('admin/reports/access-denied'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Top '. $quote .'access denied'. $quote .' errors'), t('DBLog access-denied report was displayed')); + } + + // View dblog event node. + $this->drupalGet('admin/reports/event/1'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Details'), t('DBLog event node was displayed')); + } + } + + /** + * Verify events. + */ + private function verifyEvents() { + // Invoke events. + $this->doUser(); + $this->doNode('article'); + $this->doNode('blog'); + $this->doNode('page'); + $this->doNode('poll'); + + // When a user is deleted, any content they created remains but the uid = 0. Their blog entry shows as "'s blog" on the home page. + // Records in the watchdog table related to that user have the uid set to zero. + } + + /** + * Generate and verify user events. + * + */ + private function doUser() + { + // Set user variables. + $name = $this->randomName(); + $pass = user_password(); + // Add user using form to generate add user event (which is not triggered by drupalCreateUser). + $edit = array(); + $edit['name'] = $name; + $edit['mail'] = $name .'@example.com'; + $edit['pass[pass1]'] = $pass; + $edit['pass[pass2]'] = $pass; + $edit['status'] = 1; + $this->drupalPost('admin/user/user/create', $edit, t('Create new account')); + $this->assertResponse(200); + // Retrieve user object. + $user = user_load(array('name' => $name)); //, 'pass' => $pass, 'status' => 1)); + $this->assertTrue($user != null, t('User @name was loaded', array('@name' => $name))); + $user->pass_raw = $pass; // Needed by drupalLogin. + // Login user. + $this->drupalLogin($user); + // Logout user. + $this->drupalLogout(); + // Fetch row ids in watchdog that relate to the user. + $result = db_query('SELECT wid FROM {watchdog} WHERE uid = %d', $user->uid); + while ($row = db_fetch_array($result)) { + $ids[] = $row['wid']; + } + $count_before = (isset($ids)) ? count($ids) : 0; + $this->assertTrue($count_before > 0, t('DBLog contains @count records for @name', array('@count' => $count_before, '@name' => $user->name))); + // Delete user. + user_delete(array(), $user->uid); + // Count rows that have uids for the user. + $count = db_result(db_query('SELECT COUNT(wid) FROM {watchdog} WHERE uid = %d', $user->uid)); + $this->assertTrue($count == 0, t('DBLog contains @count records for @name', array('@count' => $count, '@name' => $user->name))); + // Fetch row ids in watchdog that previously related to the deleted user. + $result = db_query('SELECT wid FROM {watchdog} WHERE uid = 0 AND wid IN (%s)', implode(', ', $ids)); + unset($ids); + while ($row = db_fetch_array($result)) { + $ids[] = $row['wid']; + } + $count_after = (isset($ids)) ? count($ids) : 0; + $this->assertTrue($count_after == $count_before, t('DBLog contains @count records for @name that now have uid = 0', array('@count' => $count_before, '@name' => $user->name))); + unset($ids); + // Fetch row ids in watchdog that relate to the user. + $result = db_query('SELECT wid FROM {watchdog} WHERE uid = %d', $user->uid); + while ($row = db_fetch_array($result)) { + $ids[] = $row['wid']; + } + $this->assertTrue(!isset($ids), t('DBLog contains no records for @name', array('@name' => $user->name))); + + // Login the admin user. + $this->drupalLogin($this->big_user); + // View the dblog report. + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse(200); + + // Verify events were recorded. + // Add user. + // Default display includes name and email address; if too long then email is replaced by three periods. + // $this->assertRaw(t('New user: %name (%mail)', array('%name' => $edit['name'], '%mail' => $edit['mail'])), t('DBLog event was recorded: [add user]')); + $this->assertRaw(t('New user: %name', array('%name' => $name)), t('DBLog event was recorded: [add user]')); + // Login user. + $this->assertRaw(t('Session opened for %name', array('%name' => $name)), t('DBLog event was recorded: [login user]')); + // Logout user. + $this->assertRaw(t('Session closed for %name', array('%name' => $name)), t('DBLog event was recorded: [logout user]')); + // Delete user. + $this->assertRaw(t('Deleted user: %name', array('%name' => $name)), t('DBLog event was recorded: [delete user]')); + } + + /** + * Generate and verify node events. + * + * @param string $type Content type. + */ + private function doNode($type) { + // Create user. + $perm = array('create '. $type .' content', 'edit own '. $type .' content', 'delete own '. $type .' content'); + $user = $this->drupalCreateUser($perm); + // Login user. + $this->drupalLogin($user); + + // Create node using form to generate add content event (which is not triggered by drupalCreateNode). + $edit = $this->getContent($type); + $title = $edit['title']; + $this->drupalPost('node/add/'. $type, $edit, t('Save')); + $this->assertResponse(200); + // Retrieve node object. + $node = node_load(array('title' => $title)); + $this->assertTrue($node != null, t('Node @title was loaded', array('@title' => $title))); + // Edit node. + $edit = $this->getContentUpdate($type); + $this->drupalPost('node/'. $node->nid .'/edit', $edit, t('Save')); + $this->assertResponse(200); + // Delete node. + $this->drupalPost('node/'. $node->nid .'/delete', array(), t('Delete')); + $this->assertResponse(200); + // View node (to generate page not found event). + $this->drupalGet('node/'. $node->nid); + $this->assertResponse(404); + // View the dblog report (to generate access denied event). + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse(403); + + // Login the admin user. + $this->drupalLogin($this->big_user); + // View the dblog report. + $this->drupalGet('admin/reports/dblog'); + $this->assertResponse(200); + + // Verify events were recorded. + // Content added. + $this->assertRaw(t('@type: added %title', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content added]')); + // Content updated. + $this->assertRaw(t('@type: updated %title', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content updated]')); + // Content deleted. + $this->assertRaw(t('@type: deleted %title', array('@type' => $type, '%title' => $title)), t('DBLog event was recorded: [content deleted]')); + + // View dblog access-denied report node. + $this->drupalGet('admin/reports/access-denied'); + $this->assertResponse(200); + // Access denied. + $this->assertText(t('admin/reports/dblog'), t('DBLog event was recorded: [access denied]')); + + // View dblog page-not-found report node. + $this->drupalGet('admin/reports/page-not-found'); + $this->assertResponse(200); + // Page not found. + $this->assertText(t('node/@nid', array('@nid' => $node->nid)), t('DBLog event was recorded: [page not found]')); + } + + /** + * Create content based on content type. + * + * @param string $type Content type. + * @return array Content. + */ + private function getContent($type) { + switch ($type) { + case 'poll': + $content = array( + 'title' => $this->randomName(8), + 'choice[0][chtext]' => $this->randomName(32), + 'choice[1][chtext]' => $this->randomName(32), + ); + break; + + default: + $content = array( + 'title' => $this->randomName(8), + 'body' => $this->randomName(32), + ); + break; + } + return $content; + } + + /** + * Create content update based on content type. + * + * @param string $type Content type. + * @return array Content. + */ + private function getContentUpdate($type) { + switch ($type) { + case 'poll': + $content = array( + 'choice[0][chtext]' => $this->randomName(32), + 'choice[1][chtext]' => $this->randomName(32), + ); + break; + + default: + $content = array( + 'body' => $this->randomName(32), + ); + break; + } + return $content; + } + + /** + * Reload variables table. + */ + private function reloadVariables() { + global $conf; + + cache_clear_all('variables', 'cache'); + $conf = variable_init(); + $this->assertTrue($conf, t('Variables reloaded')); + } +} diff --git a/modules/filter/filter.test b/modules/filter/filter.test new file mode 100644 index 00000000000..a18582ac40e --- /dev/null +++ b/modules/filter/filter.test @@ -0,0 +1,182 @@ + t('Filter administration functionality'), + 'description' => t('Thoroughly test the administrative interface of the filter module.'), + 'group' => t('Filter'), + ); + } + + /** + * Test filter administration functionality. + */ + function testFilter() { + $first_filter = 2; // URL filter. + $second_filter = 1; // Line filter. + + // Create users. + $admin_user = $this->drupalCreateUser(array('administer filters')); + $web_user = $this->drupalCreateUser(array('create page content')); + + $this->drupalLogin($admin_user); + + list($filtered, $full) = $this->checkFilterFormats(); + + // Change default filter. + $edit = array(); + $edit['default'] = $full; + $this->drupalPost('admin/settings/filters', $edit, t('Save changes')); + $this->assertText(t('Default format updated.'), t('Default filter updated successfully.')); + + $this->assertNoRaw('admin/settings/filters/delete/'. $full, t('Delete link not found.')); + + // Add an additional tag. + $edit = array(); + $edit['allowed_html_1'] = '