You are here

taxonomy_access.test in Taxonomy Access Control 7

Automated tests for the Taxonomy Access Control module.

File

taxonomy_access.test
View source
<?php

/**
 * @file
 * Automated tests for the Taxonomy Access Control module.
 */

/**
 * Provides a base test class and helper methods for automated tests.
 */
class TaxonomyAccessTestCase extends DrupalWebTestCase {

  // There are four types of users:
  // site admins, taxonomy admins, content editors, and regular users.
  protected $users = array();
  protected $user_roles = array();
  protected $user_config = array(
    'site_admin' => array(
      'access content',
      'access site reports',
      'access administration pages',
      'administer permissions',
      'create article content',
      'edit any article content',
      'create page content',
      'edit any page content',
    ),
    'tax_admin' => array(
      'access content',
      'administer taxonomy',
    ),
    'editor' => array(
      'access content',
      'create article content',
      'create page content',
    ),
    'regular_user' => array(
      'access content',
    ),
  );
  public function setUp() {

    // Enable module and dependencies.
    parent::setUp('taxonomy_access');

    // Rebuild node access on installation.
    node_access_rebuild();

    // Configure users with base permission patterns.
    foreach ($this->user_config as $user => $permissions) {
      $this->users[$user] = $this
        ->drupalCreateUser($permissions);

      // Save the role ID separately so it's easy to retrieve.
      foreach ($this->users[$user]->roles as $rid => $role) {
        if ($rid != DRUPAL_AUTHENTICATED_RID) {
          $this->user_roles[$user] = user_role_load($rid);
        }
      }
    }

    // Give the anonymous and authenticated roles ignore grants.
    $rows = array();
    foreach (array(
      DRUPAL_ANONYMOUS_RID,
      DRUPAL_AUTHENTICATED_RID,
    ) as $rid) {
      $ignore = array(
        'view' => TAXONOMY_ACCESS_NODE_IGNORE,
        'update' => TAXONOMY_ACCESS_NODE_IGNORE,
        'delete' => TAXONOMY_ACCESS_NODE_IGNORE,
      );
      $rows[] = _taxonomy_access_format_grant_record(TAXONOMY_ACCESS_GLOBAL_DEFAULT, $rid, $ignore, TRUE);
    }
    taxonomy_access_set_default_grants($rows);
    foreach (array(
      DRUPAL_ANONYMOUS_RID,
      DRUPAL_AUTHENTICATED_RID,
    ) as $rid) {
      $r = db_query('SELECT grant_view FROM {taxonomy_access_default}
           WHERE vid = :vid AND rid = :rid', array(
        ':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT,
        ':rid' => $rid,
      ))
        ->fetchField();
      $this
        ->assertTrue(is_numeric($r) && $r == 0, t("Set global default for role %rid to <em>Ignore</em>", array(
        '%rid' => $rid,
      )));
    }
  }

  /**
   * Creates a vocabulary with a certain name.
   *
   * @param string $machine_name
   *   A machine-safe name.
   *
   * @return object
   *   The vocabulary object.
   */
  function createVocab($machine_name) {
    $vocabulary = new stdClass();
    $vocabulary->name = $machine_name;
    $vocabulary->description = $this
      ->randomName();
    $vocabulary->machine_name = $machine_name;
    $vocabulary->help = '';
    $vocabulary->weight = mt_rand(0, 10);
    taxonomy_vocabulary_save($vocabulary);
    return $vocabulary;
  }

  /**
   * Creates a new term in the specified vocabulary.
   *
   * @param string $machine_name
   *   A machine-safe name.
   * @param object $vocab
   *   A vocabulary object.
   * @param int|null $parent
   *   (optional) The tid of the parent term, if any.  Defaults to NULL.
   *
   * @return object
   *   The taxonomy term object.
   */
  function createTerm($machine_name, $vocab, $parent = NULL) {
    $term = new stdClass();
    $term->name = $machine_name;
    $term->description = $machine_name;

    // Use the first available text format.
    $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)
      ->fetchField();
    $term->vid = $vocab->vid;
    $term->vocabulary_machine_name = $vocab->machine_name;
    if (!is_null($parent)) {
      $term->parent = $parent;
    }
    taxonomy_term_save($term);
    return $term;
  }

  /**
   * Creates a taxonomy field and adds it to the page content type.
   *
   * @param string $machine_name
   *   The machine name of the vocabulary to use.
   * @param string $widget
   *   (optional) The name of the widget to use.  Defaults to 'options_select'.
   * @param int $count
   *   (optional) The allowed number of values.  Defaults to unlimited.
   *
   * @return array
   *   Array of instance data.
   */
  function createField($machine_name, $widget = 'options_select', $count = FIELD_CARDINALITY_UNLIMITED) {
    $field = array(
      'field_name' => $machine_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => $count,
      'settings' => array(
        'allowed_values' => array(
          array(
            'vocabulary' => $machine_name,
            'parent' => 0,
          ),
        ),
      ),
    );
    $field = field_create_field($field);
    $instance = array(
      'field_name' => $machine_name,
      'bundle' => 'page',
      'entity_type' => 'node',
      'widget' => array(
        'type' => $widget,
      ),
      'display' => array(
        'default' => array(
          'type' => 'taxonomy_term_reference_link',
        ),
      ),
    );
    return field_create_instance($instance);
  }

  /**
   * Creates an article with the specified terms.
   *
   * @param array $autocreate
   *   (optional) An array of term names to autocreate. Defaults to array().
   * @param array $existing
   *   (optional) An array of existing term IDs to add.
   *
   * @return object
   *   The node object.
   */
  function createArticle($autocreate = array(), $existing = array()) {
    $values = array();
    foreach ($autocreate as $name) {
      $values[] = array(
        'tid' => 'autocreate',
        'vid' => 1,
        'name' => $name,
        'vocabulary_machine_name' => 'tags',
      );
    }
    foreach ($existing as $tid) {
      $values[] = array(
        'tid' => $tid,
        'vid' => 1,
        'vocabulary_machine_name' => 'tags',
      );
    }

    // Bloody $langcodes.
    $values = array(
      LANGUAGE_NONE => $values,
    );
    $settings = array(
      'type' => 'article',
      'field_tags' => $values,
    );
    return $this
      ->drupalCreateNode($settings);
  }

  /**
   * Submits the node access rebuild form.
   */
  function rebuild() {
    $this
      ->drupalPost('admin/reports/status/rebuild', array(), t('Rebuild permissions'));
    $this
      ->assertText(t('The content access permissions have been rebuilt.'));
  }

  /**
   * Asserts that a status column and "Configure" link is found for the role.
   *
   * @param array $statuses
   *   An associative array of role statuses, keyed by role ID. Each item
   *   should be TRUE if the role is enabled, and FALSE otherwise.
   */
  function checkRoleConfig(array $statuses) {
    $roles = _taxonomy_access_user_roles();

    // Log in as the administrator.
    $this
      ->drupalLogout();
    $this
      ->drupalLogin($this->users['site_admin']);
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG);
    foreach ($statuses as $rid => $status) {

      // Assert that a "Configure" link is available for the role.
      $this
        ->assertLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/edit", 0, t('"Configure" link is available for role %rid.', array(
        '%rid' => $rid,
      )));
    }

    // Retrieve the grant status table.
    $shown = array();
    $table = $this
      ->xpath('//table/tbody');
    $table = reset($table);

    // SimpleXML has fake arrays so we have to do this to get the data out.
    foreach ($table->tr as $row) {
      $tds = array();
      foreach ($row->td as $value) {
        $tds[] = (string) $value;
      }
      $shown[$tds[0]] = $tds[1];
    }
    foreach ($statuses as $rid => $status) {

      // Assert that the form shows the passed status.
      if ($status) {
        $this
          ->assertTrue($shown[$roles[$rid]] == t('Enabled'), format_string('Role %role is enabled.', array(
          '%role' => $rid,
        )));
      }
      else {
        $this
          ->assertTrue($shown[$roles[$rid]] == t('Disabled'), format_string('Role %role is disabled.', array(
          '%role' => $rid,
        )));
      }

      // Assert that a "Configure" link is available for the role.
      $this
        ->assertLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/edit", 0, t('"Configure" link is available for role %rid.', array(
        '%rid' => $rid,
      )));
    }
  }

  /**
   * Asserts that an enable link is or is not found for the role.
   *
   * @param int $rid
   *   The role ID to check.
   * @param bool $found
   *   Whether the link should be found, or not.
   */
  function checkRoleEnableLink($rid, $found) {
    if ($found) {
      $this
        ->assertLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/enable", 0, t('Enable link is available for role %rid.', array(
        '%rid' => $rid,
      )));
    }
    else {
      $this
        ->assertNoLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/enable", t('Enable link is not available for role %rid.', array(
        '%rid' => $rid,
      )));
    }
  }

  /**
   * Asserts that a disable link is or is not found for the role.
   *
   * @param int $rid
   *   The role ID to check.
   * @param bool $found
   *   Whether the link should be found, or not.
   */
  function checkRoleDisableLink($rid, $found) {
    if ($found) {
      $this
        ->assertLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/delete", 0, t('Disable link is available for role %rid.', array(
        '%rid' => $rid,
      )));
    }
    else {
      $this
        ->assertNoLinkByHref(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/delete", t('Disable link is not available for role %rid.', array(
        '%rid' => $rid,
      )));
    }
  }

  /**
   * Adds a term row on the role configuration form.
   *
   * @param array &$edit
   *   The form data to post.
   * @param int $vid
   *   (optional) The vocabulary ID. Defaults to
   *   TAXONOMY_ACCESS_GLOBAL_DEFAULT.
   * @param $int tid
   *   (optional) The term ID. Defaults to TAXONOMY_ACCESS_VOCABULARY_DEFAULT.
   * @param int $view
   *   (optional) The view grant value. Defaults to
   *    TAXONOMY_ACCESS_NODE_IGNORE.
   * @param int $update
   *   (optional) The update grant value. Defaults to
   * @param int $delete
   *   (optional) The delete grant value. Defaults to
   *   TAXONOMY_ACCESS_NODE_IGNORE.
   * @param int $create
   *   (optional) The create grant value. Defaults to
   *   TAXONOMY_ACCESS_TERM_DENY.
   * @param int $list
   *   (optional) The list grant value. Defaults to TAXONOMY_ACCESS_TERM_DENY.
   */
  function addFormRow(&$edit, $vid = TAXONOMY_ACCESS_GLOBAL_DEFAULT, $tid = TAXONOMY_ACCESS_VOCABULARY_DEFAULT, $view = TAXONOMY_ACCESS_NODE_IGNORE, $update = TAXONOMY_ACCESS_NODE_IGNORE, $delete = TAXONOMY_ACCESS_NODE_IGNORE, $create = TAXONOMY_ACCESS_TERM_DENY, $list = TAXONOMY_ACCESS_TERM_DENY) {
    $new_value = $tid ? "term {$tid}" : "default {$vid}";
    $edit["new[{$vid}][item]"] = $new_value;
    $edit["new[{$vid}][grants][{$vid}][0][view]"] = $view;
    $edit["new[{$vid}][grants][{$vid}][0][update]"] = $update;
    $edit["new[{$vid}][grants][{$vid}][0][delete]"] = $delete;
    $edit["new[{$vid}][grants][{$vid}][0][create]"] = $create;
    $edit["new[{$vid}][grants][{$vid}][0][list]"] = $list;
  }

  /**
   * Configures a row on the TAC configuration form.
   *
   * @param array &$edit
   *   The form data to post.
   * @param int $vid
   *   (optional) The vocabulary ID. Defaults to
   *   TAXONOMY_ACCESS_GLOBAL_DEFAULT.
   * @param $int tid
   *   (optional) The term ID. Defaults to TAXONOMY_ACCESS_VOCABULARY_DEFAULT.
   * @param int $view
   *   (optional) The view grant value. Defaults to
   *    TAXONOMY_ACCESS_NODE_IGNORE.
   * @param int $update
   *   (optional) The update grant value. Defaults to
   * @param int $delete
   *   (optional) The delete grant value. Defaults to
   *   TAXONOMY_ACCESS_NODE_IGNORE.
   * @param int $create
   *   (optional) The create grant value. Defaults to
   *   TAXONOMY_ACCESS_TERM_DENY.
   * @param int $list
   *   (optional) The list grant value. Defaults to TAXONOMY_ACCESS_TERM_DENY.
   */
  function configureFormRow(&$edit, $vid = TAXONOMY_ACCESS_GLOBAL_DEFAULT, $tid = TAXONOMY_ACCESS_VOCABULARY_DEFAULT, $view = TAXONOMY_ACCESS_NODE_IGNORE, $update = TAXONOMY_ACCESS_NODE_IGNORE, $delete = TAXONOMY_ACCESS_NODE_IGNORE, $create = TAXONOMY_ACCESS_TERM_DENY, $list = TAXONOMY_ACCESS_TERM_DENY) {
    $edit["grants[{$vid}][{$tid}][view]"] = $view;
    $edit["grants[{$vid}][{$tid}][update]"] = $update;
    $edit["grants[{$vid}][{$tid}][delete]"] = $delete;
    $edit["grants[{$vid}][{$tid}][create]"] = $create;
    $edit["grants[{$vid}][{$tid}][list]"] = $list;
  }

}

