You are here

better_statistics.test in Better Statistics 7

Tests for the Better Statistics Module.

File

better_statistics.test
View source
<?php

/**
 * @file
 * Tests for the Better Statistics Module.
 */

/**
 * Functional tests for Better Statistics.
 */
class BetterStatisticsTest extends DrupalWebTestCase {
  protected $stats_admin = 'admin/config/system/statistics';
  protected $perf_admin = 'admin/config/development/performance';
  protected $ajax_accesslog = 'statistics/ajax/accesslog';
  protected $ajax_entityview = 'statistics/ajax/entity_view';
  public static function getInfo() {
    return array(
      'name' => 'Better Statistics functionality',
      'description' => 'Basic functionality of the Better Statistics module',
      'group' => 'Better Statistics',
    );
  }
  public function setUp() {

    // Enable required modules.
    $modules = array(
      'statistics',
      'ctools',
      'better_statistics',
      'dblog',
    );
    parent::setUp($modules);

    // Enable the accesslog.
    variable_set('statistics_enable_access_log', BETTER_STATISTICS_ACCESSLOG_DEFAULT);

    // Enable page caching.
    variable_set('cache', 1);

    // Create a node.
    $this->node = $this
      ->drupalCreateNode();
  }
  public function tearDown() {
    parent::tearDown();
  }

  /**
   * Test admin configuration functionality of the module.
   */
  public function testConfigurationChanges() {

    // Create a user and log it in.
    $this->admin_user = $this
      ->drupalCreateUser(array(
      'access administration pages',
      'administer statistics',
      'administer site configuration',
      'access content',
      'access statistics',
    ));
    $this
      ->drupalLogin($this->admin_user);

    // Check to see that the "cache" field is available.
    $this
      ->drupalGet($this->stats_admin);
    $this
      ->assertNoFieldChecked('edit-better-statistics-better-statistics-fields-cache', t('Cache field not enabled.'));

    // Enable the cache field.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_DEFAULT,
      'better_statistics[better_statistics][fields][cache]' => TRUE,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check to see that the cache field is checked and in the table.
    $this
      ->assertFieldChecked('edit-better-statistics-better-statistics-fields-cache', t('Cache field is checked.'));
    $this
      ->assertTrue(db_field_exists('accesslog', 'cache'), t('Cache field added to the database.'));

    // Disable the cache field.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_DEFAULT,
      'better_statistics[better_statistics][fields][cache]' => FALSE,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check to see that the cache field is gone and not in the table.
    $this
      ->assertNoFieldChecked('edit-better-statistics-better-statistics-fields-cache', t('Cache field disabled.'));
    $this
      ->assertFalse(db_field_exists('accesslog', 'cache'), t('Cache field removed from the databases.'));

    // @todo Split these tests off maybe?
    // Enable the user_agent field.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_DEFAULT,
      'better_statistics[better_statistics][fields][user_agent]' => TRUE,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Visit the homepage.
    $this
      ->drupalGet('node');

    // Check for an entry in the accesslog with that user-agent.
    $query = db_query('SELECT aid FROM {accesslog} WHERE user_agent LIKE :ua', array(
      ':ua' => 'simpletest%',
    ));
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows, t('User-agent string successfully found in the database.'));

    // Check that the user agent string is added to the accesslog detail page.
    $aid = $query
      ->fetchObject();
    $this
      ->drupalGet('admin/reports/access/' . $aid->aid);
    $this
      ->assertText(t('User-agent'), t('User-agent field displayed on accesslog detail page.'));

    // Disable the user_agent field.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_DEFAULT,
      'better_statistics[better_statistics][fields][user_agent]' => FALSE,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Statistics are collected on cached pages by default. Check that this is
    // reflected in the cache fieldset.
    $this
      ->drupalGet($this->stats_admin);
    $this
      ->assertText('Tracking of page requests served from cache is currently enabled', t('Stats/performance settings properly reflect configuration.'));

    // Disable logging of cached pages.
    $edit = array(
      'statistics_access_log_restrictions_cache' => FALSE,
    );
    $this
      ->drupalPost($this->perf_admin, $edit, t('Save configuration'));

    // Ensure the configuration is reflected.
    $this
      ->drupalGet($this->stats_admin);
    $this
      ->assertText('Tracking of page requests served from cache is currently disabled', t('Stats/performance settings properly reflect configuration.'));

