cms/drupal/modules/dblog/dblog.test
changeset 541 e756a8c72c3d
equal deleted inserted replaced
540:07239de796bb 541:e756a8c72c3d
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * @file
       
     5  * Tests for dblog.module.
       
     6  */
       
     7 
       
     8 /**
       
     9  * Tests logging messages to the database.
       
    10  */
       
    11 class DBLogTestCase extends DrupalWebTestCase {
       
    12 
       
    13   /**
       
    14    * A user with some relevant administrative permissions.
       
    15    *
       
    16    * @var object
       
    17    */
       
    18   protected $big_user;
       
    19 
       
    20   /**
       
    21    * A user without any permissions.
       
    22    *
       
    23    * @var object
       
    24    */
       
    25   protected $any_user;
       
    26 
       
    27   public static function getInfo() {
       
    28     return array(
       
    29       'name' => 'DBLog functionality',
       
    30       'description' => 'Generate events and verify dblog entries; verify user access to log reports based on persmissions.',
       
    31       'group' => 'DBLog',
       
    32     );
       
    33   }
       
    34 
       
    35   /**
       
    36    * Enable modules and create users with specific permissions.
       
    37    */
       
    38   function setUp() {
       
    39     parent::setUp('dblog', 'blog', 'poll');
       
    40     // Create users.
       
    41     $this->big_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users'));
       
    42     $this->any_user = $this->drupalCreateUser(array());
       
    43   }
       
    44 
       
    45   /**
       
    46    * Tests Database Logging module functionality through interfaces.
       
    47    *
       
    48    * First logs in users, then creates database log events, and finally tests
       
    49    * Database Logging module functionality through both the admin and user
       
    50    * interfaces.
       
    51    */
       
    52   function testDBLog() {
       
    53     // Login the admin user.
       
    54     $this->drupalLogin($this->big_user);
       
    55 
       
    56     $row_limit = 100;
       
    57     $this->verifyRowLimit($row_limit);
       
    58     $this->verifyCron($row_limit);
       
    59     $this->verifyEvents();
       
    60     $this->verifyReports();
       
    61 
       
    62     // Login the regular user.
       
    63     $this->drupalLogin($this->any_user);
       
    64     $this->verifyReports(403);
       
    65   }
       
    66 
       
    67   /**
       
    68    * Verifies setting of the database log row limit.
       
    69    *
       
    70    * @param int $row_limit
       
    71    *   The row limit.
       
    72    */
       
    73   private function verifyRowLimit($row_limit) {
       
    74     // Change the database log row limit.
       
    75     $edit = array();
       
    76     $edit['dblog_row_limit'] = $row_limit;
       
    77     $this->drupalPost('admin/config/development/logging', $edit, t('Save configuration'));
       
    78     $this->assertResponse(200);
       
    79 
       
    80     // Check row limit variable.
       
    81     $current_limit = variable_get('dblog_row_limit', 1000);
       
    82     $this->assertTrue($current_limit == $row_limit, format_string('[Cache] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit)));
       
    83     // Verify dblog row limit equals specified row limit.
       
    84     $current_limit = unserialize(db_query("SELECT value FROM {variable} WHERE name = :dblog_limit", array(':dblog_limit' => 'dblog_row_limit'))->fetchField());
       
    85     $this->assertTrue($current_limit == $row_limit, format_string('[Variable table] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit)));
       
    86   }
       
    87 
       
    88   /**
       
    89    * Verifies that cron correctly applies the database log row limit.
       
    90    *
       
    91    * @param int $row_limit
       
    92    *   The row limit.
       
    93    */
       
    94   private function verifyCron($row_limit) {
       
    95     // Generate additional log entries.
       
    96     $this->generateLogEntries($row_limit + 10);
       
    97     // Verify that the database log row count exceeds the row limit.
       
    98     $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField();
       
    99     $this->assertTrue($count > $row_limit, format_string('Dblog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit)));
       
   100 
       
   101     // Run a cron job.
       
   102     $this->cronRun();
       
   103     // Verify that the database log row count equals the row limit plus one
       
   104     // because cron adds a record after it runs.
       
   105     $count = db_query('SELECT COUNT(wid) FROM {watchdog}')->fetchField();
       
   106     $this->assertTrue($count == $row_limit + 1, format_string('Dblog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit)));
       
   107   }
       
   108 
       
   109   /**
       
   110    * Generates a number of random database log events.
       
   111    *
       
   112    * @param int $count
       
   113    *   Number of watchdog entries to generate.
       
   114    * @param string $type
       
   115    *   (optional) The type of watchdog entry. Defaults to 'custom'.
       
   116    * @param int $severity
       
   117    *   (optional) The severity of the watchdog entry. Defaults to WATCHDOG_NOTICE.
       
   118    */
       
   119   private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) {
       
   120     global $base_root;
       
   121 
       
   122     // This long URL makes it just a little bit harder to pass the link part of
       
   123     // the test with a mix of English words and a repeating series of random
       
   124     // percent-encoded Chinese characters.
       
   125     $link = urldecode('/content/xo%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A%E9%85%B1%E5%87%89%E6%8B%8C%E7%B4%A0%E9%B8%A1%E7%85%A7%E7%83%A7%E9%B8%A1%E9%BB%84%E7%8E%AB%E7%91%B0-%E7%A7%91%E5%B7%9E%E7%9A%84%E5%B0%8F%E4%B9%9D%E5%AF%A8%E6%B2%9F%E7%BB%9D%E7%BE%8E%E9%AB%98%E5%B1%B1%E6%B9%96%E6%B3%8A-lake-isabelle');
       
   126 
       
   127     // Prepare the fields to be logged
       
   128     $log = array(
       
   129       'type'        => $type,
       
   130       'message'     => 'Log entry added to test the dblog row limit.',
       
   131       'variables'   => array(),
       
   132       'severity'    => $severity,
       
   133       'link'        => $link,
       
   134       'user'        => $this->big_user,
       
   135       'uid'         => isset($this->big_user->uid) ? $this->big_user->uid : 0,
       
   136       'request_uri' => $base_root . request_uri(),
       
   137       'referer'     => $_SERVER['HTTP_REFERER'],
       
   138       'ip'          => ip_address(),
       
   139       'timestamp'   => REQUEST_TIME,
       
   140       );
       
   141     $message = 'Log entry added to test the dblog row limit. Entry #';
       
   142     for ($i = 0; $i < $count; $i++) {
       
   143       $log['message'] = $message . $i;
       
   144       dblog_watchdog($log);
       
   145     }
       
   146   }
       
   147 
       
   148   /**
       
   149    * Confirms that database log reports are displayed at the correct paths.
       
   150    *
       
   151    * @param int $response
       
   152    *   (optional) HTTP response code. Defaults to 200.
       
   153    */
       
   154   private function verifyReports($response = 200) {
       
   155     $quote = '&#039;';
       
   156 
       
   157     // View the database log help page.
       
   158     $this->drupalGet('admin/help/dblog');
       
   159     $this->assertResponse($response);
       
   160     if ($response == 200) {
       
   161       $this->assertText(t('Database logging'), 'DBLog help was displayed');
       
   162     }
       
   163 
       
   164     // View the database log report page.
       
   165     $this->drupalGet('admin/reports/dblog');
       
   166     $this->assertResponse($response);
       
   167     if ($response == 200) {
       
   168       $this->assertText(t('Recent log messages'), 'DBLog report was displayed');
       
   169     }
       
   170 
       
   171     // View the database log page-not-found report page.
       
   172     $this->drupalGet('admin/reports/page-not-found');
       
   173     $this->assertResponse($response);
       
   174     if ($response == 200) {
       
   175       $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), 'DBLog page-not-found report was displayed');
       
   176     }
       
   177 
       
   178     // View the database log access-denied report page.
       
   179     $this->drupalGet('admin/reports/access-denied');
       
   180     $this->assertResponse($response);
       
   181     if ($response == 200) {
       
   182       $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), 'DBLog access-denied report was displayed');
       
   183     }
       
   184 
       
   185     // View the database log event page.
       
   186     $this->drupalGet('admin/reports/event/1');
       
   187     $this->assertResponse($response);
       
   188     if ($response == 200) {
       
   189       $this->assertText(t('Details'), 'DBLog event node was displayed');
       
   190     }
       
   191   }
       
   192 
       
   193   /**
       
   194    * Generates and then verifies various types of events.
       
   195    */
       
   196   private function verifyEvents() {
       
   197     // Invoke events.
       
   198     $this->doUser();
       
   199     $this->doNode('article');
       
   200     $this->doNode('blog');
       
   201     $this->doNode('page');
       
   202     $this->doNode('poll');
       
   203 
       
   204     // When a user account is canceled, any content they created remains but the
       
   205     // uid = 0. Their blog entry shows as "'s blog" on the home page. Records
       
   206     // in the watchdog table related to that user have the uid set to zero.
       
   207   }
       
   208 
       
   209   /**
       
   210    * Generates and then verifies some user events.
       
   211    */
       
   212   private function doUser() {
       
   213     // Set user variables.
       
   214     $name = $this->randomName();
       
   215     $pass = user_password();
       
   216     // Add a user using the form to generate an add user event (which is not
       
   217     // triggered by drupalCreateUser).
       
   218     $edit = array();
       
   219     $edit['name'] = $name;
       
   220     $edit['mail'] = $name . '@example.com';
       
   221     $edit['pass[pass1]'] = $pass;
       
   222     $edit['pass[pass2]'] = $pass;
       
   223     $edit['status'] = 1;
       
   224     $this->drupalPost('admin/people/create', $edit, t('Create new account'));
       
   225     $this->assertResponse(200);
       
   226     // Retrieve the user object.
       
   227     $user = user_load_by_name($name);
       
   228     $this->assertTrue($user != NULL, format_string('User @name was loaded', array('@name' => $name)));
       
   229     // pass_raw property is needed by drupalLogin.
       
   230     $user->pass_raw = $pass;
       
   231     // Login user.
       
   232     $this->drupalLogin($user);
       
   233     // Logout user.
       
   234     $this->drupalLogout();
       
   235     // Fetch the row IDs in watchdog that relate to the user.
       
   236     $result = db_query('SELECT wid FROM {watchdog} WHERE uid = :uid', array(':uid' => $user->uid));
       
   237     foreach ($result as $row) {
       
   238       $ids[] = $row->wid;
       
   239     }
       
   240     $count_before = (isset($ids)) ? count($ids) : 0;
       
   241     $this->assertTrue($count_before > 0, format_string('DBLog contains @count records for @name', array('@count' => $count_before, '@name' => $user->name)));
       
   242 
       
   243     // Login the admin user.
       
   244     $this->drupalLogin($this->big_user);
       
   245     // Delete the user created at the start of this test.
       
   246     // We need to POST here to invoke batch_process() in the internal browser.
       
   247     $this->drupalPost('user/' . $user->uid . '/cancel', array('user_cancel_method' => 'user_cancel_reassign'), t('Cancel account'));
       
   248 
       
   249     // View the database log report.
       
   250     $this->drupalGet('admin/reports/dblog');
       
   251     $this->assertResponse(200);
       
   252 
       
   253     // Verify that the expected events were recorded.
       
   254     // Add user.
       
   255     // Default display includes name and email address; if too long, the email
       
   256     // address is replaced by three periods.
       
   257     $this->assertLogMessage(t('New user: %name (%email).', array('%name' => $name, '%email' => $user->mail)), 'DBLog event was recorded: [add user]');
       
   258     // Login user.
       
   259     $this->assertLogMessage(t('Session opened for %name.', array('%name' => $name)), 'DBLog event was recorded: [login user]');
       
   260     // Logout user.
       
   261     $this->assertLogMessage(t('Session closed for %name.', array('%name' => $name)), 'DBLog event was recorded: [logout user]');
       
   262     // Delete user.
       
   263     $message = t('Deleted user: %name %email.', array('%name' => $name, '%email' => '<' . $user->mail . '>'));
       
   264     $message_text = truncate_utf8(filter_xss($message, array()), 56, TRUE, TRUE);
       
   265     // Verify that the full message displays on the details page.
       
   266     $link = FALSE;
       
   267     if ($links = $this->xpath('//a[text()="' . html_entity_decode($message_text) . '"]')) {
       
   268       // Found link with the message text.
       
   269       $links = array_shift($links);
       
   270       foreach ($links->attributes() as $attr => $value) {
       
   271         if ($attr == 'href') {
       
   272           // Extract link to details page.
       
   273           $link = drupal_substr($value, strpos($value, 'admin/reports/event/'));
       
   274           $this->drupalGet($link);
       
   275           // Check for full message text on the details page.
       
   276           $this->assertRaw($message, 'DBLog event details was found: [delete user]');
       
   277           break;
       
   278         }
       
   279       }
       
   280     }
       
   281     $this->assertTrue($link, 'DBLog event was recorded: [delete user]');
       
   282     // Visit random URL (to generate page not found event).
       
   283     $not_found_url = $this->randomName(60);
       
   284     $this->drupalGet($not_found_url);
       
   285     $this->assertResponse(404);
       
   286     // View the database log page-not-found report page.
       
   287     $this->drupalGet('admin/reports/page-not-found');
       
   288     $this->assertResponse(200);
       
   289     // Check that full-length URL displayed.
       
   290     $this->assertText($not_found_url, 'DBLog event was recorded: [page not found]');
       
   291   }
       
   292 
       
   293   /**
       
   294    * Generates and then verifies some node events.
       
   295    *
       
   296    * @param string $type
       
   297    *   A node type (e.g., 'article', 'page' or 'poll').
       
   298    */
       
   299   private function doNode($type) {
       
   300     // Create user.
       
   301     $perm = array('create ' . $type . ' content', 'edit own ' . $type . ' content', 'delete own ' . $type . ' content');
       
   302     $user = $this->drupalCreateUser($perm);
       
   303     // Login user.
       
   304     $this->drupalLogin($user);
       
   305 
       
   306     // Create a node using the form in order to generate an add content event
       
   307     // (which is not triggered by drupalCreateNode).
       
   308     $edit = $this->getContent($type);
       
   309     $langcode = LANGUAGE_NONE;
       
   310     $title = $edit["title"];
       
   311     $this->drupalPost('node/add/' . $type, $edit, t('Save'));
       
   312     $this->assertResponse(200);
       
   313     // Retrieve the node object.
       
   314     $node = $this->drupalGetNodeByTitle($title);
       
   315     $this->assertTrue($node != NULL, format_string('Node @title was loaded', array('@title' => $title)));
       
   316     // Edit the node.
       
   317     $edit = $this->getContentUpdate($type);
       
   318     $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));
       
   319     $this->assertResponse(200);
       
   320     // Delete the node.
       
   321     $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete'));
       
   322     $this->assertResponse(200);
       
   323     // View the node (to generate page not found event).
       
   324     $this->drupalGet('node/' . $node->nid);
       
   325     $this->assertResponse(404);
       
   326     // View the database log report (to generate access denied event).
       
   327     $this->drupalGet('admin/reports/dblog');
       
   328     $this->assertResponse(403);
       
   329 
       
   330     // Login the admin user.
       
   331     $this->drupalLogin($this->big_user);
       
   332     // View the database log report.
       
   333     $this->drupalGet('admin/reports/dblog');
       
   334     $this->assertResponse(200);
       
   335 
       
   336     // Verify that node events were recorded.
       
   337     // Was node content added?
       
   338     $this->assertLogMessage(t('@type: added %title.', array('@type' => $type, '%title' => $title)), 'DBLog event was recorded: [content added]');
       
   339     // Was node content updated?
       
   340     $this->assertLogMessage(t('@type: updated %title.', array('@type' => $type, '%title' => $title)), 'DBLog event was recorded: [content updated]');
       
   341     // Was node content deleted?
       
   342     $this->assertLogMessage(t('@type: deleted %title.', array('@type' => $type, '%title' => $title)), 'DBLog event was recorded: [content deleted]');
       
   343 
       
   344     // View the database log access-denied report page.
       
   345     $this->drupalGet('admin/reports/access-denied');
       
   346     $this->assertResponse(200);
       
   347     // Verify that the 'access denied' event was recorded.
       
   348     $this->assertText(t('admin/reports/dblog'), 'DBLog event was recorded: [access denied]');
       
   349 
       
   350     // View the database log page-not-found report page.
       
   351     $this->drupalGet('admin/reports/page-not-found');
       
   352     $this->assertResponse(200);
       
   353     // Verify that the 'page not found' event was recorded.
       
   354     $this->assertText(t('node/@nid', array('@nid' => $node->nid)), 'DBLog event was recorded: [page not found]');
       
   355   }
       
   356 
       
   357   /**
       
   358    * Creates random content based on node content type.
       
   359    *
       
   360    * @param string $type
       
   361    *   Node content type (e.g., 'article').
       
   362    *
       
   363    * @return array
       
   364    *   Random content needed by various node types.
       
   365    */
       
   366   private function getContent($type) {
       
   367     $langcode = LANGUAGE_NONE;
       
   368     switch ($type) {
       
   369       case 'poll':
       
   370         $content = array(
       
   371           "title" => $this->randomName(8),
       
   372           'choice[new:0][chtext]' => $this->randomName(32),
       
   373           'choice[new:1][chtext]' => $this->randomName(32),
       
   374         );
       
   375       break;
       
   376 
       
   377       default:
       
   378         $content = array(
       
   379           "title" => $this->randomName(8),
       
   380           "body[$langcode][0][value]" => $this->randomName(32),
       
   381         );
       
   382       break;
       
   383     }
       
   384     return $content;
       
   385   }
       
   386 
       
   387   /**
       
   388    * Creates random content as an update based on node content type.
       
   389    *
       
   390    * @param string $type
       
   391    *   Node content type (e.g., 'article').
       
   392    *
       
   393    * @return array
       
   394    *   Random content needed by various node types.
       
   395    */
       
   396   private function getContentUpdate($type) {
       
   397     switch ($type) {
       
   398       case 'poll':
       
   399         $content = array(
       
   400           'choice[chid:1][chtext]' => $this->randomName(32),
       
   401           'choice[chid:2][chtext]' => $this->randomName(32),
       
   402         );
       
   403       break;
       
   404 
       
   405       default:
       
   406         $langcode = LANGUAGE_NONE;
       
   407         $content = array(
       
   408           "body[$langcode][0][value]" => $this->randomName(32),
       
   409         );
       
   410       break;
       
   411     }
       
   412     return $content;
       
   413   }
       
   414 
       
   415   /**
       
   416    * Tests the addition and clearing of log events through the admin interface.
       
   417    *
       
   418    * Logs in the admin user, creates a database log event, and tests the
       
   419    * functionality of clearing the database log through the admin interface.
       
   420    */
       
   421   protected function testDBLogAddAndClear() {
       
   422     global $base_root;
       
   423     // Get a count of how many watchdog entries already exist.
       
   424     $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
       
   425     $log = array(
       
   426       'type'        => 'custom',
       
   427       'message'     => 'Log entry added to test the doClearTest clear down.',
       
   428       'variables'   => array(),
       
   429       'severity'    => WATCHDOG_NOTICE,
       
   430       'link'        => NULL,
       
   431       'user'        => $this->big_user,
       
   432       'uid'         => isset($this->big_user->uid) ? $this->big_user->uid : 0,
       
   433       'request_uri' => $base_root . request_uri(),
       
   434       'referer'     => $_SERVER['HTTP_REFERER'],
       
   435       'ip'          => ip_address(),
       
   436       'timestamp'   => REQUEST_TIME,
       
   437     );
       
   438     // Add a watchdog entry.
       
   439     dblog_watchdog($log);
       
   440     // Make sure the table count has actually been incremented.
       
   441     $this->assertEqual($count + 1, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), format_string('dblog_watchdog() added an entry to the dblog :count', array(':count' => $count)));
       
   442     // Login the admin user.
       
   443     $this->drupalLogin($this->big_user);
       
   444     // Post in order to clear the database table.
       
   445     $this->drupalPost('admin/reports/dblog', array(), t('Clear log messages'));
       
   446     // Count the rows in watchdog that previously related to the deleted user.
       
   447     $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField();
       
   448     $this->assertEqual($count, 0, format_string('DBLog contains :count records after a clear.', array(':count' => $count)));
       
   449   }
       
   450 
       
   451   /**
       
   452    * Tests the database log filter functionality at admin/reports/dblog.
       
   453    */
       
   454   protected function testFilter() {
       
   455     $this->drupalLogin($this->big_user);
       
   456 
       
   457     // Clear the log to ensure that only generated entries will be found.
       
   458     db_delete('watchdog')->execute();
       
   459 
       
   460     // Generate 9 random watchdog entries.
       
   461     $type_names = array();
       
   462     $types = array();
       
   463     for ($i = 0; $i < 3; $i++) {
       
   464       $type_names[] = $type_name = $this->randomName();
       
   465       $severity = WATCHDOG_EMERGENCY;
       
   466       for ($j = 0; $j < 3; $j++) {
       
   467         $types[] = $type = array(
       
   468           'count' => $j + 1,
       
   469           'type' => $type_name,
       
   470           'severity' => $severity++,
       
   471         );
       
   472         $this->generateLogEntries($type['count'], $type['type'], $type['severity']);
       
   473       }
       
   474     }
       
   475 
       
   476     // View the database log page.
       
   477     $this->drupalGet('admin/reports/dblog');
       
   478 
       
   479     // Confirm that all the entries are displayed.
       
   480     $count = $this->getTypeCount($types);
       
   481     foreach ($types as $key => $type) {
       
   482       $this->assertEqual($count[$key], $type['count'], 'Count matched');
       
   483     }
       
   484 
       
   485     // Filter by each type and confirm that entries with various severities are
       
   486     // displayed.
       
   487     foreach ($type_names as $type_name) {
       
   488       $edit = array(
       
   489         'type[]' => array($type_name),
       
   490       );
       
   491       $this->drupalPost(NULL, $edit, t('Filter'));
       
   492 
       
   493       // Count the number of entries of this type.
       
   494       $type_count = 0;
       
   495       foreach ($types as $type) {
       
   496         if ($type['type'] == $type_name) {
       
   497           $type_count += $type['count'];
       
   498         }
       
   499       }
       
   500 
       
   501       $count = $this->getTypeCount($types);
       
   502       $this->assertEqual(array_sum($count), $type_count, 'Count matched');
       
   503     }
       
   504 
       
   505     // Set the filter to match each of the two filter-type attributes and
       
   506     // confirm the correct number of entries are displayed.
       
   507     foreach ($types as $key => $type) {
       
   508       $edit = array(
       
   509         'type[]' => array($type['type']),
       
   510         'severity[]' => array($type['severity']),
       
   511       );
       
   512       $this->drupalPost(NULL, $edit, t('Filter'));
       
   513 
       
   514       $count = $this->getTypeCount($types);
       
   515       $this->assertEqual(array_sum($count), $type['count'], 'Count matched');
       
   516     }
       
   517 
       
   518     // Clear all logs and make sure the confirmation message is found.
       
   519     $this->drupalPost('admin/reports/dblog', array(), t('Clear log messages'));
       
   520     $this->assertText(t('Database log cleared.'), 'Confirmation message found');
       
   521   }
       
   522 
       
   523   /**
       
   524    * Verifies that exceptions are caught in dblog_watchdog().
       
   525    */
       
   526   protected function testDBLogException() {
       
   527     $log = array(
       
   528       'type'        => 'custom',
       
   529       'message'     => 'Log entry added to test watchdog handling of Exceptions.',
       
   530       'variables'   => array(),
       
   531       'severity'    => WATCHDOG_NOTICE,
       
   532       'link'        => NULL,
       
   533       'user'        => $this->big_user,
       
   534       'uid'         => isset($this->big_user->uid) ? $this->big_user->uid : 0,
       
   535       'request_uri' => request_uri(),
       
   536       'referer'     => $_SERVER['HTTP_REFERER'],
       
   537       'ip'          => ip_address(),
       
   538       'timestamp'   => REQUEST_TIME,
       
   539     );
       
   540 
       
   541     // Remove watchdog table temporarily to simulate it missing during
       
   542     // installation.
       
   543     db_query("DROP TABLE {watchdog}");
       
   544 
       
   545     // Add a watchdog entry.
       
   546     // This should not throw an Exception, but fail silently.
       
   547     dblog_watchdog($log);
       
   548   }
       
   549 
       
   550   /**
       
   551    * Gets the database log event information from the browser page.
       
   552    *
       
   553    * @return array
       
   554    *   List of log events where each event is an array with following keys:
       
   555    *   - severity: (int) A database log severity constant.
       
   556    *   - type: (string) The type of database log event.
       
   557    *   - message: (string) The message for this database log event.
       
   558    *   - user: (string) The user associated with this database log event.
       
   559    */
       
   560   protected function getLogEntries() {
       
   561     $entries = array();
       
   562     if ($table = $this->xpath('.//table[@id="admin-dblog"]')) {
       
   563       $table = array_shift($table);
       
   564       foreach ($table->tbody->tr as $row) {
       
   565         $entries[] = array(
       
   566           'severity' => $this->getSeverityConstant($row['class']),
       
   567           'type' => $this->asText($row->td[1]),
       
   568           'message' => $this->asText($row->td[3]),
       
   569           'user' => $this->asText($row->td[4]),
       
   570         );
       
   571       }
       
   572     }
       
   573     return $entries;
       
   574   }
       
   575 
       
   576   /**
       
   577    * Gets the count of database log entries by database log event type.
       
   578    *
       
   579    * @param array $types
       
   580    *   The type information to compare against.
       
   581    *
       
   582    * @return array
       
   583    *   The count of each type keyed by the key of the $types array.
       
   584    */
       
   585   protected function getTypeCount(array $types) {
       
   586     $entries = $this->getLogEntries();
       
   587     $count = array_fill(0, count($types), 0);
       
   588     foreach ($entries as $entry) {
       
   589       foreach ($types as $key => $type) {
       
   590         if ($entry['type'] == $type['type'] && $entry['severity'] == $type['severity']) {
       
   591           $count[$key]++;
       
   592           break;
       
   593         }
       
   594       }
       
   595     }
       
   596     return $count;
       
   597   }
       
   598 
       
   599   /**
       
   600    * Gets the watchdog severity constant corresponding to the CSS class.
       
   601    *
       
   602    * @param string $class
       
   603    *   CSS class attribute.
       
   604    *
       
   605    * @return int|null
       
   606    *   The watchdog severity constant or NULL if not found.
       
   607    *
       
   608    * @ingroup logging_severity_levels
       
   609    */
       
   610   protected function getSeverityConstant($class) {
       
   611     // Reversed array from dblog_overview().
       
   612     $map = array(
       
   613       'dblog-debug' => WATCHDOG_DEBUG,
       
   614       'dblog-info' => WATCHDOG_INFO,
       
   615       'dblog-notice' => WATCHDOG_NOTICE,
       
   616       'dblog-warning' => WATCHDOG_WARNING,
       
   617       'dblog-error' => WATCHDOG_ERROR,
       
   618       'dblog-critical' => WATCHDOG_CRITICAL,
       
   619       'dblog-alert' => WATCHDOG_ALERT,
       
   620       'dblog-emerg' => WATCHDOG_EMERGENCY,
       
   621     );
       
   622 
       
   623     // Find the class that contains the severity.
       
   624     $classes = explode(' ', $class);
       
   625     foreach ($classes as $class) {
       
   626       if (isset($map[$class])) {
       
   627         return $map[$class];
       
   628       }
       
   629     }
       
   630     return NULL;
       
   631   }
       
   632 
       
   633   /**
       
   634    * Extracts the text contained by the XHTML element.
       
   635    *
       
   636    * @param SimpleXMLElement $element
       
   637    *   Element to extract text from.
       
   638    *
       
   639    * @return string
       
   640    *   Extracted text.
       
   641    */
       
   642   protected function asText(SimpleXMLElement $element) {
       
   643     if (!is_object($element)) {
       
   644       return $this->fail('The element is not an element.');
       
   645     }
       
   646     return trim(html_entity_decode(strip_tags($element->asXML())));
       
   647   }
       
   648 
       
   649   /**
       
   650    * Confirms that a log message appears on the database log overview screen.
       
   651    *
       
   652    * This function should only be used for the admin/reports/dblog page, because
       
   653    * it checks for the message link text truncated to 56 characters. Other log
       
   654    * pages have no detail links so they contain the full message text.
       
   655    *
       
   656    * @param string $log_message
       
   657    *   The database log message to check.
       
   658    * @param string $message
       
   659    *   The message to pass to simpletest.
       
   660    */
       
   661   protected function assertLogMessage($log_message, $message) {
       
   662     $message_text = truncate_utf8(filter_xss($log_message, array()), 56, TRUE, TRUE);
       
   663     // After filter_xss(), HTML entities should be converted to their character
       
   664     // equivalents because assertLink() uses this string in xpath() to query the
       
   665     // Document Object Model (DOM).
       
   666     $this->assertLink(html_entity_decode($message_text), 0, $message);
       
   667   }
       
   668 
       
   669   /**
       
   670    * Make sure HTML tags are filtered out in the log detail page.
       
   671    */
       
   672   public function testLogMessageSanitized() {
       
   673     $this->drupalLogin($this->big_user);
       
   674 
       
   675     // Make sure dangerous HTML tags are filtered out in log detail page.
       
   676     $log = array(
       
   677       'uid' => 0,
       
   678       'type' => 'custom',
       
   679       'message' => "<script>alert('foo');</script> <strong>Lorem ipsum</strong>",
       
   680       'variables' => NULL,
       
   681       'severity' => WATCHDOG_NOTICE,
       
   682       'link' => 'foo/bar',
       
   683       'request_uri' => 'http://example.com?dblog=1',
       
   684       'referer' => 'http://example.org?dblog=2',
       
   685       'ip' => '0.0.1.0',
       
   686       'timestamp' => REQUEST_TIME,
       
   687     );
       
   688     dblog_watchdog($log);
       
   689 
       
   690     $wid = db_query('SELECT MAX(wid) FROM {watchdog}')->fetchField();
       
   691     $this->drupalGet('admin/reports/event/' . $wid);
       
   692     $this->assertResponse(200);
       
   693     $this->assertNoRaw("<script>alert('foo');</script>");
       
   694     $this->assertRaw("alert('foo'); <strong>Lorem ipsum</strong>");
       
   695   }
       
   696 }