/**
 * Tests the module's response to changes from other modules.
 */
class TaxonomyAccessExternalChanges extends TaxonomyAccessTestCase {
  public static function getInfo() {
    return array(
      'name' => 'External changes',
      'description' => "Test the module's response to changes from other modules.",
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp();
  }

}

/**
 * Tests the module's configuration forms.
 */
class TaxonomyAccessConfigTest extends TaxonomyAccessTestCase {
  protected $articles = array();
  protected $pages = array();
  protected $vocabs = array();
  protected $terms = array();
  public static function getInfo() {
    return array(
      'name' => 'Configuration forms',
      'description' => 'Test module configuration forms.',
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp();

    // Add two taxonomy fields to pages.
    foreach (array(
      'v1',
      'v2',
    ) as $vocab) {
      $this->vocabs[$vocab] = $this
        ->createVocab($vocab);
      $this
        ->createField($vocab);
      $this->terms[$vocab . 't1'] = $this
        ->createTerm($vocab . 't1', $this->vocabs[$vocab]);
      $this->terms[$vocab . 't2'] = $this
        ->createTerm($vocab . 't2', $this->vocabs[$vocab]);
    }

    // Set up a variety of nodes with different term combinations.
    $this->articles['no_tags'] = $this
      ->createArticle();
    $this->articles['one_tag'] = $this
      ->createArticle(array(
      $this
        ->randomName(),
    ));
    $this->articles['two_tags'] = $this
      ->createArticle(array(
      $this
        ->randomName(),
      $this
        ->randomName(),
    ));
    $this->pages['no_tags'] = $this
      ->createPage();
    foreach ($this->terms as $t1) {
      $this->pages[$t1->name] = $this
        ->createPage(array(
        $t1->name,
      ));
      foreach ($this->terms as $t2) {
        $this->pages[$t1->name . '_' . $t2->name] = $this
          ->createPage(array(
          $t1->name,
          $t2->name,
        ));
      }
    }
  }

  /**
   * Creates a page with the specified terms.
   *
   * @param array $terms
   *   (optional) An array of term names to tag the page.  Defaults to array().
   *
   * @return object
   *   The node object.
   */
  function createPage($tags = array()) {
    $v1 = array();
    $v2 = array();
    foreach ($tags as $name) {
      switch ($this->terms[$name]->vid) {
        case $this->vocabs['v1']->vid:
          $v1[] = array(
            'tid' => $this->terms[$name]->tid,
          );
          break;
        case $this->vocabs['v2']->vid:
          $v2[] = array(
            'tid' => $this->terms[$name]->tid,
          );
          break;
      }
    }

    // Bloody $langcodes.
    $v1 = array(
      LANGUAGE_NONE => $v1,
    );
    $v2 = array(
      LANGUAGE_NONE => $v2,
    );
    $settings = array(
      'type' => 'page',
      'v1' => $v1,
      'v2' => $v2,
    );
    return $this
      ->drupalCreateNode($settings);
  }

  /*
  @todo
  - check anon and auth forms
  - add recursive for vocab and for term
  - change multiple
  - delete multiple
  - configure create and list
  */

  /**
   * Tests the initial state of the test environment.
   *
   * Verifies that:
   * - Access to all nodes is denied for anonymous users.
   * - The main admin page provides the correct configuration links.
   */
  public function testSetUpCheck() {

    // Visit all nodes as anonymous and verify that access is denied.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(403, t("Access to %name article (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      $this
        ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }

    // Log in as the regular_user.
    $this
      ->drupalLogin($this->users['regular_user']);

    // Visit all nodes and verify that access is denied.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(403, t("Access to %name article (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      $this
        ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Confirm that only edit links are available for anon. and auth.
    $this
      ->checkRoleConfig(array(
      DRUPAL_ANONYMOUS_RID => TRUE,
      DRUPAL_AUTHENTICATED_RID => TRUE,
    ));
  }

