<?php
// $Id: taxonomy.test,v 1.77 2010/04/20 09:48:06 webchick Exp $

/**
 * @file
 * Tests for Taxonomy module.
 */

/**
* Class with common helper methods.
*/
class TaxonomyWebTestCase extends DrupalWebTestCase {

  /**
   * Returns a new vocabulary with random properties.
   */
  function createVocabulary() {
    // Create a vocabulary.
    $vocabulary = new stdClass();
    $vocabulary->name = $this->randomName();
    $vocabulary->description = $this->randomName();
    $vocabulary->machine_name = drupal_strtolower($this->randomName());
    $vocabulary->help = '';
    $vocabulary->nodes = array('article' => 'article');
    $vocabulary->weight = mt_rand(0, 10);
    taxonomy_vocabulary_save($vocabulary);
    return $vocabulary;
  }

  /**
   * Returns a new term with random properties in vocabulary $vid.
   */
  function createTerm($vocabulary) {
    $term = new stdClass();
    $term->name = $this->randomName();
    $term->description = $this->randomName();
    $term->vid = $vocabulary->vid;
    taxonomy_term_save($term);
    return $term;
  }
}

/**
* Tests for the taxonomy vocabulary interface.
*/
class TaxonomyVocabularyFunctionalTest extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Taxonomy vocabulary interface',
      'description' => 'Test the taxonomy vocabulary interface.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp();
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy'));
    $this->drupalLogin($this->admin_user);
    $this->vocabulary = $this->createVocabulary();
  }

  /**
   * Create, edit and delete a vocabulary via the user interface.
   */
  function testVocabularyInterface() {
    // Visit the main taxonomy administration page.
    $this->drupalGet('admin/structure/taxonomy');

    // Create a new vocabulary.
    $this->clickLink(t('Add vocabulary'));
    $edit = array();
    $machine_name = drupal_strtolower($this->randomName());
    $edit['name'] = $this->randomName();
    $edit['description'] = $this->randomName();
    $edit['machine_name'] = $machine_name;
    $this->drupalPost(NULL, $edit, t('Save'));
    $this->assertRaw(t('Created new vocabulary %name.', array('%name' => $edit['name'])), t('Vocabulary created successfully'));

    // Edit the vocabulary.
    $this->drupalGet('admin/structure/taxonomy');
    $this->assertText($edit['name'], t('Vocabulary found in the vocabulary overview listing.'));
    $this->clickLink(t('edit vocabulary'));
    $edit = array();
    $edit['name'] = $this->randomName();
    $this->drupalPost(NULL, $edit, t('Save'));
    $this->drupalGet('admin/structure/taxonomy');
    $this->assertText($edit['name'], t('Vocabulary found in the vocabulary overview listing.'));

    // Try to submit a vocabulary with a duplicate machine name.
    $edit['machine_name'] = $machine_name;
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
    $this->assertText(t('This machine-readable name is already in use by another vocabulary and must be unique.'), t('Duplicate machine name validation was successful'));

    // Try to submit an invalid machine name.
    $edit['machine_name'] = '!&^%';
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
    $this->assertText(t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
  }

  /**
   * Changing weights on the vocabulary overview with two or more vocabularies.
   */
  function testTaxonomyAdminChangingWeights() {
    // Create some vocabularies.
    for ($i = 0; $i < 10; $i++) {
      $this->createVocabulary();
    }
    // Get all vocabularies and change their weights.
    $vocabularies = taxonomy_get_vocabularies();
    $edit = array();
    foreach ($vocabularies as $key => $vocabulary) {
      $vocabulary->weight = -$vocabulary->weight;
      $vocabularies[$key]->weight = $vocabulary->weight;
      $edit[$key . '[weight]'] = $vocabulary->weight;
    }
    // Saving the new weights via the interface.
    $this->drupalPost('admin/structure/taxonomy', $edit, t('Save'));

    // Load the vocabularies from the database.
    $new_vocabularies = taxonomy_get_vocabularies();

    // Check that the weights are saved in the database correctly.
    foreach ($vocabularies as $key => $vocabulary) {
      $this->assertEqual($new_vocabularies[$key]->weight, $vocabularies[$key]->weight, t('The vocabulary weight was changed.'));
    }
  }

  /**
   * Test the vocabulary overview with no vocabularies.
   */
  function testTaxonomyAdminNoVocabularies() {
    // Delete all vocabularies.
    $vocabularies = taxonomy_get_vocabularies();
    foreach ($vocabularies as $key => $vocabulary) {
      taxonomy_vocabulary_delete($key);
    }
    // Confirm that no vocabularies are found in the database.
    $this->assertFalse(taxonomy_get_vocabularies(), t('No vocabularies found in the database'));
    $this->drupalGet('admin/structure/taxonomy');
    // Check the default message for no vocabularies.
    $this->assertText(t('No vocabularies available.'), t('No vocabularies were found.'));
  }

  /**
   * Deleting a vocabulary.
   */
  function testTaxonomyAdminDeletingVocabulary() {
    // Create a vocabulary.
    $edit = array(
      'name' => $this->randomName(),
      'machine_name' => drupal_strtolower($this->randomName()),
    );
    $this->drupalPost('admin/structure/taxonomy/add', $edit, t('Save'));
    $this->assertText(t('Created new vocabulary'), t('New vocabulary was created.'));

    // Check the created vocabulary.
    $vocabularies = taxonomy_get_vocabularies();
    $vid = $vocabularies[count($vocabularies)-1]->vid;
    entity_get_controller('taxonomy_vocabulary')->resetCache();
    $vocabulary = taxonomy_vocabulary_load($vid);
    $this->assertTrue($vocabulary, t('Vocabulary found in database'));

    // Delete the vocabulary.
    $edit = array();
    $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/edit', $edit, t('Delete'));
    $this->assertRaw(t('Are you sure you want to delete the vocabulary %name?', array('%name' => $vocabulary->name)), t('[confirm deletion] Asks for confirmation.'));
    $this->assertText(t('Deleting a vocabulary will delete all the terms in it. This action cannot be undone.'), t('[confirm deletion] Inform that all terms will be deleted.'));

    // Confirm deletion.
    $this->drupalPost(NULL, NULL, t('Delete'));
    $this->assertRaw(t('Deleted vocabulary %name.', array('%name' => $vocabulary->name)), t('Vocabulary deleted'));
    entity_get_controller('taxonomy_vocabulary')->resetCache();
    $this->assertFalse(taxonomy_vocabulary_load($vid), t('Vocabulary is not found in the database'));
  }
}


