Projet

Général

Profil

Paste
Télécharger (91,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / rules / tests / rules.test @ 950416da

1
<?php
2

    
3
/**
4
 * @file
5
 * Rules tests.
6
 */
7

    
8
/**
9
 * Rules test cases.
10
 */
11
class RulesTestCase extends DrupalWebTestCase {
12

    
13
  /**
14
   * Declares test metadata.
15
   */
16
  public static function getInfo() {
17
    return array(
18
      'name' => 'Rules Engine tests',
19
      'description' => 'Test using the rules API to create and evaluate rules.',
20
      'group' => 'Rules',
21
    );
22
  }
23

    
24
  /**
25
   * Overrides DrupalWebTestCase::setUp().
26
   */
27
  protected function setUp() {
28
    parent::setUp('rules', 'rules_test');
29
    RulesLog::logger()->clear();
30
    variable_set('rules_debug_log', TRUE);
31
  }
32

    
33
  /**
34
   * Calculates the output of t() given an array of placeholders to replace.
35
   */
36
  public static function t($text, $strings) {
37
    $placeholders = array();
38
    foreach ($strings as $key => $string) {
39
      $key = !is_numeric($key) ? $key : $string;
40
      $placeholders['%' . $key] = drupal_placeholder($string);
41
    }
42
    return strtr($text, $placeholders);
43
  }
44

    
45
  /**
46
   * Helper function to create a test Rule.
47
   */
48
  protected function createTestRule() {
49
    $rule = rule();
50
    $rule->condition('rules_test_condition_true')
51
         ->condition('rules_test_condition_true')
52
         ->condition(rules_or()
53
           ->condition(rules_condition('rules_test_condition_true')->negate())
54
           ->condition('rules_test_condition_false')
55
           ->condition(rules_and()
56
             ->condition('rules_test_condition_false')
57
             ->condition('rules_test_condition_true')
58
             ->negate()
59
           )
60
         );
61
    $rule->action('rules_test_action');
62
    return $rule;
63
  }
64

    
65
  /**
66
   * Tests creating a rule and iterating over the rule elements.
67
   */
68
  public function testRuleCreation() {
69
    $rule = $this->createTestRule();
70
    $rule->integrityCheck();
71
    $rule->execute();
72
    $log = RulesLog::logger()->get();
73
    $last = array_pop($log);
74
    $last = array_pop($log);
75
    $last = array_pop($log);
76
    $this->assertEqual($last[0], 'action called', 'Action called');
77
    RulesLog::logger()->checkLog();
78

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

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

    
92
  /**
93
   * Tests handling dependencies.
94
   */
95
  public function testDependencies() {
96
    $action = rules_action('rules_node_publish_action');
97
    $this->assertEqual($action->dependencies(), array('rules_test'), 'Providing module is returned as dependency.');
98

    
99
    $container = new RulesTestContainer();
100
    $this->assertEqual($container->dependencies(), array('rules_test'), 'Providing module for container plugin is returned as dependency.');
101

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

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

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

    
118
    // Test it with components.
119
    module_enable(array('path'));
120
    $action_set = rules_action_set(array('node' => array('type' => 'node')));
121
    $action_set->action('node_path_alias');
122
    $action_set->save('rules_test_alias');
123

    
124
    $rule = rule(array('node' => array('type' => 'node')));
125
    $rule->action('component_rules_test_alias');
126
    $rule->integrityCheck();
127
    $rule->save('rules_test_rule');
128

    
129
    $rule = rules_config_load('rules_test_rule');
130
    $component = rules_config_load('rules_test_alias');
131
    $this->assertTrue(in_array('path', $component->dependencies) && !$rule->dirty && !$component->dirty, 'Component has path module dependency.');
132

    
133
    // Now disable path module and make sure both configs are marked as dirty.
134
    module_disable(array('path'));
135
    rules_clear_cache();
136
    $rule = rules_config_load('rules_test_rule');
137
    $component = rules_config_load('rules_test_alias');
138

    
139
    $this->assertTrue($component->dirty, 'Component has been marked as dirty');
140
    $node = $this->drupalCreateNode();
141
    $result = rules_invoke_component('rules_test_alias', $node);
142
    $this->assertTrue($result === FALSE, 'Unable to execute a dirty component.');
143

    
144
    // When the rule is evaluated, the broken component is detected and the
145
    // rule should be marked as dirty too.
146
    $rule->execute($node);
147
    $this->assertTrue($rule->dirty, 'Rule has been marked as dirty');
148

    
149
    module_enable(array('path'));
150
    rules_clear_cache();
151

    
152
    // Trigger rebuilding the cache, so configs are checked again.
153
    rules_get_cache();
154

    
155
    $rule = rules_config_load('rules_test_rule');
156
    $component = rules_config_load('rules_test_alias');
157
    $this->assertTrue(!$component->dirty, 'Component has been marked as not dirty again.');
158
    $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');
159
  }
160

    
161
  /**
162
   * Tests setting up an action, serializing, and executing it.
163
   */
164
  public function testActionSetup() {
165
    $action = rules_action('rules_node_publish_action');
166

    
167
    $s = serialize($action);
168
    $action2 = unserialize($s);
169
    $node = (object) array('status' => 0, 'type' => 'page');
170
    $node->title = 'test';
171

    
172
    $action2->execute($node);
173
    $this->assertEqual($node->status, 1, 'Action executed correctly');
174

    
175
    $this->assertTrue(in_array('node', array_keys($action2->parameterInfo())), 'Parameter info returned.');
176

    
177
    $node->status = 0;
178
    $action2->integrityCheck();
179
    $action2->executeByArgs(array('node' => $node));
180
    $this->assertEqual($node->status, 1, 'Action executed correctly');
181

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

    
185
    // Inspect the cache
186
    //$this->pass(serialize(rules_get_cache()));
187
    RulesLog::logger()->checkLog();
188
  }
189

    
190
  /**
191
   * Tests executing with wrong arguments.
192
   */
193
  public function testActionExecutionFails() {
194
    $action = rules_action('rules_node_publish_action');
195
    try {
196
      $action->execute();
197
      $this->fail("Execution hasn't created an exception.");
198
    }
199
    catch (RulesEvaluationException $e) {
200
      $this->pass("RulesEvaluationException was thrown: " . $e);
201
    }
202
  }
203

    
204
  /**
205
   * Tests setting up a rule and mapping variables.
206
   */
207
  public function testVariableMapping() {
208
    $rule = rule(array(
209
      'node' => array('type' => 'node'),
210
      'node_unchanged' => array('type' => 'node'),
211
    ));
212
    $rule->condition(rules_condition('rules_condition_content_is_published')->negate())
213
         ->condition('rules_condition_content_is_type', array('type' => array('page', 'story')))
214
         ->action('rules_node_publish_action', array('node:select' => 'node_unchanged'));
215

    
216
    $node1 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
217
    $node2 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
218
    $rule->integrityCheck();
219
    $rule->execute($node1, $node2);
220
    $this->assertEqual($node2->status, 1, 'Action executed correctly on node2.');
221
    $this->assertEqual($node1->status, 0, 'Action not executed on node1.');
222

    
223
    RulesLog::logger()->checkLog();
224
  }
225

    
226
  /**
227
   * Tests making use of class based actions.
228
   */
229
  public function testClassBasedActions() {
230
    $cache = rules_get_cache();
231
    $this->assertTrue(!empty($cache['action_info']['rules_test_class_action']), 'Action has been discovered.');
232
    $action = rules_action('rules_test_class_action');
233

    
234
    $parameters = $action->parameterInfo();
235
    $this->assertTrue($parameters['node'], 'Action parameter needs a value.');
236

    
237
    $node = $this->drupalCreateNode();
238
    $action->execute($node);
239
    $log = RulesLog::logger()->get();
240
    $last = array_pop($log);
241
    $last = array_pop($log);
242
    $this->assertEqual($last[0], 'Action called with node ' . $node->nid, 'Action called');
243
    RulesLog::logger()->checkLog();
244
  }
245

    
246
  /**
247
   * Tests CRUD functionality.
248
   */
249
  public function testRulesCRUD() {
250
    $rule = $this->createTestRule();
251
    $rule->integrityCheck()->save('test');
252

    
253
    $this->assertEqual(TRUE, $rule->active, 'Rule is active.');
254
    $this->assertEqual(0, $rule->weight, 'Rule weight is zero.');
255

    
256
    $results = entity_load('rules_config', array('test'));
257
    $rule2 = array_pop($results);
258
    $this->assertEqual($rule->id, $rule2->id, 'Rule created and loaded');
259
    $this->assertEqual(get_class($rule2), get_class($rule), 'Class properly instantiated.');
260
    $rule2->execute();
261
    // Update.
262
    $rule2->save();
263

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

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

    
275
    // Tests CRUD for tags - making sure the tags are stored properly..
276
    $rule = $this->createTestRule();
277
    $tag = $this->randomString();
278
    $rule->tags = array($tag);
279
    $rule->save();
280
    $result = db_select('rules_tags')
281
      ->fields('rules_tags', array('tag'))
282
      ->condition('id', $rule->id)
283
      ->execute();
284
    $this->assertEqual($result->fetchField(), $tag, 'Associated tag has been saved.');
285
    // Try updating.
286
    $rule->tags = array($this->randomName(), $this->randomName());
287
    $rule->integrityCheck()->save();
288
    $result = db_select('rules_tags')
289
      ->fields('rules_tags', array('tag'))
290
      ->condition('id', $rule->id)
291
      ->execute()
292
      ->fetchCol();
293
    $this->assertTrue(in_array($rule->tags[0], $result) && in_array($rule->tags[1], $result), 'Updated associated tags.');
294
    // Try loading multiple rules by tags.
295
    $rule2 = $this->createTestRule();
296
    $rule2->tags = array($this->randomName());
297
    $rule2->save();
298
    $loaded = entity_load('rules_config', FALSE, array('tags' => array($rule->tags[0], $rule2->tags[0])));
299
    $this->assertTrue($loaded[$rule->id]->id == $rule->id && $loaded[$rule2->id]->id == $rule2->id, 'Loading configs by tags');
300
    // Try deleting.
301
    $rule->delete();
302
    $result = db_select('rules_tags')
303
      ->fields('rules_tags', array('tag'))
304
      ->condition('id', $rule->id)
305
      ->execute();
306
    $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated tags.');
307
  }
308

    
309
  /**
310
   * Tests automatic saving of variables.
311
   */
312
  public function testActionSaving() {
313
    // Test saving a parameter.
314
    $action = rules_action('rules_node_publish_action_save');
315
    $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
316
    $action->executeByArgs(array('node' => $node));
317

    
318
    $this->assertEqual($node->status, 1, 'Action executed correctly on node.');
319
    // Sync node_load cache with node_save.
320
    entity_get_controller('node')->resetCache();
321

    
322
    $node = node_load($node->nid);
323
    $this->assertEqual($node->status, 1, 'Node has been saved.');
324

    
325
    // Now test saving a provided variable, which is renamed and modified before
326
    // it is saved.
327
    $title = $this->randomName();
328
    $rule = rule();
329
    $rule->action('entity_create', array(
330
      'type' => 'node',
331
      'param_type' => 'article',
332
      'param_author:select' => 'site:current-user',
333
      'param_title' => $title,
334
      'entity_created:var' => 'node',
335
    ));
336
    $rule->action('data_set', array(
337
      'data:select' => 'node:body',
338
      'value' => array('value' => 'body'),
339
    ));
340
    $rule->integrityCheck();
341
    $rule->execute();
342

    
343
    $node = $this->drupalGetNodeByTitle($title);
344
    $this->assertTrue(!empty($node) && $node->body[LANGUAGE_NONE][0]['value'] == 'body', 'Saved a provided variable');
345
    RulesLog::logger()->checkLog();
346
  }
347

    
348
  /**
349
   * Tests adding a variable and optional parameters.
350
   */
351
  public function testVariableAdding() {
352
    $node = $this->drupalCreateNode();
353
    $rule = rule(array('nid' => array('type' => 'integer')));
354
    $rule->condition('rules_test_condition_true')
355
         ->action('rules_action_load_node')
356
         ->action('rules_action_delete_node', array('node:select' => 'node_loaded'))
357
         ->execute($node->nid);
358

    
359
    $this->assertEqual(FALSE, node_load($node->nid), 'Variable added and skipped optional parameter.');
360
    RulesLog::logger()->checkLog();
361

    
362
    $vars = $rule->conditions()->offsetGet(0)->availableVariables();
363
    $this->assertEqual(!isset($vars['node_loaded']), 'Loaded variable is not available to conditions.');
364

    
365
    // Test adding a variable with a custom variable name.
366
    $node = $this->drupalCreateNode();
367
    $rule = rule(array('nid' => array('type' => 'integer')));
368
    $rule->action('rules_action_load_node', array('node_loaded:var' => 'node'))
369
         ->action('rules_action_delete_node')
370
         ->execute($node->nid);
371

    
372
    $this->assertEqual(FALSE, node_load($node->nid), 'Variable with custom name added.');
373
    RulesLog::logger()->checkLog();
374
  }
375

    
376
  /**
377
   * Tests custom access for using component actions/conditions.
378
   */
379
  public function testRuleComponentAccess() {
380
    // Create a normal user.
381
    $normal_user = $this->drupalCreateUser();
382
    // Create a role for granting access to the rule component.
383
    $this->normal_role = $this->drupalCreateRole(array(), 'test_role');
384
    $normal_user->roles[$this->normal_role] = 'test_role';
385
    user_save($normal_user, array('roles' => $normal_user->roles));
386
    // Create an 'action set' rule component making use of a permission.
387
    $action_set = rules_action_set(array('node' => array('type' => 'node')));
388
    $action_set->access_exposed = TRUE;
389
    $action_set->save('rules_test_roles');
390

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

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

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

    
406
  /**
407
   * Tests passing arguments by reference to an action.
408
   */
409
  public function testPassingByReference() {
410
    // Keeping references of variables is unsupported, though the
411
    // EntityMetadataArrayObject may be used to achieve that.
412
    $array = array('foo' => 'bar');
413
    $data = new EntityMetadataArrayObject($array);
414
    rules_action('rules_action_test_reference')->execute($data);
415
    $this->assertTrue($data['changed'], 'Parameter has been passed by reference');
416
  }
417

    
418
  /**
419
   * Tests sorting rule elements.
420
   */
421
  public function testSorting() {
422
    $rule = $this->createTestRule();
423
    $conditions = $rule->conditions();
424
    $conditions[0]->weight = 10;
425
    $conditions[2]->weight = 10;
426
    $id[0] = $conditions[0]->elementId();
427
    $id[1] = $conditions[1]->elementId();
428
    $id[2] = $conditions[2]->elementId();
429
    // For testing use a deep sort, even if not necessary here.
430
    $rule->sortChildren(TRUE);
431
    $conditions = $rule->conditions();
432
    $this->assertEqual($conditions[0]->elementId(), $id[1], 'Condition sorted correctly.');
433
    $this->assertEqual($conditions[1]->elementId(), $id[0], 'Condition sorted correctly.');
434
    $this->assertEqual($conditions[2]->elementId(), $id[2], 'Condition sorted correctly.');
435
  }
436

    
437
  /**
438
   * Tests using data selectors.
439
   */
440
  public function testDataSelectors() {
441
    $body[LANGUAGE_NONE][0] = array('value' => '<b>The body & nothing.</b>');
442
    $node = $this->drupalCreateNode(array('body' => $body, 'type' => 'page', 'summary' => ''));
443

    
444
    $rule = rule(array('nid' => array('type' => 'integer')));
445
    $rule->action('rules_action_load_node')
446
         ->action('drupal_message', array('message:select' => 'node_loaded:body:value'))
447
         ->execute($node->nid);
448

    
449
    RulesLog::logger()->checkLog();
450
    $msg = drupal_get_messages('status');
451
    $last_msg = array_pop($msg['status']);
452
    $wrapper = entity_metadata_wrapper('node', $node);
453
    $this->assertEqual($last_msg, $wrapper->body->value->value(array('sanitize' => TRUE)), 'Data selector for getting parameter applied.');
454

    
455
    // Get a "reference" on the same object as returned by node_load().
456
    $node = node_load($node->nid);
457
    $rule = rule(array('nid' => array('type' => 'integer')));
458
    $rule->action('rules_action_load_node')
459
         ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title'))
460
         // Use two actions and make sure the node get saved only once.
461
         ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title2'))
462
         ->execute($node->nid);
463

    
464
    $wrapper = entity_metadata_wrapper('node', $node);
465
    $this->assertEqual('Test title2', $wrapper->title->value(), 'Data has been modified and saved.');
466

    
467
    RulesLog::logger()->checkLog();
468
    $text = RulesLog::logger()->render();
469
    $msg = RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node'));
470
    if ($pos1 = strpos($text, $msg)) {
471
      $pos2 = strpos($text, $msg, $pos1 + 1);
472
    }
473
    $this->assertTrue($pos1 && $pos2 === FALSE, 'Data has been saved only once.');
474

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

    
484
    // Test auto creation of nested data structures, like the node body field.
485
    // I.e. if $node->body is not set, it is automatically initialized to an
486
    // empty array, so that the nested value can be set and the wrappers do not
487
    // complain about missing parent data structures.
488
    $rule = rule();
489
    $rule->action('entity_create', array(
490
      'type' => 'node',
491
      'param_type' => 'page',
492
      'param_title' => 'foo',
493
      'param_author' => $GLOBALS['user'],
494
    ));
495
    $rule->action('data_set', array('data:select' => 'entity_created:body:value', 'value' => 'test content'))
496
         ->execute();
497
    try {
498
      RulesLog::logger()->checkLog();
499
      $this->pass('Auto creation of nested data structures.');
500
    }
501
    catch (Exception $e) {
502
      $this->fail('Auto creation of nested data structures.');
503
    }
504

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

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

    
518
  /**
519
   * Tests making use of rule sets.
520
   */
521
  public function testRuleSets() {
522
    $set = rules_rule_set(array(
523
      'node' => array('type' => 'node', 'label' => 'node'),
524
    ));
525
    $set->rule(rule()->action('drupal_message', array('message:select' => 'node:title')))
526
        ->rule(rule()->condition('rules_condition_content_is_published')
527
                     ->action('drupal_message', array('message' => 'Node is published.'))
528
               );
529
    $set->integrityCheck()->save('rules_test_set_1');
530

    
531
    $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1));
532
    // Execute.
533
    rules_invoke_component('rules_test_set_1', $node);
534

    
535
    $msg = drupal_get_messages();
536
    $this->assertEqual($msg['status'][0], 'The title.', 'First rule evaluated.');
537
    $this->assertEqual($msg['status'][1], 'Node is published.', 'Second rule evaluated.');
538

    
539
    // Test a condition set.
540
    $set = rules_or(array(
541
      'node' => array('type' => 'node', 'label' => 'node'),
542
    ));
543
    $set->condition('data_is', array('data:select' => 'node:author:name', 'value' => 'notthename'))
544
        ->condition('data_is', array('data:select' => 'node:nid', 'value' => $node->nid))
545
        ->integrityCheck()
546
        ->save('test', 'rules_test');
547
    // Load and execute condition set.
548
    $set = rules_config_load('test');
549
    $this->assertTrue($set->execute($node), 'Set has been correctly evaluated.');
550
    RulesLog::logger()->checkLog();
551
  }
552

    
553
  /**
554
   * Tests invoking components from the action.
555
   */
556
  public function testComponentInvocations() {
557
    $set = rules_rule_set(array(
558
      'node1' => array('type' => 'node', 'label' => 'node'),
559
    ));
560
    $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1'))
561
                     ->action('node_unpublish', array('node:select' => 'node1'))
562
               );
563
    $set->integrityCheck()->save('rules_test_set_2');
564

    
565
    // Use different names for the variables to ensure they are properly mapped
566
    // when taking over the variables to be saved.
567
    $rule = rule(array(
568
      'node2' => array('type' => 'node', 'label' => 'node'),
569
    ));
570
    $rule->action('component_rules_test_set_2', array('node1:select' => 'node2'));
571
    $rule->action('node_make_sticky', array('node:select' => 'node2'));
572

    
573
    $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1, 'sticky' => 0));
574
    $rule->execute($node);
575

    
576
    $node = node_load($node->nid, NULL, TRUE);
577
    $this->assertFalse($node->status, 'The component changes have been saved correctly.');
578
    $this->assertTrue($node->sticky, 'The action changes have been saved correctly.');
579

    
580
    // Check that we have saved the changes only once.
581
    $text = RulesLog::logger()->render();
582
    // Make sure both saves are handled in one save operation.
583
    $this->assertEqual(substr_count($text, 'Saved'), 1, 'Changes have been saved in one save operation.');
584
    RulesLog::logger()->checkLog();
585

    
586
    // Test recursion prevention on components by invoking the component from
587
    // itself, what should be prevented.
588
    $set->action('component_rules_test_set_2', array('node1:select' => 'node1'))
589
        ->save();
590

    
591
    $rule->execute($node);
592
    $text1 = RulesLog::logger()->render();
593
    $text2 = RulesTestCase::t('Not evaluating rule set %rules_test_set_2 to prevent recursion.', array('rules_test_set_2'));
594
    $this->assertTrue((strpos($text1, $text2) !== FALSE), "Recursion of component invocation prevented.");
595

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

    
604
  /**
605
   * Tests asserting metadata.
606
   *
607
   * Customizes action info and makes sure integrity is checked.
608
   */
609
  public function testMetadataAssertion() {
610
    $action = rules_action('rules_node_make_sticky_action');
611

    
612
    // Test failing integrity check.
613
    try {
614
      $rule = rule(array('node' => array('type' => 'entity')));
615
      $rule->action($action);
616
      // Fails due to the 'node' variable not matching the node type.
617
      $rule->integrityCheck();
618
      $this->fail('Integrity check has not thrown an exception.');
619
    }
620
    catch (RulesIntegrityException $e) {
621
      $this->pass('Integrity check has thrown exception: ' . $e->getMessage());
622
    }
623

    
624
    // Test asserting additional metadata.
625
    $rule = rule(array('node' => array('type' => 'node')));
626
    // Customize action info using the settings.
627
    $rule->condition('data_is', array('data:select' => 'node:type', 'value' => 'page'))
628
         // Configure an condition using the body. As the body is a field,
629
         // this requires the bundle to be correctly asserted.
630
         ->condition(rules_condition('data_is', array('data:select' => 'node:body:value', 'value' => 'foo'))->negate())
631
         // The action also requires the page bundle in order to work.
632
         ->action($action);
633
    // Make sure the integrity check doesn't throw an exception.
634
    $rule->integrityCheck();
635
    // Test the rule.
636
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
637
    $rule->execute($node);
638
    $this->assertTrue($node->sticky, 'Rule with asserted metadata executed.');
639

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

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

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

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

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

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

    
687
  /**
688
   * Tests using loops.
689
   */
690
  public function testLoops() {
691
    // Test passing the list parameter as argument to ensure that is working
692
    // generally for plugin container too.
693
    drupal_get_messages(NULL, TRUE);
694
    $loop = rules_loop();
695
    $loop->action('drupal_message', array('message' => 'test'));
696
    $arg_info = $loop->parameterInfo();
697
    $this->assert($arg_info['list']['type'] == 'list', 'Argument info contains list.');
698
    $loop->execute(array(1, 2));
699

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

    
704
    // Now test looping over nodes.
705
    $node1 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
706
    $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
707
    $node3 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
708

    
709
    $rule = rule(array(
710
      'list' => array(
711
        'type' => 'list<node>',
712
        'label' => 'A list of nodes',
713
      ),
714
    ));
715
    $loop = rules_loop(array('list:select' => 'list', 'item:var' => 'node'));
716
    $loop->action('data_set', array('data:select' => 'node:sticky', 'value' => TRUE));
717
    $rule->action($loop);
718
    // Test using a list with data selectors, just output the last nodes type.
719
    $rule->action('drupal_message', array('message:select' => 'list:2:type'));
720

    
721
    $rule->execute(array($node1->nid, $node2->nid, $node3->nid));
722
    $text = RulesLog::logger()->render();
723
    $save_msg = RulesTestCase::t('Saved %node of type %node.', array('node', 'node'));
724
    $this->assertTrue(substr_count($text, $save_msg) == 3, 'List item variables have been saved.');
725
    RulesLog::logger()->checkLog();
726
  }
727

    
728
  /**
729
   * Tests access checks.
730
   */
731
  public function testAccessCheck() {
732
    $rule = rule();
733
    // Try to set a property which is provided by the test module and is not
734
    // accessible, so the access check has to return FALSE.
735
    $rule->action('data_set', array('data:select' => 'site:no-access-user', 'value' => 'foo'));
736
    $this->assertTrue($rule->access() === FALSE, 'Access check is working.');
737
  }
738

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

    
748
    // Create a simple set that just passed through the given node.
749
    $set = rules_rule_set(array('node' => array('type' => 'node')), array('node'));
750
    $set->integrityCheck()->save('rules_test_set_1');
751

    
752
    $provides = $set->providesVariables();
753
    $this->assertTrue($provides['node']['type'] == 'node', 'Rule set correctly passed through the node.');
754

    
755
    list($node2) = $set->execute($node);
756
    $this->assertTrue($node2->nid == $node->nid, 'Rule set returned a variable.');
757

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

    
770
    // Test the same again with a variable holding a not wrapped data type.
771
    $set = rules_action_set(array(
772
      'number' => array(
773
        'type' => 'integer',
774
        'parameter' => FALSE,
775
      )), array('number'));
776
    $set->action('data_set', array('data:select' => 'number', 'value' => 3));
777
    $set->integrityCheck();
778
    list($number) = $set->execute();
779
    $this->assertTrue($number == 3, 'Actions set returned a number.');
780
  }
781

    
782
  /**
783
   * Tests using input evaluators.
784
   */
785
  public function testInputEvaluators() {
786
    $node = $this->drupalCreateNode(array('title' => '<b>The body & nothing.</b>', 'type' => 'page'));
787

    
788
    $rule = rule(array('nid' => array('type' => 'integer')));
789
    $rule->action('rules_action_load_node')
790
         ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'))
791
         ->execute($node->nid);
792

    
793
    RulesLog::logger()->checkLog();
794
    $msg = drupal_get_messages();
795
    $this->assertEqual(array_pop($msg['status']), 'Title: ' . check_plain('<b>The body & nothing.</b>'), 'Token input evaluator applied.');
796

    
797
    // Test token replacements on a list of text values.
798
    $component = rules_action_set(array('var' => array('type' => 'list<text>', 'label' => 'var')), array('var'));
799
    $component->save('rules_test_input');
800

    
801
    $action = rules_action('component_rules_test_input', array('var' => array('uid: [site:current-user:uid]')));
802
    list($var) = $action->execute();
803
    $uid = $GLOBALS['user']->uid;
804
    $this->assertEqual(array("uid: $uid"), $var, 'Token replacements on a list of values applied.');
805
  }
806

    
807
  /**
808
   * Tests importing and exporting a rule.
809
   */
810
  public function testRuleImportExport() {
811
    $rule = rule(array('nid' => array('type' => 'integer')));
812
    $rule->name = "rules_export_test";
813
    $rule->action('rules_action_load_node')
814
         ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'));
815

    
816
    $export =
817
'{ "rules_export_test" : {
818
    "PLUGIN" : "rule",
819
    "REQUIRES" : [ "rules_test", "rules" ],
820
    "USES VARIABLES" : { "nid" : { "type" : "integer" } },
821
    "DO" : [
822
      { "rules_action_load_node" : { "PROVIDE" : { "node_loaded" : { "node_loaded" : "Loaded content" } } } },
823
      { "drupal_message" : { "message" : "Title: [node_loaded:title]" } }
824
    ]
825
  }
826
}';
827
    $this->assertEqual($export, $rule->export(), 'Rule has been exported correctly.');