    // Check that client-side only mode does not log any data for both
    // HTML-based and callback-based URLs from the server side.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_CLIENT,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('rss.xml');
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not being collected server-side when in client-side only mode.'));

    // Ensure the BS JS API snippet and file are included on the page.
    $js_cache = variable_get('drupal_js_cache_files');
    $bs_js_file = file_create_url($js_cache[key($js_cache)]);
    $this
      ->assertRaw("w['BetterStatsObj']", t('Found reference to BS JS API async loader.'));
    $this
      ->assertRaw($bs_js_file, t('Found reference to BS JS API file.'));

    // Check that mixed-mode logs data on callback-based pages, but not
    // HTML-based pages from the server-side.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_MIXED,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('rss.xml');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected server-side when in mixed mode on non-HTML pages.'));
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data not being collected server-side when in mixed mode on HTML pages.'));

    // Ensure the BS JS API snippet and file are included on the page.
    $js_cache = variable_get('drupal_js_cache_files');
    $bs_js_file = file_create_url($js_cache[key($js_cache)]);
    $this
      ->assertRaw("w['BetterStatsObj']", t('Found reference to BS JS API async loader.'));
    $this
      ->assertRaw($bs_js_file, t('Found reference to BS JS API file.'));

    // Revert to the default configuration.
    $edit = array(
      'statistics_enable_access_log' => BETTER_STATISTICS_ACCESSLOG_DEFAULT,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check that client-side only mode does not log any data for both
    // HTML-based and callback-based URLs from the server side.
    $edit = array(
      'statistics_count_content_views' => BETTER_STATISTICS_ENTITY_VIEW_CLIENT,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));
    db_truncate('node_counter')
      ->execute();
    $this
      ->drupalGet('node/' . $this->node->nid);
    $query = db_query('SELECT * FROM {node_counter} WHERE nid=:nid AND daycount=1', array(
      ':nid' => $this->node->nid,
    ));
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Entity View data not collected server-side when in client-side only mode.'));

    // Ensure the BS JS API snippet and file are included on the page.
    $js_cache = variable_get('drupal_js_cache_files');
    $bs_js_file = file_create_url($js_cache[key($js_cache)]);
    $this
      ->assertRaw("w['BetterStatsObj']", t('Found reference to BS JS API async loader.'));
    $this
      ->assertRaw($bs_js_file, t('Found reference to BS JS API file.'));