/**
 * Tests for taxonomy vocabulary functions.
 */
class TaxonomyVocabularyUnitTest extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Taxonomy vocabularies',
      'description' => 'Test loading, saving and deleting vocabularies.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp('taxonomy');
    $admin_user = $this->drupalCreateUser(array('create article content', 'administer taxonomy'));
    $this->drupalLogin($admin_user);
    $this->vocabulary = $this->createVocabulary();
  }

  /**
   * Ensure that when an invalid vocabulary vid is loaded, it is possible
   * to load the same vid successfully if it subsequently becomes valid.
   */
  function testTaxonomyVocabularyLoadReturnFalse() {
    // Load a vocabulary that doesn't exist.
    $vocabularies = taxonomy_get_vocabularies();
    $vid = count($vocabularies) + 1;
    $vocabulary = taxonomy_vocabulary_load($vid);
    // This should not return an object because no such vocabulary exists.
    $this->assertTrue(empty($vocabulary), t('No object loaded.'));

    // Create a new vocabulary.
    $this->createVocabulary();
    // Load the vocabulary with the same $vid from earlier.
    // This should return a vocabulary object since it now matches a real vid.
    $vocabulary = taxonomy_vocabulary_load($vid);
    $this->assertTrue(!empty($vocabulary) && is_object($vocabulary), t('Vocabulary is an object'));
    $this->assertTrue($vocabulary->vid == $vid, t('Valid vocabulary vid is the same as our previously invalid one.'));
  }

  /**
   * Ensure that the vocabulary static reset works correctly.
   */
  function testTaxonomyVocabularyLoadStaticReset() {
    $original_vocabulary = taxonomy_vocabulary_load($this->vocabulary->vid);
    $this->assertTrue(is_object($original_vocabulary), t('Vocabulary loaded successfully'));
    $this->assertEqual($this->vocabulary->name, $original_vocabulary->name, t('Vocabulary loaded successfully'));

    // Change the name and description.
    $vocabulary = $original_vocabulary;
    $vocabulary->name = $this->randomName();
    $vocabulary->description = $this->randomName();
    taxonomy_vocabulary_save($vocabulary);

    // Load the vocabulary.
    $new_vocabulary = taxonomy_vocabulary_load($original_vocabulary->vid);
    $this->assertEqual($new_vocabulary->name, $vocabulary->name);
    $this->assertEqual($new_vocabulary->name, $vocabulary->name);

    // Delete the vocabulary.
    taxonomy_vocabulary_delete($this->vocabulary->vid);
    $vocabularies = taxonomy_get_vocabularies();
    $this->assertTrue(!isset($vocabularies[$this->vocabulary->vid]), t('The vocabulary was deleted'));
  }

  /**
   * Tests for loading multiple vocabularies.
   */
  function testTaxonomyVocabularyLoadMultiple() {

    // Delete any existing vocabularies.
    foreach (taxonomy_get_vocabularies() as $vocabulary) {
      taxonomy_vocabulary_delete($vocabulary->vid);
    }

    // Create some vocabularies and assign weights.
    $vocabulary1 = $this->createVocabulary();
    $vocabulary1->weight = 0;
    taxonomy_vocabulary_save($vocabulary1);
    $vocabulary2 = $this->createVocabulary();
    $vocabulary2->weight = 1;
    taxonomy_vocabulary_save($vocabulary2);
    $vocabulary3 = $this->createVocabulary();
    $vocabulary3->weight = 2;
    taxonomy_vocabulary_save($vocabulary3);

    // Fetch the names for all vocabularies, confirm that they are keyed by
    // machine name.
    $names = taxonomy_vocabulary_get_names();
    $this->assertEqual($names[$vocabulary1->machine_name]->name, $vocabulary1->name, t('Vocabulary 1 name found.'));

    // Fetch all of the vocabularies using taxonomy_get_vocabularies().
    // Confirm that the vocabularies are ordered by weight.
    $vocabularies = taxonomy_get_vocabularies();
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, t('Vocabulary was found in the vocabularies array.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, t('Vocabulary was found in the vocabularies array.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, t('Vocabulary was found in the vocabularies array.'));

    // Fetch the vocabularies with taxonomy_vocabulary_load_multiple(), specifying IDs.
    // Ensure they are returned in the same order as the original array.
    $vocabularies = taxonomy_vocabulary_load_multiple(array($vocabulary3->vid, $vocabulary2->vid, $vocabulary1->vid));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary3->vid, t('Vocabulary loaded successfully by ID.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary2->vid, t('Vocabulary loaded successfully by ID.'));
    $this->assertEqual(array_shift($vocabularies)->vid, $vocabulary1->vid, t('Vocabulary loaded successfully by ID.'));

    // Fetch vocabulary 1 by name.
    $vocabulary = current(taxonomy_vocabulary_load_multiple(array(), array('name' => $vocabulary1->name)));
    $this->assertTrue($vocabulary->vid == $vocabulary1->vid, t('Vocabulary loaded successfully by name.'));

    // Fetch vocabulary 1 by name and ID.
    $this->assertTrue(current(taxonomy_vocabulary_load_multiple(array($vocabulary1->vid), array('name' => $vocabulary1->name)))->vid == $vocabulary1->vid, t('Vocabulary loaded successfully by name and ID.'));
  }
}