828

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

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

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

    
841
    // Now try importing a rule set.
842
    $export =
843
'{ "rules_test_set" : {
844
    "LABEL" : "Test set",
845
    "PLUGIN" : "rule set",
846
    "REQUIRES" : [ "rules" ],
847
    "USES VARIABLES" : { "node" : { "label" : "Test node", "type" : "node" } },
848
    "RULES" : [
849
      { "RULE" : {
850
          "IF" : [ { "NOT data_is" : { "data" : [ "node:title" ], "value" : "test" } } ],
851
          "DO" : [ { "data_set" : { "data" : [ "node:title" ], "value" : "test" } } ],
852
          "LABEL" : "Test Rule"
853
        }
854
      },
855
      { "RULE" : {
856
          "DO" : [ { "drupal_message" : { "message" : "hi" } } ],
857
          "LABEL" : "Test Rule 2"
858
        }
859
      }
860
    ]
861
  }
862
}';
863
    $set = rules_import($export);
864
    $this->assertTrue(!empty($set) && $set->integrityCheck(), 'Rule set has been imported.');
865
    // Export it and make sure the same export is generated again.
866
    $this->assertEqual($export, $set->export(), 'Export of imported rule set equals original export.');
867

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

    
874
    // Try import / export for a rule component providing a variable.