  /**
   * Tests configuring a global default.
   *
   * Verifies that:
   * - Access is updated for all nodes when there are no other configurations.
   * - Access is updated for the correct nodes when there are specific term
   *    and vocabulary configurations.
   */
  public function testGlobalDefaultConfig() {

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to give anonymous view allow in the global default.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->configureFormRow($edit, TAXONOMY_ACCESS_GLOBAL_DEFAULT, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each node and verify that access is allowed.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(200, t("Access to %name article (nid %nid) is allowed.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      $this
        ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }

    // Add some specific configurations programmatically.
    // Set the v1 default to view allow.
    $default_config = _taxonomy_access_format_grant_record($this->vocabs['v1']->vid, DRUPAL_ANONYMOUS_RID, array(
      'view' => TAXONOMY_ACCESS_NODE_ALLOW,
    ), TRUE);
    taxonomy_access_set_default_grants(array(
      $default_config,
    ));

    // Set v1t1 and v2t1 to view allow.
    $term_configs = array();
    foreach (array(
      'v1t1',
      'v2t1',
    ) as $name) {
      $term_configs[] = _taxonomy_access_format_grant_record($this->terms[$name]->vid, DRUPAL_ANONYMOUS_RID, array(
        'view' => TAXONOMY_ACCESS_NODE_ALLOW,
      ));
    }
    taxonomy_access_set_term_grants($term_configs);

    // This leaves articles and the v2t2 page controlled by the global default.
    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to give anonymous view deny in the global default.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->configureFormRow($edit, TAXONOMY_ACCESS_GLOBAL_DEFAULT, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_DENY);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each artile and verify that access is denied.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(403, t("Access to %name article (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }

    // Visit each page.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      switch (TRUE) {

        // If the page has no tags, access should be denied.
        case $key == 'no_tags':

        // If the page is tagged with v2t2, access should be denied.
        case strpos($key, 'v2t2') !== FALSE:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Otherwise, access should be allowed.
        default:
          $this
            ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;
      }
    }
  }

  /**
   * Tests configuring vocabulary defaults.
   *
   * Verifies that:
   * - Access is updated correctly when the vocabulary default is added and
   *   configured.
   * - Access is updated correctly when there is a specific term configuration
   *   in the vocabulary.
   * - Access is updated correctly when multiple defaults are changed.
   * - Access is updated correctly when the vocabulary default is deleted.
   */
  public function testVocabularyDefaultConfig() {

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Enable the vocabulary.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');

    // @todo
    //   - Ensure that all vocabularies are options in the "Add" fieldset.
    $edit = array();
    $edit['enable_vocab'] = $this->vocabs['v1']->vid;
    $this
      ->drupalPost(NULL, $edit, t('Add'));

    // @todo
    //   - Ensure that the vocabulary is removed from the "Add" fieldset.
    //   - Ensure that the fieldset for the vocabulary appears.
    //   - Ensure that no other fieldsets or rows appear.
    // Give anonymous view allow for the v1 default.
    $edit = array();
    $this
      ->configureFormRow($edit, $this->vocabs['v1']->vid, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);

      // If the page is tagged with a v1 term, access should be allowed.
      if (strpos($key, 'v1') !== FALSE) {
        $this
          ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
      else {
        $this
          ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
    }

    // Programmatically enable v2 and add a specific configuration for v2t1.
    taxonomy_access_enable_vocab($this->vocabs['v2']->vid, DRUPAL_ANONYMOUS_RID);
    $term_config = _taxonomy_access_format_grant_record($this->terms['v2t1']->tid, DRUPAL_ANONYMOUS_RID, array(
      'view' => TAXONOMY_ACCESS_NODE_IGNORE,
    ));
    taxonomy_access_set_term_grants(array(
      $term_config,
    ));

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to give anonymous view deny for the v2 default.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->configureFormRow($edit, $this->vocabs['v2']->vid, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_DENY);
    $this
      ->drupalPost(NULL, $edit, 'Save all');
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      switch (TRUE) {

        // If the page is tagged with v2t2, the v2 default is inherited: Deny.
        case strpos($key, 'v2t2') !== FALSE:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Otherwise, if the page is tagged with v1, it's allowed.
        case strpos($key, 'v1') !== FALSE:
          $this
            ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Access should be denied by default.
        default:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;
      }
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the form to change the configuration: Allow for v2; Deny for v1.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->configureFormRow($edit, $this->vocabs['v2']->vid, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->configureFormRow($edit, $this->vocabs['v1']->vid, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_DENY);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      switch (TRUE) {

        // If the page is tagged with a v1 term, access should be denied.
        case strpos($key, 'v1') !== FALSE:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Otherwise, if the page is tagged with v2t2, the default is
        // inherited and access should be allowed.
        case strpos($key, 'v2t2') !== FALSE:
          $this
            ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Access should be denied by default.
        default:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;
      }
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to disable v1.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $this
      ->clickLink(t('delete all v1 access rules'));
    $this
      ->assertText("Are you sure you want to delete all Taxonomy access rules for v1", t('Disable form for vocabulary loaded.'));
    $this
      ->drupalPost(NULL, array(), 'Delete all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);

      // If the page is tagged with v2t2, access should be allowed.
      if (strpos($key, 'v2t2') !== FALSE) {
        $this
          ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
      else {
        $this
          ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
    }
  }

  /**
   * Tests configuring specific terms.
   *
   * Verifies that:
   * - Access is updated correctly when the term configuration is added.
   * - Access is updated correctly when there is a vocabulary default.
   * - Access is updated correctly when multiple configurations are changed.
   * - Access is updated correctly when the term configuration is deleted.
   */
  public function testTermConfig() {

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to enable v1 and give anonymous view allow for v1t1.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $edit['enable_vocab'] = $this->vocabs['v1']->vid;
    $this
      ->drupalPost(NULL, $edit, t('Add'));
    $edit = array();
    $this
      ->addFormRow($edit, $this->vocabs['v1']->vid, $this->terms['v1t1']->tid, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->drupalPost(NULL, $edit, 'Add');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);

      // If the page is tagged with v1t1, access should be allowed.
      if (strpos($key, 'v1t1') !== FALSE) {
        $this
          ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
      else {
        $this
          ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
          '%name' => $key,
          '%nid' => $page->nid,
        )));
      }
    }

    // Enable v2 programmatically.
    taxonomy_access_enable_vocab($this->vocabs['v2']->vid, DRUPAL_ANONYMOUS_RID);

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the admin form to give anonymous view deny for v2t1.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->addFormRow($edit, $this->vocabs['v2']->vid, $this->terms['v2t1']->tid, TAXONOMY_ACCESS_NODE_DENY);
    $this
      ->drupalPost(NULL, $edit, 'Add');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      switch (TRUE) {

        // If the page is tagged with v2t1, access should be denied.
        case strpos($key, 'v2t1') !== FALSE:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Otherwise, if the page is tagged with v1t1, it's allowed.
        case strpos($key, 'v1t1') !== FALSE:
          $this
            ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Access should be denied by default.
        default:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;
      }
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the form to change the configuration: Allow for v2t1; Deny for v1t1.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $this
      ->configureFormRow($edit, $this->vocabs['v2']->vid, $this->terms['v2t1']->tid, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->configureFormRow($edit, $this->vocabs['v1']->vid, $this->terms['v1t1']->tid, TAXONOMY_ACCESS_NODE_DENY);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      switch (TRUE) {

        // If the page is tagged with v1t1, access should be denied.
        case strpos($key, 'v1t1') !== FALSE:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Otherwise, if the page is tagged with v2t1, it's allowed.
        case strpos($key, 'v2t1') !== FALSE:
          $this
            ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;

        // Access should be denied by default.
        default:
          $this
            ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
            '%name' => $key,
            '%nid' => $page->nid,
          )));
          break;
      }
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Use the form to delete the v2t1 configuration.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $edit["grants[{$this->vocabs['v2']->vid}][{$this->terms['v2t1']->tid}][remove]"] = 1;
    $this
      ->drupalPost(NULL, $edit, 'Delete selected');

    // Log out.
    $this
      ->drupalLogout();

    // Visit each page and verify whether access is allowed or denied.
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);

      // Access to all pages should be denied.
      $this
        ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }
  }