/**
 * Unit tests for taxonomy term functions.
 */
class TaxonomyTermUnitTest extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Taxonomy term unit tests',
      'description' => 'Unit tests for taxonomy term functions.',
      'group' => 'Taxonomy',
    );
  }
}

/**
 * Tests for taxonomy term functions.
 */
class TaxonomyTermTestCase extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Taxonomy term functions and forms.',
      'description' => 'Test load, save and delete for taxonomy terms.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp('taxonomy');
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
    $this->drupalLogin($this->admin_user);
    $this->vocabulary = $this->createVocabulary();

    $field = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'settings' => array(
        'allowed_values' => array(
          array(
            'vid' => $this->vocabulary->vid,
            'parent' => 0,
          ),
        ),
      ),
    );
    field_create_field($field);

    $this->instance = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'bundle' => 'article',
      'entity_type' => 'node',
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
        'full' => array(
          'type' => 'taxonomy_term_reference_link',
        ),
      ),
    );
    field_create_instance($this->instance);
  }

  /**
   * Test terms in a single and multiple hierarchy.
   */
  function testTaxonomyTermHierarchy() {
    // Create two taxonomy terms.
    $term1 = $this->createTerm($this->vocabulary);
    $term2 = $this->createTerm($this->vocabulary);

    // Edit $term2, setting $term1 as parent.
    $edit = array();
    $edit['parent[]'] = array($term1->tid);
    $this->drupalPost('taxonomy/term/' . $term2->tid . '/edit', $edit, t('Save'));

    // Check the hierarchy.
    $children = taxonomy_get_children($term1->tid);
    $parents = taxonomy_get_parents($term2->tid);
    $this->assertTrue(isset($children[$term2->tid]), t('Child found correctly.'));
    $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));

    // Load and save a term, confirming that parents are still set.
    $term = taxonomy_term_load($term2->tid);
    taxonomy_term_save($term);
    $parents = taxonomy_get_parents($term2->tid);
    $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));

    // Create a third term and save this as a parent of term2.
    $term3 = $this->createTerm($this->vocabulary);
    $term2->parent = array($term1->tid, $term3->tid);
    taxonomy_term_save($term2);
    $parents = taxonomy_get_parents($term2->tid);
    $this->assertTrue(isset($parents[$term1->tid]) && isset($parents[$term3->tid]), t('Both parents found successfully.'));
  }

  /**
   * Test that hook_node_$op implementations work correctly.
   *
   * Save & edit a node and assert that taxonomy terms are saved/loaded properly.
   */
  function testTaxonomyNode() {
    // Create two taxonomy terms.
    $term1 = $this->createTerm($this->vocabulary);
    $term2 = $this->createTerm($this->vocabulary);

    // Post an article.
    $edit = array();
    $langcode = LANGUAGE_NONE;
    $edit["title"] = $this->randomName();
    $edit["body[$langcode][0][value]"] = $this->randomName();
    $edit[$this->instance['field_name'] . '[' . $langcode .'][]'] = $term1->tid;
    $this->drupalPost('node/add/article', $edit, t('Save'));

    // Check that the term is displayed when the node is viewed.
    $node = $this->drupalGetNodeByTitle($edit["title"]);
    $this->drupalGet('node/' . $node->nid);
    $this->assertText($term1->name, t('Term is displayed when viewing the node.'));

    // Edit the node with a different term.
    $edit[$this->instance['field_name'] . '[' . $langcode . '][]'] = $term2->tid;
    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));

    $this->drupalGet('node/' . $node->nid);
    $this->assertText($term2->name, t('Term is displayed when viewing the node.'));
  }

  /**
   * Test term creation with a free-tagging vocabulary from the node form.
   */
  function testNodeTermCreation() {
    // Enable tags in the vocabulary.
    $instance = $this->instance;
    $instance['widget'] = array('type' => 'taxonomy_autocomplete');
    $instance['bundle'] = 'page';
    field_create_instance($instance);
    $terms = array(
      $this->randomName(),
      $this->randomName(),
      $this->randomName(),
    );

    $edit = array();
    $langcode = LANGUAGE_NONE;
    $edit["title"] = $this->randomName();
    $edit["body[$langcode][0][value]"] = $this->randomName();
    // Insert the terms in a comma separated list. Vocabulary 1 is a
    // free-tagging field created by the default profile.
    $edit[$instance['field_name'] . "[$langcode]"] = implode(', ', $terms);
    $this->drupalPost('node/add/page', $edit, t('Save'));
    $this->assertRaw(t('@type %title has been created.', array('@type' => t('Basic page'), '%title' => $edit["title"])), t('The node was created successfully'));
    foreach ($terms as $term) {
      $this->assertText($term, t('The term was saved and appears on the node page'));
    }
  }

  /**
   * Save, edit and delete a term using the user interface.
   */
  function testTermInterface() {
    $edit = array(
      'name' => $this->randomName(12),
      'description[value]' => $this->randomName(100),
    );
    // Explicitly set the parents field to 'root', to ensure that
    // taxonomy_form_term_submit() handles the invalid term ID correctly.
    $edit['parent[]'] = array(0);

    // Create the term to edit.
    $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name . '/add', $edit, t('Save'));

    $terms = taxonomy_get_term_by_name($edit['name']);
    $term = reset($terms);
    $this->assertNotNull($term, t('Term found in database'));

    // Submitting a term takes us to the add page; we need the List page.
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);

    // Test edit link as accessed from Taxonomy administration pages.
    // Because Simpletest creates its own database when running tests, we know
    // the first edit link found on the listing page is to our term.
    $this->clickLink(t('edit'));

    $this->assertText($edit['name'], t('The randomly generated term name is present.'));
    $this->assertText($edit['description[value]'], t('The randomly generated term description is present.'));

    $edit = array(
      'name' => $this->randomName(14),
      'description[value]' => $this->randomName(102),
    );

    // Edit the term.
    $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save'));

    // Check that the term is still present at admin UI after edit.
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
    $this->assertText($edit['name'], t('The randomly generated term name is present.'));
    $this->assertLink(t('edit'));

    // View the term and check that it is correct.
    $this->drupalGet('taxonomy/term/' . $term->tid);
    $this->assertText($edit['name'], t('The randomly generated term name is present.'));
    $this->assertText($edit['description[value]'], t('The randomly generated term description is present.'));

    // Check that the term feed page is working.
    $this->drupalGet('taxonomy/term/' . $term->tid . '/feed');

    // Delete the term.
    $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', array(), t('Delete'));
    $this->drupalPost(NULL, NULL, t('Delete'));

    // Assert that the term no longer exists.
    $this->drupalGet('taxonomy/term/' . $term->tid);
    $this->assertResponse(404, t('The taxonomy term page was not found'));
  }

  /**
   * Save, edit and delete a term using the user interface.
   */
  function testTermReorder() {
    $this->createTerm($this->vocabulary);
    $this->createTerm($this->vocabulary);
    $this->createTerm($this->vocabulary);

    // Fetch the created terms in the default alphabetical order, i.e. term1
    // precedes term2 alphabetically, and term2 precedes term3.
    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    list($term1, $term2, $term3) = taxonomy_get_tree($this->vocabulary->vid);

    // Change the order to term2, term3, term1. Emulate the reordering done by
    // tabledrag.js by changing the page HTML source. Each term has three hidden
    // fields, "tid:1:0[tid]", "tid:1:0[parent]", and "tid:1:0[depth]". The
    // order of the input fields in the page is used when the form is processed.
    $this->drupalGet('admin/structure/taxonomy/' . $this->vocabulary->machine_name);
    $reorder = array(
      'tid:' . $term1->tid . ':0' => 'tid:' . $term2->tid . ':0',
      'tid:' . $term2->tid . ':0' => 'tid:' . $term3->tid . ':0',
      'tid:' . $term3->tid . ':0' => 'tid:' . $term1->tid . ':0',
      );
    $this->drupalSetContent(strtr($this->drupalGetContent(), $reorder));

    // Make term3 a child of term2, and update all hidden fields.
    $edit = array(
      'tid:' . $term2->tid . ':0[tid]' => $term2->tid,
      'tid:' . $term2->tid . ':0[parent]' => 0,
      'tid:' . $term2->tid . ':0[depth]' => 0,
      'tid:' . $term3->tid . ':0[tid]' => $term3->tid,
      'tid:' . $term3->tid . ':0[parent]' => $term2->tid,
      'tid:' . $term3->tid . ':0[depth]' => 1,
      'tid:' . $term1->tid . ':0[tid]' => $term1->tid,
      'tid:' . $term1->tid . ':0[parent]' => 0,
      'tid:' . $term1->tid . ':0[depth]' => 0,
    );
    $this->drupalPost(NULL, $edit, t('Save'));

    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    $terms = taxonomy_get_tree($this->vocabulary->vid);
    $this->assertEqual($terms[0]->tid, $term2->tid, t('Term 2 was moved above term 1.'));
    $this->assertEqual($terms[1]->parents, array($term2->tid), t('Term 3 was made a child of term 2.'));
    $this->assertEqual($terms[2]->tid, $term1->tid, t('Term 1 was moved below term 2.'));

    $this->drupalPost('admin/structure/taxonomy/' . $this->vocabulary->machine_name, array(), t('Reset to alphabetical'));
    // Submit confirmation form.
    $this->drupalPost(NULL, array(), t('Reset to alphabetical'));

    drupal_static_reset('taxonomy_get_tree');
    drupal_static_reset('taxonomy_get_treeparent');
    drupal_static_reset('taxonomy_get_treeterms');
    $terms = taxonomy_get_tree($this->vocabulary->vid);
    $this->assertEqual($terms[0]->tid, $term1->tid, t('Term 1 was moved to back above term 2.'));
    $this->assertEqual($terms[1]->tid, $term2->tid, t('Term 2 was moved to back below term 1.'));
    $this->assertEqual($terms[2]->tid, $term3->tid, t('Term 3 is still below term 2.'));
    $this->assertEqual($terms[2]->parents, array($term2->tid), t('Term 3 is still a child of term 2.').var_export($terms[1]->tid,1));
  }

  /**
   * Test taxonomy_get_term_by_name().
   */
  function testTaxonomyGetTermByName() {
    $term = $this->createTerm($this->vocabulary);

    // Load the term with the exact name.
    $terms = taxonomy_get_term_by_name($term->name);
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded using exact name.'));

    // Load the term with space concatenated.
    $terms  = taxonomy_get_term_by_name('  ' . $term->name . '   ');
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with extra whitespace.'));

    // Load the term with name uppercased.
    $terms = taxonomy_get_term_by_name(strtoupper($term->name));
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with uppercased name.'));

    // Load the term with name lowercased.
    $terms = taxonomy_get_term_by_name(strtolower($term->name));
    $this->assertTrue(isset($terms[$term->tid]), t('Term loaded with lowercased name.'));

    // Try to load an invalid term name.
    $terms = taxonomy_get_term_by_name('Banana');
    $this->assertFalse($terms);

    // Try to load the term using a substring of the name.
    $terms = taxonomy_get_term_by_name(drupal_substr($term->name, 2));
    $this->assertFalse($terms);
  }
}