875
    $rule = rule(array(
876
      'number' => array(
877
        'type' => 'integer',
878
        'label' => 'Number',
879
        'parameter' => FALSE,
880
      )), array('number'));
881
    $rule->action('data_set', array('data:select' => 'number', 'value' => 3));
882
    $rule->name = 'rules_test_provides';
883

    
884
    $export = '{ "rules_test_provides" : {
885
    "PLUGIN" : "rule",
886
    "REQUIRES" : [ "rules" ],
887
    "USES VARIABLES" : { "number" : { "type" : "integer", "label" : "Number", "parameter" : false } },
888
    "DO" : [ { "data_set" : { "data" : [ "number" ], "value" : 3 } } ],
889
    "PROVIDES VARIABLES" : [ "number" ]
890
  }
891
}';
892
    $this->assertEqual($export, $rule->export(), 'Rule 2 has been exported correctly.');
893
    $imported_rule = rules_import($rule->export());
894

    
895
    $this->assertTrue(!empty($imported_rule) && $imported_rule->integrityCheck(), 'Rule 2 has been imported.');
896
    $this->assertEqual($export, $imported_rule->export(), 'Export of imported rule 2 equals original export.');
897

    
898
    // Test importing a negated condition component.
899
    $export = '{ "rules_negated_component" : {
