You are here

class RulesTestCase in Rules 8.3

Same name and namespace in other branches
  1. 6 rules_test/rules_test.test \RulesTestCase
  2. 7.2 tests/rules.test \RulesTestCase

Hierarchy

Expanded class hierarchy of RulesTestCase

File

d7-tests/rules_test_case.test, line 14
Rules 7.x tests.

View source
class RulesTestCase extends DrupalWebTestCase {
  static function getInfo() {
    return array(
      'name' => 'Rules Engine tests',
      'description' => 'Test using the rules API to create and evaluate rules.',
      'group' => 'Rules',
    );
  }
  function setUp() {
    parent::setUp('rules', 'rules_test');
    RulesLog::logger()
      ->clear();
    variable_set('rules_debug_log', 1);
  }

  /**
   * Calculates the output of t() given an array of placeholders to replace.
   */
  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);
  }
  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;
  }

  /**
   * Test handling dependencies.
   */
  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.');
  }

  /**
   * Test setting up an action with some action_info and serializing and
   * executing it.
   */
  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();
  }

  /**
   * Test executing with wrong arguments.
   */
  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);
    }
  }

  /**
   * Test setting up a rule and mapping variables.
   */
  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.
   */
  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.
   */
  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.');
  }

  /**
   * Test automatic saving of variables.
   */
  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();
  }

  /**
   * Test adding a variable and optional parameters.
   */
  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();
  }

  /**
   * Test custom access for using component actions/conditions.
   */
  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 anonyous.
    $user = user_load(0);
    $this
      ->assertFalse(rules_action('component_rules_test_roles')
      ->access(), 'Anonymous user can\'t use the rule component.');
  }

  /**
   * Test passing arguments by reference to an action.
   */
  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');
  }

  /**
   * Test sorting rule elements.
   */
  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.
   */
  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.
   */
  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.
   */
  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.');
  }

  /**
   * Test asserting metadata, customizing action info and make sure integrity
   * is checked.
   */
  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.');
    }
  }

  /**
   * Test using loops.
   */
  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();
  }

  /**
   * Test access checks.
   */
  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.');
  }

  /**
   * Test returning provided variables.
   */
  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.
   */
  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.');
  }

  /**
   * Test importing and exporting a rule.
   */
  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.');
  }

  /**
   * Test the named parameter mode.
   */
  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();
  }

  /**
   * Make sure Rules aborts when NULL values are used.
   */
  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.');
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RulesTestCase::createTestRule protected function
RulesTestCase::getInfo static function
RulesTestCase::setUp function
RulesTestCase::t static function Calculates the output of t() given an array of placeholders to replace.
RulesTestCase::testAbortOnNULLValues function Make sure Rules aborts when NULL values are used.
RulesTestCase::testAccessCheck function Test access checks.
RulesTestCase::testActionExecutionFails function Test executing with wrong arguments.
RulesTestCase::testActionSaving function Test automatic saving of variables.
RulesTestCase::testActionSetup function Test setting up an action with some action_info and serializing and executing it.
RulesTestCase::testClassBasedActions function Tests making use of class based actions.
RulesTestCase::testComponentInvocations function Tests invoking components from the action.
RulesTestCase::testDataSelectors function Tests using data selectors.
RulesTestCase::testdependencies function Test handling dependencies.
RulesTestCase::testInputEvaluators function Tests using input evaluators.
RulesTestCase::testLoops function Test using loops.
RulesTestCase::testMetadataAssertion function Test asserting metadata, customizing action info and make sure integrity is checked.
RulesTestCase::testNamedParameters function Test the named parameter mode.
RulesTestCase::testPassingByReference function Test passing arguments by reference to an action.
RulesTestCase::testReturningVariables function Test returning provided variables.
RulesTestCase::testRuleComponentAccess function Test custom access for using component actions/conditions.
RulesTestCase::testRuleImportExport function Test importing and exporting a rule.
RulesTestCase::testRulesCRUD function Tests CRUD functionality.
RulesTestCase::testRuleSets function Tests making use of rule sets.
RulesTestCase::testSorting function Test sorting rule elements.
RulesTestCase::testVariableAdding function Test adding a variable and optional parameters.
RulesTestCase::testVariableMapping function Test setting up a rule and mapping variables.