/**
 * Test the taxonomy_term_load_multiple() function.
 */
class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {

  public static function getInfo() {
    return array(
      'name' => 'Taxonomy term multiple loading',
      'description' => 'Test the loading of multiple taxonomy terms at once',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp();
    $this->taxonomy_admin = $this->drupalCreateUser(array('administer taxonomy'));
    $this->drupalLogin($this->taxonomy_admin);
  }

  /**
   * Create a vocabulary and some taxonomy terms, ensuring they're loaded
   * correctly using taxonomy_term_load_multiple().
   */
  function testTaxonomyTermMultipleLoad() {
    // Create a vocabulary.
    $vocabulary = $this->createVocabulary();

    // Create five terms in the vocabulary.
    $i = 0;
    while ($i < 5) {
      $i++;
      $this->createTerm($vocabulary);
    }
    // Load the terms from the vocabulary.
    $terms = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid));
    $count = count($terms);
    $this->assertTrue($count == 5, t('Correct number of terms were loaded. !count terms.', array('!count' => $count)));

    // Load the same terms again by tid.
    $terms2 = taxonomy_term_load_multiple(array_keys($terms));
    $this->assertTrue($count == count($terms2), t('Five terms were loaded by tid'));
    $this->assertEqual($terms, $terms2, t('Both arrays contain the same terms'));