  /**
   * Tests adding a term configuration with children.
   *
   * @todo
   *   Check that node access is updated for these as well.
   */
  public function testTermWithChildren() {

    // Create some additional taxonomy terms in a hierarchy:
    // v1
    // - v1t1
    // - - v1t1c1
    // - - - v1t1c1g1
    // - - - v1t1c1g2
    // - - v1t1c2
    // - - v1t2
    $this->terms['v1t1c1'] = $this
      ->createTerm('v1t1c1', $this->vocabs['v1'], $this->terms['v1t1']->tid);
    $this->terms['v1t1c2'] = $this
      ->createTerm('v1t1c2', $this->vocabs['v1'], $this->terms['v1t1']->tid);
    $this->terms['v1t1c1g1'] = $this
      ->createTerm('v1t1c1g1', $this->vocabs['v1'], $this->terms['v1t1c1']->tid);
    $this->terms['v1t1c1g2'] = $this
      ->createTerm('v1t1c1g2', $this->vocabs['v1'], $this->terms['v1t1c1']->tid);

    // Add pages tagged with each.
    foreach (array(
      'v1t1c1',
      'v1t1c2',
      'v1t1c1g1',
      'v1t1c1g2',
    ) as $name) {
      $this->pages[$name] = $this
        ->createPage(array(
        $name,
      ));
    }

    // Log in as the administrator.
    $this
      ->drupalLogin($this->users['site_admin']);

    // Enable v1 programmatically.
    taxonomy_access_enable_vocab($this->vocabs['v1']->vid, DRUPAL_ANONYMOUS_RID);

    // Use the admin form to give anonymous view allow for v1t1 and children.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . '/role/' . DRUPAL_ANONYMOUS_RID . '/edit');
    $edit = array();
    $edit["new[{$this->vocabs['v1']->vid}][recursive]"] = 1;
    $this
      ->addFormRow($edit, $this->vocabs['v1']->vid, $this->terms['v1t1']->tid, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->drupalPost(NULL, $edit, 'Add');
  }

  /**
   * Tests enabling and disabling TAC for a custom role.
   */
  public function testRoleEnableDisable() {

    // Save some typing.
    $rid = $this->user_roles['regular_user']->rid;
    $name = $this->user_roles['regular_user']->name;

    // Check that the role is disabled by default.
    $this
      ->checkRoleConfig(array(
      DRUPAL_ANONYMOUS_RID => TRUE,
      DRUPAL_AUTHENTICATED_RID => TRUE,
      $rid => FALSE,
    ));

    // Test enabling the role.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/edit");

    // Check that there is:
    // - An enable link
    // - No disable link
    // @todo
    //   - No grant tables.
    $this
      ->checkRoleEnableLink($rid, TRUE);
    $this
      ->checkRoleDisableLink($rid, FALSE);

    // Enable the role and check that there is:
    // - A disable link
    // - No enable link
    // @todo
    //   - A global default table (with correct values?)
    //   - An "Add vocabulary" fieldset.
    //   - No vocabulary fieldsets or term data.
    $this
      ->clickLink(format_string('Enable @name', array(
      '@name' => $name,
    )));
    $this
      ->checkRoleEnableLink($rid, FALSE);
    $this
      ->checkRoleDisableLink($rid, TRUE);

    // Update the global default to allow view.
    $edit = array();
    $this
      ->configureFormRow($edit, TAXONOMY_ACCESS_GLOBAL_DEFAULT, TAXONOMY_ACCESS_VOCABULARY_DEFAULT, TAXONOMY_ACCESS_NODE_ALLOW);
    $this
      ->drupalPost(NULL, $edit, 'Save all');

    // Confirm that all three roles are enabled.
    $this
      ->checkRoleConfig(array(
      DRUPAL_ANONYMOUS_RID => TRUE,
      DRUPAL_AUTHENTICATED_RID => TRUE,
      $rid => TRUE,
    ));

    // Check that the role is configured.
    $r = db_query('SELECT grant_view FROM {taxonomy_access_default}
         WHERE vid = :vid AND rid = :rid', array(
      ':vid' => TAXONOMY_ACCESS_GLOBAL_DEFAULT,
      ':rid' => $rid,
    ))
      ->fetchField();
    $this
      ->assertTrue($r == TAXONOMY_ACCESS_NODE_ALLOW, t('Used form to grant the role %role view in the global default.', array(
      '%role' => $name,
    )));

    // Log in as the regular_user.
    $this
      ->drupalLogout();
    $this
      ->drupalLogin($this->users['regular_user']);

    // Visit each node and verify that access is allowed.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(200, t("Access to %name article (nid %nid) is allowed.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      $this
        ->assertResponse(200, t("Access to %name page (nid %nid) is allowed.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }

    // Log in as the administrator.
    $this
      ->drupalLogout();
    $this
      ->drupalLogin($this->users['site_admin']);

    // Test disabling the role.
    $this
      ->drupalGet(TAXONOMY_ACCESS_CONFIG . "/role/{$rid}/edit");
    $this
      ->clickLink(t('Disable @name', array(
      '@name' => $name,
    )));
    $this
      ->assertText("Are you sure you want to delete all taxonomy access rules for the role {$name}", t('Disable form for role loaded.'));
    $this
      ->drupalPost(NULL, array(), 'Delete all');

    // Confirm that a confirmation message appears.
    $this
      ->assertText("All taxonomy access rules deleted for role {$name}", t('Confirmation message found.'));

    // Check that there is:
    // - An enable link
    // - No disable link
    // @todo
    //   - No grant tables.
    $this
      ->checkRoleEnableLink($rid, TRUE);
    $this
      ->checkRoleDisableLink($rid, FALSE);

    // Confirm edit/enable/disable links are in their original state.
    $this
      ->checkRoleConfig(array(
      DRUPAL_ANONYMOUS_RID => TRUE,
      DRUPAL_AUTHENTICATED_RID => TRUE,
      $rid => FALSE,
    ));

    // Check that the role is no longer configured.
    $r = db_query('SELECT grant_view FROM {taxonomy_access_default}
         WHERE rid = :rid', array(
      ':rid' => $rid,
    ))
      ->fetchAll();
    $this
      ->assertTrue(empty($r), t('All records removed for role %role.', array(
      '%role' => $name,
    )));

    // @todo
    //   - Add a term configuration and make sure that gets deleted too.
    // Log in as the regular_user.
    $this
      ->drupalLogout();
    $this
      ->drupalLogin($this->users['regular_user']);

    // Visit all nodes and verify that access is again denied.
    foreach ($this->articles as $key => $article) {
      $this
        ->drupalGet('node/' . $article->nid);
      $this
        ->assertResponse(403, t("Access to %name article (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $article->nid,
      )));
    }
    foreach ($this->pages as $key => $page) {
      $this
        ->drupalGet('node/' . $page->nid);
      $this
        ->assertResponse(403, t("Access to %name page (nid %nid) is denied.", array(
        '%name' => $key,
        '%nid' => $page->nid,
      )));
    }
  }

}