900
    "LABEL" : "negated_component",
901
    "PLUGIN" : "or",
902
    "REQUIRES" : [ "rules" ],
903
    "NOT OR" : [ { "data_is_empty" : { "data" : [ "site:slogan" ] } } ]
904
  }
905
}';
906
    $or = rules_import($export);
907
    $this->assertTrue($or->integrityCheck() && $or->isNegated(), 'Negated condition component imported.');
908
  }
909

    
910
  /**
911
   * Tests the named parameter mode.
912
   */
913
  public function testNamedParameters() {
914
    $rule = rule(array('node' => array('type' => 'node')));
915
    $rule->action('rules_action_node_set_title', array('title' => 'foo'));
916
    $rule->integrityCheck();
917

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

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

    
933
    // Test the rule.
934
    $node = $this->drupalCreateNode();
935
    $node->log = NULL;
936
    $rule->execute($node);
937

    
938
    $text = RulesLog::logger()->render();
939
    $msg = RulesTestCase::t('The variable or parameter %message is empty.', array('message'));
940
    $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
941
  }
942

    
943
}
944

    
945
/**
946
 * Test rules data wrappers.
947
 */
948
class RulesTestDataCase extends DrupalWebTestCase {
949

    
950
  /**
951
   * Declares test metadata.
952
   */
953
  public static function getInfo() {
954
    return array(
955
      'name' => 'Rules Data tests',
956
      'description' => 'Tests rules data saving and type matching.',
957
      'group' => 'Rules',
958
    );
959
  }
960

    
961
  /**
962
   * Overrides DrupalWebTestCase::setUp().
963
   */
964
  protected function setUp() {
965
    parent::setUp('rules', 'rules_test');
966
    variable_set('rules_debug_log', TRUE);
967
    // Make sure we don't ran over issues with the node_load static cache.
968
    entity_get_controller('node')->resetCache();
969
  }
970

    
971
  /**
972
   * Tests intelligently saving data.
973
   */
974
  public function testDataSaving() {
975
    $node = $this->drupalCreateNode();
976
    $state = new RulesState(rule());
977
    $state->addVariable('node', $node, array('type' => 'node'));
978
    $wrapper = $state->get('node');
979
    $node->title = 'test';
980
    $wrapper->set($node);
981
    $state->saveChanges('node', $wrapper, FALSE);
982

    
983
    $this->assertFalse($this->drupalGetNodeByTitle('test'), 'Changes have not been saved.');
984
    $state->saveChanges('node', $wrapper, TRUE);
985
    $this->assertTrue($this->drupalGetNodeByTitle('test'), 'Changes have been saved.');
986

    
987
    // Test skipping saving.
988
    $state->addVariable('node2', $node, array(
989
      'type' => 'node',
990
      'skip save' => TRUE,
991
    ));
992
    $wrapper = $state->get('node2');
993
    $node->title = 'test2';
994
    $wrapper->set($node);
995
    $state->saveChanges('node2', $wrapper, TRUE);
996
    $this->assertFalse($this->drupalGetNodeByTitle('test2'), 'Changes have not been saved.');
997

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

    
1006
  /**
1007
   * Tests type matching.
1008
   */
1009
  public function testTypeMatching() {
1010
    $entity = array('type' => 'entity');
1011
    $node = array('type' => 'node');
1012
    $this->assertTrue(RulesData::typesMatch($node, $entity), 'Types match.');
1013
    $this->assertFalse(RulesData::typesMatch($entity, $node), 'Types don\'t match.');
1014

    
1015
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node), 'Types match.');
1016
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $entity), 'Types match.');
1017
    $this->assertTrue(RulesData::typesMatch(array('type' => 'list<node>'), array('type' => 'list')), 'Types match.');
1018
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node + array('bundles' => array('page', 'story'))), 'Types match.');
1019
    $this->assertFalse(RulesData::typesMatch($node, $node + array('bundles' => array('page', 'story'))), 'Types don\'t match.');
1020

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

    
1026
  /**
1027
   * Tests making use of custom wrapper classes.
1028
   */
1029
  public function testCustomWrapperClasses() {
1030
    // Test loading a vocabulary by name, which is done by a custom wrapper.
1031
    $set = rules_action_set(array('vocab' => array('type' => 'taxonomy_vocabulary')), array('vocab'));
1032
    $set->action('drupal_message', array('message:select' => 'vocab:name'));
1033
    $set->integrityCheck();
1034
    list($vocab) = $set->execute('tags');
1035
    $this->assertTrue($vocab->machine_name == 'tags', 'Loaded vocabulary by name.');
1036

    
1037
    // Now test wrapper creation for a direct input argument value.
1038
    $set = rules_action_set(array('term' => array('type' => 'taxonomy_term')));
1039
    $set->action('data_set', array('data:select' => 'term:vocabulary', 'value' => 'tags'));
1040
    $set->integrityCheck();
1041

    
1042
    $vocab = entity_create('taxonomy_vocabulary', array(
1043
      'name' => 'foo',
1044
      'machine_name' => 'foo',
1045
    ));
1046
    entity_save('taxonomy_vocabulary', $vocab);
1047
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1048
      'name' => $this->randomName(),
1049
      'vocabulary' => $vocab,
1050
    ))->save();
1051
    $set->execute($term_wrapped);
1052
    $this->assertEqual($term_wrapped->vocabulary->machine_name->value(), 'tags', 'Vocabulary name used as direct input value.');
1053
    RulesLog::logger()->checkLog();
1054
  }
1055

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

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

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

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

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

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

    
1095
}
1096

    
1097
/**
1098
 * Test triggering rules.
1099
 */
1100
class RulesTriggerTestCase extends DrupalWebTestCase {
1101

    
1102
  /**
1103
   * Declares test metadata.
1104
   */
1105
  public static function getInfo() {
1106
    return array(
1107
      'name' => 'Reaction Rules',
1108
      'description' => 'Tests triggering reactive rules.',
1109
      'group' => 'Rules',
1110
    );
1111
  }
1112

    
1113
  /**
1114
   * Overrides DrupalWebTestCase::setUp().
1115
   */
1116
  protected function setUp() {
1117
    parent::setUp('rules', 'rules_test');
1118
    RulesLog::logger()->clear();
1119
    variable_set('rules_debug_log', TRUE);
1120
  }
1121

    
1122
  /**
1123
   * Helper function to create a test Rule.
1124
   */
1125
  protected function createTestRule($action = TRUE, $event = 'node_presave') {
1126
    $rule = rules_reaction_rule();
1127
    $rule->event($event)
1128
         ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate())
1129
         ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page'));
1130
    if ($action) {
1131
      $rule->action('rules_action_delete_node');
1132
    }
1133
    return $rule;
1134
  }
