You are here

rules.test in Rules 7.2

Rules tests.

File

tests/rules.test
View source
<?php

/**
 * @file
 * Rules tests.
 */

/**
 * Rules test cases.
 */
class RulesTestCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Rules Engine tests',
      'description' => 'Test using the rules API to create and evaluate rules.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('rules', 'rules_test');
    RulesLog::logger()
      ->clear();
    variable_set('rules_debug_log', TRUE);
  }

  /**
   * Calculates the output of t() given an array of placeholders to replace.
   */
  public static function t($text, $strings) {
    $placeholders = array();
    foreach ($strings as $key => $string) {
      $key = !is_numeric($key) ? $key : $string;
      $placeholders['%' . $key] = drupal_placeholder($string);
    }
    return strtr($text, $placeholders);
  }

  /**
   * Helper function to create a test Rule.
   */
  protected function createTestRule() {
    $rule = rule();
    $rule
      ->condition('rules_test_condition_true')
      ->condition('rules_test_condition_true')
      ->condition(rules_or()
      ->condition(rules_condition('rules_test_condition_true')
      ->negate())
      ->condition('rules_test_condition_false')
      ->condition(rules_and()
      ->condition('rules_test_condition_false')
      ->condition('rules_test_condition_true')
      ->negate()));
    $rule
      ->action('rules_test_action');
    return $rule;
  }

  /**
   * Tests creating a rule and iterating over the rule elements.
   */
  public function testRuleCreation() {
    $rule = $this
      ->createTestRule();
    $rule
      ->integrityCheck();
    $rule
      ->execute();
    $log = RulesLog::logger()
      ->get();
    $last = array_pop($log);
    $last = array_pop($log);
    $last = array_pop($log);
    $this
      ->assertEqual($last[0], 'action called', 'Action called');
    RulesLog::logger()
      ->checkLog();

    // Make sure condition and action iterators are working.
    $it = new RecursiveIteratorIterator($rule
      ->conditions(), RecursiveIteratorIterator::SELF_FIRST);
    $this
      ->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers');
    $it = new RecursiveIteratorIterator($rule
      ->conditions());
    $this
      ->assertEqual(iterator_count($it), 6, 'Iterated over all conditions');
    $this
      ->assertEqual(iterator_count($rule
      ->actions()), 1, 'Iterated over all actions');
    $this
      ->assertEqual(iterator_count($rule
      ->elements()), 10, 'Iterated over all rule elements.');

    // Test getting dependencies and the integrity check.
    $rule
      ->integrityCheck();
    $this
      ->assertTrue($rule
      ->dependencies() === array(
      'rules_test',
    ), 'Dependencies correctly returned.');
  }

  /**
   * Tests handling dependencies.
   */
  public function testDependencies() {
    $action = rules_action('rules_node_publish_action');
    $this
      ->assertEqual($action
      ->dependencies(), array(
      'rules_test',
    ), 'Providing module is returned as dependency.');
    $container = new RulesTestContainer();
    $this
      ->assertEqual($container
      ->dependencies(), array(
      'rules_test',
    ), 'Providing module for container plugin is returned as dependency.');

    // Test handling unmet dependencies.
    $rule = rules_config_load('rules_export_test');
    $this
      ->assertTrue(in_array('comment', $rule->dependencies) && !$rule->dirty, 'Dependencies have been imported.');

    // Remove the required comment module and make sure the rule is dirty then.
    module_disable(array(
      'comment',
    ));
    rules_clear_cache();
    $rule = rules_config_load('rules_export_test');
    $this
      ->assertTrue($rule->dirty, 'Rule has been marked as dirty');

    // Now try re-enabling.
    module_enable(array(
      'comment',
    ));
    rules_clear_cache();
    $rule = rules_config_load('rules_export_test');
    $this
      ->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');

    // Test it with components.
    module_enable(array(
      'path',
    ));
    $action_set = rules_action_set(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $action_set
      ->action('node_path_alias');
    $action_set
      ->save('rules_test_alias');
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('component_rules_test_alias');
    $rule
      ->integrityCheck();
    $rule
      ->save('rules_test_rule');
    $rule = rules_config_load('rules_test_rule');
    $component = rules_config_load('rules_test_alias');
    $this
      ->assertTrue(in_array('path', $component->dependencies) && !$rule->dirty && !$component->dirty, 'Component has path module dependency.');

    // Now disable path module and make sure both configs are marked as dirty.
    module_disable(array(
      'path',
    ));
    rules_clear_cache();
    $rule = rules_config_load('rules_test_rule');
    $component = rules_config_load('rules_test_alias');
    $this
      ->assertTrue($component->dirty, 'Component has been marked as dirty');
    $node = $this
      ->drupalCreateNode();
    $result = rules_invoke_component('rules_test_alias', $node);
    $this
      ->assertTrue($result === FALSE, 'Unable to execute a dirty component.');

    // When the rule is evaluated, the broken component is detected and the
    // rule should be marked as dirty too.
    $rule
      ->execute($node);
    $this
      ->assertTrue($rule->dirty, 'Rule has been marked as dirty');
    module_enable(array(
      'path',
    ));
    rules_clear_cache();

    // Trigger rebuilding the cache, so configs are checked again.
    rules_get_cache();
    $rule = rules_config_load('rules_test_rule');
    $component = rules_config_load('rules_test_alias');
    $this
      ->assertTrue(!$component->dirty, 'Component has been marked as not dirty again.');
    $this
      ->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');
  }

  /**
   * Tests setting up an action, serializing, and executing it.
   */
  public function testActionSetup() {
    $action = rules_action('rules_node_publish_action');
    $s = serialize($action);
    $action2 = unserialize($s);
    $node = (object) array(
      'status' => 0,
      'type' => 'page',
    );
    $node->title = 'test';
    $action2
      ->execute($node);
    $this
      ->assertEqual($node->status, 1, 'Action executed correctly');
    $this
      ->assertTrue(in_array('node', array_keys($action2
      ->parameterInfo())), 'Parameter info returned.');
    $node->status = 0;
    $action2
      ->integrityCheck();
    $action2
      ->executeByArgs(array(
      'node' => $node,
    ));
    $this
      ->assertEqual($node->status, 1, 'Action executed correctly');

    // Test calling an extended + overridden method.
    $this
      ->assertEqual($action2
      ->help(), 'custom', 'Using custom help callback.');

    // Inspect the cache
    // $this->pass(serialize(rules_get_cache()));
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests executing with wrong arguments.
   */
  public function testActionExecutionFails() {
    $action = rules_action('rules_node_publish_action');
    try {
      $action
        ->execute();
      $this
        ->fail("Execution hasn't created an exception.");
    } catch (RulesEvaluationException $e) {
      $this
        ->pass("RulesEvaluationException was thrown: " . $e);
    }
  }

  /**
   * Tests setting up a rule and mapping variables.
   */
  public function testVariableMapping() {
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
      'node_unchanged' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition(rules_condition('rules_condition_content_is_published')
      ->negate())
      ->condition('rules_condition_content_is_type', array(
      'type' => array(
        'page',
        'story',
      ),
    ))
      ->action('rules_node_publish_action', array(
      'node:select' => 'node_unchanged',
    ));
    $node1 = $this
      ->drupalCreateNode(array(
      'status' => 0,
      'type' => 'page',
    ));
    $node2 = $this
      ->drupalCreateNode(array(
      'status' => 0,
      'type' => 'page',
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node1, $node2);
    $this
      ->assertEqual($node2->status, 1, 'Action executed correctly on node2.');
    $this
      ->assertEqual($node1->status, 0, 'Action not executed on node1.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests making use of class based actions.
   */
  public function testClassBasedActions() {
    $cache = rules_get_cache();
    $this
      ->assertTrue(!empty($cache['action_info']['rules_test_class_action']), 'Action has been discovered.');
    $action = rules_action('rules_test_class_action');
    $parameters = $action
      ->parameterInfo();
    $this
      ->assertTrue($parameters['node'], 'Action parameter needs a value.');
    $node = $this
      ->drupalCreateNode();
    $action
      ->execute($node);
    $log = RulesLog::logger()
      ->get();
    $last = array_pop($log);
    $last = array_pop($log);
    $this
      ->assertEqual($last[0], 'Action called with node ' . $node->nid, 'Action called');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests CRUD functionality.
   */
  public function testRulesCrud() {
    $rule = $this
      ->createTestRule();
    $rule
      ->integrityCheck()
      ->save('test');
    $this
      ->assertEqual(TRUE, $rule->active, 'Rule is active.');
    $this
      ->assertEqual(0, $rule->weight, 'Rule weight is zero.');
    $results = entity_load('rules_config', array(
      'test',
    ));
    $rule2 = array_pop($results);
    $this
      ->assertEqual($rule->id, $rule2->id, 'Rule created and loaded');
    $this
      ->assertEqual(get_class($rule2), get_class($rule), 'Class properly instantiated.');
    $rule2
      ->execute();

    // Update.
    $rule2
      ->save();

    // Make sure all rule elements are still here.
    $it = new RecursiveIteratorIterator($rule2
      ->conditions(), RecursiveIteratorIterator::SELF_FIRST);
    $this
      ->assertEqual(iterator_count($it), 8, 'Iterated over all conditions and condition containers');
    $it = new RecursiveIteratorIterator($rule2
      ->conditions());
    $this
      ->assertEqual(iterator_count($it), 6, 'Iterated over all conditions');
    $this
      ->assertEqual(iterator_count($rule2
      ->actions()), 1, 'Iterated over all actions');

    // Delete.
    $rule2
      ->delete();
    $this
      ->assertEqual(entity_load('rules_config', FALSE, array(
      'id' => $rule->id,
    )), array(), 'Deleted.');

    // Tests CRUD for tags - making sure the tags are stored properly..
    $rule = $this
      ->createTestRule();
    $tag = $this
      ->randomString();
    $rule->tags = array(
      $tag,
    );
    $rule
      ->save();
    $result = db_select('rules_tags')
      ->fields('rules_tags', array(
      'tag',
    ))
      ->condition('id', $rule->id)
      ->execute();
    $this
      ->assertEqual($result
      ->fetchField(), $tag, 'Associated tag has been saved.');

    // Try updating.
    $rule->tags = array(
      $this
        ->randomName(),
      $this
        ->randomName(),
    );
    $rule
      ->integrityCheck()
      ->save();
    $result = db_select('rules_tags')
      ->fields('rules_tags', array(
      'tag',
    ))
      ->condition('id', $rule->id)
      ->execute()
      ->fetchCol();
    $this
      ->assertTrue(in_array($rule->tags[0], $result) && in_array($rule->tags[1], $result), 'Updated associated tags.');

    // Try loading multiple rules by tags.
    $rule2 = $this
      ->createTestRule();
    $rule2->tags = array(
      $this
        ->randomName(),
    );
    $rule2
      ->save();
    $loaded = entity_load('rules_config', FALSE, array(
      'tags' => array(
        $rule->tags[0],
        $rule2->tags[0],
      ),
    ));
    $this
      ->assertTrue($loaded[$rule->id]->id == $rule->id && $loaded[$rule2->id]->id == $rule2->id, 'Loading configs by tags');

    // Try deleting.
    $rule
      ->delete();
    $result = db_select('rules_tags')
      ->fields('rules_tags', array(
      'tag',
    ))
      ->condition('id', $rule->id)
      ->execute();
    $this
      ->assertEqual($result
      ->fetchField(), FALSE, 'Deleted associated tags.');
  }

  /**
   * Tests automatic saving of variables.
   */
  public function testActionSaving() {

    // Test saving a parameter.
    $action = rules_action('rules_node_publish_action_save');
    $node = $this
      ->drupalCreateNode(array(
      'status' => 0,
      'type' => 'page',
    ));
    $action
      ->executeByArgs(array(
      'node' => $node,
    ));
    $this
      ->assertEqual($node->status, 1, 'Action executed correctly on node.');

    // Sync node_load cache with node_save.
    entity_get_controller('node')
      ->resetCache();
    $node = node_load($node->nid);
    $this
      ->assertEqual($node->status, 1, 'Node has been saved.');

    // Now test saving a provided variable, which is renamed and modified before
    // it is saved.
    $title = $this
      ->randomName();
    $rule = rule();
    $rule
      ->action('entity_create', array(
      'type' => 'node',
      'param_type' => 'article',
      'param_author:select' => 'site:current-user',
      'param_title' => $title,
      'entity_created:var' => 'node',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:body',
      'value' => array(
        'value' => 'body',
      ),
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute();
    $node = $this
      ->drupalGetNodeByTitle($title);
    $this
      ->assertTrue(!empty($node) && $node->body[LANGUAGE_NONE][0]['value'] == 'body', 'Saved a provided variable');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests adding a variable and optional parameters.
   */
  public function testVariableAdding() {
    $node = $this
      ->drupalCreateNode();
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule
      ->condition('rules_test_condition_true')
      ->action('rules_action_load_node')
      ->action('rules_action_delete_node', array(
      'node:select' => 'node_loaded',
    ))
      ->execute($node->nid);
    $this
      ->assertEqual(FALSE, node_load($node->nid), 'Variable added and skipped optional parameter.');
    RulesLog::logger()
      ->checkLog();
    $vars = $rule
      ->conditions()
      ->offsetGet(0)
      ->availableVariables();
    $this
      ->assertEqual(!isset($vars['node_loaded']), 'Loaded variable is not available to conditions.');

    // Test adding a variable with a custom variable name.
    $node = $this
      ->drupalCreateNode();
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule
      ->action('rules_action_load_node', array(
      'node_loaded:var' => 'node',
    ))
      ->action('rules_action_delete_node')
      ->execute($node->nid);
    $this
      ->assertEqual(FALSE, node_load($node->nid), 'Variable with custom name added.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests custom access for using component actions/conditions.
   */
  public function testRuleComponentAccess() {

    // Create a normal user.
    $normal_user = $this
      ->drupalCreateUser();

    // Create a role for granting access to the rule component.
    $this->normal_role = $this
      ->drupalCreateRole(array(), 'test_role');
    $normal_user->roles[$this->normal_role] = 'test_role';
    user_save($normal_user, array(
      'roles' => $normal_user->roles,
    ));

    // Create an 'action set' rule component making use of a permission.
    $action_set = rules_action_set(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $action_set->access_exposed = TRUE;
    $action_set
      ->save('rules_test_roles');

    // Set the global user to be the current one as access is checked for the
    // global user.
    global $user;
    $user = user_load($normal_user->uid);
    $this
      ->assertFalse(rules_action('component_rules_test_roles')
      ->access(), 'Authenticated user without the correct role can\'t use the rule component.');

    // Assign the role that will have permissions for the rule component.
    user_role_change_permissions($this->normal_role, array(
      'use Rules component rules_test_roles' => TRUE,
    ));
    $this
      ->assertTrue(rules_action('component_rules_test_roles')
      ->access(), 'Authenticated user with the correct role can use the rule component.');

    // Reset global user to anonymous.
    $user = user_load(0);
    $this
      ->assertFalse(rules_action('component_rules_test_roles')
      ->access(), 'Anonymous user can\'t use the rule component.');
  }

  /**
   * Tests passing arguments by reference to an action.
   */
  public function testPassingByReference() {

    // Keeping references of variables is unsupported, though the
    // EntityMetadataArrayObject may be used to achieve that.
    $array = array(
      'foo' => 'bar',
    );
    $data = new EntityMetadataArrayObject($array);
    rules_action('rules_action_test_reference')
      ->execute($data);
    $this
      ->assertTrue($data['changed'], 'Parameter has been passed by reference');
  }

  /**
   * Tests sorting rule elements.
   */
  public function testSorting() {
    $rule = $this
      ->createTestRule();
    $conditions = $rule
      ->conditions();
    $conditions[0]->weight = 10;
    $conditions[2]->weight = 10;
    $id[0] = $conditions[0]
      ->elementId();
    $id[1] = $conditions[1]
      ->elementId();
    $id[2] = $conditions[2]
      ->elementId();

    // For testing use a deep sort, even if not necessary here.
    $rule
      ->sortChildren(TRUE);
    $conditions = $rule
      ->conditions();
    $this
      ->assertEqual($conditions[0]
      ->elementId(), $id[1], 'Condition sorted correctly.');
    $this
      ->assertEqual($conditions[1]
      ->elementId(), $id[0], 'Condition sorted correctly.');
    $this
      ->assertEqual($conditions[2]
      ->elementId(), $id[2], 'Condition sorted correctly.');
  }

  /**
   * Tests using data selectors.
   */
  public function testDataSelectors() {
    $body[LANGUAGE_NONE][0] = array(
      'value' => '<b>The body & nothing.</b>',
    );
    $node = $this
      ->drupalCreateNode(array(
      'body' => $body,
      'type' => 'page',
      'summary' => '',
    ));
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule
      ->action('rules_action_load_node')
      ->action('drupal_message', array(
      'message:select' => 'node_loaded:body:value',
    ))
      ->execute($node->nid);
    RulesLog::logger()
      ->checkLog();
    $msg = drupal_get_messages('status');
    $last_msg = array_pop($msg['status']);
    $wrapper = entity_metadata_wrapper('node', $node);
    $this
      ->assertEqual($last_msg, $wrapper->body->value
      ->value(array(
      'sanitize' => TRUE,
    )), 'Data selector for getting parameter applied.');

    // Get a "reference" on the same object as returned by node_load().
    $node = node_load($node->nid);
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule
      ->action('rules_action_load_node')
      ->action('data_set', array(
      'data:select' => 'node_loaded:title',
      'value' => 'Test title',
    ))
      ->action('data_set', array(
      'data:select' => 'node_loaded:title',
      'value' => 'Test title2',
    ))
      ->execute($node->nid);
    $wrapper = entity_metadata_wrapper('node', $node);
    $this
      ->assertEqual('Test title2', $wrapper->title
      ->value(), 'Data has been modified and saved.');
    RulesLog::logger()
      ->checkLog();
    $text = RulesLog::logger()
      ->render();
    $msg = RulesTestCase::t('Saved %node_loaded of type %node.', array(
      'node_loaded',
      'node',
    ));
    if ($pos1 = strpos($text, $msg)) {
      $pos2 = strpos($text, $msg, $pos1 + 1);
    }
    $this
      ->assertTrue($pos1 && $pos2 === FALSE, 'Data has been saved only once.');

    // Test validation.
    try {
      rules_action('data_set', array(
        'data' => 'no-selector',
        'value' => '',
      ))
        ->integrityCheck();
      $this
        ->fail("Validation hasn't created an exception.");
    } catch (RulesIntegrityException $e) {
      $this
        ->pass("Validation error correctly detected: " . $e);
    }

    // Test auto creation of nested data structures, like the node body field.
    // I.e. if $node->body is not set, it is automatically initialized to an
    // empty array, so that the nested value can be set and the wrappers do not
    // complain about missing parent data structures.
    $rule = rule();
    $rule
      ->action('entity_create', array(
      'type' => 'node',
      'param_type' => 'page',
      'param_title' => 'foo',
      'param_author' => $GLOBALS['user'],
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'entity_created:body:value',
      'value' => 'test content',
    ))
      ->execute();
    try {
      RulesLog::logger()
        ->checkLog();
      $this
        ->pass('Auto creation of nested data structures.');
    } catch (Exception $e) {
      $this
        ->fail('Auto creation of nested data structures.');
    }

    // Make sure variables that are passed wrapped work.
    $result = rules_condition('rules_test_condition_node_wrapped')
      ->execute($node->nid);
    $this
      ->assertTrue($result, 'Condition receiving wrapped parameter.');

    // Make sure wrapped parameters are checked for containing NULL values.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
        'optional' => TRUE,
      ),
    ));
    $rule
      ->condition('rules_test_condition_node_wrapped', array(
      'node:select' => 'node',
    ));
    $rule
      ->execute(entity_metadata_wrapper('node'));
    $text = RulesLog::logger()
      ->render();
    $msg = RulesTestCase::t('The variable or parameter %node is empty.', array(
      'node',
    ));
    $this
      ->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
  }

  /**
   * Tests making use of rule sets.
   */
  public function testRuleSets() {
    $set = rules_rule_set(array(
      'node' => array(
        'type' => 'node',
        'label' => 'node',
      ),
    ));
    $set
      ->rule(rule()
      ->action('drupal_message', array(
      'message:select' => 'node:title',
    )))
      ->rule(rule()
      ->condition('rules_condition_content_is_published')
      ->action('drupal_message', array(
      'message' => 'Node is published.',
    )));
    $set
      ->integrityCheck()
      ->save('rules_test_set_1');
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'The title.',
      'status' => 1,
    ));

    // Execute.
    rules_invoke_component('rules_test_set_1', $node);
    $msg = drupal_get_messages();
    $this
      ->assertEqual($msg['status'][0], 'The title.', 'First rule evaluated.');
    $this
      ->assertEqual($msg['status'][1], 'Node is published.', 'Second rule evaluated.');

    // Test a condition set.
    $set = rules_or(array(
      'node' => array(
        'type' => 'node',
        'label' => 'node',
      ),
    ));
    $set
      ->condition('data_is', array(
      'data:select' => 'node:author:name',
      'value' => 'notthename',
    ))
      ->condition('data_is', array(
      'data:select' => 'node:nid',
      'value' => $node->nid,
    ))
      ->integrityCheck()
      ->save('test', 'rules_test');

    // Load and execute condition set.
    $set = rules_config_load('test');
    $this
      ->assertTrue($set
      ->execute($node), 'Set has been correctly evaluated.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests invoking components from the action.
   */
  public function testComponentInvocations() {
    $set = rules_rule_set(array(
      'node1' => array(
        'type' => 'node',
        'label' => 'node',
      ),
    ));
    $set
      ->rule(rule()
      ->condition('node_is_published', array(
      'node:select' => 'node1',
    ))
      ->action('node_unpublish', array(
      'node:select' => 'node1',
    )));
    $set
      ->integrityCheck()
      ->save('rules_test_set_2');

    // Use different names for the variables to ensure they are properly mapped
    // when taking over the variables to be saved.
    $rule = rule(array(
      'node2' => array(
        'type' => 'node',
        'label' => 'node',
      ),
    ));
    $rule
      ->action('component_rules_test_set_2', array(
      'node1:select' => 'node2',
    ));
    $rule
      ->action('node_make_sticky', array(
      'node:select' => 'node2',
    ));
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'The title.',
      'status' => 1,
      'sticky' => 0,
    ));
    $rule
      ->execute($node);
    $node = node_load($node->nid, NULL, TRUE);
    $this
      ->assertFalse($node->status, 'The component changes have been saved correctly.');
    $this
      ->assertTrue($node->sticky, 'The action changes have been saved correctly.');

    // Check that we have saved the changes only once.
    $text = RulesLog::logger()
      ->render();

    // Make sure both saves are handled in one save operation.
    $this
      ->assertEqual(substr_count($text, 'Saved'), 1, 'Changes have been saved in one save operation.');
    RulesLog::logger()
      ->checkLog();

    // Test recursion prevention on components by invoking the component from
    // itself, what should be prevented.
    $set
      ->action('component_rules_test_set_2', array(
      'node1:select' => 'node1',
    ))
      ->save();
    $rule
      ->execute($node);
    $text1 = RulesLog::logger()
      ->render();
    $text2 = RulesTestCase::t('Not evaluating rule set %rules_test_set_2 to prevent recursion.', array(
      'rules_test_set_2',
    ));
    $this
      ->assertTrue(strpos($text1, $text2) !== FALSE, "Recursion of component invocation prevented.");

    // Test executing the component provided in code via the action. This makes
    // sure the component in code has been properly picked up.
    $node->status = 0;
    node_save($node);
    rules_action('component_rules_test_action_set')
      ->execute($node);
    $this
      ->assertTrue($node->status == 1, 'Component provided in code has been executed.');
  }

  /**
   * Tests asserting metadata.
   *
   * Customizes action info and makes sure integrity is checked.
   */
  public function testMetadataAssertion() {
    $action = rules_action('rules_node_make_sticky_action');

    // Test failing integrity check.
    try {
      $rule = rule(array(
        'node' => array(
          'type' => 'entity',
        ),
      ));
      $rule
        ->action($action);

      // Fails due to the 'node' variable not matching the node type.
      $rule
        ->integrityCheck();
      $this
        ->fail('Integrity check has not thrown an exception.');
    } catch (RulesIntegrityException $e) {
      $this
        ->pass('Integrity check has thrown exception: ' . $e
        ->getMessage());
    }

    // Test asserting additional metadata.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));

    // Customize action info using the settings.
    $rule
      ->condition('data_is', array(
      'data:select' => 'node:type',
      'value' => 'page',
    ))
      ->condition(rules_condition('data_is', array(
      'data:select' => 'node:body:value',
      'value' => 'foo',
    ))
      ->negate())
      ->action($action);

    // Make sure the integrity check doesn't throw an exception.
    $rule
      ->integrityCheck();

    // Test the rule.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
    ));
    $rule
      ->execute($node);
    $this
      ->assertTrue($node->sticky, 'Rule with asserted metadata executed.');

    // Test asserting metadata on a derived property, i.e. not a variable.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('entity_is_of_type', array(
      'entity:select' => 'node:reference',
      'type' => 'node',
    ))
      ->condition('data_is', array(
      'data:select' => 'node:reference:type',
      'value' => 'page',
    ))
      ->action('rules_node_page_make_sticky_action', array(
      'node:select' => 'node:reference',
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);

    // Test asserting an entity field.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('entity_has_field', array(
      'entity:select' => 'node:reference',
      'field' => 'field_tags',
    ))
      ->action('data_set', array(
      'data:select' => 'node:reference:field-tags',
      'value' => array(),
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);

    // Make sure an asserted bundle can be used as argument.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('entity_is_of_type', array(
      'entity:select' => 'node:reference',
      'type' => 'node',
    ))
      ->condition('node_is_of_type', array(
      'node:select' => 'node:reference',
      'type' => array(
        'page',
      ),
    ))
      ->action('rules_node_page_make_sticky_action', array(
      'node:select' => 'node:reference',
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);

    // Test asserting metadata on a derived property being a list item.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('node_is_of_type', array(
      'node:select' => 'node:ref-nodes:0',
      'type' => array(
        'article',
      ),
    ))
      ->action('data_set', array(
      'data:select' => 'node:ref-nodes:0:field-tags',
      'value' => array(),
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);

    // Give green lights if there were no exceptions and check rules-log errors.
    $this
      ->pass('Rules asserting metadata on a derived property pass integrity checks.');
    RulesLog::logger()
      ->checkLog();

    // Make sure assertions of a one list item are not valid for another item.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('node_is_of_type', array(
      'node:select' => 'node:ref-nodes:0',
      'type' => array(
        'article',
      ),
    ))
      ->action('data_set', array(
      'data:select' => 'node:ref-nodes:1:field-tags',
      'value' => array(),
    ));
    try {
      $rule
        ->integrityCheck();
      $this
        ->fail('Assertion of a list item is not valid for another item.');
    } catch (RulesException $e) {
      $this
        ->pass('Assertion of a list item is not valid for another item.');
    }
  }

  /**
   * Tests using loops.
   */
  public function testLoops() {

    // Test passing the list parameter as argument to ensure that is working
    // generally for plugin container too.
    drupal_get_messages(NULL, TRUE);
    $loop = rules_loop();
    $loop
      ->action('drupal_message', array(
      'message' => 'test',
    ));
    $arg_info = $loop
      ->parameterInfo();
    $this
      ->assert($arg_info['list']['type'] == 'list', 'Argument info contains list.');
    $loop
      ->execute(array(
      1,
      2,
    ));

    // Ensure the action has been executed twice, once for each list item.
    $msg = drupal_get_messages();
    $this
      ->assert($msg['status'][0] == 'test' && $msg['status'][1], 'Loop has been properly executed');

    // Now test looping over nodes.
    $node1 = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
    ));
    $node2 = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
    ));
    $node3 = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
    ));
    $rule = rule(array(
      'list' => array(
        'type' => 'list<node>',
        'label' => 'A list of nodes',
      ),
    ));
    $loop = rules_loop(array(
      'list:select' => 'list',
      'item:var' => 'node',
    ));
    $loop
      ->action('data_set', array(
      'data:select' => 'node:sticky',
      'value' => TRUE,
    ));
    $rule
      ->action($loop);

    // Test using a list with data selectors, just output the last nodes type.
    $rule
      ->action('drupal_message', array(
      'message:select' => 'list:2:type',
    ));
    $rule
      ->execute(array(
      $node1->nid,
      $node2->nid,
      $node3->nid,
    ));
    $text = RulesLog::logger()
      ->render();
    $save_msg = RulesTestCase::t('Saved %node of type %node.', array(
      'node',
      'node',
    ));
    $this
      ->assertTrue(substr_count($text, $save_msg) == 3, 'List item variables have been saved.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests access checks.
   */
  public function testAccessCheck() {
    $rule = rule();

    // Try to set a property which is provided by the test module and is not
    // accessible, so the access check has to return FALSE.
    $rule
      ->action('data_set', array(
      'data:select' => 'site:no-access-user',
      'value' => 'foo',
    ));
    $this
      ->assertTrue($rule
      ->access() === FALSE, 'Access check is working.');
  }

  /**
   * Tests returning provided variables.
   */
  public function testReturningVariables() {
    $node = $this
      ->drupalCreateNode();
    $action = rules_action('entity_fetch', array(
      'type' => 'node',
      'id' => $node->nid,
    ));
    list($node2) = $action
      ->execute();
    $this
      ->assertTrue($node2->nid == $node->nid, 'Action returned a variable.');

    // Create a simple set that just passed through the given node.
    $set = rules_rule_set(array(
      'node' => array(
        'type' => 'node',
      ),
    ), array(
      'node',
    ));
    $set
      ->integrityCheck()
      ->save('rules_test_set_1');
    $provides = $set
      ->providesVariables();
    $this
      ->assertTrue($provides['node']['type'] == 'node', 'Rule set correctly passed through the node.');
    list($node2) = $set
      ->execute($node);
    $this
      ->assertTrue($node2->nid == $node->nid, 'Rule set returned a variable.');

    // Create an action set returning a variable that is no parameter.
    $set = rules_action_set(array(
      'node' => array(
        'type' => 'node',
        'parameter' => FALSE,
      ),
    ), array(
      'node',
    ));
    $set
      ->action('entity_fetch', array(
      'type' => 'node',
      'id' => $node->nid,
    ))
      ->action('data_set', array(
      'data:select' => 'node',
      'value:select' => 'entity_fetched',
    ));
    $set
      ->integrityCheck();
    list($node3) = $set
      ->execute();
    $this
      ->assertTrue($node3->nid == $node->nid, 'Action set returned a variable that has not been passed as parameter.');

    // Test the same again with a variable holding a not wrapped data type.
    $set = rules_action_set(array(
      'number' => array(
        'type' => 'integer',
        'parameter' => FALSE,
      ),
    ), array(
      'number',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'number',
      'value' => 3,
    ));
    $set
      ->integrityCheck();
    list($number) = $set
      ->execute();
    $this
      ->assertTrue($number == 3, 'Actions set returned a number.');
  }

  /**
   * Tests using input evaluators.
   */
  public function testInputEvaluators() {
    $node = $this
      ->drupalCreateNode(array(
      'title' => '<b>The body & nothing.</b>',
      'type' => 'page',
    ));
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule
      ->action('rules_action_load_node')
      ->action('drupal_message', array(
      'message' => 'Title: [node_loaded:title]',
    ))
      ->execute($node->nid);
    RulesLog::logger()
      ->checkLog();
    $msg = drupal_get_messages();
    $this
      ->assertEqual(array_pop($msg['status']), 'Title: ' . check_plain('<b>The body & nothing.</b>'), 'Token input evaluator applied.');

    // Test token replacements on a list of text values.
    $component = rules_action_set(array(
      'var' => array(
        'type' => 'list<text>',
        'label' => 'var',
      ),
    ), array(
      'var',
    ));
    $component
      ->save('rules_test_input');
    $action = rules_action('component_rules_test_input', array(
      'var' => array(
        'uid: [site:current-user:uid]',
      ),
    ));
    list($var) = $action
      ->execute();
    $uid = $GLOBALS['user']->uid;
    $this
      ->assertEqual(array(
      "uid: {$uid}",
    ), $var, 'Token replacements on a list of values applied.');
  }

  /**
   * Tests importing and exporting a rule.
   */
  public function testRuleImportExport() {
    $rule = rule(array(
      'nid' => array(
        'type' => 'integer',
      ),
    ));
    $rule->name = "rules_export_test";
    $rule
      ->action('rules_action_load_node')
      ->action('drupal_message', array(
      'message' => 'Title: [node_loaded:title]',
    ));
    $export = '{ "rules_export_test" : {
    "PLUGIN" : "rule",
    "REQUIRES" : [ "rules_test", "rules" ],
    "USES VARIABLES" : { "nid" : { "type" : "integer" } },
    "DO" : [
      { "rules_action_load_node" : { "PROVIDE" : { "node_loaded" : { "node_loaded" : "Loaded content" } } } },
      { "drupal_message" : { "message" : "Title: [node_loaded:title]" } }
    ]
  }
}';
    $this
      ->assertEqual($export, $rule
      ->export(), 'Rule has been exported correctly.');

    // Test importing a rule which makes use of almost all features.
    $export = _rules_export_get_test_export();
    $rule = rules_import($export);
    $this
      ->assertTrue(!empty($rule) && $rule
      ->integrityCheck(), 'Rule has been imported.');

    // Test loading the same export provided as default rule.
    $rule = rules_config_load('rules_export_test');
    $this
      ->assertTrue(!empty($rule) && $rule
      ->integrityCheck(), 'Export has been provided in code.');

    // Export it and make sure the same export is generated again.
    $this
      ->assertEqual($export, $rule
      ->export(), 'Export of imported rule equals original export.');

    // Now try importing a rule set.
    $export = '{ "rules_test_set" : {
    "LABEL" : "Test set",
    "PLUGIN" : "rule set",
    "REQUIRES" : [ "rules" ],
    "USES VARIABLES" : { "node" : { "label" : "Test node", "type" : "node" } },
    "RULES" : [
      { "RULE" : {
          "IF" : [ { "NOT data_is" : { "data" : [ "node:title" ], "value" : "test" } } ],
          "DO" : [ { "data_set" : { "data" : [ "node:title" ], "value" : "test" } } ],
          "LABEL" : "Test Rule"
        }
      },
      { "RULE" : {
          "DO" : [ { "drupal_message" : { "message" : "hi" } } ],
          "LABEL" : "Test Rule 2"
        }
      }
    ]
  }
}';
    $set = rules_import($export);
    $this
      ->assertTrue(!empty($set) && $set
      ->integrityCheck(), 'Rule set has been imported.');

    // Export it and make sure the same export is generated again.
    $this
      ->assertEqual($export, $set
      ->export(), 'Export of imported rule set equals original export.');

    // Try executing the imported rule set.
    $node = $this
      ->drupalCreateNode();
    $set
      ->execute($node);
    $this
      ->assertEqual($node->title, 'test', 'Imported rule set has been executed.');
    RulesLog::logger()
      ->checkLog();

    // Try import / export for a rule component providing a variable.
    $rule = rule(array(
      'number' => array(
        'type' => 'integer',
        'label' => 'Number',
        'parameter' => FALSE,
      ),
    ), array(
      'number',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'number',
      'value' => 3,
    ));
    $rule->name = 'rules_test_provides';
    $export = '{ "rules_test_provides" : {
    "PLUGIN" : "rule",
    "REQUIRES" : [ "rules" ],
    "USES VARIABLES" : { "number" : { "type" : "integer", "label" : "Number", "parameter" : false } },
    "DO" : [ { "data_set" : { "data" : [ "number" ], "value" : 3 } } ],
    "PROVIDES VARIABLES" : [ "number" ]
  }
}';
    $this
      ->assertEqual($export, $rule
      ->export(), 'Rule 2 has been exported correctly.');
    $imported_rule = rules_import($rule
      ->export());
    $this
      ->assertTrue(!empty($imported_rule) && $imported_rule
      ->integrityCheck(), 'Rule 2 has been imported.');
    $this
      ->assertEqual($export, $imported_rule
      ->export(), 'Export of imported rule 2 equals original export.');

    // Test importing a negated condition component.
    $export = '{ "rules_negated_component" : {
    "LABEL" : "negated_component",
    "PLUGIN" : "or",
    "REQUIRES" : [ "rules" ],
    "NOT OR" : [ { "data_is_empty" : { "data" : [ "site:slogan" ] } } ]
  }
}';
    $or = rules_import($export);
    $this
      ->assertTrue($or
      ->integrityCheck() && $or
      ->isNegated(), 'Negated condition component imported.');
  }

  /**
   * Tests the named parameter mode.
   */
  public function testNamedParameters() {
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('rules_action_node_set_title', array(
      'title' => 'foo',
    ));
    $rule
      ->integrityCheck();

    // Test the rule.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
    ));
    $rule
      ->execute($node);
    $this
      ->assertTrue($node->title == 'foo', 'Action with named parameters has been correctly executed.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Makes sure Rules aborts when NULL values are used.
   */
  public function testAbortOnNULLValues() {
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('drupal_message', array(
      'message:select' => 'node:log',
    ));
    $rule
      ->integrityCheck();

    // Test the rule.
    $node = $this
      ->drupalCreateNode();
    $node->log = NULL;
    $rule
      ->execute($node);
    $text = RulesLog::logger()
      ->render();
    $msg = RulesTestCase::t('The variable or parameter %message is empty.', array(
      'message',
    ));
    $this
      ->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
  }

}