/**
 * Tests node access for all possible grant combinations.
 */
class TaxonomyAccessNodeGrantTest extends TaxonomyAccessTestCase {

  // There are three roles for node access testing:
  // global_allow   Receives "Allow" in the global default.
  // global_ignore  Receives "Ignore" in the global default.
  // global_deny    Receives "Deny" in the global default.
  // All roles receive the same permissions for terms and vocab defaults.
  protected $roles = array();
  protected $role_config = array(
    'global_allow' => array(),
    'global_ignore' => array(),
    'global_deny' => array(),
  );
  protected $vocabs = array();
  public static function getInfo() {
    return array(
      'name' => 'Node access',
      'description' => 'Test node access for various grant configurations.',
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp();

    // Configure roles with no additional permissions.
    foreach ($this->role_config as $role_name => $permissions) {
      $this->roles[$role_name] = $this
        ->drupalCreateRole(array(), $role_name);
    }
    $node_grants = array(
      'view',
      'update',
      'delete',
    );

    // Set up our testing taxonomy.
    // We will create 4 vocabularies: a, i, d, and nc
    // These names indicate what grant the vocab. default will have for view.
    // (NC means the vocab default is not configured.)
    $grant_types = array(
      'a' => array(),
      'i' => array(),
      'd' => array(),
      'nc' => array(),
    );

    // View alone can be used to test V/U/D because the logic is identical.
    foreach ($node_grants as $grant) {
      $grant_types['a'][$grant] = TAXONOMY_ACCESS_NODE_ALLOW;
      $grant_types['i'][$grant] = TAXONOMY_ACCESS_NODE_IGNORE;
      $grant_types['d'][$grant] = TAXONOMY_ACCESS_NODE_DENY;
    }

    // Each vocabulary will have four parent terms in the same fashion:
    // a_parent, i_parent, d_parent, and nc_parent.
    // Each of these_parent terms will have children in each class, as well:
    // a_child, i_child, d_child, and nc_child.
    // So, each vocab looks something like:
    // - a_parent
    // - - a_child
    // - - i_child
    // - - d_child
    // - - nc_child
    // - i_parent
    // - - a_child
    // - - i_child
    // - - d_child
    // - - nc_child
    // - d_parent
    // - - a_child
    // - - i_child
    // - - d_child
    // - - nc_child
    // - nc_parent
    // - - a_child
    // - - i_child
    // - - d_child
    // - - nc_child
    $term_rows = array();
    $default_rows = array();
    $this->setUpAssertions = array();

    // Configure terms, vocabularies, and grants.
    foreach ($grant_types as $vocab_name => $default_grants) {

      // Create the vocabulary.
      $vocab_name = "v" . $vocab_name;
      $this->vocabs[$vocab_name] = array();
      $this->vocabs[$vocab_name]['vocab'] = parent::createVocab($vocab_name);
      $this->vocabs[$vocab_name]['terms'] = array();
      $vocab = $this->vocabs[$vocab_name]['vocab'];

      // Add a field for the vocabulary to pages.
      $this
        ->createField($vocab_name);

      // Configure default grants for the vocabulary for each role.
      if (!empty($default_grants)) {
        foreach ($this->roles as $name => $role) {
          $default_rows[] = _taxonomy_access_format_grant_record($vocab->vid, $role, $default_grants, TRUE);
          $this->setUpAssertions[] = array(
            'grant' => $default_grants['view'],
            'query' => 'SELECT grant_view FROM {taxonomy_access_default} WHERE vid = :vid AND rid = :rid',
            'args' => array(
              ':vid' => $vocab->vid,
              ':rid' => $role,
            ),
            'message' => t('Configured default grants for vocab %vocab, role %role', array(
              '%vocab' => $vocab->machine_name,
              '%role' => $name,
            )),
          );
        }
      }

      // Create terms.
      foreach ($grant_types as $parent_name => $parent_grants) {

        // Create parent term.
        $parent_name = $vocab_name . "__" . $parent_name . "_parent";
        $this->vocabs[$vocab_name]['terms'][$parent_name] = parent::createTerm($parent_name, $vocab);
        $parent_id = $this->vocabs[$vocab_name]['terms'][$parent_name]->tid;

        // Configure grants for the parent term for each role.
        if (!empty($parent_grants)) {
          foreach ($this->roles as $name => $role) {
            $term_rows[] = _taxonomy_access_format_grant_record($parent_id, $role, $parent_grants);
            $this->setUpAssertions[] = array(
              'grant' => $parent_grants['view'],
              'query' => 'SELECT grant_view FROM {taxonomy_access_term} WHERE tid = :tid AND rid = :rid',
              'args' => array(
                ':tid' => $parent_id,
                ':rid' => $role,
              ),
              'message' => t('Configured grants for term %term, role %role', array(
                '%term' => $parent_name,
                '%role' => $name,
              )),
            );
          }
        }

        // Create child terms.
        foreach ($grant_types as $child_name => $child_grants) {
          $child_name = $parent_name . "__" . $child_name . "_child";
          $this->vocabs[$vocab_name]['terms'][$child_name] = parent::createTerm($child_name, $vocab, $parent_id);
          $child_id = $this->vocabs[$vocab_name]['terms'][$child_name]->tid;

          // Configure grants for the child term for each role.
          if (!empty($child_grants)) {
            foreach ($this->roles as $name => $role) {
              $term_rows[] = _taxonomy_access_format_grant_record($child_id, $role, $child_grants);
              $this->setUpAssertions[] = array(
                'grant' => $child_grants['view'],
                'query' => 'SELECT grant_view FROM {taxonomy_access_term} WHERE tid = :tid AND rid = :rid',
                'args' => array(
                  ':tid' => $child_id,
                  ':rid' => $role,
                ),
                'message' => t('Configured grants for term %term, role %role', array(
                  '%term' => $child_name,
                  '%role' => $name,
                )),
              );
            }
          }
        }
      }
    }

    // Set the grants.
    taxonomy_access_set_default_grants($default_rows);
    taxonomy_access_set_term_grants($term_rows);
  }

  /**
   * Verifies that all grants were properly stored during setup.
   */
  public function testSetUpCheck() {

    // Check that all records were properly stored.
    foreach ($this->setUpAssertions as $assertion) {
      $r = db_query($assertion['query'], $assertion['args'])
        ->fetchField();
      $this
        ->assertTrue(is_numeric($r) && $r == $assertion['grant'], $assertion['message']);
    }
  }

}

/**
 * Tests term grants for all possible grant combinations.
 */
class TaxonomyAccessTermGrantTest extends TaxonomyAccessTestCase {