1135

    
1136
  /**
1137
   * Tests CRUD for reaction rules - making sure the events are stored properly.
1138
   */
1139
  public function testReactiveRuleCreation() {
1140
    $rule = $this->createTestRule();
1141
    $rule->save();
1142
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1143
    $this->assertEqual($result->fetchField(), 'node_presave', 'Associated event has been saved.');
1144
    // Try updating.
1145
    $rule->removeEvent('node_presave');
1146
    $rule->event('node_insert');
1147
    $rule->event('node_update');
1148
    $rule->active = FALSE;
1149
    $rule->integrityCheck()->save();
1150
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1151
    $this->assertEqual($result->fetchCol(), array_values($rule->events()), 'Updated associated events.');
1152
    // Try deleting.
1153
    $rule->delete();
1154
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1155
    $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated events.');
1156
  }
1157

    
1158
  /**
1159
   * Tests creating and triggering a basic reaction rule.
1160
   */
1161
  public function testBasicReactionRule() {
1162
    $node = $this->drupalCreateNode(array('type' => 'page'));
1163
    $rule = $this->createTestRule();
1164
    $rule->integrityCheck()->save();
1165
    // Test the basics of the event set work right.
1166
    $event = rules_get_cache('event_node_presave');
1167
    $this->assertEqual(array_keys($event->parameterInfo()), array('node'), 'EventSet returns correct argument info.');
1168

    
1169
    // Trigger the rule by updating the node.
1170
    $nid = $node->nid;
1171
    $node->status = 0;
1172
    node_save($node);
1173

    
1174
    RulesLog::logger()->checkLog();
1175
    $this->assertFalse(node_load($nid), 'Rule successfully triggered and executed');
1176
    // debug(RulesLog::logger()->render());
1177
  }
1178

    
1179
  /**
1180
   * Tests a rule using a handler to load a variable.
1181
   */
1182
  public function testVariableHandler() {
1183
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1184
    $rule = $this->createTestRule(FALSE, 'node_update');
1185
    $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged'));
1186
    // Test without recursion prevention to make sure recursive invocations
1187
    // work right too. This rule won't ran in an infinite loop anyway.
1188
    $rule->recursion = TRUE;
1189
    $rule->label = 'rule 1';
1190
    $rule->integrityCheck()->save();
1191

    
1192
    $node->status = 0;
1193
    $node->sticky = 1;
1194
    node_save($node);
1195

    
1196
    RulesLog::logger()->checkLog();
1197
    entity_get_controller('node')->resetCache();
1198
    $node = node_load($node->nid);
1199

    
1200
    $this->assertFalse($node->sticky, 'Parameter has been loaded and saved.');
1201
    $this->assertTrue($node->status, 'Action has been executed.');
1202

    
1203
    // Ensure the rule was evaluated a second time.
1204
    $text = RulesLog::logger()->render();
1205
    $msg = RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1'));
1206
    $pos = strpos($text, $msg);
1207
    $pos = ($pos !== FALSE) ? strpos($text, $msg, $pos) : FALSE;
1208
    $this->assertTrue($pos !== FALSE, "Recursion prevented.");
1209
    // debug(RulesLog::logger()->render());
1210
  }
1211

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

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

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

    
1228
  /**
1229
   * Tests preventing recursive rule invocations.
1230
   *
1231
   * Creates a rule that reacts on node-update then generates a node update
1232
   * that would trigger it itself.
1233
   */
1234
  public function testRecursionPrevention() {
1235
    $rule = $this->createTestRule(FALSE, 'node_update');
1236
    $rule->action('rules_node_make_sticky_action');
1237
    $rule->integrityCheck()->save();
1238

    
1239
    // Now trigger the rule.
1240
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1241
    node_save($node);
1242

    
1243
    $text = RulesTestCase::t('Not evaluating reaction rule %label to prevent recursion.', array('label' => $rule->name));
1244
    // debug(RulesLog::logger()->render());
1245
    $this->assertTrue((strpos(RulesLog::logger()->render(), $text) !== FALSE), "Recursion prevented.");
1246
    // debug(RulesLog::logger()->render());
1247
  }
1248

    
1249
  /**
1250
   * Tests recursion prevention with altered arguments.
1251
   *
1252
   * Ensure the recursion prevention still allows the rule to trigger again
1253
   * during evaluation of the same event set, if the event isn't caused by the
1254
   * rule itself - thus we won't run in an infinite loop.
1255
   */
1256
  public function testRecursionOnDifferentArguments() {
1257
    // Create rule1 - which might recurse.
1258
    $rule = $this->createTestRule(FALSE, 'node_update');
1259
    $rule->action('rules_node_make_sticky_action');
1260
    $rule->label = 'rule 1';
1261
    $rule->integrityCheck()->save();
1262

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

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

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

    
1285
  /**
1286
   * Tests the provided default rule 'rules_test_default_1'.
1287
   */
1288
  public function testDefaultRule() {
1289
    $rule = rules_config_load('rules_test_default_1');
1290
    $this->assertTrue($rule->status & ENTITY_IN_CODE && !($rule->status & ENTITY_IN_DB), 'Default rule can be loaded and has the right status.');
1291
    $this->assertTrue($rule->tags == array('Admin', 'Tag2'), 'Default rule has correct tags.');
1292
    // Enable.
1293
    $rule->active = TRUE;
1294
    $rule->save();
1295

    
1296
    // Create a node that triggers the rule.
1297
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1298
    // Clear messages.
1299
    drupal_get_messages();
1300
    // Let event node_update occur.
1301
    node_save($node);
1302

    
1303
    $msg = drupal_get_messages();
1304
    $this->assertEqual($msg['status'][0], 'A node has been updated.', 'Default rule has been triggered.');
1305
  }
1306

    
1307
  /**
1308
   * Tests creating and triggering a reaction rule with event settings.
1309
   */
1310
  public function testEventSettings() {
1311
    $rule = rules_reaction_rule();
1312
    $rule->event('node_presave', array('bundle' => 'article'))
1313
      ->condition('data_is_empty', array('data:select' => 'node:field-tags'))
1314
      ->action('node_publish', array('node:select' => 'node'));
1315
    $rule->integrityCheck()->save();
1316

    
1317
    $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 0));
1318
    $this->assertEqual($node->status, 0, 'Rule has not been triggered.');
1319
    $node = $this->drupalCreateNode(array('type' => 'article', 'status' => 0));
1320
    $this->assertEqual($node->status, 1, 'Rule has been triggered.');
1321
    RulesLog::logger()->checkLog();
1322

    
1323
    // Make sure an invalid bundle raises integrity problems.
1324
    $rule->event('node_presave', array('bundle' => 'invalid'));
1325
    try {
1326
      $rule->integrityCheck();
1327
      $this->fail('Integrity check failed.');
1328
    }
1329
    catch (RulesIntegrityException $e) {
1330
      $this->pass('Integrity check failed: ' . $e);
1331
    }
1332
  }
1333

    
1334
}
1335

    
1336
/**
1337
 * Tests provided module integration.
1338
 */
1339
class RulesIntegrationTestCase extends DrupalWebTestCase {
1340

    
1341
  /**
1342
   * Declares test metadata.
1343
   */
1344
  public static function getInfo() {
1345
    return array(
1346
      'name' => 'Rules Core Integration',
1347
      'description' => 'Tests provided integration for drupal core.',
1348
      'group' => 'Rules',
1349
    );
1350
  }
1351

    
1352
  /**
1353
   * Overrides DrupalWebTestCase::setUp().
1354
   */
1355
  protected function setUp() {
1356
    parent::setUp('rules', 'rules_test', 'php', 'path');
1357
    RulesLog::logger()->clear();
1358
    variable_set('rules_debug_log', TRUE);
1359
  }
1360

    
1361
  /**
1362
   * Just makes sure the access callback run without errors.
1363
   */
1364
  public function testAccessCallbacks() {
1365
    $cache = rules_get_cache();
1366
    foreach (array('action', 'condition', 'event') as $type) {
1367
      foreach (rules_fetch_data($type . '_info') as $name => $info) {
1368
        if (isset($info['access callback'])) {
1369
          $info['access callback']($type, $name);
1370
        }
1371
      }
1372
    }
1373
  }
1374

    
1375
  /**
1376
   * Tests data integration.
1377
   */
1378
  public function testDataIntegration() {
1379
    // Test data_create action.
1380
    $action = rules_action('data_create', array(
1381
      'type' => 'log_entry',
1382
      'param_type' => 'rules_test',
1383
      'param_message' => 'Rules test log message',
1384
      'param_severity' => WATCHDOG_WARNING,
1385
      'param_request_uri' => 'http://example.com',
1386
      'param_link' => '',
1387
    ));
1388
    $action->access();
1389
    $action->execute();
1390
    $text = RulesLog::logger()->render();
1391
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %data_created of type %log_entry', array('data_created', 'log_entry')));
1392
    $this->assertTrue($pos !== FALSE, 'Data of type log entry has been created.');
1393

    
1394
    // Test variable_add action.
1395
    $action = rules_action('variable_add', array(
1396
      'type' => 'text_formatted',
1397
      'value' => array(
1398
        'value' => 'test text',
1399
        'format' => 1,
1400
      ),
1401
    ));
1402
    $action->access();
1403
    $action->execute();
1404
    $text = RulesLog::logger()->render();
1405
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %variable_added of type %text_formatted', array('variable_added', 'text_formatted')));
1406
    $this->assertTrue($pos !== FALSE, 'Data of type text formatted has been created.');
1407

    
1408
    // Test using the list actions.
1409
    $rule = rule(array(
1410
      'list' => array(
1411
        'type' => 'list<text>',
1412
        'label' => 'A list of text',
1413
      ),
1414
    ));
1415
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar2'));
1416
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'pos' => 'start'));
1417
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'unique' => TRUE));
1418
    $rule->action('list_remove', array('list:select' => 'list', 'item' => 'bar2'));