/**
 * Test rules data wrappers.
 */
class RulesTestDataCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Rules Data tests',
      'description' => 'Tests rules data saving and type matching.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('rules', 'rules_test');
    variable_set('rules_debug_log', TRUE);

    // Make sure we don't ran over issues with the node_load static cache.
    entity_get_controller('node')
      ->resetCache();
  }

  /**
   * Tests intelligently saving data.
   */
  public function testDataSaving() {
    $node = $this
      ->drupalCreateNode();
    $state = new RulesState(rule());
    $state
      ->addVariable('node', $node, array(
      'type' => 'node',
    ));
    $wrapper = $state
      ->get('node');
    $node->title = 'test';
    $wrapper
      ->set($node);
    $state
      ->saveChanges('node', $wrapper, FALSE);
    $this
      ->assertFalse($this
      ->drupalGetNodeByTitle('test'), 'Changes have not been saved.');
    $state
      ->saveChanges('node', $wrapper, TRUE);
    $this
      ->assertTrue($this
      ->drupalGetNodeByTitle('test'), 'Changes have been saved.');

    // Test skipping saving.
    $state
      ->addVariable('node2', $node, array(
      'type' => 'node',
      'skip save' => TRUE,
    ));
    $wrapper = $state
      ->get('node2');
    $node->title = 'test2';
    $wrapper
      ->set($node);
    $state
      ->saveChanges('node2', $wrapper, TRUE);
    $this
      ->assertFalse($this
      ->drupalGetNodeByTitle('test2'), 'Changes have not been saved.');

    // Try saving a non-entity wrapper, which should result in saving the
    // parent entity containing the property.
    $wrapper = $state
      ->get('node');
    $wrapper->title
      ->set('test3');
    $state
      ->saveChanges('node:title', $wrapper, TRUE);
    $this
      ->assertTrue($this
      ->drupalGetNodeByTitle('test3'), 'Parent entity has been saved.');
  }

  /**
   * Tests type matching.
   */
  public function testTypeMatching() {
    $entity = array(
      'type' => 'entity',
    );
    $node = array(
      'type' => 'node',
    );
    $this
      ->assertTrue(RulesData::typesMatch($node, $entity), 'Types match.');
    $this
      ->assertFalse(RulesData::typesMatch($entity, $node), 'Types don\'t match.');
    $this
      ->assertTrue(RulesData::typesMatch($node + array(
      'bundle' => 'page',
    ), $node), 'Types match.');
    $this
      ->assertTrue(RulesData::typesMatch($node + array(
      'bundle' => 'page',
    ), $entity), 'Types match.');
    $this
      ->assertTrue(RulesData::typesMatch(array(
      'type' => 'list<node>',
    ), array(
      'type' => 'list',
    )), 'Types match.');
    $this
      ->assertTrue(RulesData::typesMatch($node + array(
      'bundle' => 'page',
    ), $node + array(
      'bundles' => array(
        'page',
        'story',
      ),
    )), 'Types match.');
    $this
      ->assertFalse(RulesData::typesMatch($node, $node + array(
      'bundles' => array(
        'page',
        'story',
      ),
    )), 'Types don\'t match.');

    // Test that a type matches its grand-parent type (text > decimal > integer)
    $this
      ->assertTrue(RulesData::typesMatch(array(
      'type' => 'integer',
    ), array(
      'type' => 'text',
    )), 'Types match.');
    $this
      ->assertFalse(RulesData::typesMatch(array(
      'type' => 'text',
    ), array(
      'type' => 'integer',
    )), 'Types don\'t match.');
  }

  /**
   * Tests making use of custom wrapper classes.
   */
  public function testCustomWrapperClasses() {

    // Test loading a vocabulary by name, which is done by a custom wrapper.
    $set = rules_action_set(array(
      'vocab' => array(
        'type' => 'taxonomy_vocabulary',
      ),
    ), array(
      'vocab',
    ));
    $set
      ->action('drupal_message', array(
      'message:select' => 'vocab:name',
    ));
    $set
      ->integrityCheck();
    list($vocab) = $set
      ->execute('tags');
    $this
      ->assertTrue($vocab->machine_name == 'tags', 'Loaded vocabulary by name.');

    // Now test wrapper creation for a direct input argument value.
    $set = rules_action_set(array(
      'term' => array(
        'type' => 'taxonomy_term',
      ),
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'term:vocabulary',
      'value' => 'tags',
    ));
    $set
      ->integrityCheck();
    $vocab = entity_create('taxonomy_vocabulary', array(
      'name' => 'foo',
      'machine_name' => 'foo',
    ));
    entity_save('taxonomy_vocabulary', $vocab);
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => $vocab,
    ))
      ->save();
    $set
      ->execute($term_wrapped);
    $this
      ->assertEqual($term_wrapped->vocabulary->machine_name
      ->value(), 'tags', 'Vocabulary name used as direct input value.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Makes sure the RulesIdentifiableDataWrapper is working correctly.
   */
  public function testRulesIdentifiableDataWrapper() {
    $node = $this
      ->drupalCreateNode();
    $wrapper = new RulesTestTypeWrapper('rules_test_type', $node);
    $this
      ->assertTrue($wrapper
      ->value() == $node, 'Data correctly wrapped.');

    // Test serializing and make sure only the id is stored.
    $this
      ->assertTrue(strpos(serialize($wrapper), $node->title) === FALSE, 'Data has been correctly serialized.');
    $this
      ->assertEqual(unserialize(serialize($wrapper))
      ->value()->title, $node->title, 'Serializing works right.');
    $wrapper2 = unserialize(serialize($wrapper));

    // Test serializing the unloaded wrapper.
    $this
      ->assertEqual(unserialize(serialize($wrapper2))
      ->value()->title, $node->title, 'Serializing works right.');

    // Test loading a not more existing node.
    $s = serialize($wrapper2);
    node_delete($node->nid);
    $this
      ->assertFalse(node_load($node->nid), 'Node deleted.');
    try {
      unserialize($s)
        ->value();
      $this
        ->fail("Loading hasn't created an exception.");
    } catch (EntityMetadataWrapperException $e) {
      $this
        ->pass("Exception was thrown: " . $e
        ->getMessage());
    }

    // Test saving a savable custom, identifiable wrapper.
    $action = rules_action('test_type_save');
    $node = $this
      ->drupalCreateNode(array(
      'status' => 0,
      'type' => 'page',
    ));
    $node->status = 1;
    $action
      ->execute($node);

    // Load the node fresh from the db.
    $node = node_load($node->nid, NULL, TRUE);
    $this
      ->assertEqual($node->status, 1, 'Savable non-entity has been saved.');
  }

}