    // Load the terms by tid, with a condition on vid.
    $terms3 = taxonomy_term_load_multiple(array_keys($terms2), array('vid' => $vocabulary->vid));
    $this->assertEqual($terms2, $terms3);

    // Remove one term from the array, then delete it.
    $deleted = array_shift($terms3);
    taxonomy_term_delete($deleted->tid);
    $deleted_term = taxonomy_term_load($deleted->tid);
    $this->assertFalse($deleted_term);

    // Load terms from the vocabulary by vid.
    $terms4 = taxonomy_term_load_multiple(NULL, array('vid' => $vocabulary->vid));
    $this->assertTrue(count($terms4 == 4), t('Correct number of terms were loaded.'));
    $this->assertFalse(isset($terms4[$deleted->tid]));

    // Create a single term and load it by name.
    $term = $this->createTerm($vocabulary);
    $loaded_terms = taxonomy_term_load_multiple(array(), array('name' => $term->name));
    $this->assertEqual(count($loaded_terms), 1, t('One term was loaded'));
    $loaded_term = reset($loaded_terms);
    $this->assertEqual($term->tid, $loaded_term->tid, t('Term loaded by name successfully.'));
  }
}

/**
 * Tests for taxonomy hook invocation.
 */
class TaxonomyHooksTestCase extends TaxonomyWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Taxonomy term hooks',
      'description' => 'Hooks for taxonomy term load/save/delete.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp('taxonomy', 'taxonomy_test');
    $taxonomy_admin = $this->drupalCreateUser(array('administer taxonomy'));
    $this->drupalLogin($taxonomy_admin);
  }

  /**
   * Test that hooks are run correctly on creating, editing and deleting a term.
   */
  function testTaxonomyTermHooks() {
    $vocabulary = $this->createVocabulary();

    // Create a term with one antonym.
    $edit = array(
      'name' => $this->randomName(),
      'antonym' => 'Long',
    );
    $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->machine_name . '/add', $edit, t('Save'));
    $terms = taxonomy_get_term_by_name($edit['name']);
    $term = reset($terms);
    $this->assertEqual($term->antonym, $edit['antonym'], t('Antonym was loaded into the term object'));

    // Update the term with a different antonym.
    $edit = array(
      'name' => $this->randomName(),
      'antonym' => 'Short',
    );
    $this->drupalPost('taxonomy/term/' . $term->tid . '/edit', $edit, t('Save'));
    taxonomy_terms_static_reset();
    $term = taxonomy_term_load($term->tid);
    $this->assertEqual($edit['antonym'], $term->antonym, t('Antonym was successfully edited'));

    // Delete the term.
    taxonomy_term_delete($term->tid);
    $antonym = db_query('SELECT tid FROM {taxonomy_term_antonym} WHERE tid = :tid', array(':tid' => $term->tid))->fetchField();
    $this->assertFalse($antonym, t('The antonym were deleted from the database.'));
  }
}