    // Revert to the default configuration.
    $edit = array(
      'statistics_count_content_views' => BETTER_STATISTICS_ENTITY_VIEW_DEFAULT,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));
  }

  /**
   * Test data collection functionality.
   */
  public function testDataCollection() {

    // Ensure statistics are collected when caching is disabled.
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected when page cache is disabled.'));

    // Ensure statistics are collected when caching is enabled (cache miss).
    variable_set('cache', 1);
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 2, t('Accesslog data being collected when page cache is enabled (cache miss).'));

    // Ensure statistics are collected when caching is enabled (cache hit).
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 3, t('Accesslog data being collected when page cache is enabled (cache hit).'));

    // Create a user and log it in.
    $this->admin_user = $this
      ->drupalCreateUser(array(
      'access administration pages',
      'administer statistics',
      'access content',
      'access statistics',
      'administer site configuration',
    ));
    $this
      ->drupalLogin($this->admin_user);

    // Disable logging of <front> page.
    $edit = array(
      'statistics_access_log_restrictions_pages' => '<front>',
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check page access to <front> is not logged, and no issues with
    // alias resolution in case of caching.
    $this
      ->drupalLogout();
    db_truncate('accesslog')
      ->execute();
    db_truncate('watchdog')
      ->execute();

    // First get will be a MISS.
    $this
      ->drupalGet('node');

    // Second get will be a HIT.
    $this
      ->drupalGet('node');

    // We expect no access to be logged.
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data for access to "node" not being collected, when page restriction for "front" is set.'));

    // We expect no php type entries in the watchdog.
    $query = db_query("SELECT wid FROM {watchdog} where type = 'php'");
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('No errors matching "node" request path to "front" alias.'));

    // Reset pages restrictions.
    $this
      ->drupalLogin($this->admin_user);
    $edit = array(
      'statistics_access_log_restrictions_pages' => '',
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Disable logging of cached page requests.
    $edit = array(
      'statistics_access_log_restrictions_cache' => FALSE,
    );
    $this
      ->drupalPost($this->perf_admin, $edit, t('Save configuration'));

    // Check page access is not logged.
    $this
      ->drupalLogout();
    db_truncate('accesslog')
      ->execute();

    // First get will be a MISS.
    $this
      ->drupalGet('node');

    // Second get will be a HIT.
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();

    // We expect the MISS to be logged, the HIT not.
    $this
      ->assertTrue($rows == 1, t('Accesslog data not being collected when page cache is enabled, and logging of cached pages is disabled.'));

    // Re-enable logging of cached page requests.
    $this
      ->drupalLogin($this->admin_user);
    $edit = array(
      'statistics_access_log_restrictions_cache' => TRUE,
    );
    $this
      ->drupalPost($this->perf_admin, $edit, t('Save configuration'));

    // Set statistics to only log anonymous users.
    $edit = array(
      'statistics_access_log_restrictions_roles[1]' => 1,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check page access is not logged.
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not being collected when logging is only enabled for anonymous users.'));

    // Log out and check page access is logged.
    $this
      ->drupalLogout();
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected when logging is only enabled for anonymous users.'));
    $this
      ->drupalLogin($this->admin_user);

    // Re-enable logging of all roles.
    $edit = array(
      'statistics_access_log_restrictions_roles[1]' => FALSE,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Disable logging of admin pages.
    $edit = array(
      'statistics_access_log_restrictions_pages' => 'admin/*',
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check admin page access is not logged.
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('admin/config');

    // for some reason, this logs an access to 'node'
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('admin/config');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not being collected on defined exclusion.'));

    // Check normal page access is logged.
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected when exclusion is defined, but a non-excluded page is loaded.'));

    // Enable logging of only admin pages.
    $edit = array(
      'statistics_access_log_restrictions_page' => 1,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // Check normal page access is not logged.
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not being collected when inclusion is defined, but a non-included page is loaded.'));

    // Check admin page access is logged.
    $this
      ->drupalGet('admin/config');
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected on defined inclusion.'));

    // Reset page settings.
    $edit = array(
      'statistics_access_log_restrictions_page' => 0,
    );
    $this
      ->drupalPost($this->stats_admin, $edit, t('Save configuration'));

    // By default, a request with a DNT header should have no effect.
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node', array(), array(
      'DNT: 1',
    ));
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data being collected by default when do not track headers are provided.'));

    // Configure the module to respect the DNT header.
    variable_set('statistics_access_log_restrictions_dnt', TRUE);

    // A request with a DNT header should not be logged.
    $this
      ->drupalGet('node', array(), array(
      'DNT: 1',
    ));
    $query = db_query('SELECT aid FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data not collected when do not track headers are provided.'));

    // Reset the DNT configuration.
    variable_set('statistics_access_log_restrictions_dnt', FALSE);

    // Ensure that POSTing to the Accesslog callback creates a valid entry.
    db_truncate('accesslog')
      ->execute();
    $this
      ->betterStatisticsPost($this->ajax_accesslog);
    $query = db_query('SELECT * FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog data collected via AJAX callback.'));

    // Ensure data POSTed to the Accesslog callback overrides server-side data.
    $custom_path = 'custom/path';
    $this
      ->betterStatisticsPost($this->ajax_accesslog, array(
      'path' => $custom_path,
    ));
    $query = db_query('SELECT * FROM {accesslog} WHERE path = :path', array(
      ':path' => $custom_path,
    ));
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Accesslog server-side data successfully overridden by JSON overrides.'));

    // Ensure that data POSTed to the Accesslog callback that doesn't correspond
    // to a field is safely ignored.
    db_truncate('accesslog')
      ->execute();
    $this
      ->betterStatisticsPost($this->ajax_accesslog, array(
      'fake_field' => 'fake data',
    ));
    $query = db_query('SELECT * FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Irrelevant accesslog data sucessfully ignored on AJAX callback.'));

    // Ensure that when cache is enabled in mixed mode, duplicate entries are
    // not created because hook_exit() is still run.
    $this
      ->drupalLogout();
    variable_set('cache', 1);
    variable_set('statistics_enable_access_log', BETTER_STATISTICS_ACCESSLOG_MIXED);
    db_truncate('accesslog')
      ->execute();
    $this
      ->drupalGet('node');
    $this
      ->drupalGet('node');
    $query = db_query('SELECT * FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not collected in mixed mode when cache is enabled.'));
    db_truncate('accesslog')
      ->execute();
    variable_set('statistics_enable_access_log', BETTER_STATISTICS_ACCESSLOG_CLIENT);
    $this
      ->drupalGet('node');
    $this
      ->drupalGet('node');
    $query = db_query('SELECT * FROM {accesslog}');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Accesslog data not collected in client-side mode when cache is enabled.'));
    variable_set('cache', 0);

    // Ensure that data POSTed to the Entity View callback creates a valid entry.
    $entity_view_data = array(
      'entity_type' => 'node',
      'entity_id' => 1,
    );
    db_truncate('node_counter')
      ->execute();
    $this
      ->betterStatisticsPost($this->ajax_entityview, $entity_view_data);
    $query = db_query('SELECT * FROM {node_counter} WHERE nid=1 AND daycount=1');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Entity View data collected via AJAX callback.'));
    db_truncate('node_counter')
      ->execute();
    $this
      ->betterStatisticsPost($this->ajax_entityview, $entity_view_data);
    $query = db_query('SELECT * FROM {node_counter} WHERE nid=1 AND daycount=1');
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 1, t('Entity View data collected via AJAX callback.'));

    // Ensure that when cache is enabled in mixed mode, duplicate entries are
    // not created because hook_exit() is still run.
    variable_set('cache', 1);
    variable_set('statistics_count_content_views', BETTER_STATISTICS_ENTITY_VIEW_CLIENT);
    db_truncate('node_counter')
      ->execute();
    $this
      ->drupalGet('node/' . $this->node->nid);
    $this
      ->drupalGet('node/' . $this->node->nid);
    $query = db_query('SELECT * FROM {node_counter} WHERE nid=:nid', array(
      ':nid' => $this->node->nid,
    ));
    $rows = $query
      ->rowCount();
    $this
      ->assertTrue($rows == 0, t('Entity View data not collected in client-side only mode when cache is enabled.'));
    variable_set('cache', 0);
  }

  /**
   * Execute a POST request against a Drupal path with a JSON payload.
   *
   * @param $path
   *   Endpoint against which to POST. Either a Drupal path or an absolute path.
   * @param $data
   *   Data in an associative array. This will be JSON encoded.
   * @param $options
   *   Options to be forwarded to url().
   * @param $headers
   *   An array containing additional HTTP request headers, each formatted as
   *   "name: value".
   */
  protected function betterStatisticsPost($path, array $data = array(), array $options = array(), array $headers = array()) {
    $url = $this
      ->getAbsoluteUrl(url($path, $options));
    $post = drupal_json_encode($data);
    $headers[] = 'Content-Type: application/json; charset=utf-8';
    $headers[] = 'Content-Length: ' . strlen($post);
    $out = $this
      ->simpleCurlExec(array(
      CURLOPT_URL => $url,
      CURLOPT_POST => TRUE,
      CURLOPT_POSTFIELDS => $post,
      CURLOPT_HTTPHEADER => $headers,
    ));
    $this
      ->verbose('POST request to: ' . $url . '<hr />Data: ' . highlight_string('<?php ' . var_export($data, TRUE), TRUE) . '<hr />' . $out);
    return $out;
  }

  /**
   * @see DrupalWebTestCase::curlExec()
   */
  protected function simpleCurlExec($curl_options, $redirect = FALSE) {
    $this
      ->curlInitialize();
    if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) {
      $original_url = $curl_options[CURLOPT_URL];
      $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
    }
    $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
    if (!empty($curl_options[CURLOPT_POST])) {
      $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
    }
    curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
    if (!$redirect) {

      // Reset headers, the session ID and the redirect counter.
      $this->session_id = NULL;
      $this->headers = array();
      $this->redirect_count = 0;
    }
    $content = curl_exec($this->curlHandle);
    $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);
    if (in_array($status, array(
      300,
      301,
      302,
      303,
      305,
      307,
    )) && $this->redirect_count < variable_get('simpletest_maximum_redirects', 5)) {
      if ($this
        ->drupalGetHeader('location')) {
        $this->redirect_count++;
        $curl_options = array();
        $curl_options[CURLOPT_URL] = $this
          ->drupalGetHeader('location');
        $curl_options[CURLOPT_HTTPGET] = TRUE;
        return $this
          ->simpleCurlExec($curl_options, TRUE);
      }
    }
    $this
      ->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
  }

}

Classes

Namesort descending Description
BetterStatisticsTest Functional tests for Better Statistics.