/**
 * Test triggering rules.
 */
class RulesTriggerTestCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Reaction Rules',
      'description' => 'Tests triggering reactive rules.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('rules', 'rules_test');
    RulesLog::logger()
      ->clear();
    variable_set('rules_debug_log', TRUE);
  }

  /**
   * Helper function to create a test Rule.
   */
  protected function createTestRule($action = TRUE, $event = 'node_presave') {
    $rule = rules_reaction_rule();
    $rule
      ->event($event)
      ->condition(rules_condition('data_is', array(
      'data:select' => 'node:status',
      'value' => TRUE,
    ))
      ->negate())
      ->condition('data_is', array(
      'data:select' => 'node:type',
      'value' => 'page',
    ));
    if ($action) {
      $rule
        ->action('rules_action_delete_node');
    }
    return $rule;
  }

  /**
   * Tests CRUD for reaction rules - making sure the events are stored properly.
   */
  public function testReactiveRuleCreation() {
    $rule = $this
      ->createTestRule();
    $rule
      ->save();
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(
      ':id' => $rule->id,
    ));
    $this
      ->assertEqual($result
      ->fetchField(), 'node_presave', 'Associated event has been saved.');

    // Try updating.
    $rule
      ->removeEvent('node_presave');
    $rule
      ->event('node_insert');
    $rule
      ->event('node_update');
    $rule->active = FALSE;
    $rule
      ->integrityCheck()
      ->save();
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(
      ':id' => $rule->id,
    ));
    $this
      ->assertEqual($result
      ->fetchCol(), array_values($rule
      ->events()), 'Updated associated events.');

    // Try deleting.
    $rule
      ->delete();
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(
      ':id' => $rule->id,
    ));
    $this
      ->assertEqual($result
      ->fetchField(), FALSE, 'Deleted associated events.');
  }

  /**
   * Tests creating and triggering a basic reaction rule.
   */
  public function testBasicReactionRule() {
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
    ));
    $rule = $this
      ->createTestRule();
    $rule
      ->integrityCheck()
      ->save();

    // Test the basics of the event set work right.
    $event = rules_get_cache('event_node_presave');
    $this
      ->assertEqual(array_keys($event
      ->parameterInfo()), array(
      'node',
    ), 'EventSet returns correct argument info.');

    // Trigger the rule by updating the node.
    $nid = $node->nid;
    $node->status = 0;
    node_save($node);
    RulesLog::logger()
      ->checkLog();
    $this
      ->assertFalse(node_load($nid), 'Rule successfully triggered and executed');

    // debug(RulesLog::logger()->render());
  }

  /**
   * Tests a rule using a handler to load a variable.
   */
  public function testVariableHandler() {
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));
    $rule = $this
      ->createTestRule(FALSE, 'node_update');
    $rule
      ->action('rules_node_publish_action_save', array(
      'node:select' => 'node_unchanged',
    ));

    // Test without recursion prevention to make sure recursive invocations
    // work right too. This rule won't ran in an infinite loop anyway.
    $rule->recursion = TRUE;
    $rule->label = 'rule 1';
    $rule
      ->integrityCheck()
      ->save();
    $node->status = 0;
    $node->sticky = 1;
    node_save($node);
    RulesLog::logger()
      ->checkLog();
    entity_get_controller('node')
      ->resetCache();
    $node = node_load($node->nid);
    $this
      ->assertFalse($node->sticky, 'Parameter has been loaded and saved.');
    $this
      ->assertTrue($node->status, 'Action has been executed.');

    // Ensure the rule was evaluated a second time.
    $text = RulesLog::logger()
      ->render();
    $msg = RulesTestCase::t('Evaluating conditions of rule %rule 1', array(
      'rule 1',
    ));
    $pos = strpos($text, $msg);
    $pos = $pos !== FALSE ? strpos($text, $msg, $pos) : FALSE;
    $this
      ->assertTrue($pos !== FALSE, "Recursion prevented.");

    // debug(RulesLog::logger()->render());
  }

  /**
   * Tests aborting silently when handlers are not able to load.
   */
  public function testVariableHandlerFailing() {
    $rule = $this
      ->createTestRule(FALSE, 'node_presave');
    $rule
      ->action('rules_node_publish_action_save', array(
      'node:select' => 'node_unchanged',
    ));
    $rule
      ->integrityCheck()
      ->save();

    // On insert it's not possible to get the unchanged node during presave.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));

    // debug(RulesLog::logger()->render());
    $text = RulesTestCase::t('Unable to load variable %node_unchanged, aborting.', array(
      'node_unchanged',
    ));
    $this
      ->assertTrue(strpos(RulesLog::logger()
      ->render(), $text) !== FALSE, "Aborted evaluation.");
  }

  /**
   * Tests preventing recursive rule invocations.
   *
   * Creates a rule that reacts on node-update then generates a node update
   * that would trigger it itself.
   */
  public function testRecursionPrevention() {
    $rule = $this
      ->createTestRule(FALSE, 'node_update');
    $rule
      ->action('rules_node_make_sticky_action');
    $rule
      ->integrityCheck()
      ->save();

    // Now trigger the rule.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));
    node_save($node);
    $text = RulesTestCase::t('Not evaluating reaction rule %label to prevent recursion.', array(
      'label' => $rule->name,
    ));

    // debug(RulesLog::logger()->render());
    $this
      ->assertTrue(strpos(RulesLog::logger()
      ->render(), $text) !== FALSE, "Recursion prevented.");

    // debug(RulesLog::logger()->render());
  }

  /**
   * Tests recursion prevention with altered arguments.
   *
   * Ensure the recursion prevention still allows the rule to trigger again
   * during evaluation of the same event set, if the event isn't caused by the
   * rule itself - thus we won't run in an infinite loop.
   */
  public function testRecursionOnDifferentArguments() {

    // Create rule1 - which might recurse.
    $rule = $this
      ->createTestRule(FALSE, 'node_update');
    $rule
      ->action('rules_node_make_sticky_action');
    $rule->label = 'rule 1';
    $rule
      ->integrityCheck()
      ->save();

    // Create rule2 - which triggers rule1 on another node.
    $node2 = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));
    $rule2 = $this
      ->createTestRule(FALSE, 'node_update');
    $rule2
      ->action('rules_action_load_node', array(
      'nid' => $node2->nid,
    ))
      ->action('rules_node_make_sticky_action', array(
      'node:select' => 'node_loaded',
    ));
    $rule2->label = 'rule 2';
    $rule2
      ->save();

    // Now trigger both rules by generating the event.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));
    node_save($node);

    // debug(RulesLog::logger()->render());
    $text = RulesLog::logger()
      ->render();
    $pos = strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array(
      'rule 1',
    )));
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 2', array(
      'rule 2',
    )), $pos) : FALSE;
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Saved %node_loaded of type %node.', array(
      'node_loaded',
      'node',
    )), $pos) : FALSE;
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Evaluating conditions of rule %rule 1', array(
      'rule 1',
    )), $pos) : FALSE;
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Not evaluating reaction rule %rule 2 to prevent recursion', array(
      'rule 2',
    )), $pos) : FALSE;
    $this
      ->assertTrue($pos !== FALSE, 'Rule1 was triggered on the event caused by Rule2.');
  }

  /**
   * Tests the provided default rule 'rules_test_default_1'.
   */
  public function testDefaultRule() {
    $rule = rules_config_load('rules_test_default_1');
    $this
      ->assertTrue($rule->status & ENTITY_IN_CODE && !($rule->status & ENTITY_IN_DB), 'Default rule can be loaded and has the right status.');
    $this
      ->assertTrue($rule->tags == array(
      'Admin',
      'Tag2',
    ), 'Default rule has correct tags.');

    // Enable.
    $rule->active = TRUE;
    $rule
      ->save();

    // Create a node that triggers the rule.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));

    // Clear messages.
    drupal_get_messages();

    // Let event node_update occur.
    node_save($node);
    $msg = drupal_get_messages();
    $this
      ->assertEqual($msg['status'][0], 'A node has been updated.', 'Default rule has been triggered.');
  }

  /**
   * Tests creating and triggering a reaction rule with event settings.
   */
  public function testEventSettings() {
    $rule = rules_reaction_rule();
    $rule
      ->event('node_presave', array(
      'bundle' => 'article',
    ))
      ->condition('data_is_empty', array(
      'data:select' => 'node:field-tags',
    ))
      ->action('node_publish', array(
      'node:select' => 'node',
    ));
    $rule
      ->integrityCheck()
      ->save();
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'status' => 0,
    ));
    $this
      ->assertEqual($node->status, 0, 'Rule has not been triggered.');
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'article',
      'status' => 0,
    ));
    $this
      ->assertEqual($node->status, 1, 'Rule has been triggered.');
    RulesLog::logger()
      ->checkLog();

    // Make sure an invalid bundle raises integrity problems.
    $rule
      ->event('node_presave', array(
      'bundle' => 'invalid',
    ));
    try {
      $rule
        ->integrityCheck();
      $this
        ->fail('Integrity check failed.');
    } catch (RulesIntegrityException $e) {
      $this
        ->pass('Integrity check failed: ' . $e);
    }
  }

}