1419
    $list = entity_metadata_wrapper('list', array('foo', 'foo2'));
1420
    $rule->execute($list);
1421
    RulesLog::logger()->checkLog();
1422
    $this->assertEqual($list->value(), array('bar', 'foo', 'foo2'), 'List items removed and added.');
1423
    $this->assertFalse(rules_condition('list_contains')->execute($list, 'foo-bar'), 'Condition "List item contains" evaluates to FALSE');
1424
    $this->assertTrue(rules_condition('list_contains')->execute($list, 'foo'), 'Condition "List item contains" evaluates to TRUE');
1425
    // debug(RulesLog::logger()->render());
1426

    
1427
    // Test data_is condition with IN operation.
1428
    $rule = rule(array('node' => array('type' => 'node')));
1429
    $rule->condition('data_is', array('data:select' => 'node:title', 'op' => 'IN', 'value' => array('foo', 'bar')));
1430
    $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar'));
1431
    $rule->integrityCheck();
1432

    
1433
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1434
    $rule->execute($node);
1435
    $this->assertEqual($node->title, 'bar', "Data comparison using IN operation evaluates to TRUE.");
1436

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

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

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

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

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

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

    
1482
    // Test data type conversion action.
1483
    $set = rules_action_set(array('result' => array('type' => 'text', 'parameter' => FALSE)), array('result'));
1484
    $set->action('data_convert', array('type' => 'text', 'value:select' => 'site:login-url'));
1485
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1486
    list($result) = $set->execute();
1487
    $set->integrityCheck();
1488
    $this->assertEqual($result, url('user', array('absolute' => TRUE)), 'Converted URI to text.');
1489

    
1490
    $set = rules_action_set(array(
1491
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1492
      'source' => array('type' => 'text'),
1493
    ), array('result'));
1494
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source'));
1495
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1496
    list($result) = $set->execute('9.4');
1497
    $this->assertEqual($result, 9, 'Converted decimal to integer using rounding.');
1498

    
1499
    $set = rules_action_set(array(
1500
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1501
      'source' => array('type' => 'text'),
1502
    ), array('result'));
1503
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'down'));
1504
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1505
    list($result) = $set->execute('9.6');
1506
    $this->assertEqual($result, 9, 'Converted decimal to integer using rounding behavior down.');
1507

    
1508
    $set = rules_action_set(array(
1509
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1510
      'source' => array('type' => 'text'),
1511
    ), array('result'));
1512
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'up'));
1513
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1514
    list($result) = $set->execute('9.4');
1515
    $this->assertEqual($result, 10, 'Converted decimal to integer using rounding behavior up.');
1516

    
1517
    // Test text matching condition.
1518
    $result = rules_condition('text_matches')->execute('my-text', 'text', 'contains');
1519
    $result2 = rules_condition('text_matches')->execute('my-text', 'tex2t', 'contains');
1520
    $this->assertTrue($result && !$result2, 'Text matching condition using operation contain evaluated.');
1521

    
1522
    $result = rules_condition('text_matches')->execute('my-text', 'my', 'starts');
1523
    $result2 = rules_condition('text_matches')->execute('my-text', 'text', 'starts');
1524
    $this->assertTrue($result && !$result2, 'Text matching condition using operation starts evaluated.');
1525

    
1526
    $result = rules_condition('text_matches')->execute('my-text', 'text', 'ends');
1527
    $result2 = rules_condition('text_matches')->execute('my-text', 'my', 'ends');
1528
    $this->assertTrue($result && !$result2, 'Text matching condition using operation ends evaluated.');
1529

    
1530
    $result = rules_condition('text_matches')->execute('my-text', 'me?y-texx?t', 'regex');
1531
    $result2 = rules_condition('text_matches')->execute('my-text', 'me+y-texx?t', 'regex');
1532
    $this->assertTrue($result && !$result2, 'Text matching condition using operation regex evaluated.');
1533
  }
1534

    
1535
  /**
1536
   * Tests entity related integration.
1537
   */
1538
  public function testEntityIntegration() {
1539
    global $user;
1540

    
1541
    $page = $this->drupalCreateNode(array('type' => 'page'));
1542
    $article = $this->drupalCreateNode(array('type' => 'article'));
1543

    
1544
    $result = rules_condition('entity_field_access')
1545
      ->execute(entity_metadata_wrapper('node', $article), 'field_tags');
1546
    $this->assertTrue($result);
1547

    
1548
    // Test entity_is_of_bundle condition.
1549
    $result = rules_condition('entity_is_of_bundle', array(
1550
      'type' => 'node',
1551
      'bundle' => array('article'),
1552
    ))->execute(entity_metadata_wrapper('node', $page));
1553
    $this->assertFalse($result, 'Entity is of bundle condition has not been met.');
1554
    $result = rules_condition('entity_is_of_bundle', array(
1555
      'type' => 'node',
1556
      'bundle' => array('article'),
1557
    ))->execute(entity_metadata_wrapper('node', $article));
1558
    $this->assertTrue($result, 'Entity is of bundle condition has been met.');
1559

    
1560
    // Also test a full rule so the integrity check must work.
1561
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1562
      'name' => $this->randomName(),
1563
      'vocabulary' => 1,
1564
    ))->save();
1565
    $rule = rule(array(
1566
      'node' => array('type' => 'node'),
1567
    ));
1568
    $rule->condition('entity_is_of_bundle', array(
1569
      'entity:select' => 'node',
1570
      'bundle' => array('article'),
1571
    ));
1572
    $rule->action('data_set', array('data:select' => 'node:field_tags', 'value' => array($term_wrapped->getIdentifier())));
1573
    $rule->integrityCheck();
1574
    $rule->execute($article);
1575
    $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
1576

    
1577
    // Test again using an entity variable.
1578
    $article = $this->drupalCreateNode(array('type' => 'article'));
1579
    $rule = rule(array(
1580
      'entity' => array('type' => 'entity'),
1581
    ));
1582
    $rule->condition('entity_is_of_bundle', array(
1583
      'entity:select' => 'entity',
1584
      'type' => 'node',
1585
      'bundle' => array('article'),
1586
    ));
1587
    $rule->action('data_set', array('data:select' => 'entity:field_tags', 'value' => array($term_wrapped->getIdentifier())));
1588
    $rule->integrityCheck();
1589
    $rule->execute(entity_metadata_wrapper('node', $article));
1590
    $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
1591

    
1592
    // Test CRUD actions.
1593
    $action = rules_action('entity_create', array(
1594
      'type' => 'node',
1595
      'param_type' => 'page',
1596
      'param_title' => 'foo',
1597
      'param_author' => $GLOBALS['user'],
1598
    ));
1599
    $action->access();
1600
    $action->execute();
1601
    $text = RulesLog::logger()->render();
1602
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %entity_created of type %node', array('entity_created', 'node')));
1603
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %entity_created of type %node.', array('entity_created', 'node')), $pos) : FALSE;
1604
    $this->assertTrue($pos !== FALSE, 'Data has been created and saved.');
1605

    
1606
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1607
    $rule = rule();
1608
    $rule->action('entity_fetch', array('type' => 'node', 'id' => $node->nid, 'entity_fetched:var' => 'node'));
1609
    $rule->action('entity_save', array('data:select' => 'node', 'immediate' => TRUE));
1610
    $rule->action('entity_delete', array('data:select' => 'node'));
1611
    $rule->access();
1612
    $rule->integrityCheck()->execute();
1613

    
1614
    $text = RulesLog::logger()->render();
1615
    $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch')));
1616
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Added the provided variable %node of type %node', array('node')), $pos) : FALSE;
1617
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node of type %node.', array('node')), $pos) : FALSE;
1618
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE;
1619
    $this->assertTrue($pos !== FALSE, 'Data has been fetched, saved and deleted.');
1620
    // debug(RulesLog::logger()->render());
1621

    
1622
    $node = entity_property_values_create_entity('node', array(
1623
      'type' => 'article',
1624
      'author' => $user,
1625
      'title' => 'foo',
1626
    ))->value();
1627
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1628
      'name' => $this->randomName(),
1629
      'vocabulary' => 1,
1630
    ))->save();
1631

    
1632
    // Test asserting the field and using it afterwards.
1633
    $rule = rule(array('node' => array('type' => 'node')));
1634
    $rule->condition('entity_has_field', array('entity:select' => 'node', 'field' => 'field_tags'));
1635
    $rule->condition('entity_is_new', array('entity:select' => 'node'));
1636
    $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped));
1637
    $rule->integrityCheck();
1638
    $rule->execute($node);
1639

    
1640
    $tid = $term_wrapped->getIdentifier();