  // There are four roles for term access testing:
  // ctlt   Receives both "Create" and "List" in the global default.
  // ctlf   Receives "Create" but not "List" in the global default.
  // cflt   Receives "List" but not "Create" in the global default.
  // cflf   Receives neither "Create" nor "List" in the global default.
  // All roles receive the same permissions for terms and vocab defaults.
  protected $roles = array();
  protected $role_config = array(
    'ctlt' => array(),
    'ctlf' => array(),
    'cflt' => array(),
    'cflf' => array(),
  );
  protected $vocabs = array();
  public static function getInfo() {
    return array(
      'name' => 'Term grants',
      'description' => 'Test node access for View tag (create) and Add tag (list) grants.',
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp();

    // Configure roles with no additional permissions.
    foreach ($this->role_config as $role_name => $permissions) {
      $this->roles[$role_name] = $this
        ->drupalCreateRole(array(), $role_name);
    }

    // Set up our testing taxonomy.
    // We will create four vocabularies:
    // vctlt   Receives both "Create" and "List" in the vocabulary default.
    // vctlf   Receives "Create" but not "List" in the vocabulary default.
    // vcflt   Receives "List" but not "Create" in the vocabulary default.
    // vcflf   Receives neither "Create" nor "List" in the vocabulary default.
    $grant_combos = array(
      'ctlt' => array(
        'create' => TAXONOMY_ACCESS_TERM_ALLOW,
        'list' => TAXONOMY_ACCESS_TERM_ALLOW,
      ),
      'ctlf' => array(
        'create' => TAXONOMY_ACCESS_TERM_ALLOW,
        'list' => TAXONOMY_ACCESS_TERM_DENY,
      ),
      'cflt' => array(
        'create' => TAXONOMY_ACCESS_TERM_DENY,
        'list' => TAXONOMY_ACCESS_TERM_ALLOW,
      ),
      'cflf' => array(
        'create' => TAXONOMY_ACCESS_TERM_DENY,
        'list' => TAXONOMY_ACCESS_TERM_DENY,
      ),
    );

    // Grant all rows view, update, and delete.
    foreach ($grant_combos as $combo) {
      $combo['view'] = TAXONOMY_ACCESS_NODE_ALLOW;
      $combo['update'] = TAXONOMY_ACCESS_NODE_ALLOW;
      $combo['delete'] = TAXONOMY_ACCESS_NODE_ALLOW;
    }

    // Each vocabulary will have four parent terms in the same fashion:
    // ctlt_parent, ctlf_parent, cflt_parent, and cflf_parent.
    // Each of these_parent terms will have children in each class, as well:
    // ctlt_child, ctlf_child, cflt_child, and cflf_child.
    // So, each vocab looks something like:
    // - ctlt_parent
    // - - ctlt_child
    // - - ctlf_child
    // - - cflt_child
    // - - cflf_child
    // - ctlf_parent
    // - - ctlt_child
    // - - ctlf_child
    // - - cflt_child
    // - - cfl_fchild
    // - cflt_parent
    // - - ctlt_child
    // - - ctlf_child
    // - - cflt_child
    // - - cflf_child
    // - cflf_parent
    // - - ctlt_child
    // - - ctlf_child
    // - - cflt_child
    // - - cflf_child
    // Configure terms, vocabularies, and grants.
    foreach ($grant_combos as $vocab_name => $default_grants) {

      // Create the vocabulary.
      $vocab_name = "v" . $vocab_name;
      $this->vocabs[$vocab_name] = array();
      $this->vocabs[$vocab_name]['vocab'] = parent::createVocab($vocab_name);
      $this->vocabs[$vocab_name]['terms'] = array();
      $vocab = $this->vocabs[$vocab_name]['vocab'];

      // Add a field for the vocabulary to pages.
      $this
        ->createField($vocab_name);

      // Configure default grants for the vocabulary for each role.
      if (!empty($default_grants)) {
        foreach ($this->roles as $name => $role) {
          $default_rows[] = _taxonomy_access_format_grant_record($vocab->vid, $role, $default_grants, TRUE);
          $this->setUpAssertions[] = array(
            'create' => $default_grants['create'],
            'list' => $default_grants['list'],
            'query' => 'SELECT grant_create, grant_list FROM {taxonomy_access_default} WHERE vid = :vid AND rid = :rid',
            'args' => array(
              ':vid' => $vocab->vid,
              ':rid' => $role,
            ),
            'message' => t('Configured default grants for vocab %vocab, role %role', array(
              '%vocab' => $vocab->machine_name,
              '%role' => $name,
            )),
          );
        }
      }

      // Create terms.
      foreach ($grant_combos as $parent_name => $parent_grants) {

        // Create parent term.
        $parent_name = $vocab_name . "__" . $parent_name . "_parent";
        $this->vocabs[$vocab_name]['terms'][$parent_name] = parent::createTerm($parent_name, $vocab);
        $parent_id = $this->vocabs[$vocab_name]['terms'][$parent_name]->tid;

        // Configure grants for the parent term for each role.
        if (!empty($parent_grants)) {
          foreach ($this->roles as $name => $role) {
            $term_rows[] = _taxonomy_access_format_grant_record($parent_id, $role, $parent_grants);
            $this->setUpAssertions[] = array(
              'create' => $parent_grants['create'],
              'list' => $parent_grants['list'],
              'query' => 'SELECT grant_create, grant_list FROM {taxonomy_access_term} WHERE tid = :tid AND rid = :rid',
              'args' => array(
                ':tid' => $parent_id,
                ':rid' => $role,
              ),
              'message' => t('Configured grants for term %term, role %role', array(
                '%term' => $parent_name,
                '%role' => $name,
              )),
            );
          }
        }

        // Create child terms.
        foreach ($grant_combos as $child_name => $child_grants) {
          $child_name = $parent_name . "__" . $child_name . "_child";
          $this->vocabs[$vocab_name]['terms'][$child_name] = parent::createTerm($child_name, $vocab, $parent_id);
          $child_id = $this->vocabs[$vocab_name]['terms'][$child_name]->tid;

          // Configure grants for the child term for each role.
          if (!empty($child_grants)) {
            foreach ($this->roles as $name => $role) {
              $term_rows[] = _taxonomy_access_format_grant_record($child_id, $role, $child_grants);
              $this->setUpAssertions[] = array(
                'create' => $child_grants['create'],
                'list' => $child_grants['list'],
                'query' => 'SELECT grant_create, grant_list FROM {taxonomy_access_term} WHERE tid = :tid AND rid = :rid',
                'args' => array(
                  ':tid' => $child_id,
                  ':rid' => $role,
                ),
                'message' => t('Configured grants for term %term, role %role', array(
                  '%term' => $child_name,
                  '%role' => $name,
                )),
              );
            }
          }
        }
      }
    }

    // Set the grants.
    taxonomy_access_set_default_grants($default_rows);
    taxonomy_access_set_term_grants($term_rows);
  }

  /**
   * Verifies that all grants were properly stored during setup.
   */
  public function testSetUpCheck() {

    // Check that all records were properly stored.
    foreach ($this->setUpAssertions as $assertion) {
      $r = db_query($assertion['query'], $assertion['args'])
        ->fetchAssoc();
      $this
        ->assertTrue(is_array($r) && $r['grant_create'] == $assertion['create'] && $r['grant_list'] == $assertion['list'], $assertion['message']);
    }
  }

}
class TaxonomyAccessWeightTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Weight',
      'description' => 'Test module weight.',
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp('taxonomy_access');
  }