/**
 * Tests for taxonomy term field and formatter.
 */
class TaxonomyTermFieldTestCase extends TaxonomyWebTestCase {
  protected $instance;
  protected $vocabulary;

  public static function getInfo() {
    return array(
      'name'  => 'Taxonomy term reference field',
      'description'  => 'Test the creation of term fields.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp('field_test');

    $web_user = $this->drupalCreateUser(array('access field_test content', 'administer field_test content', 'administer taxonomy'));
    $this->drupalLogin($web_user);
    $this->vocabulary = $this->createVocabulary();
  }

  /**
   * Test term field validation.
   */
  function testTaxonomyTermFieldValidation() {
    $this->field_name = drupal_strtolower($this->randomName());

    // Create a field with settings to validate.
    $this->field = array(
      'field_name' => $this->field_name,
      'type' => 'taxonomy_term_reference',
      'settings' => array(
        'allowed_values' => array(
          array(
            'vid' => $this->vocabulary->vid,
            'parent' => '0',
          ),
        ),
      )
    );
    field_create_field($this->field);
    $this->instance = array(
      'field_name' => $this->field_name,
      'entity_type' => 'test_entity',
      'bundle' => 'test_bundle',
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
        'full' => array(
          'type' => 'taxonomy_term_reference_link',
        ),
      ),
    );
    field_create_instance($this->instance);

    // Test valid and invalid values with field_attach_validate().
    $langcode = LANGUAGE_NONE;
    $entity = field_test_create_stub_entity();
    $term = $this->createTerm($this->vocabulary);
    $entity->{$this->field_name}[$langcode][0]['tid'] = $term->tid;
    try {
      field_attach_validate('test_entity', $entity);
      $this->pass(t('Correct term does not cause validation error'));
    }
    catch (FieldValidationException $e) {
      $this->fail(t('Correct term does not cause validation error'));
    }

    $entity = field_test_create_stub_entity();
    $bad_term = $this->createTerm($this->createVocabulary());
    $entity->{$this->field_name}[$langcode][0]['tid'] = $bad_term->tid;
    try {
      field_attach_validate('test_entity', $entity);
      $this->fail(t('Wrong term causes validation error'));
    }
    catch (FieldValidationException $e) {
      $this->pass(t('Wrong term causes validation error'));
    }
  }