1641
    $this->assertEqual(array_values($node->field_tags[LANGUAGE_NONE]), array(0 => array('tid' => $tid)), 'Entity has field conditions evaluted.');
1642

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

    
1648
    // Test the entity is of type condition.
1649
    $rule = rule(array('entity' => array('type' => 'entity', 'label' => 'entity')));
1650
    $rule->condition('entity_is_of_type', array('type' => 'node'));
1651
    $rule->action('data_set', array('data:select' => 'entity:title', 'value' => 'bar'));
1652
    $rule->integrityCheck();
1653
    $rule->execute(entity_metadata_wrapper('node', $node));
1654

    
1655
    $this->assertEqual(entity_metadata_wrapper('node', $node->nid)->title->value(), 'bar', 'Entity is of type condition correctly asserts the entity type.');
1656

    
1657
    // Test the entity_query action.
1658
    $node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'foo2'));
1659
    $rule = rule();
1660
    $rule->action('entity_query', array('type' => 'node', 'property' => 'title', 'value' => 'foo2'))
1661
         ->action('data_set', array('data:select' => 'entity_fetched:0:title', 'value' => 'bar'));
1662
    $rule->access();
1663
    $rule->integrityCheck();
1664
    $rule->execute();
1665
    $node = node_load($node->nid);
1666
    $this->assertEqual('bar', $node->title, 'Fetched a node by title and modified it.');
1667

    
1668
    RulesLog::logger()->checkLog();
1669
  }
1670

    
1671
  /**
1672
   * Tests integration for the taxonomy module.
1673
   */
1674
  public function testTaxonomyIntegration() {
1675
    $term = entity_property_values_create_entity('taxonomy_term', array(
1676
      'name' => $this->randomName(),
1677
      'vocabulary' => 1,
1678
    ))->value();
1679
    $term2 = clone $term;
1680
    taxonomy_term_save($term);
1681
    taxonomy_term_save($term2);
1682

    
1683
    $tags[LANGUAGE_NONE][0]['tid'] = $term->tid;
1684
    $node = $this->drupalCreateNode(array('title' => 'foo', 'type' => 'article', 'field_tags' => $tags));
1685

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

    
1696
    // Test using the taxonomy term reference field on a term object.
1697
    $field_name = drupal_strtolower($this->randomName() . '_field_name');
1698
    $field = field_create_field(array(
1699
      'field_name' => $field_name,
1700
      'type' => 'taxonomy_term_reference',
1701
      // Set cardinality to unlimited for tagging.
1702
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
1703
      'settings' => array(
1704
        'allowed_values' => array(
1705
          array(
1706
            'vocabulary' => 'tags',
1707
            'parent' => 0,
1708
          ),
1709
        ),
1710
      ),
1711
    ));
1712
    $instance = array(
1713
      'field_name' => $field_name,
1714
      'entity_type' => 'taxonomy_term',
1715
      'bundle' => 'tags', // Machine name of vocabulary.
1716
      'label' => $this->randomName() . '_label',
1717
      'description' => $this->randomName() . '_description',
1718
      'weight' => mt_rand(0, 127),
1719
      'widget' => array(
1720
        'type' => 'taxonomy_autocomplete',
1721
        'weight' => -4,
1722
      ),
1723
      'display' => array(
1724
        'default' => array(
1725
          'type' => 'taxonomy_term_reference_link',
1726
          'weight' => 10,
1727
        ),
1728
      ),
1729
    );
1730
    field_create_instance($instance);
1731

    
1732
    $term1 = entity_property_values_create_entity('taxonomy_term', array(
1733
      'name' => $this->randomName(),
1734
      'vocabulary' => 1,
1735
    ))->save();
1736
    $term2 = entity_property_values_create_entity('taxonomy_term', array(
1737
      'name' => $this->randomName(),
1738
      'vocabulary' => 1,
1739
    ))->save();
1740

    
1741
    // Test asserting the term reference field and using it afterwards.
1742
    $rule = rule(array('taxonomy_term' => array('type' => 'taxonomy_term')));
1743
    $rule->condition('entity_has_field', array('entity:select' => 'taxonomy-term', 'field' => $field_name));
1744
    // Add $term2 to $term1 using the term reference field.
1745
    $selector = str_replace('_', '-', 'taxonomy_term:' . $field_name);
1746
    $rule->action('list_add', array('list:select' => $selector, 'item' => $term2));
1747
    $rule->integrityCheck();
1748
    $rule->execute($term1);
1749

    
1750
    RulesLog::logger()->checkLog();
1751
    $this->assertEqual($term1->{$field_name}[0]->getIdentifier(), $term2->getIdentifier(), 'Rule appended a term to the term reference field on a term.');
1752

    
1753
    // Test an action set for merging term parents, which is provided as default
1754
    // config.
1755
    $term = entity_property_values_create_entity('taxonomy_term', array(
1756
      'name' => $this->randomName(),
1757
      'vocabulary' => 1,
1758
      'parent' => array($term1->value()),
1759
    ))->save();
1760

    
1761
    $action = rules_action('component_rules_retrieve_term_parents');
1762
    list($parents) = $action->execute(array($term->getIdentifier()));
1763
    $this->assertTrue($parents[0]->tid == $term1->getIdentifier(), 'Invoked component to retrieve term parents.');
1764
    RulesLog::logger()->checkLog();
1765
  }
1766

    
1767
  /**
1768
   * Tests integration for the node module.
1769
   */
1770
  public function testNodeIntegration() {
1771
    $tests = array(
1772
      array('node_unpublish', 'node_is_published', 'node_publish', 'status'),
1773
      array('node_make_unsticky', 'node_is_sticky', 'node_make_sticky', 'sticky'),
1774
      array('node_unpromote', 'node_is_promoted', 'node_promote', 'promote'),
1775
    );
1776
    $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 1, 'sticky' => 1, 'promote' => 1));
1777

    
1778
    foreach ($tests as $info) {
1779
      list($action1, $condition, $action2, $property) = $info;
1780
      rules_action($action1)->execute($node);
1781

    
1782
      $node = node_load($node->nid, NULL, TRUE);
1783
      $this->assertFalse($node->$property, 'Action has permanently disabled node ' . $property);
1784
      $return = rules_condition($condition)->execute($node);
1785
      $this->assertFalse($return, 'Condition determines node ' . $property . ' is disabled.');
1786

    
1787
      rules_action($action2)->execute($node);
1788
      $node = node_load($node->nid, NULL, TRUE);
1789
      $this->assertTrue($node->$property, 'Action has permanently enabled node ' . $property);
1790
      $return = rules_condition($condition)->execute($node);
1791
      $this->assertTrue($return, 'Condition determines node ' . $property . ' is enabled.');
1792
    }
1793

    
1794
    $return = rules_condition('node_is_of_type', array('type' => array('page', 'article')))->execute($node);
1795
    $this->assertTrue($return, 'Condition determines node is of type page.');
1796
    $return = rules_condition('node_is_of_type', array('type' => array('article')))->execute($node);
1797
    $this->assertFalse($return, 'Condition determines node is not of type article.');
1798

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

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

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

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

    
1830
    // Test assigning a role with user_add_role action.
1831
    $rule = rule(array('user' => array('type' => 'user')));
1832
    $rule->action('user_add_role', array('account:select' => 'user', 'roles' => array($rid)));
1833
    $rule->execute($user);
1834

    
1835
    $user = user_load($user->uid, TRUE);
1836
    $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
1837
    $this->assertTrue($result, 'Role assigned to user.');
1838

    
1839
    // Test removing a role with the user_remove_role action.
1840
    $rule = rule(array('user' => array('type' => 'user')));
1841
    $rule->action('user_remove_role', array('account:select' => 'user', 'roles' => array($rid)));
1842
    $rule->execute($user);
1843

    
1844
    $user = user_load($user->uid, TRUE);
1845
    $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
1846
    $this->assertFalse($result, 'Role removed from user.');
1847

    
1848
    // Test user blocking.
1849
    rules_action('user_block')->execute($user);
1850
    $user = user_load($user->uid, TRUE);
1851
    $this->assertTrue(rules_condition('user_is_blocked')->execute($user), 'User has been blocked.');
1852

    
1853
    rules_action('user_unblock')->execute($user);
1854
    $user = user_load($user->uid, TRUE);
1855
    $this->assertFalse(rules_condition('user_is_blocked')->execute($user), 'User has been unblocked.');
1856

    
1857
    RulesLog::logger()->checkLog();
1858
  }
1859

    
1860
  /**
1861
   * Tests integration for the php module.
1862
   */
1863
  public function testPHPIntegration() {
1864
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1865
    $rule = rule(array('var_name' => array('type' => 'node')));
1866
    $rule->condition('php_eval', array('code' => 'return TRUE;'))
1867
         ->action('php_eval', array('code' => 'drupal_set_message("Executed-" . $var_name->title);'))
1868
         ->action('drupal_message', array('message' => 'Title: <?php echo $var_name->title; ?> Token: [var_name:title]'));
1869

    
1870
    $rule->execute($node);
1871
    $rule->access();
1872
    RulesLog::logger()->checkLog();
1873
    $msg = drupal_get_messages();
1874
    $this->assertEqual(array_pop($msg['status']), "Title: foo Token: foo", 'PHP input evaluation has been applied.');
1875
    $this->assertEqual(array_pop($msg['status']), "Executed-foo", 'PHP code condition and action have been evaluated.');
1876

    
1877
    // Test PHP data processor.
1878
    $rule = rule(array('var_name' => array('type' => 'node')));
1879
    $rule->action('drupal_message', array(
1880
      'message:select' => 'var_name:title',
1881
      'message:process' => array(
1882
        'php' => array('code' => 'return "Title: $value";'),
1883
      ),
1884
    ));
1885
    $rule->execute($node);
1886
    $rule->access();
1887
    RulesLog::logger()->checkLog();
1888
    $msg = drupal_get_messages();
1889
    $this->assertEqual(array_pop($msg['status']), "Title: foo", 'PHP data processor has been applied.');
1890
  }