  /**
   * Verifies that this module is weighted below the Taxonomy module.
   */
  public function testWeight() {

    // Verify weight.
    $tax_weight = db_query("SELECT weight FROM {system}\n         WHERE name = 'taxonomy'")
      ->fetchField();
    $tax_access_weight = db_query("SELECT weight FROM {system}\n         WHERE name = 'taxonomy_access'")
      ->fetchField();
    $this
      ->assertTrue($tax_access_weight > $tax_weight, t("Weight of this module is @tax_access_weight. Weight of the Taxonomy module is @tax_weight.", array(
      '@tax_access_weight' => $tax_access_weight,
      '@tax_weight' => $tax_weight,
    )));

    // Disable module and set weight of the Taxonomy module to a high number.
    module_disable(array(
      'taxonomy_access',
    ), TRUE);
    db_update('system')
      ->fields(array(
      'weight' => rand(5000, 9000),
    ))
      ->condition('name', 'taxonomy')
      ->execute();

    // Re-enable module and re-verify weight.
    module_enable(array(
      'taxonomy_access',
    ), TRUE);
    $tax_weight = db_query("SELECT weight FROM {system}\n         WHERE name = 'taxonomy'")
      ->fetchField();
    $tax_access_weight = db_query("SELECT weight FROM {system}\n         WHERE name = 'taxonomy_access'")
      ->fetchField();
    $this
      ->assertTrue($tax_access_weight > $tax_weight, t("Weight of this module is @tax_access_weight. Weight of the Taxonomy module is @tax_weight.", array(
      '@tax_access_weight' => $tax_access_weight,
      '@tax_weight' => $tax_weight,
    )));
  }

}