  /**
   * Test widgets.
   */
  function testTaxonomyTermFieldWidgets() {
    // Setup a field and instance.
    $entity_type = 'test_entity';
    $this->field_name = drupal_strtolower($this->randomName());
    $this->field = array(
      'field_name' => $this->field_name,
      'type' => 'taxonomy_term_reference',
      'settings' => array(
        'allowed_values' => array(
          array(
            'vid' => $this->vocabulary->vid,
            'parent' => '0',
          ),
        ),
      )
    );
    field_create_field($this->field);
    $this->instance = array(
      'field_name' => $this->field_name,
      'entity_type' => 'test_entity',
      'bundle' => 'test_bundle',
      'label' => $this->randomName() . '_label',
      'widget' => array(
        'type' => 'options_select',
      )
    );
    field_create_instance($this->instance);

    // Create a term in the vocabulary.
    $term = $this->createTerm($this->vocabulary);

    // Display creation form.
    $langcode = LANGUAGE_NONE;
    $this->drupalGet('test-entity/add/test-bundle');
    $this->assertFieldByName("{$this->field_name}[$langcode]", '', t('Widget is displayed'));

    // Submit with some value.
    $edit = array(
      "{$this->field_name}[$langcode]" => array($term->tid),
    );
    $this->drupalPost(NULL, $edit, t('Save'));
    preg_match('|test-entity/(\d+)/edit|', $this->url, $match);
    $id = $match[1];
    $this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), t('Entity was created'));

    // Display the object.
    $entity = field_test_entity_test_load($id);
    $entities = array($id => $entity);
    field_attach_prepare_view($entity_type, $entities, 'full');
    $entity->content = field_attach_view($entity_type, $entity);
    $this->content = drupal_render($entity->content);
    $this->assertText($term->name, t('Term name is displayed'));
  }
}