1891

    
1892
  /**
1893
   * Tests the "rules_core" integration.
1894
   */
1895
  public function testRulesCoreIntegration() {
1896
    // Make sure the date input evaluator evaluates properly using strtotime().
1897
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1898
    $rule = rule(array('node' => array('type' => 'node')));
1899
    $rule->action('data_set', array('data:select' => 'node:created', 'value' => '+1 day'));
1900

    
1901
    $rule->execute($node);
1902
    RulesLog::logger()->checkLog();
1903
    $node = node_load($node->nid, NULL, TRUE);
1904
    $now = RulesDateInputEvaluator::gmstrtotime('now');
1905
    // Tolerate a difference of a second.
1906
    $this->assertTrue(abs($node->created - $now - 86400) <= 1, 'Date input has been evaluated.');
1907

    
1908
    // Test using a numeric offset.
1909
    $rule = rule(array('number' => array('type' => 'decimal')), array('number'));
1910
    $rule->action('data_set', array(
1911
      'data:select' => 'number',
1912
      'value:select' => 'number',
1913
      'value:process' => array(
1914
        'num_offset' => array('value' => 1),
1915
      ),
1916
    ));
1917
    $rule->integrityCheck();
1918
    list($result) = $rule->execute(10);
1919
    $this->assertTrue($result == 11, 'Numeric offset has been applied');
1920

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

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

    
1947
    // Test using a positive offset of 1 year 6 months and 30 minutes.
1948
    $set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
1949
    $set->action('data_set', array(
1950
      'data:select' => 'date',
1951
      'value:select' => 'date',
1952
      'value:process' => array(
1953
        'date_offset' => array('value' => 86400 * 30 * 18 + 30 * 60),
1954
      ),
1955
    ));
1956
    $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U');
1957
    list($result) = $set->execute($date);
1958
    $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.');
1959

    
1960
    RulesLog::logger()->checkLog();
1961
  }
1962

    
1963
  /**
1964
   * Tests site/system integration.
1965
   */
1966
  public function testSystemIntegration() {
1967
    // Test using the 'site' variable.
1968
    $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => $GLOBALS['user']->name));
1969
    $this->assertTrue($condition->execute(), 'Retrieved the current user\'s name.');
1970
    // Another test using a token replacement.
1971
    $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => '[site:current-user:name]'));
1972
    $this->assertTrue($condition->execute(), 'Replaced the token for the current user\'s name.');
1973

    
1974
    // Test breadcrumbs and drupal set message.
1975
    $rule = rules_reaction_rule();
1976
    $rule->event('init')
1977
         ->action('breadcrumb_set', array('titles' => array('foo'), 'paths' => array('bar')))
1978
         ->action('drupal_message', array('message' => 'A message.'));
1979
    $rule->save('test');
1980

    
1981
    $this->drupalGet('node');
1982
    $this->assertLink('foo', 0, 'Breadcrumb has been set.');
1983
    $this->assertText('A message.', 'Drupal message has been shown.');
1984

    
1985
    // Test the page redirect.
1986
    $node = $this->drupalCreateNode();
1987
    $rule = rules_reaction_rule();
1988
    $rule->event('node_view')
1989
         ->action('redirect', array('url' => 'user'));
1990
    $rule->save('test2');
1991

    
1992
    $this->drupalGet('node/' . $node->nid);
1993
    $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE)), 'Redirect has been issued.');
1994

    
1995
    // Also test using a url including a fragment.
1996
    $actions = $rule->actions();
1997
    $actions[0]->settings['url'] = 'user#fragment';
1998
    $rule->save();
1999

    
2000
    $this->drupalGet('node/' . $node->nid);
2001
    $this->assertEqual($this->getUrl(), url('user', array('absolute' => TRUE, 'fragment' => 'fragment')), 'Redirect has been issued.');
2002

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

    
2009
    rules_action('mail', $settings + array('from' => 'sender@example.com'))->execute();
2010
    $this->assertMail('from', 'sender@example.com', 'Specified from address has been used');
2011

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

    
2016
    // Now make sure there is a custom role and two users with that role.
2017
    $user1 = $this->drupalCreateUser(array('administer nodes'));
2018
    $roles = $user1->roles;
2019
    // Remove the authenticated role so we only use the new role created by
2020
    // drupalCreateUser().
2021
    unset($roles[DRUPAL_AUTHENTICATED_RID]);
2022

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

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

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

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

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

    
2051
    // Test reacting on new log entries and make sure the log entry is usable.
2052
    $rule = rules_reaction_rule();
2053
    $rule->event('watchdog');
2054
    $rule->action('drupal_message', array('message:select' => 'log_entry:message'));
2055
    $rule->integrityCheck()->save('test_watchdog');
2056

    
2057
    watchdog('php', 'test %message', array('%message' => 'message'));
2058
    $msg = drupal_get_messages();
2059
    $this->assertEqual(array_pop($msg['status']), t('test %message', array('%message' => 'message')), 'Watchdog event occurred and log entry properties can be used.');
2060
  }
2061

    
2062
  /**
2063
   * Tests the path module integration.
2064
   */
2065
  public function testPathIntegration() {
2066
    rules_action('path_alias')->execute('foo', 'bar');
2067
    $path = path_load('foo');
2068
    $this->assertTrue($path['alias'] == 'bar', 'URL alias has been created.');
2069

    
2070
    $alias_exists = rules_condition('path_alias_exists', array('alias' => 'bar'))->execute();
2071
    $this->assertTrue($alias_exists, 'Created URL alias exists.');
2072

    
2073
    $has_alias = rules_condition('path_has_alias', array('source' => 'foo'))->execute();
2074
    $this->assertTrue($has_alias, 'System path has an alias.');
2075

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

    
2082
    // Test term alias action.
2083
    $term = entity_property_values_create_entity('taxonomy_term', array(
2084
      'name' => $this->randomName(),
2085
      'vocabulary' => 1,
2086
    ))->value();
2087
    rules_action('taxonomy_term_path_alias')->execute($term, 'term-test');
2088
    $path = path_load("taxonomy/term/$term->tid");
2089
    $this->assertTrue($path['alias'] == 'term-test', 'Term URL alias has been created.');
2090

    
2091
    RulesLog::logger()->checkLog();
2092
  }
2093

    
2094
}
2095

    
2096
/**
2097
 * Tests event dispatcher functionality.
2098
 */
2099
class RulesEventDispatcherTestCase extends DrupalWebTestCase {
2100

    
2101
  /**
2102
   * Declares test metadata.
2103
   */
2104
  public static function getInfo() {
2105
    return array(
2106
      'name' => 'Rules event dispatchers',
2107
      'description' => 'Tests event dispatcher functionality.',
2108
      'group' => 'Rules',
2109
    );
2110
  }
2111

    
2112
  /**
2113
   * Overrides DrupalWebTestCase::setUp().
2114
   */
2115
  protected function setUp() {
2116
    parent::setUp('rules', 'rules_test');
2117
  }
2118

    
2119
  /**
2120
   * Tests start and stop functionality.
2121
   */
2122
  public function testStartAndStop() {
2123
    $handler = rules_get_event_handler('rules_test_event');
2124
    $rule = rules_reaction_rule();
2125
    $rule->event('rules_test_event');
2126

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

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

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

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

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

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

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

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

    
2168
}
2169

    
2170
/**
2171
 * Test early bootstrap Rules invocation.
2172
 */
2173
class RulesInvocationEnabledTestCase extends DrupalWebTestCase {
2174

    
2175
  /**
2176
   * Declares test metadata.
2177
   */
2178
  public static function getInfo() {
2179
    return array(
2180
      'name' => 'Rules invocation enabled',
2181
      'description' => 'Tests that Rules events are enabled during menu item loads.',
2182
      'group' => 'Rules',
2183
    );
2184
  }
2185

    
2186
  /**
2187
   * Overrides DrupalWebTestCase::setUp().
2188
   */
2189
  protected function setUp() {
2190
    parent::setUp('dblog', 'rules', 'rules_test', 'rules_test_invocation');
2191
  }
2192

    
2193
  /**
2194
   * Tests that a Rules event is triggered on node menu item loading.
2195
   *
2196
   * @see rules_test_invocation_node_load()
2197
   */
2198
  public function testInvocationOnNodeMenuLoading() {
2199
    // Create a test node.
2200
    $node = $this->drupalCreateNode(array('title' => 'Test'));
2201
    // Enable Rules logging on the INFO level so that entries are written to
2202
    // dblog.
2203
    variable_set('rules_log_errors', RulesLog::INFO);
2204
    // Create an empty rule that will fire in our node load hook.
2205
    $rule = rules_reaction_rule();
2206
    $rule->event('rules_test_event');
2207
    $rule->save('test_rule');
2208

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

    
2215
}