/**
 * Tests that callbacks are cleaned up when the module is disabled.
 */
class TaxonomyAccessCallbackCleanupTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Callback Cleanup',
      'description' => 'Test callback cleanup during disabling of module works.',
      'group' => 'Taxonomy Access Control',
    );
  }
  public function setUp() {
    parent::setUp('taxonomy_access');
  }

  /**
   * Verifies that the module's callbacks are cleaned up during disable.
   */
  public function testCallbackCleanup() {

    // The problem only happens on new fields after the module is installed.
    $content_type = $this
      ->drupalCreateContentType();

    // Create a new field with type taxonomy_term_reference.
    $field_name = drupal_strtolower($this
      ->randomName() . '_field_name');
    $field_type = array(
      'field_name' => $field_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => 1,
    );
    $field_type = field_create_field($field_type);

    // Add an instance of the field to content type.
    $field_instance = array(
      'field_name' => $field_name,
      'entity_type' => 'node',
      'bundle' => $content_type->name,
    );
    $field_instance = field_create_instance($field_instance);

    // Trigger hook_disable to see if the callbacks are cleaned up.
    module_disable(array(
      'taxonomy_access',
    ), TRUE);

    // Create a user so that we can check if we can access the node add pages.
    $this->privileged_user = $this
      ->drupalCreateUser(array(
      'bypass node access',
    ));
    $this
      ->drupalLogin($this->privileged_user);

    // If the callbacks are not cleaned up we would get a fatal error.
    $this
      ->drupalGet('node/add/' . $content_type->name);
    $this
      ->assertText(t('Create @name', array(
      '@name' => $content_type->name,
    )), t('New content can be added'));
  }

}

Classes

Namesort descending Description
TaxonomyAccessCallbackCleanupTest Tests that callbacks are cleaned up when the module is disabled.
TaxonomyAccessConfigTest Tests the module's configuration forms.
TaxonomyAccessExternalChanges Tests the module's response to changes from other modules.
TaxonomyAccessNodeGrantTest Tests node access for all possible grant combinations.
TaxonomyAccessTermGrantTest Tests term grants for all possible grant combinations.
TaxonomyAccessTestCase Provides a base test class and helper methods for automated tests.
TaxonomyAccessWeightTest