/**
 * Tests provided module integration.
 */
class RulesIntegrationTestCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Rules Core Integration',
      'description' => 'Tests provided integration for drupal core.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('rules', 'rules_test', 'php', 'path');
    RulesLog::logger()
      ->clear();
    variable_set('rules_debug_log', TRUE);
  }

  /**
   * Just makes sure the access callback run without errors.
   */
  public function testAccessCallbacks() {
    $cache = rules_get_cache();
    foreach (array(
      'action',
      'condition',
      'event',
    ) as $type) {
      foreach (rules_fetch_data($type . '_info') as $name => $info) {
        if (isset($info['access callback'])) {
          $info['access callback']($type, $name);
        }
      }
    }
  }

  /**
   * Tests data integration.
   */
  public function testDataIntegration() {

    // Test data_create action.
    $action = rules_action('data_create', array(
      'type' => 'log_entry',
      'param_type' => 'rules_test',
      'param_message' => 'Rules test log message',
      'param_severity' => WATCHDOG_WARNING,
      'param_request_uri' => 'http://example.com',
      'param_link' => '',
    ));
    $action
      ->access();
    $action
      ->execute();
    $text = RulesLog::logger()
      ->render();
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %data_created of type %log_entry', array(
      'data_created',
      'log_entry',
    )));
    $this
      ->assertTrue($pos !== FALSE, 'Data of type log entry has been created.');

    // Test variable_add action.
    $action = rules_action('variable_add', array(
      'type' => 'text_formatted',
      'value' => array(
        'value' => 'test text',
        'format' => 1,
      ),
    ));
    $action
      ->access();
    $action
      ->execute();
    $text = RulesLog::logger()
      ->render();
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %variable_added of type %text_formatted', array(
      'variable_added',
      'text_formatted',
    )));
    $this
      ->assertTrue($pos !== FALSE, 'Data of type text formatted has been created.');

    // Test using the list actions.
    $rule = rule(array(
      'list' => array(
        'type' => 'list<text>',
        'label' => 'A list of text',
      ),
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'list',
      'item' => 'bar2',
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'list',
      'item' => 'bar',
      'pos' => 'start',
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'list',
      'item' => 'bar',
      'unique' => TRUE,
    ));
    $rule
      ->action('list_remove', array(
      'list:select' => 'list',
      'item' => 'bar2',
    ));
    $list = entity_metadata_wrapper('list', array(
      'foo',
      'foo2',
    ));
    $rule
      ->execute($list);
    RulesLog::logger()
      ->checkLog();
    $this
      ->assertEqual($list
      ->value(), array(
      'bar',
      'foo',
      'foo2',
    ), 'List items removed and added.');
    $this
      ->assertFalse(rules_condition('list_contains')
      ->execute($list, 'foo-bar'), 'Condition "List item contains" evaluates to FALSE');
    $this
      ->assertTrue(rules_condition('list_contains')
      ->execute($list, 'foo'), 'Condition "List item contains" evaluates to TRUE');

    // debug(RulesLog::logger()->render());
    // Test data_is condition with IN operation.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('data_is', array(
      'data:select' => 'node:title',
      'op' => 'IN',
      'value' => array(
        'foo',
        'bar',
      ),
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:title',
      'value' => 'bar',
    ));
    $rule
      ->integrityCheck();
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
    ));
    $rule
      ->execute($node);
    $this
      ->assertEqual($node->title, 'bar', "Data comparison using IN operation evaluates to TRUE.");

    // Test Condition: Data is empty.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('data_is_empty', array(
      'data:select' => 'node:title',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:title',
      'value' => 'bar',
    ));
    $rule
      ->integrityCheck();

    // Data is empty condition evaluates to TRUE
    // for node with empty title, action sets title to 'bar'.
    $node = $this
      ->drupalCreateNode(array(
      'title' => '',
      'type' => 'article',
    ));
    $rule
      ->execute($node);
    $this
      ->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for node with empty title, action sets title to 'bar'.");

    // Data is empty condition evaluates to FALSE
    // for node with title 'foo', action is not executed.
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
      'type' => 'article',
    ));
    $rule
      ->execute($node);
    $this
      ->assertEqual($node->title, 'foo', "Data is empty condition evaluates to FALSE for node with title 'foo', action is not executed.");

    // Data is empty condition evaluates to TRUE for the parent of a
    // not existing term in the tags field of the node.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('node_is_of_type', array(
      'type' => array(
        'article',
      ),
    ));
    $rule
      ->condition('data_is_empty', array(
      'data:select' => 'node:field-tags:0:parent',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:title',
      'value' => 'bar',
    ));
    $rule
      ->integrityCheck();
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
      'type' => 'article',
    ));
    $rule
      ->execute($node);
    $this
      ->assertEqual($node->title, 'bar', "Data is empty condition evaluates to TRUE for not existing data structures");

    // Test Action: Calculate a value.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('data_calc', array(
      'input_1:select' => 'node:nid',
      'op' => '*',
      'input_2' => 2,
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:title',
      'value:select' => 'result',
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);
    $this
      ->assertEqual($node->title, $node->nid * 2, "Value has been calculated.");

    // Test moving a date.
    $action_set = rules_action_set(array(
      'date' => array(
        'type' => 'date',
      ),
    ), array(
      'date',
    ));
    $action_set
      ->action('data_calc', array(
      'input_1:select' => 'date',
      'op' => '+',
      'input_2' => 3600,
    ))
      ->action('data_set', array(
      'data:select' => 'date',
      'value:select' => 'result',
    ));
    $action_set
      ->integrityCheck();
    list($result) = $action_set
      ->execute(REQUEST_TIME);
    $this
      ->assertEqual($result, REQUEST_TIME + 3600, 'Used data calculation action to move a date by an hour.');

    // Test data type conversion action.
    $set = rules_action_set(array(
      'result' => array(
        'type' => 'text',
        'parameter' => FALSE,
      ),
    ), array(
      'result',
    ));
    $set
      ->action('data_convert', array(
      'type' => 'text',
      'value:select' => 'site:login-url',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'result',
      'value:select' => 'conversion_result',
    ));
    list($result) = $set
      ->execute();
    $set
      ->integrityCheck();
    $this
      ->assertEqual($result, url('user', array(
      'absolute' => TRUE,
    )), 'Converted URI to text.');
    $set = rules_action_set(array(
      'result' => array(
        'type' => 'integer',
        'parameter' => FALSE,
      ),
      'source' => array(
        'type' => 'text',
      ),
    ), array(
      'result',
    ));
    $set
      ->action('data_convert', array(
      'type' => 'integer',
      'value:select' => 'source',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'result',
      'value:select' => 'conversion_result',
    ));
    list($result) = $set
      ->execute('9.4');
    $this
      ->assertEqual($result, 9, 'Converted decimal to integer using rounding.');
    $set = rules_action_set(array(
      'result' => array(
        'type' => 'integer',
        'parameter' => FALSE,
      ),
      'source' => array(
        'type' => 'text',
      ),
    ), array(
      'result',
    ));
    $set
      ->action('data_convert', array(
      'type' => 'integer',
      'value:select' => 'source',
      'rounding_behavior' => 'down',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'result',
      'value:select' => 'conversion_result',
    ));
    list($result) = $set
      ->execute('9.6');
    $this
      ->assertEqual($result, 9, 'Converted decimal to integer using rounding behavior down.');
    $set = rules_action_set(array(
      'result' => array(
        'type' => 'integer',
        'parameter' => FALSE,
      ),
      'source' => array(
        'type' => 'text',
      ),
    ), array(
      'result',
    ));
    $set
      ->action('data_convert', array(
      'type' => 'integer',
      'value:select' => 'source',
      'rounding_behavior' => 'up',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'result',
      'value:select' => 'conversion_result',
    ));
    list($result) = $set
      ->execute('9.4');
    $this
      ->assertEqual($result, 10, 'Converted decimal to integer using rounding behavior up.');

    // Test text matching condition.
    $result = rules_condition('text_matches')
      ->execute('my-text', 'text', 'contains');
    $result2 = rules_condition('text_matches')
      ->execute('my-text', 'tex2t', 'contains');
    $this
      ->assertTrue($result && !$result2, 'Text matching condition using operation contain evaluated.');
    $result = rules_condition('text_matches')
      ->execute('my-text', 'my', 'starts');
    $result2 = rules_condition('text_matches')
      ->execute('my-text', 'text', 'starts');
    $this
      ->assertTrue($result && !$result2, 'Text matching condition using operation starts evaluated.');
    $result = rules_condition('text_matches')
      ->execute('my-text', 'text', 'ends');
    $result2 = rules_condition('text_matches')
      ->execute('my-text', 'my', 'ends');
    $this
      ->assertTrue($result && !$result2, 'Text matching condition using operation ends evaluated.');
    $result = rules_condition('text_matches')
      ->execute('my-text', 'me?y-texx?t', 'regex');
    $result2 = rules_condition('text_matches')
      ->execute('my-text', 'me+y-texx?t', 'regex');
    $this
      ->assertTrue($result && !$result2, 'Text matching condition using operation regex evaluated.');
  }

  /**
   * Tests entity related integration.
   */
  public function testEntityIntegration() {
    global $user;
    $page = $this
      ->drupalCreateNode(array(
      'type' => 'page',
    ));
    $article = $this
      ->drupalCreateNode(array(
      'type' => 'article',
    ));
    $result = rules_condition('entity_field_access')
      ->execute(entity_metadata_wrapper('node', $article), 'field_tags');
    $this
      ->assertTrue($result);

    // Test entity_is_of_bundle condition.
    $result = rules_condition('entity_is_of_bundle', array(
      'type' => 'node',
      'bundle' => array(
        'article',
      ),
    ))
      ->execute(entity_metadata_wrapper('node', $page));
    $this
      ->assertFalse($result, 'Entity is of bundle condition has not been met.');
    $result = rules_condition('entity_is_of_bundle', array(
      'type' => 'node',
      'bundle' => array(
        'article',
      ),
    ))
      ->execute(entity_metadata_wrapper('node', $article));
    $this
      ->assertTrue($result, 'Entity is of bundle condition has been met.');

    // Also test a full rule so the integrity check must work.
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->save();
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('entity_is_of_bundle', array(
      'entity:select' => 'node',
      'bundle' => array(
        'article',
      ),
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:field_tags',
      'value' => array(
        $term_wrapped
          ->getIdentifier(),
      ),
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($article);
    $this
      ->assertEqual($term_wrapped
      ->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');

    // Test again using an entity variable.
    $article = $this
      ->drupalCreateNode(array(
      'type' => 'article',
    ));
    $rule = rule(array(
      'entity' => array(
        'type' => 'entity',
      ),
    ));
    $rule
      ->condition('entity_is_of_bundle', array(
      'entity:select' => 'entity',
      'type' => 'node',
      'bundle' => array(
        'article',
      ),
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'entity:field_tags',
      'value' => array(
        $term_wrapped
          ->getIdentifier(),
      ),
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute(entity_metadata_wrapper('node', $article));
    $this
      ->assertEqual($term_wrapped
      ->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');

    // Test CRUD actions.
    $action = rules_action('entity_create', array(
      'type' => 'node',
      'param_type' => 'page',
      'param_title' => 'foo',
      'param_author' => $GLOBALS['user'],
    ));
    $action
      ->access();
    $action
      ->execute();
    $text = RulesLog::logger()
      ->render();
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %entity_created of type %node', array(
      'entity_created',
      'node',
    )));
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Saved %entity_created of type %node.', array(
      'entity_created',
      'node',
    )), $pos) : FALSE;
    $this
      ->assertTrue($pos !== FALSE, 'Data has been created and saved.');
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'sticky' => 0,
      'status' => 0,
    ));
    $rule = rule();
    $rule
      ->action('entity_fetch', array(
      'type' => 'node',
      'id' => $node->nid,
      'entity_fetched:var' => 'node',
    ));
    $rule
      ->action('entity_save', array(
      'data:select' => 'node',
      'immediate' => TRUE,
    ));
    $rule
      ->action('entity_delete', array(
      'data:select' => 'node',
    ));
    $rule
      ->access();
    $rule
      ->integrityCheck()
      ->execute();
    $text = RulesLog::logger()
      ->render();
    $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array(
      'entity_fetch' => 'Fetch entity by id',
    )));
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Added the provided variable %node of type %node', array(
      'node',
    )), $pos) : FALSE;
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Saved %node of type %node.', array(
      'node',
    )), $pos) : FALSE;
    $pos = $pos !== FALSE ? strpos($text, RulesTestCase::t('Evaluating the action %entity_delete.', array(
      'entity_delete' => 'Delete entity',
    )), $pos) : FALSE;
    $this
      ->assertTrue($pos !== FALSE, 'Data has been fetched, saved and deleted.');

    // debug(RulesLog::logger()->render());
    $node = entity_property_values_create_entity('node', array(
      'type' => 'article',
      'author' => $user,
      'title' => 'foo',
    ))
      ->value();
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->save();

    // Test asserting the field and using it afterwards.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('entity_has_field', array(
      'entity:select' => 'node',
      'field' => 'field_tags',
    ));
    $rule
      ->condition('entity_is_new', array(
      'entity:select' => 'node',
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'node:field-tags',
      'item' => $term_wrapped,
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($node);
    $tid = $term_wrapped
      ->getIdentifier();
    $this
      ->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(
      0 => array(
        'tid' => $tid,
      ),
    ), 'Entity has field conditions evaluted.');

    // Test loading a non-node entity.
    $action = rules_action('entity_fetch', array(
      'type' => 'taxonomy_term',
      'id' => $tid,
    ));
    list($term) = $action
      ->execute();
    $this
      ->assertEqual($term->tid, $tid, 'Fetched a taxonomy term using "entity_fetch".');

    // Test the entity is of type condition.
    $rule = rule(array(
      'entity' => array(
        'type' => 'entity',
        'label' => 'entity',
      ),
    ));
    $rule
      ->condition('entity_is_of_type', array(
      'type' => 'node',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'entity:title',
      'value' => 'bar',
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute(entity_metadata_wrapper('node', $node));
    $this
      ->assertEqual(entity_metadata_wrapper('node', $node->nid)->title
      ->value(), 'bar', 'Entity is of type condition correctly asserts the entity type.');

    // Test the entity_query action.
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'title' => 'foo2',
    ));
    $rule = rule();
    $rule
      ->action('entity_query', array(
      'type' => 'node',
      'property' => 'title',
      'value' => 'foo2',
    ))
      ->action('data_set', array(
      'data:select' => 'entity_fetched:0:title',
      'value' => 'bar',
    ));
    $rule
      ->access();
    $rule
      ->integrityCheck();
    $rule
      ->execute();
    $node = node_load($node->nid);
    $this
      ->assertEqual('bar', $node->title, 'Fetched a node by title and modified it.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests integration for the taxonomy module.
   */
  public function testTaxonomyIntegration() {
    $term = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->value();
    $term2 = clone $term;
    taxonomy_term_save($term);
    taxonomy_term_save($term2);
    $tags[LANGUAGE_NONE][0]['tid'] = $term->tid;
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
      'type' => 'article',
      'field_tags' => $tags,
    ));

    // Test assigning and remove a term from an article.
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
        'bundle' => 'article',
      ),
    ));
    $term_wrapped = rules_wrap_data($term->tid, array(
      'type' => 'taxonomy_term',
    ));
    $term_wrapped2 = rules_wrap_data($term2->tid, array(
      'type' => 'taxonomy_term',
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'node:field-tags',
      'item' => $term_wrapped2,
    ));
    $rule
      ->action('list_remove', array(
      'list:select' => 'node:field-tags',
      'item' => $term_wrapped,
    ));
    $rule
      ->execute($node);
    RulesLog::logger()
      ->checkLog();
    $this
      ->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(
      0 => array(
        'tid' => $term2->tid,
      ),
    ), 'Term removed and added from a node.');

    // Test using the taxonomy term reference field on a term object.
    $field_name = drupal_strtolower($this
      ->randomName() . '_field_name');
    $field = field_create_field(array(
      'field_name' => $field_name,
      'type' => 'taxonomy_term_reference',
      // Set cardinality to unlimited for tagging.
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
      'settings' => array(
        'allowed_values' => array(
          array(
            'vocabulary' => 'tags',
            'parent' => 0,
          ),
        ),
      ),
    ));
    $instance = array(
      'field_name' => $field_name,
      'entity_type' => 'taxonomy_term',
      'bundle' => 'tags',
      // Machine name of vocabulary.
      'label' => $this
        ->randomName() . '_label',
      'description' => $this
        ->randomName() . '_description',
      'weight' => mt_rand(0, 127),
      'widget' => array(
        'type' => 'taxonomy_autocomplete',
        'weight' => -4,
      ),
      'display' => array(
        'default' => array(
          'type' => 'taxonomy_term_reference_link',
          'weight' => 10,
        ),
      ),
    );
    field_create_instance($instance);
    $term1 = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->save();
    $term2 = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->save();

    // Test asserting the term reference field and using it afterwards.
    $rule = rule(array(
      'taxonomy_term' => array(
        'type' => 'taxonomy_term',
      ),
    ));
    $rule
      ->condition('entity_has_field', array(
      'entity:select' => 'taxonomy-term',
      'field' => $field_name,
    ));

    // Add $term2 to $term1 using the term reference field.
    $selector = str_replace('_', '-', 'taxonomy_term:' . $field_name);
    $rule
      ->action('list_add', array(
      'list:select' => $selector,
      'item' => $term2,
    ));
    $rule
      ->integrityCheck();
    $rule
      ->execute($term1);
    RulesLog::logger()
      ->checkLog();
    $this
      ->assertEqual($term1->{$field_name}[0]
      ->getIdentifier(), $term2
      ->getIdentifier(), 'Rule appended a term to the term reference field on a term.');

    // Test an action set for merging term parents, which is provided as default
    // config.
    $term = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
      'parent' => array(
        $term1
          ->value(),
      ),
    ))
      ->save();
    $action = rules_action('component_rules_retrieve_term_parents');
    list($parents) = $action
      ->execute(array(
      $term
        ->getIdentifier(),
    ));
    $this
      ->assertTrue($parents[0]->tid == $term1
      ->getIdentifier(), 'Invoked component to retrieve term parents.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests integration for the node module.
   */
  public function testNodeIntegration() {
    $tests = array(
      array(
        'node_unpublish',
        'node_is_published',
        'node_publish',
        'status',
      ),
      array(
        'node_make_unsticky',
        'node_is_sticky',
        'node_make_sticky',
        'sticky',
      ),
      array(
        'node_unpromote',
        'node_is_promoted',
        'node_promote',
        'promote',
      ),
    );
    $node = $this
      ->drupalCreateNode(array(
      'type' => 'page',
      'status' => 1,
      'sticky' => 1,
      'promote' => 1,
    ));
    foreach ($tests as $info) {
      list($action1, $condition, $action2, $property) = $info;
      rules_action($action1)
        ->execute($node);
      $node = node_load($node->nid, NULL, TRUE);
      $this
        ->assertFalse($node->{$property}, 'Action has permanently disabled node ' . $property);
      $return = rules_condition($condition)
        ->execute($node);
      $this
        ->assertFalse($return, 'Condition determines node ' . $property . ' is disabled.');
      rules_action($action2)
        ->execute($node);
      $node = node_load($node->nid, NULL, TRUE);
      $this
        ->assertTrue($node->{$property}, 'Action has permanently enabled node ' . $property);
      $return = rules_condition($condition)
        ->execute($node);
      $this
        ->assertTrue($return, 'Condition determines node ' . $property . ' is enabled.');
    }
    $return = rules_condition('node_is_of_type', array(
      'type' => array(
        'page',
        'article',
      ),
    ))
      ->execute($node);
    $this
      ->assertTrue($return, 'Condition determines node is of type page.');
    $return = rules_condition('node_is_of_type', array(
      'type' => array(
        'article',
      ),
    ))
      ->execute($node);
    $this
      ->assertFalse($return, 'Condition determines node is not of type article.');

    // Test auto saving of a new node after it has been inserted into the DB.
    $rule = rules_reaction_rule();
    $rand = $this
      ->randomName();
    $rule
      ->event('node_insert')
      ->action('data_set', array(
      'data:select' => 'node:title',
      'value' => $rand,
    ));
    $rule
      ->save('test');
    $node = $this
      ->drupalCreateNode();
    $node = node_load($node->nid);
    $this
      ->assertEqual($node->title, $rand, 'Node title is correct.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests integration for the user module.
   */
  public function testUserIntegration() {
    $rid = $this
      ->drupalCreateRole(array(
      'administer nodes',
    ), 'foo');
    $user = $this
      ->drupalCreateUser();

    // Test assigning a role with the list_add action.
    $rule = rule(array(
      'user' => array(
        'type' => 'user',
      ),
    ));
    $rule
      ->action('list_add', array(
      'list:select' => 'user:roles',
      'item' => $rid,
    ));
    $rule
      ->execute($user);
    $this
      ->assertTrue(isset($user->roles[$rid]), 'Role assigned to user.');

    // Test removing a role with the list_remove action.
    $rule = rule(array(
      'user' => array(
        'type' => 'user',
      ),
    ));
    $rule
      ->action('list_remove', array(
      'list:select' => 'user:roles',
      'item' => $rid,
    ));
    $rule
      ->execute($user);
    $this
      ->assertTrue(!isset($user->roles[$rid]), 'Role removed from user.');

    // Test assigning a role with user_add_role action.
    $rule = rule(array(
      'user' => array(
        'type' => 'user',
      ),
    ));
    $rule
      ->action('user_add_role', array(
      'account:select' => 'user',
      'roles' => array(
        $rid,
      ),
    ));
    $rule
      ->execute($user);
    $user = user_load($user->uid, TRUE);
    $result = rules_condition('user_has_role', array(
      'roles' => array(
        $rid,
      ),
    ))
      ->execute($user);
    $this
      ->assertTrue($result, 'Role assigned to user.');

    // Test removing a role with the user_remove_role action.
    $rule = rule(array(
      'user' => array(
        'type' => 'user',
      ),
    ));
    $rule
      ->action('user_remove_role', array(
      'account:select' => 'user',
      'roles' => array(
        $rid,
      ),
    ));
    $rule
      ->execute($user);
    $user = user_load($user->uid, TRUE);
    $result = rules_condition('user_has_role', array(
      'roles' => array(
        $rid,
      ),
    ))
      ->execute($user);
    $this
      ->assertFalse($result, 'Role removed from user.');

    // Test user blocking.
    rules_action('user_block')
      ->execute($user);
    $user = user_load($user->uid, TRUE);
    $this
      ->assertTrue(rules_condition('user_is_blocked')
      ->execute($user), 'User has been blocked.');
    rules_action('user_unblock')
      ->execute($user);
    $user = user_load($user->uid, TRUE);
    $this
      ->assertFalse(rules_condition('user_is_blocked')
      ->execute($user), 'User has been unblocked.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests integration for the php module.
   */
  public function testPHPIntegration() {
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
    ));
    $rule = rule(array(
      'var_name' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->condition('php_eval', array(
      'code' => 'return TRUE;',
    ))
      ->action('php_eval', array(
      'code' => 'drupal_set_message("Executed-" . $var_name->title);',
    ))
      ->action('drupal_message', array(
      'message' => 'Title: <?php echo $var_name->title; ?> Token: [var_name:title]',
    ));
    $rule
      ->execute($node);
    $rule
      ->access();
    RulesLog::logger()
      ->checkLog();
    $msg = drupal_get_messages();
    $this
      ->assertEqual(array_pop($msg['status']), "Title: foo Token: foo", 'PHP input evaluation has been applied.');
    $this
      ->assertEqual(array_pop($msg['status']), "Executed-foo", 'PHP code condition and action have been evaluated.');

    // Test PHP data processor.
    $rule = rule(array(
      'var_name' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('drupal_message', array(
      'message:select' => 'var_name:title',
      'message:process' => array(
        'php' => array(
          'code' => 'return "Title: $value";',
        ),
      ),
    ));
    $rule
      ->execute($node);
    $rule
      ->access();
    RulesLog::logger()
      ->checkLog();
    $msg = drupal_get_messages();
    $this
      ->assertEqual(array_pop($msg['status']), "Title: foo", 'PHP data processor has been applied.');
  }

  /**
   * Tests the "rules_core" integration.
   */
  public function testRulesCoreIntegration() {

    // Make sure the date input evaluator evaluates properly using strtotime().
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'foo',
    ));
    $rule = rule(array(
      'node' => array(
        'type' => 'node',
      ),
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'node:created',
      'value' => '+1 day',
    ));
    $rule
      ->execute($node);
    RulesLog::logger()
      ->checkLog();
    $node = node_load($node->nid, NULL, TRUE);
    $now = RulesDateInputEvaluator::gmstrtotime('now');

    // Tolerate a difference of a second.
    $this
      ->assertTrue(abs($node->created - $now - 86400) <= 1, 'Date input has been evaluated.');

    // Test using a numeric offset.
    $rule = rule(array(
      'number' => array(
        'type' => 'decimal',
      ),
    ), array(
      'number',
    ));
    $rule
      ->action('data_set', array(
      'data:select' => 'number',
      'value:select' => 'number',
      'value:process' => array(
        'num_offset' => array(
          'value' => 1,
        ),
      ),
    ));
    $rule
      ->integrityCheck();
    list($result) = $rule
      ->execute(10);
    $this
      ->assertTrue($result == 11, 'Numeric offset has been applied');

    // Test using a date offset.
    $set = rules_action_set(array(
      'date' => array(
        'type' => 'date',
      ),
    ), array(
      'date',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'date',
      'value:select' => 'date',
      'value:process' => array(
        'date_offset' => array(
          'value' => 1000,
        ),
      ),
    ));
    $date = date_create("14 Mar 1984 10:19:23 +01:00")
      ->format('U');
    list($result) = $set
      ->execute($date);
    $this
      ->assertEqual($result, $date + 1000, 'Date offset in seconds has been added.');

    // Test using a negative offset of 2 months.
    $set = rules_action_set(array(
      'date' => array(
        'type' => 'date',
      ),
    ), array(
      'date',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'date',
      'value:select' => 'date',
      'value:process' => array(
        'date_offset' => array(
          'value' => -86400 * 30 * 2,
        ),
      ),
    ));
    $date = date_create("14 Mar 1984 10:19:23 +01:00")
      ->format('U');
    list($result) = $set
      ->execute($date);
    $this
      ->assertEqual($result, date_create("14 Jan 1984 10:19:23 +01:00")
      ->format('U'), 'Date offset of -2 months has been added.');

    // Test using a positive offset of 1 year 6 months and 30 minutes.
    $set = rules_action_set(array(
      'date' => array(
        'type' => 'date',
      ),
    ), array(
      'date',
    ));
    $set
      ->action('data_set', array(
      'data:select' => 'date',
      'value:select' => 'date',
      'value:process' => array(
        'date_offset' => array(
          'value' => 86400 * 30 * 18 + 30 * 60,
        ),
      ),
    ));
    $date = date_create("14 Mar 1984 10:19:23 +01:00")
      ->format('U');
    list($result) = $set
      ->execute($date);
    $this
      ->assertEqual($result, date_create("14 Sep 1985 10:49:23 +01:00")
      ->format('U'), 'Date offset of 1 year 6 months and 30 minutes has been added.');
    RulesLog::logger()
      ->checkLog();
  }

  /**
   * Tests site/system integration.
   */
  public function testSystemIntegration() {

    // Test using the 'site' variable.
    $condition = rules_condition('data_is', array(
      'data:select' => 'site:current-user:name',
      'value' => $GLOBALS['user']->name,
    ));
    $this
      ->assertTrue($condition
      ->execute(), 'Retrieved the current user\'s name.');

    // Another test using a token replacement.
    $condition = rules_condition('data_is', array(
      'data:select' => 'site:current-user:name',
      'value' => '[site:current-user:name]',
    ));
    $this
      ->assertTrue($condition
      ->execute(), 'Replaced the token for the current user\'s name.');

    // Test breadcrumbs and drupal set message.
    $rule = rules_reaction_rule();
    $rule
      ->event('init')
      ->action('breadcrumb_set', array(
      'titles' => array(
        'foo',
      ),
      'paths' => array(
        'bar',
      ),
    ))
      ->action('drupal_message', array(
      'message' => 'A message.',
    ));
    $rule
      ->save('test');
    $this
      ->drupalGet('node');
    $this
      ->assertLink('foo', 0, 'Breadcrumb has been set.');
    $this
      ->assertText('A message.', 'Drupal message has been shown.');

    // Test the page redirect.
    $node = $this
      ->drupalCreateNode();
    $rule = rules_reaction_rule();
    $rule
      ->event('node_view')
      ->action('redirect', array(
      'url' => 'user',
    ));
    $rule
      ->save('test2');
    $this
      ->drupalGet('node/' . $node->nid);
    $this
      ->assertEqual($this
      ->getUrl(), url('user', array(
      'absolute' => TRUE,
    )), 'Redirect has been issued.');

    // Also test using a url including a fragment.
    $actions = $rule
      ->actions();
    $actions[0]->settings['url'] = 'user#fragment';
    $rule
      ->save();
    $this
      ->drupalGet('node/' . $node->nid);
    $this
      ->assertEqual($this
      ->getUrl(), url('user', array(
      'absolute' => TRUE,
      'fragment' => 'fragment',
    )), 'Redirect has been issued.');

    // Test sending mail.
    $settings = array(
      'to' => 'mail@example.com',
      'subject' => 'subject',
      'message' => 'hello.',
    );
    rules_action('mail', $settings)
      ->execute();
    $this
      ->assertMail('to', 'mail@example.com', 'Mail has been sent.');
    $this
      ->assertMail('from', variable_get('site_mail', ini_get('sendmail_from')), 'Default from address has been used');
    rules_action('mail', $settings + array(
      'from' => 'sender@example.com',
    ))
      ->execute();
    $this
      ->assertMail('from', 'sender@example.com', 'Specified from address has been used');

    // Test sending mail to all users of a role. First clear the mail
    // collector to remove the mail sent in the previous line of code.
    variable_set('drupal_test_email_collector', array());

    // Now make sure there is a custom role and two users with that role.
    $user1 = $this
      ->drupalCreateUser(array(
      'administer nodes',
    ));
    $roles = $user1->roles;

    // Remove the authenticated role so we only use the new role created by
    // drupalCreateUser().
    unset($roles[DRUPAL_AUTHENTICATED_RID]);

    // Now create a second user with the same role.
    $user2 = $this
      ->drupalCreateUser();
    user_save($user2, array(
      'roles' => $roles,
    ));

    // Now create a third user without the same role - this user should NOT
    // receive the role email.
    $user3 = $this
      ->drupalCreateUser(array(
      'administer blocks',
    ));
    $additional_roles = $user3->roles;
    unset($additional_roles[DRUPAL_AUTHENTICATED_RID]);

    // Execute action and check that only two mails were sent.
    rules_action('mail_to_users_of_role', $settings + array(
      'roles' => array_keys($roles),
    ))
      ->execute();
    $mails = $this
      ->drupalGetMails();
    $this
      ->assertEqual(count($mails), 2, '2 e-mails were sent to users of a role.');

    // Check each mail to ensure that only $user1 and $user2 got the mail.
    $mail = array_pop($mails);
    $this
      ->assertTrue($mail['to'] == $user2->mail, 'Mail to user of a role has been sent.');
    $mail = array_pop($mails);
    $this
      ->assertTrue($mail['to'] == $user1->mail, 'Mail to user of a role has been sent.');

    // Execute action again, this time to send mail to both roles.
    // This time check that three mails were sent - one for each user..
    variable_set('drupal_test_email_collector', array());
    rules_action('mail_to_users_of_role', $settings + array(
      'roles' => array_keys($roles + $additional_roles),
    ))
      ->execute();
    $mails = $this
      ->drupalGetMails();
    $this
      ->assertEqual(count($mails), 3, '3 e-mails were sent to users of multiple roles.');

    // Test reacting on new log entries and make sure the log entry is usable.
    $rule = rules_reaction_rule();
    $rule
      ->event('watchdog');
    $rule
      ->action('drupal_message', array(
      'message:select' => 'log_entry:message',
    ));
    $rule
      ->integrityCheck()
      ->save('test_watchdog');
    watchdog('php', 'test %message', array(
      '%message' => 'message',
    ));
    $msg = drupal_get_messages();
    $this
      ->assertEqual(array_pop($msg['status']), t('test %message', array(
      '%message' => 'message',
    )), 'Watchdog event occurred and log entry properties can be used.');
  }

  /**
   * Tests the path module integration.
   */
  public function testPathIntegration() {
    rules_action('path_alias')
      ->execute('foo', 'bar');
    $path = path_load('foo');
    $this
      ->assertTrue($path['alias'] == 'bar', 'URL alias has been created.');
    $alias_exists = rules_condition('path_alias_exists', array(
      'alias' => 'bar',
    ))
      ->execute();
    $this
      ->assertTrue($alias_exists, 'Created URL alias exists.');
    $has_alias = rules_condition('path_has_alias', array(
      'source' => 'foo',
    ))
      ->execute();
    $this
      ->assertTrue($has_alias, 'System path has an alias.');

    // Test node alias action.
    $node = $this
      ->drupalCreateNode();
    rules_action('node_path_alias')
      ->execute($node, 'test');
    $path = path_load("node/{$node->nid}");
    $this
      ->assertTrue($path['alias'] == 'test', 'Node URL alias has been created.');

    // Test term alias action.
    $term = entity_property_values_create_entity('taxonomy_term', array(
      'name' => $this
        ->randomName(),
      'vocabulary' => 1,
    ))
      ->value();
    rules_action('taxonomy_term_path_alias')
      ->execute($term, 'term-test');
    $path = path_load("taxonomy/term/{$term->tid}");
    $this
      ->assertTrue($path['alias'] == 'term-test', 'Term URL alias has been created.');
    RulesLog::logger()
      ->checkLog();
  }

}

/**
 * Tests event dispatcher functionality.
 */
class RulesEventDispatcherTestCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Rules event dispatchers',
      'description' => 'Tests event dispatcher functionality.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('rules', 'rules_test');
  }

  /**
   * Tests start and stop functionality.
   */
  public function testStartAndStop() {
    $handler = rules_get_event_handler('rules_test_event');
    $rule = rules_reaction_rule();
    $rule
      ->event('rules_test_event');

    // The handler should not yet be watching.
    $this
      ->assertFalse($handler
      ->isWatching());

    // Once saved, the event cache rebuild should start the watcher.
    $rule
      ->save();
    RulesEventSet::rebuildEventCache();
    $this
      ->assertTrue($handler
      ->isWatching());

    // Deleting should stop the watcher.
    $rule
      ->delete();
    $this
      ->assertFalse($handler
      ->isWatching());
  }

  /**
   * Tests start and stop functionality when used with multiple events.
   */
  public function testStartAndStopMultiple() {
    $handler = rules_get_event_handler('rules_test_event');

    // Initially, the task handler should not be watching.
    $this
      ->assertFalse($handler
      ->isWatching());

    // Set up five rules that all use the same event.
    $rules = array();
    foreach (array(
      1,
      2,
      3,
      4,
      5,
    ) as $key) {
      $rules[$key] = rules_reaction_rule();
      $rules[$key]
        ->event('rules_test_event');
      $rules[$key]
        ->save();
    }

    // Once saved, the event cache rebuild should start the watcher.
    RulesEventSet::rebuildEventCache();
    $this
      ->assertTrue($handler
      ->isWatching());

    // It should continue watching until all events are deleted.
    foreach ($rules as $key => $rule) {
      $rule
        ->delete();
      $this
        ->assertEqual($key !== 5, $handler
        ->isWatching());
    }
  }

}

/**
 * Test early bootstrap Rules invocation.
 */
class RulesInvocationEnabledTestCase extends DrupalWebTestCase {

  /**
   * Declares test metadata.
   */
  public static function getInfo() {
    return array(
      'name' => 'Rules invocation enabled',
      'description' => 'Tests that Rules events are enabled during menu item loads.',
      'group' => 'Rules',
    );
  }

  /**
   * Overrides DrupalWebTestCase::setUp().
   */
  protected function setUp() {
    parent::setUp('dblog', 'rules', 'rules_test', 'rules_test_invocation');
  }

  /**
   * Tests that a Rules event is triggered on node menu item loading.
   *
   * @see rules_test_invocation_node_load()
   */
  public function testInvocationOnNodeMenuLoading() {

    // Create a test node.
    $node = $this
      ->drupalCreateNode(array(
      'title' => 'Test',
    ));

    // Enable Rules logging on the INFO level so that entries are written to
    // dblog.
    variable_set('rules_log_errors', RulesLog::INFO);

    // Create an empty rule that will fire in our node load hook.
    $rule = rules_reaction_rule();
    $rule
      ->event('rules_test_event');
    $rule
      ->save('test_rule');

    // Visit the node page which should trigger the load hook.
    $this
      ->drupalGet('node/' . $node->nid);
    $result = db_query("SELECT * FROM {watchdog} WHERE type = 'rules' AND message = 'Reacting on event %label.'")
      ->fetch();
    $this
      ->assertFalse(empty($result), 'Rules event was triggered and logged.');
  }

}

Classes

Namesort descending Description
RulesEventDispatcherTestCase Tests event dispatcher functionality.
RulesIntegrationTestCase Tests provided module integration.
RulesInvocationEnabledTestCase Test early bootstrap Rules invocation.
RulesTestCase Rules test cases.
RulesTestDataCase Test rules data wrappers.
RulesTriggerTestCase Test triggering rules.