/**
 * Test taxonomy token replacement in strings.
 */
class TaxonomyTokenReplaceTestCase extends TaxonomyWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Taxonomy token replacement',
      'description' => 'Generates text using placeholders for dummy content to check taxonomy token replacement.',
      'group' => 'Taxonomy',
    );
  }

  function setUp() {
    parent::setUp();
    $this->admin_user = $this->drupalCreateUser(array('administer taxonomy', 'bypass node access'));
    $this->drupalLogin($this->admin_user);
    $this->vocabulary = $this->createVocabulary();
    $this->langcode = LANGUAGE_NONE;

    $field = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'type' => 'taxonomy_term_reference',
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'settings' => array(
        'allowed_values' => array(
          array(
            'vid' => $this->vocabulary->vid,
            'parent' => 0,
          ),
        ),
      ),
    );
    field_create_field($field);

    $this->instance = array(
      'field_name' => 'taxonomy_' . $this->vocabulary->machine_name,
      'bundle' => 'article',
      'entity_type' => 'node',
      'widget' => array(
        'type' => 'options_select',
      ),
      'display' => array(
        'full' => array(
          'type' => 'taxonomy_term_reference_link',
        ),
      ),
    );
    field_create_instance($this->instance);
  }

  /**
   * Creates some terms and a node, then tests the tokens generated from them.
   */
  function testTaxonomyTokenReplacement() {
    global $language;

    // Create two taxonomy terms.
    $term1 = $this->createTerm($this->vocabulary);
    $term2 = $this->createTerm($this->vocabulary);

    // Edit $term2, setting $term1 as parent.
    $edit = array();
    $edit['name'] = '<blink>Blinking Text</blink>';
    $edit['parent[]'] = array($term1->tid);
    $this->drupalPost('taxonomy/term/' . $term2->tid . '/edit', $edit, t('Save'));

    // Create node with term2.
    $edit = array();
    $node = $this->drupalCreateNode(array('type' => 'article'));
    $edit[$this->instance['field_name'] . '[' . $this->langcode . '][]'] = $term2->tid;
    $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save'));

    // Generate and test sanitized tokens.
    $tests = array();
    $tests['[term:tid]'] = $term2->tid;
    $tests['[term:vid]'] = $term2->vid;
    $tests['[term:name]'] = check_plain($term2->name);
    $tests['[term:description]'] = check_markup($term2->description, $term2->format);
    $tests['[term:url]'] = url('taxonomy/term/' . $term2->tid, array('absolute' => TRUE));
    $tests['[term:node-count]'] = 1;
    $tests['[term:parent:name]'] = check_plain($term1->name);
    $tests['[term:vocabulary:name]'] = check_plain($this->vocabulary->name);

    // Test to make sure that we generated something for each token.
    $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('term' => $term2), array('language' => $language));
      $this->assertFalse(strcmp($output, $expected), t('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
    }

    // Generate and test unsanitized tokens.
    $tests['[term:name]'] = $term2->name;
    $tests['[term:description]'] = $term2->description;
    $tests['[term:parent:name]'] = $term1->name;
    $tests['[term:vocabulary:name]'] = $this->vocabulary->name;

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('term' => $term2), array('language' => $language, 'sanitize' => FALSE));
      $this->assertFalse(strcmp($output, $expected), t('Unsanitized taxonomy term token %token replaced.', array('%token' => $input)));
    }

    // Generate and test sanitized tokens.
    $tests = array();
    $tests['[vocabulary:vid]'] = $this->vocabulary->vid;
    $tests['[vocabulary:name]'] = check_plain($this->vocabulary->name);
    $tests['[vocabulary:description]'] = filter_xss($this->vocabulary->description);
    $tests['[vocabulary:node-count]'] = 1;
    $tests['[vocabulary:term-count]'] = 2;

    // Test to make sure that we generated something for each token.
    $this->assertFalse(in_array(0, array_map('strlen', $tests)), t('No empty tokens generated.'));

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('language' => $language));
      $this->assertFalse(strcmp($output, $expected), t('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
    }

    // Generate and test unsanitized tokens.
    $tests['[vocabulary:name]'] = $this->vocabulary->name;
    $tests['[vocabulary:description]'] = $this->vocabulary->description;

    foreach ($tests as $input => $expected) {
      $output = token_replace($input, array('vocabulary' => $this->vocabulary), array('language' => $language, 'sanitize' => FALSE));
      $this->assertFalse(strcmp($output, $expected), t('Unsanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
    }
  }
}
