Projet

Général

Profil

Paste
Télécharger (88,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / rules / tests / rules.test @ d719f12f

1
<?php
2

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

    
8
class RulesTestCase extends DrupalWebTestCase {
9

    
10
  static function getInfo() {
11
    return array(
12
      'name' => 'Rules Engine tests',
13
      'description' => 'Test using the rules API to create and evaluate rules.',
14
      'group' => 'Rules',
15
    );
16
  }
17

    
18
  function setUp() {
19
    parent::setUp('rules', 'rules_test');
20
    RulesLog::logger()->clear();
21
    variable_set('rules_debug_log', 1);
22
  }
23

    
24
  /**
25
   * Calculates the output of t() given an array of placeholders to replace.
26
   */
27
  static function t($text, $strings) {
28
    $placeholders = array();
29
    foreach ($strings as $key => $string) {
30
      $key = !is_numeric($key) ? $key : $string;
31
      $placeholders['%' . $key] = drupal_placeholder($string);
32
    }
33
    return strtr($text, $placeholders);
34
  }
35

    
36
  protected function createTestRule() {
37
    $rule = rule();
38
    $rule->condition('rules_test_condition_true')
39
         ->condition('rules_test_condition_true')
40
         ->condition(rules_or()
41
           ->condition(rules_condition('rules_test_condition_true')->negate())
42
           ->condition('rules_test_condition_false')
43
           ->condition(rules_and()
44
             ->condition('rules_test_condition_false')
45
             ->condition('rules_test_condition_true')
46
             ->negate()
47
           )
48
         );
49
    $rule->action('rules_test_action');
50
    return $rule;
51
  }
52

    
53
  /**
54
   * Tests creating a rule and iterating over the rule elements.
55
   */
56
  function testRuleCreation() {
57
    $rule = $this->createTestRule();
58
    $rule->integrityCheck();
59
    $rule->execute();
60
    $log = RulesLog::logger()->get();
61
    $last = array_pop($log);
62
    $last = array_pop($log);
63
    $last = array_pop($log);
64
    $this->assertEqual($last[0], 'action called', 'Action called');
65
    RulesLog::logger()->checkLog();
66

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

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

    
80
  /**
81
   * Test handling dependencies.
82
   */
83
  function testdependencies() {
84
    $action = rules_action('rules_node_publish_action');
85
    $this->assertEqual($action->dependencies(), array('rules_test'), 'Providing module is returned as dependency.');
86

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

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

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

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

    
106
    // Test it with components.
107
    module_enable(array('path'));
108
    $action_set = rules_action_set(array('node' => array('type' => 'node')));
109
    $action_set->action('node_path_alias');
110
    $action_set->save('rules_test_alias');
111

    
112
    $rule = rule(array('node' => array('type' => 'node')));
113
    $rule->action('component_rules_test_alias');
114
    $rule->integrityCheck();
115
    $rule->save('rules_test_rule');
116

    
117
    $rule = rules_config_load('rules_test_rule');
118
    $component = rules_config_load('rules_test_alias');
119
    $this->assertTrue(in_array('path', $component->dependencies) && !$rule->dirty && !$component->dirty, 'Component has path module dependency.');
120

    
121
    // Now disable path module and make sure both configs are marked as dirty.
122
    module_disable(array('path'));
123
    rules_clear_cache();
124
    $rule = rules_config_load('rules_test_rule');
125
    $component = rules_config_load('rules_test_alias');
126

    
127
    $this->assertTrue($component->dirty, 'Component has been marked as dirty');
128
    $node = $this->drupalCreateNode();
129
    $result = rules_invoke_component('rules_test_alias', $node);
130
    $this->assertTrue($result === FALSE, 'Unable to execute a dirty component.');
131

    
132
    // When the rule is evaluated, the broken component is detected and the
133
    // rule should be marked as dirty too.
134
    $rule->execute($node);
135
    $this->assertTrue($rule->dirty, 'Rule has been marked as dirty');
136

    
137
    module_enable(array('path'));
138
    rules_clear_cache();
139

    
140
    // Trigger rebuilding the cache, so configs are checked again.
141
    rules_get_cache();
142

    
143
    $rule = rules_config_load('rules_test_rule');
144
    $component = rules_config_load('rules_test_alias');
145
    $this->assertTrue(!$component->dirty, 'Component has been marked as not dirty again.');
146
    $this->assertTrue(!$rule->dirty, 'Rule has been marked as not dirty again.');
147
  }
148

    
149
  /**
150
   * Test setting up an action with some action_info and serializing and
151
   * executing it.
152
   */
153
  function testActionSetup() {
154
    $action = rules_action('rules_node_publish_action');
155

    
156
    $s = serialize($action);
157
    $action2 = unserialize($s);
158
    $node = (object) array('status' => 0, 'type' => 'page');
159
    $node->title = 'test';
160

    
161
    $action2->execute($node);
162
    $this->assertEqual($node->status, 1, 'Action executed correctly');
163

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

    
166
    $node->status = 0;
167
    $action2->integrityCheck();
168
    $action2->executeByArgs(array('node' => $node));
169
    $this->assertEqual($node->status, 1, 'Action executed correctly');
170

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

    
174
    // Inspect the cache
175
    //$this->pass(serialize(rules_get_cache()));
176
    RulesLog::logger()->checkLog();
177
  }
178

    
179
  /**
180
   * Test executing with wrong arguments.
181
   */
182
  function testActionExecutionFails() {
183
    $action = rules_action('rules_node_publish_action');
184
    try {
185
      $action->execute();
186
      $this->fail("Execution hasn't created an exception.");
187
    }
188
    catch (RulesEvaluationException $e) {
189
      $this->pass("RulesEvaluationException was thrown: ". $e);
190
    }
191
  }
192

    
193
  /**
194
   * Test setting up a rule and mapping variables.
195
   */
196
  function testVariableMapping() {
197
    $rule = rule(array(
198
      'node' => array('type' => 'node'),
199
      'node_unchanged' => array('type' => 'node'),
200
    ));
201
    $rule->condition(rules_condition('rules_condition_content_is_published')->negate())
202
         ->condition('rules_condition_content_is_type', array('type' => array('page', 'story')))
203
         ->action('rules_node_publish_action', array('node:select' => 'node_unchanged'));
204

    
205
    $node1 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
206
    $node2 = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
207
    $rule->integrityCheck();
208
    $rule->execute($node1, $node2);
209
    $this->assertEqual($node2->status, 1, 'Action executed correctly on node2.');
210
    $this->assertEqual($node1->status, 0, 'Action not executed on node1.');
211

    
212
    RulesLog::logger()->checkLog();
213
  }
214

    
215
  /**
216
   * Tests making use of class based actions.
217
   */
218
  function testClassBasedActions() {
219
    $cache = rules_get_cache();
220
    $this->assertTrue(!empty($cache['action_info']['rules_test_class_action']), 'Action has been discovered.');
221
    $action = rules_action('rules_test_class_action');
222

    
223
    $parameters = $action->parameterInfo();
224
    $this->assertTrue($parameters['node'], 'Action parameter needs a value.');
225

    
226
    $node = $this->drupalCreateNode();
227
    $action->execute($node);
228
    $log = RulesLog::logger()->get();
229
    $last = array_pop($log);
230
    $last = array_pop($log);
231
    $this->assertEqual($last[0], 'Action called with node ' . $node->nid, 'Action called');
232
    RulesLog::logger()->checkLog();
233
  }
234

    
235
  /**
236
   * Tests CRUD functionality.
237
   */
238
  function testRulesCRUD() {
239
    $rule = $this->createTestRule();
240
    $rule->integrityCheck()->save('test');
241

    
242
    $this->assertEqual(TRUE, $rule->active, 'Rule is active.');
243
    $this->assertEqual(0, $rule->weight, 'Rule weight is zero.');
244

    
245
    $results = entity_load('rules_config', array('test'));
246
    $rule2 = array_pop($results);
247
    $this->assertEqual($rule->id, $rule2->id, 'Rule created and loaded');
248
    $this->assertEqual(get_class($rule2), get_class($rule), 'Class properly instantiated.');
249
    $rule2->execute();
250
    // Update.
251
    $rule2->save();
252

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

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

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

    
298
  /**
299
   * Test automatic saving of variables.
300
   */
301
  function testActionSaving() {
302
    // Test saving a parameter.
303
    $action = rules_action('rules_node_publish_action_save');
304
    $node = $this->drupalCreateNode(array('status' => 0, 'type' => 'page'));
305
    $action->executeByArgs(array('node' => $node));
306

    
307
    $this->assertEqual($node->status, 1, 'Action executed correctly on node.');
308
    // Sync node_load cache with node_save
309
    entity_get_controller('node')->resetCache();
310

    
311
    $node = node_load($node->nid);
312
    $this->assertEqual($node->status, 1, 'Node has been saved.');
313

    
314
    // Now test saving a provided variable, which is renamed and modified before
315
    // it is saved.
316
    $title = $this->randomName();
317
    $rule = rule();
318
    $rule->action('entity_create', array(
319
      'type' => 'node',
320
      'param_type' => 'article',
321
      'param_author:select' => 'site:current-user',
322
      'param_title' => $title,
323
      'entity_created:var' => 'node',
324
    ));
325
    $rule->action('data_set', array(
326
      'data:select' => 'node:body',
327
      'value' => array('value' => 'body'),
328
    ));
329
    $rule->integrityCheck();
330
    $rule->execute();
331

    
332
    $node = $this->drupalGetNodeByTitle($title);
333
    $this->assertTrue(!empty($node) && $node->body[LANGUAGE_NONE][0]['value'] == 'body', 'Saved a provided variable');
334
    RulesLog::logger()->checkLog();
335
  }
336

    
337
  /**
338
   * Test adding a variable and optional parameters.
339
   */
340
  function testVariableAdding() {
341
    $node = $this->drupalCreateNode();
342
    $rule = rule(array('nid' => array('type' => 'integer')));
343
    $rule->condition('rules_test_condition_true')
344
         ->action('rules_action_load_node')
345
         ->action('rules_action_delete_node', array('node:select' => 'node_loaded'))
346
         ->execute($node->nid);
347

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

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

    
354

    
355
    // Test adding a variable with a custom variable name.
356
    $node = $this->drupalCreateNode();
357
    $rule = rule(array('nid' => array('type' => 'integer')));
358
    $rule->action('rules_action_load_node', array('node_loaded:var' => 'node'))
359
         ->action('rules_action_delete_node')
360
         ->execute($node->nid);
361

    
362
    $this->assertEqual(FALSE, node_load($node->nid), 'Variable with custom name added.');
363
    RulesLog::logger()->checkLog();
364
  }
365

    
366
  /**
367
   * Test custom access for using component actions/conditions.
368
   */
369
  function testRuleComponentAccess() {
370
    // Create a normal user.
371
    $normal_user = $this->drupalCreateUser();
372
    // Create a role for granting access to the rule component.
373
    $this->normal_role = $this->drupalCreateRole(array(), 'test_role');
374
    $normal_user->roles[$this->normal_role] = 'test_role';
375
    user_save($normal_user, array('roles' => $normal_user->roles));
376
    // Create an 'action set' rule component making use of a permission.
377
    $action_set = rules_action_set(array('node' => array('type' => 'node')));
378
    $action_set->access_exposed = TRUE;
379
    $action_set->save('rules_test_roles');
380

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

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

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

    
396
  /**
397
   * Test passing arguments by reference to an action.
398
   */
399
  function testPassingByReference() {
400
    // Keeping references of variables is unsupported, though the
401
    // EntityMetadataArrayObject may be used to achieve that.
402
    $array = array('foo' => 'bar');
403
    $data = new EntityMetadataArrayObject($array);
404
    rules_action('rules_action_test_reference')->execute($data);
405
    $this->assertTrue($data['changed'], 'Parameter has been passed by reference');
406
  }
407

    
408
  /**
409
   * Test sorting rule elements.
410
   */
411
  function testSorting() {
412
    $rule = $this->createTestRule();
413
    $conditions = $rule->conditions();
414
    $conditions[0]->weight = 10;
415
    $conditions[2]->weight = 10;
416
    $id[0] = $conditions[0]->elementId();
417
    $id[1] = $conditions[1]->elementId();
418
    $id[2] = $conditions[2]->elementId();
419
    // For testing use a deep sort, even if not necessary here.
420
    $rule->sortChildren(TRUE);
421
    $conditions = $rule->conditions();
422
    $this->assertEqual($conditions[0]->elementId(), $id[1], 'Condition sorted correctly.');
423
    $this->assertEqual($conditions[1]->elementId(), $id[0], 'Condition sorted correctly.');
424
    $this->assertEqual($conditions[2]->elementId(), $id[2], 'Condition sorted correctly.');
425
  }
426

    
427
  /**
428
   * Tests using data selectors.
429
   */
430
  function testDataSelectors() {
431
    $body[LANGUAGE_NONE][0] = array('value' => '<b>The body & nothing.</b>');
432
    $node = $this->drupalCreateNode(array('body' => $body, 'type' => 'page', 'summary' => ''));
433

    
434
    $rule = rule(array('nid' => array('type' => 'integer')));
435
    $rule->action('rules_action_load_node')
436
         ->action('drupal_message', array('message:select' => 'node_loaded:body:value'))
437
         ->execute($node->nid);
438

    
439
    RulesLog::logger()->checkLog();
440
    $msg = drupal_get_messages('status');
441
    $last_msg = array_pop($msg['status']);
442
    $wrapper = entity_metadata_wrapper('node', $node);
443
    $this->assertEqual($last_msg, $wrapper->body->value->value(array('sanitize' => TRUE)), 'Data selector for getting parameter applied.');
444

    
445
    // Get a "reference" on the same object as returned by node_load().
446
    $node = node_load($node->nid);
447
    $rule = rule(array('nid' => array('type' => 'integer')));
448
    $rule->action('rules_action_load_node')
449
         ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title'))
450
         // Use two actions and make sure the node get saved only once.
451
         ->action('data_set', array('data:select' => 'node_loaded:title', 'value' => 'Test title2'))
452
         ->execute($node->nid);
453

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

    
457
    RulesLog::logger()->checkLog();
458
    $text = RulesLog::logger()->render();
459
    $msg = RulesTestCase::t('Saved %node_loaded of type %node.', array('node_loaded', 'node'));
460
    if ($pos1 = strpos($text, $msg)) {
461
      $pos2 = strpos($text, $msg, $pos1 + 1);
462
    }
463
    $this->assertTrue($pos1 && $pos2 === FALSE, 'Data has been saved only once.');
464

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

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

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

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

    
508
  /**
509
   * Tests making use of rule sets.
510
   */
511
  function testRuleSets() {
512
    $set = rules_rule_set(array(
513
      'node' => array('type' => 'node', 'label' => 'node'),
514
    ));
515
    $set->rule(rule()->action('drupal_message', array('message:select' => 'node:title')))
516
        ->rule(rule()->condition('rules_condition_content_is_published')
517
                     ->action('drupal_message', array('message' => 'Node is published.'))
518
               );
519
    $set->integrityCheck()->save('rules_test_set_1');
520

    
521
    $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1));
522
    // Execute.
523
    rules_invoke_component('rules_test_set_1', $node);
524

    
525
    $msg = drupal_get_messages();
526
    $this->assertEqual($msg['status'][0], 'The title.', 'First rule evaluated.');
527
    $this->assertEqual($msg['status'][1], 'Node is published.', 'Second rule evaluated.');
528

    
529
    // Test a condition set.
530
    $set = rules_or(array(
531
      'node' => array('type' => 'node', 'label' => 'node'),
532
    ));
533
    $set->condition('data_is', array('data:select' => 'node:author:name', 'value' => 'notthename'))
534
        ->condition('data_is', array('data:select' => 'node:nid', 'value' => $node->nid))
535
        ->integrityCheck()
536
        ->save('test', 'rules_test');
537
    // Load and execute condition set.
538
    $set = rules_config_load('test');
539
    $this->assertTrue($set->execute($node), 'Set has been correctly evaluated.');
540
    RulesLog::logger()->checkLog();
541
  }
542

    
543
  /**
544
   * Tests invoking components from the action.
545
   */
546
  function testComponentInvocations() {
547
    $set = rules_rule_set(array(
548
      'node1' => array('type' => 'node', 'label' => 'node'),
549
    ));
550
    $set->rule(rule()->condition('node_is_published', array('node:select' => 'node1'))
551
                     ->action('node_unpublish', array('node:select' => 'node1'))
552
               );
553
    $set->integrityCheck()->save('rules_test_set_2');
554

    
555
    // Use different names for the variables to ensure they are properly mapped
556
    // when taking over the variables to be saved.
557
    $rule = rule(array(
558
      'node2' => array('type' => 'node', 'label' => 'node'),
559
    ));
560
    $rule->action('component_rules_test_set_2', array('node1:select' => 'node2'));
561
    $rule->action('node_make_sticky', array('node:select' => 'node2'));
562

    
563
    $node = $this->drupalCreateNode(array('title' => 'The title.', 'status' => 1, 'sticky' => 0));
564
    $rule->execute($node);
565

    
566
    $node = node_load($node->nid, NULL, TRUE);
567
    $this->assertFalse($node->status, 'The component changes have been saved correctly.');
568
    $this->assertTrue($node->sticky, 'The action changes have been saved correctly.');
569

    
570
    // Check that we have saved the changes only once.
571
    $text = RulesLog::logger()->render();
572
    // Make sure both saves are handled in one save operation.
573
    $this->assertEqual(substr_count($text, 'Saved'), 1, 'Changes have been saved in one save operation.');
574
    RulesLog::logger()->checkLog();
575

    
576
    // Test recursion prevention on components by invoking the component from
577
    // itself, what should be prevented.
578
    $set->action('component_rules_test_set_2', array('node1:select' => 'node1'))
579
        ->save();
580

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

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

    
594

    
595
  /**
596
   * Test asserting metadata, customizing action info and make sure integrity
597
   * is checked.
598
   */
599
  function testMetadataAssertion() {
600
    $action = rules_action('rules_node_make_sticky_action');
601

    
602
    // Test failing integrity check.
603
    try {
604
      $rule = rule(array('node' => array('type' => 'entity')));
605
      $rule->action($action);
606
      // Fails due to the 'node' variable not matching the node type.
607
      $rule->integrityCheck();
608
      $this->fail('Integrity check has not thrown an exception.');
609
    }
610
    catch (RulesIntegrityException $e) {
611
      $this->pass('Integrity check has thrown exception: ' . $e->getMessage());
612
    }
613

    
614
    // Test asserting additional metadata.
615
    $rule = rule(array('node' => array('type' => 'node')));
616
    // Customize action info using the settings.
617
    $rule->condition('data_is', array('data:select'   => 'node:type', 'value' => 'page'))
618
         // Configure an condition using the body. As the body is a field,
619
         // tis requires the bundle to be correctly asserted.
620
         ->condition(rules_condition('data_is', array('data:select' => 'node:body:value', 'value' => 'foo'))->negate())
621
         // The action also requires the page bundle in order to work.
622
         ->action($action);
623
    // Make sure the integrity check doesn't throw an exception.
624
    $rule->integrityCheck();
625
    // Test the rule.
626
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
627
    $rule->execute($node);
628
    $this->assertTrue($node->sticky, 'Rule with asserted metadata executed.');
629

    
630

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

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

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

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

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

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

    
678
  /**
679
   * Test using loops.
680
   */
681
  function testLoops() {
682
    // Test passing the list parameter as argument to ensure that is working
683
    // generally for plugin container too.
684
    drupal_get_messages(NULL, TRUE);
685
    $loop = rules_loop();
686
    $loop->action('drupal_message', array('message' => 'test'));
687
    $arg_info = $loop->parameterInfo();
688
    $this->assert($arg_info['list']['type'] == 'list', 'Argument info contains list.');
689
    $loop->execute(array(1, 2));
690

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

    
695
    // Now test looping over nodes.
696
    $node1 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
697
    $node2 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
698
    $node3 = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0));
699

    
700
    $rule = rule(array(
701
      'list' => array(
702
        'type' => 'list<node>',
703
        'label' => 'A list of nodes',
704
      )
705
    ));
706
    $loop = rules_loop(array('list:select' => 'list', 'item:var' => 'node'));
707
    $loop->action('data_set', array('data:select' => 'node:sticky', 'value' => TRUE));
708
    $rule->action($loop);
709
    // Test using a list with data selectors, just output the last nodes type.
710
    $rule->action('drupal_message', array('message:select' => 'list:2:type'));
711

    
712
    $rule->execute(array($node1->nid, $node2->nid, $node3->nid));
713
    $text = RulesLog::logger()->render();
714
    $save_msg = RulesTestCase::t('Saved %node of type %node.', array('node', 'node'));
715
    $this->assertTrue(substr_count($text, $save_msg) == 3, 'List item variables have been saved.');
716
    RulesLog::logger()->checkLog();
717
  }
718

    
719
  /**
720
   * Test access checks.
721
   */
722
  function testAccessCheck() {
723
    $rule = rule();
724
    // Try to set a property which is provided by the test module and is not
725
    // accessible, so the access check has to return FALSE.
726
    $rule->action('data_set', array('data:select' => 'site:no-access-user', 'value' => 'foo'));
727
    $this->assertTrue($rule->access() === FALSE, 'Access check is working.');
728
  }
729

    
730
  /**
731
   * Test returning provided variables.
732
   */
733
  function testReturningVariables() {
734
    $node = $this->drupalCreateNode();
735
    $action = rules_action('entity_fetch', array('type' => 'node', 'id' => $node->nid));
736
    list($node2) = $action->execute();
737
    $this->assertTrue($node2->nid == $node->nid, 'Action returned a variable.');
738

    
739
    // Create a simple set that just passed through the given node.
740
    $set = rules_rule_set(array('node' => array('type' => 'node')), array('node'));
741
    $set->integrityCheck()->save('rules_test_set_1');
742

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

    
746
    list($node2) = $set->execute($node);
747
    $this->assertTrue($node2->nid == $node->nid, 'Rule set returned a variable.');
748

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

    
761
    // Test the same again with a variable holding a not wrapped data type.
762
    $set = rules_action_set(array(
763
      'number' => array(
764
        'type' => 'integer',
765
        'parameter' => FALSE,
766
      )), array('number'));
767
    $set->action('data_set', array('data:select' => 'number', 'value' => 3));
768
    $set->integrityCheck();
769
    list($number) = $set->execute();
770
    $this->assertTrue($number == 3, 'Actions set returned a number.');
771
  }
772

    
773
  /**
774
   * Tests using input evaluators.
775
   */
776
  function testInputEvaluators() {
777
    $node = $this->drupalCreateNode(array('title' => '<b>The body & nothing.</b>', 'type' => 'page'));
778

    
779
    $rule = rule(array('nid' => array('type' => 'integer')));
780
    $rule->action('rules_action_load_node')
781
         ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'))
782
         ->execute($node->nid);
783

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

    
788
    // Test token replacements on a list of text values.
789
    $component = rules_action_set(array('var' => array('type' => 'list<text>', 'label' => 'var')), array('var'));
790
    $component->save('rules_test_input');
791

    
792
    $action = rules_action('component_rules_test_input', array('var' => array('uid: [site:current-user:uid]')));
793
    list($var) = $action->execute();
794
    $uid = $GLOBALS['user']->uid;
795
    $this->assertEqual(array("uid: $uid"), $var, 'Token replacements on a list of values applied.');
796
  }
797

    
798
  /**
799
   * Test importing and exporting a rule.
800
   */
801
  function testRuleImportExport() {
802
    $rule = rule(array('nid' => array('type' => 'integer')));
803
    $rule->name = "rules_export_test";
804
    $rule->action('rules_action_load_node')
805
         ->action('drupal_message', array('message' => 'Title: [node_loaded:title]'));
806

    
807
    $export =
808
'{ "rules_export_test" : {
809
    "PLUGIN" : "rule",
810
    "REQUIRES" : [ "rules_test", "rules" ],
811
    "USES VARIABLES" : { "nid" : { "type" : "integer" } },
812
    "DO" : [
813
      { "rules_action_load_node" : { "PROVIDE" : { "node_loaded" : { "node_loaded" : "Loaded content" } } } },
814
      { "drupal_message" : { "message" : "Title: [node_loaded:title]" } }
815
    ]
816
  }
817
}';
818
    $this->assertEqual($export, $rule->export(), 'Rule has been exported correctly.');
819

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

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

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

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

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

    
865
    // Try import / export for a rule component providing a variable.
866
    $rule = rule(array(
867
      'number' => array(
868
        'type' => 'integer',
869
        'label' => 'Number',
870
        'parameter' => FALSE,
871
      )), array('number'));
872
    $rule->action('data_set', array('data:select' => 'number', 'value' => 3));
873
    $rule->name = 'rules_test_provides';
874

    
875
    $export = '{ "rules_test_provides" : {
876
    "PLUGIN" : "rule",
877
    "REQUIRES" : [ "rules" ],
878
    "USES VARIABLES" : { "number" : { "type" : "integer", "label" : "Number", "parameter" : false } },
879
    "DO" : [ { "data_set" : { "data" : [ "number" ], "value" : 3 } } ],
880
    "PROVIDES VARIABLES" : [ "number" ]
881
  }
882
}';
883
    $this->assertEqual($export, $rule->export(), 'Rule 2 has been exported correctly.');
884
    $imported_rule = rules_import($rule->export());
885

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

    
889
    // Test importing a negated condition component.
890
    $export = '{ "rules_negated_component" : {
891
    "LABEL" : "negated_component",
892
    "PLUGIN" : "or",
893
    "REQUIRES" : [ "rules" ],
894
    "NOT OR" : [ { "data_is_empty" : { "data" : [ "site:slogan" ] } } ]
895
  }
896
}';
897
    $or = rules_import($export);
898
    $this->assertTrue($or->integrityCheck() && $or->isNegated(), 'Negated condition component imported.');
899
  }
900

    
901
  /**
902
   * Test the named parameter mode.
903
   */
904
  function testNamedParameters() {
905
    $rule = rule(array('node' => array('type' => 'node')));
906
    $rule->action('rules_action_node_set_title', array('title' => 'foo'));
907
    $rule->integrityCheck();
908

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

    
916
  /**
917
   * Make sure Rules aborts when NULL values are used.
918
   */
919
  function testAbortOnNULLValues() {
920
    $rule = rule(array('node' => array('type' => 'node')));
921
    $rule->action('drupal_message', array('message:select' => 'node:log'));
922
    $rule->integrityCheck();
923

    
924
    // Test the rule.
925
    $node = $this->drupalCreateNode();
926
    $node->log = NULL;
927
    $rule->execute($node);
928

    
929
    $text = RulesLog::logger()->render();
930
    $msg = RulesTestCase::t('The variable or parameter %message is empty.', array('message'));
931
    $this->assertTrue(strpos($text, $msg) !== FALSE, 'Evaluation aborted due to an empty argument value.');
932
  }
933
}
934

    
935
/**
936
 * Test rules data wrappers.
937
 */
938
class RulesTestDataCase extends DrupalWebTestCase {
939

    
940
  static function getInfo() {
941
    return array(
942
      'name' => 'Rules Data tests',
943
      'description' => 'Tests rules data saving and type matching.',
944
      'group' => 'Rules',
945
    );
946
  }
947

    
948
  function setUp() {
949
    parent::setUp('rules', 'rules_test');
950
    variable_set('rules_debug_log', 1);
951
    // Make sure we don't ran over issues with the node_load static cache.
952
    entity_get_controller('node')->resetCache();
953
  }
954

    
955
  /**
956
   * Tests intelligently saving data.
957
   */
958
  function testDataSaving() {
959
    $node = $this->drupalCreateNode();
960
    $state = new RulesState(rule());
961
    $state->addVariable('node', $node, array('type' => 'node'));
962
    $wrapper = $state->get('node');
963
    $node->title = 'test';
964
    $wrapper->set($node);
965
    $state->saveChanges('node', $wrapper, FALSE);
966

    
967
    $this->assertFalse($this->drupalGetNodeByTitle('test'), 'Changes have not been saved.');
968
    $state->saveChanges('node', $wrapper, TRUE);
969
    $this->assertTrue($this->drupalGetNodeByTitle('test'), 'Changes have been saved.');
970

    
971
    // Test skipping saving.
972
    $state->addVariable('node2', $node, array(
973
      'type' => 'node',
974
      'skip save' => TRUE,
975
    ));
976
    $wrapper = $state->get('node2');
977
    $node->title = 'test2';
978
    $wrapper->set($node);
979
    $state->saveChanges('node2', $wrapper, TRUE);
980
    $this->assertFalse($this->drupalGetNodeByTitle('test2'), 'Changes have not been saved.');
981

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

    
990
  /**
991
   * Test type matching
992
   */
993
  function testTypeMatching() {
994
    $entity = array('type' => 'entity');
995
    $node = array('type' => 'node');
996
    $this->assertTrue(RulesData::typesMatch($node, $entity), 'Types match.');
997
    $this->assertFalse(RulesData::typesMatch($entity, $node), 'Types don\'t match.');
998

    
999
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node), 'Types match.');
1000
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $entity), 'Types match.');
1001
    $this->assertTrue(RulesData::typesMatch(array('type' => 'list<node>'), array('type' => 'list')), 'Types match.');
1002
    $this->assertTrue(RulesData::typesMatch($node + array('bundle' => 'page'), $node + array('bundles' => array('page', 'story'))), 'Types match.');
1003
    $this->assertFalse(RulesData::typesMatch($node, $node + array('bundles' => array('page', 'story'))), 'Types don\'t match.');
1004

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

    
1010
  /**
1011
   * Tests making use of custom wrapper classes.
1012
   */
1013
  function testCustomWrapperClasses() {
1014
    // Test loading a vocabulary by name, which is done by a custom wrapper.
1015
    $set = rules_action_set(array('vocab' => array('type' => 'taxonomy_vocabulary')), array('vocab'));
1016
    $set->action('drupal_message', array('message:select' => 'vocab:name'));
1017
    $set->integrityCheck();
1018
    list($vocab) = $set->execute('tags');
1019
    $this->assertTrue($vocab->machine_name == 'tags', 'Loaded vocabulary by name.');
1020

    
1021
    // Now test wrapper creation for a direct input argument value.
1022
    $set = rules_action_set(array('term' => array('type' => 'taxonomy_term')));
1023
    $set->action('data_set', array('data:select' => 'term:vocabulary', 'value' => 'tags'));
1024
    $set->integrityCheck();
1025

    
1026
    $vocab = entity_create('taxonomy_vocabulary', array(
1027
      'name' => 'foo',
1028
      'machine_name' => 'foo',
1029
    ));
1030
    entity_save('taxonomy_vocabulary', $vocab);
1031
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1032
      'name' => $this->randomName(),
1033
      'vocabulary' => $vocab,
1034
    ))->save();
1035
    $set->execute($term_wrapped);
1036
    $this->assertEqual($term_wrapped->vocabulary->machine_name->value(), 'tags', 'Vocabulary name used as direct input value.');
1037
    RulesLog::logger()->checkLog();
1038
  }
1039

    
1040
  /**
1041
   * Makes sure the RulesIdentifiableDataWrapper is working correctly.
1042
   */
1043
  function testRulesIdentifiableDataWrapper() {
1044
    $node = $this->drupalCreateNode();
1045
    $wrapper = new RulesTestTypeWrapper('rules_test_type', $node);
1046
    $this->assertTrue($wrapper->value() == $node, 'Data correctly wrapped.');
1047

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

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

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

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

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

    
1080
/**
1081
 * Test triggering rules.
1082
 */
1083
class RulesTriggerTestCase extends DrupalWebTestCase {
1084

    
1085
  static function getInfo() {
1086
    return array(
1087
      'name' => 'Reaction Rules',
1088
      'description' => 'Tests triggering reactive rules.',
1089
      'group' => 'Rules',
1090
    );
1091
  }
1092

    
1093
  function setUp() {
1094
    parent::setUp('rules', 'rules_test');
1095
    RulesLog::logger()->clear();
1096
    variable_set('rules_debug_log', 1);
1097
  }
1098

    
1099
  protected function createTestRule($action = TRUE, $event = 'node_presave') {
1100
    $rule = rules_reaction_rule();
1101
    $rule->event($event)
1102
         ->condition(rules_condition('data_is', array('data:select' => 'node:status', 'value' => TRUE))->negate())
1103
         ->condition('data_is', array('data:select' => 'node:type', 'value' => 'page'));
1104
    if ($action) {
1105
      $rule->action('rules_action_delete_node');
1106
    }
1107
    return $rule;
1108
  }
1109

    
1110
  /**
1111
   * Tests CRUD for reaction rules - making sure the events are stored properly.
1112
   */
1113
  function testReactiveRuleCreation() {
1114
    $rule = $this->createTestRule();
1115
    $rule->save();
1116
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1117
    $this->assertEqual($result->fetchField(), 'node_presave', 'Associated event has been saved.');
1118
    // Try updating.
1119
    $rule->removeEvent('node_presave');
1120
    $rule->event('node_insert');
1121
    $rule->event('node_update');
1122
    $rule->active = FALSE;
1123
    $rule->integrityCheck()->save();
1124
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1125
    $this->assertEqual($result->fetchCol(), array_values($rule->events()), 'Updated associated events.');
1126
    // Try deleting.
1127
    $rule->delete();
1128
    $result = db_query("SELECT event FROM {rules_trigger} WHERE id = :id", array(':id' => $rule->id));
1129
    $this->assertEqual($result->fetchField(), FALSE, 'Deleted associated events.');
1130
  }
1131

    
1132
  /**
1133
   * Tests creating and triggering a basic reaction rule.
1134
   */
1135
  function testBasicReactionRule() {
1136
    $node = $this->drupalCreateNode(array('type' => 'page'));
1137
    $rule = $this->createTestRule();
1138
    $rule->integrityCheck()->save();
1139
    // Test the basics of the event set work right.
1140
    $event = rules_get_cache('event_node_presave');
1141
    $this->assertEqual(array_keys($event->parameterInfo()), array('node'), 'EventSet returns correct argument info.');
1142

    
1143
    // Trigger the rule by updating the node.
1144
    $nid = $node->nid;
1145
    $node->status = 0;
1146
    node_save($node);
1147

    
1148
    RulesLog::logger()->checkLog();
1149
    $this->assertFalse(node_load($nid), 'Rule successfully triggered and executed');
1150
    //debug(RulesLog::logger()->render());
1151
  }
1152

    
1153
  /**
1154
   * Test a rule using a handler to load a variable.
1155
   */
1156
  function testVariableHandler() {
1157
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1158
    $rule = $this->createTestRule(FALSE, 'node_update');
1159
    $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged'));
1160
    // Test without recursion prevention to make sure recursive invocations
1161
    // work right too. This rule won't ran in an infinite loop anyway.
1162
    $rule->recursion = TRUE;
1163
    $rule->label = 'rule 1';
1164
    $rule->integrityCheck()->save();
1165

    
1166
    $node->status = 0;
1167
    $node->sticky = 1;
1168
    node_save($node);
1169

    
1170
    RulesLog::logger()->checkLog();
1171
    entity_get_controller('node')->resetCache();
1172
    $node = node_load($node->nid);
1173

    
1174
    $this->assertFalse($node->sticky, 'Parameter has been loaded and saved.');
1175
    $this->assertTrue($node->status, 'Action has been executed.');
1176

    
1177
    // Ensure the rule was evaluated a second time
1178
    $text = RulesLog::logger()->render();
1179
    $msg = RulesTestCase::t('Evaluating conditions of rule %rule 1', array('rule 1'));
1180
    $pos = strpos($text, $msg);
1181
    $pos = ($pos !== FALSE) ? strpos($text, $msg, $pos) : FALSE;
1182
    $this->assertTrue($pos !== FALSE, "Recursion prevented.");
1183
    //debug(RulesLog::logger()->render());
1184
  }
1185

    
1186
  /**
1187
   * Test aborting silently when handlers are not able to load.
1188
   */
1189
  function testVariableHandlerFailing() {
1190
    $rule = $this->createTestRule(FALSE, 'node_presave');
1191
    $rule->action('rules_node_publish_action_save', array('node:select' => 'node_unchanged'));
1192
    $rule->integrityCheck()->save();
1193

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

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

    
1202
  /**
1203
   * Tests preventing recursive rule invocations by creating a rule that reacts
1204
   * on node-update and generates a node update that would trigger it itself.
1205
   */
1206
  function testRecursionPrevention() {
1207
    $rule = $this->createTestRule(FALSE, 'node_update');
1208
    $rule->action('rules_node_make_sticky_action');
1209
    $rule->integrityCheck()->save();
1210

    
1211
    // Now trigger the rule.
1212
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1213
    node_save($node);
1214

    
1215
    $text = RulesTestCase::t('Not evaluating reaction rule %label to prevent recursion.', array('label' => $rule->name));
1216
    //debug(RulesLog::logger()->render());
1217
    $this->assertTrue((strpos(RulesLog::logger()->render(), $text) !== FALSE), "Recursion prevented.");
1218
    //debug(RulesLog::logger()->render());
1219
  }
1220

    
1221
  /**
1222
   * Ensure the recursion prevention still allows to let the rule trigger again
1223
   * during evaluation of the same event set, if the event isn't caused by the
1224
   * rule itself - thus we won't run in an infinte loop.
1225
   */
1226
  function testRecursionOnDifferentArguments() {
1227
    // Create rule1 - which might recurse.
1228
    $rule = $this->createTestRule(FALSE, 'node_update');
1229
    $rule->action('rules_node_make_sticky_action');
1230
    $rule->label = 'rule 1';
1231
    $rule->integrityCheck()->save();
1232

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

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

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

    
1255
  /**
1256
   * Tests the provided default rule 'rules_test_default_1'.
1257
   */
1258
  function testDefaultRule() {
1259
    $rule = rules_config_load('rules_test_default_1');
1260
    $this->assertTrue($rule->status & ENTITY_IN_CODE && !($rule->status & ENTITY_IN_DB), 'Default rule can be loaded and has the right status.');
1261
    // Enable.
1262
    $rule->active = TRUE;
1263
    $rule->save();
1264

    
1265
    // Create a node that triggers the rule.
1266
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1267
    // Clear messages.
1268
    drupal_get_messages();
1269
    // Let event node_update occur.
1270
    node_save($node);
1271

    
1272
    $msg = drupal_get_messages();
1273
    $this->assertEqual($msg['status'][0], 'A node has been updated.', 'Default rule has been triggered.');
1274
  }
1275

    
1276
  /**
1277
   * Tests creating and triggering a reaction rule with event settings.
1278
   */
1279
  function testEventSettings() {
1280
    $rule = rules_reaction_rule();
1281
    $rule->event('node_presave', array('bundle' => 'article'))
1282
      ->condition('data_is_empty', array('data:select' => 'node:field-tags'))
1283
      ->action('node_publish', array('node:select' => 'node'));
1284
    $rule->integrityCheck()->save();
1285

    
1286
    $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 0));
1287
    $this->assertEqual($node->status, 0, 'Rule has not been triggered.');
1288
    $node = $this->drupalCreateNode(array('type' => 'article', 'status' => 0));
1289
    $this->assertEqual($node->status, 1, 'Rule has been triggered.');
1290
    RulesLog::logger()->checkLog();
1291

    
1292
    // Make sure an invalid bundle raises integrity problems.
1293
    $rule->event('node_presave', array('bundle' => 'invalid'));
1294
    try {
1295
      $rule->integrityCheck();
1296
      $this->fail('Integrity check failed.');
1297
    }
1298
    catch (RulesIntegrityException $e) {
1299
      $this->pass('Integrity check failed: ' . $e);
1300
    }
1301
  }
1302
}
1303

    
1304
/**
1305
 * Tests provided module integration.
1306
 */
1307
class RulesIntegrationTestCase extends DrupalWebTestCase {
1308

    
1309
  static function getInfo() {
1310
    return array(
1311
      'name' => 'Rules Core Integration',
1312
      'description' => 'Tests provided integration for drupal core.',
1313
      'group' => 'Rules',
1314
    );
1315
  }
1316

    
1317
  function setUp() {
1318
    parent::setUp('rules', 'rules_test', 'php', 'path');
1319
    RulesLog::logger()->clear();
1320
    variable_set('rules_debug_log', 1);
1321
  }
1322

    
1323
  /**
1324
   * Just make sure the access callback run without errors.
1325
   */
1326
  function testAccessCallbacks() {
1327
    $cache = rules_get_cache();
1328
    foreach (array('action', 'condition', 'event') as $type) {
1329
      foreach (rules_fetch_data($type . '_info') as $name => $info) {
1330
        if (isset($info['access callback'])) {
1331
          $info['access callback']($type, $name);
1332
        }
1333
      }
1334
    }
1335
  }
1336

    
1337
  /**
1338
   * Test data integration.
1339
   */
1340
  function testDataIntegration() {
1341
    // Test data_create action.
1342
    $action = rules_action('data_create', array(
1343
      'type' => 'log_entry',
1344
      'param_type' => 'rules_test',
1345
      'param_message' => 'Rules test log message',
1346
      'param_severity' => WATCHDOG_WARNING,
1347
      'param_request_uri' => 'http://example.com',
1348
      'param_link' => '',
1349
    ));
1350
    $action->access();
1351
    $action->execute();
1352
    $text = RulesLog::logger()->render();
1353
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %data_created of type %log_entry', array('data_created', 'log_entry')));
1354
    $this->assertTrue($pos !== FALSE, 'Data of type log entry has been created.');
1355

    
1356

    
1357
    // Test variable_add action.
1358
    $action = rules_action('variable_add', array(
1359
      'type' => 'text_formatted',
1360
      'value' => array(
1361
        'value' => 'test text',
1362
        'format' => 1,
1363
      )
1364
    ));
1365
    $action->access();
1366
    $action->execute();
1367
    $text = RulesLog::logger()->render();
1368
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %variable_added of type %text_formatted', array('variable_added', 'text_formatted')));
1369
    $this->assertTrue($pos !== FALSE, 'Data of type text formatted has been created.');
1370

    
1371

    
1372
    // Test using the list actions.
1373
    $rule = rule(array(
1374
      'list' => array(
1375
        'type' => 'list<text>',
1376
        'label' => 'A list of text',
1377
      )
1378
    ));
1379
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar2'));
1380
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'pos' => 'start'));
1381
    $rule->action('list_add', array('list:select' => 'list', 'item' => 'bar', 'unique' => TRUE));
1382
    $rule->action('list_remove', array('list:select' => 'list', 'item' => 'bar2'));
1383
    $list = entity_metadata_wrapper('list', array('foo', 'foo2'));
1384
    $rule->execute($list);
1385
    RulesLog::logger()->checkLog();
1386
    $this->assertEqual($list->value(), array('bar', 'foo', 'foo2'), 'List items removed and added.');
1387
    $this->assertFalse(rules_condition('list_contains')->execute($list, 'foo-bar'), 'Condition "List item contains" evaluates to FALSE');
1388
    $this->assertTrue(rules_condition('list_contains')->execute($list, 'foo'), 'Condition "List item contains" evaluates to TRUE');
1389
    //debug(RulesLog::logger()->render());
1390

    
1391
    // Test data_is condition with IN operation.
1392
    $rule = rule(array('node' => array('type' => 'node')));
1393
    $rule->condition('data_is', array('data:select' => 'node:title', 'op' => 'IN', 'value' => array('foo', 'bar')));
1394
    $rule->action('data_set', array('data:select' => 'node:title', 'value' => 'bar'));
1395
    $rule->integrityCheck();
1396

    
1397
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1398
    $rule->execute($node);
1399
    $this->assertEqual($node->title, 'bar', "Data comparision using IN operation evaluates to TRUE.");
1400

    
1401

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

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

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

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

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

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

    
1447
    // Test data type conversion action.
1448
    $set = rules_action_set(array('result' => array('type' => 'text', 'parameter' => FALSE)), array('result'));
1449
    $set->action('data_convert', array('type' => 'text', 'value:select' => 'site:login-url'));
1450
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1451
    list($result) = $set->execute();
1452
    $set->integrityCheck();
1453
    $this->assertEqual($result, url('user', array('absolute' => TRUE)), 'Converted URI to text.');
1454

    
1455
    $set = rules_action_set(array(
1456
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1457
      'source' => array('type' => 'text'),
1458
    ), array('result'));
1459
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source'));
1460
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1461
    list($result) = $set->execute('9.4');
1462
    $this->assertEqual($result, 9, 'Converted decimal to integer using rounding.');
1463

    
1464
    $set = rules_action_set(array(
1465
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1466
      'source' => array('type' => 'text'),
1467
    ), array('result'));
1468
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'down'));
1469
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1470
    list($result) = $set->execute('9.6');
1471
    $this->assertEqual($result, 9, 'Converted decimal to integer using roundin behavio down.');
1472

    
1473
    $set = rules_action_set(array(
1474
      'result' => array('type' => 'integer', 'parameter' => FALSE),
1475
      'source' => array('type' => 'text'),
1476
    ), array('result'));
1477
    $set->action('data_convert', array('type' => 'integer', 'value:select' => 'source', 'rounding_behavior' => 'up'));
1478
    $set->action('data_set', array('data:select' => 'result', 'value:select' => 'conversion_result'));
1479
    list($result) = $set->execute('9.4');
1480
    $this->assertEqual($result, 10, 'Converted decimal to integer using rounding behavior up.');
1481

    
1482
    // Test text matching condition.
1483
    $result = rules_condition('text_matches')->execute('my-text', 'text', 'contains');
1484
    $result2 = rules_condition('text_matches')->execute('my-text', 'tex2t', 'contains');
1485
    $this->assertTrue($result && !$result2, 'Text matching condition using operation contain evaluated.');
1486

    
1487
    $result = rules_condition('text_matches')->execute('my-text', 'my', 'starts');
1488
    $result2 = rules_condition('text_matches')->execute('my-text', 'text', 'starts');
1489
    $this->assertTrue($result && !$result2, 'Text matching condition using operation starts evaluated.');
1490

    
1491
    $result = rules_condition('text_matches')->execute('my-text', 'text', 'ends');
1492
    $result2 = rules_condition('text_matches')->execute('my-text', 'my', 'ends');
1493
    $this->assertTrue($result && !$result2, 'Text matching condition using operation ends evaluated.');
1494

    
1495
    $result = rules_condition('text_matches')->execute('my-text', 'me?y-texx?t', 'regex');
1496
    $result2 = rules_condition('text_matches')->execute('my-text', 'me+y-texx?t', 'regex');
1497
    $this->assertTrue($result && !$result2, 'Text matching condition using operation regex evaluated.');
1498
  }
1499

    
1500
  /**
1501
   * Tests entity related integration.
1502
   */
1503
  function testEntityIntegration() {
1504
    global $user;
1505

    
1506
    $page = $this->drupalCreateNode(array('type' => 'page'));
1507
    $article = $this->drupalCreateNode(array('type' => 'article'));
1508

    
1509
    $result = rules_condition('entity_field_access')
1510
      ->execute(entity_metadata_wrapper('node', $article), 'field_tags');
1511
    $this->assertTrue($result);
1512

    
1513
    // Test entiy_is_of_bundle condition.
1514
    $result = rules_condition('entity_is_of_bundle', array(
1515
      'type' => 'node',
1516
      'bundle' => array('article'),
1517
    ))->execute(entity_metadata_wrapper('node', $page));
1518
    $this->assertFalse($result, 'Entity is of bundle condition has not been met.');
1519
    $result = rules_condition('entity_is_of_bundle', array(
1520
      'type' => 'node',
1521
      'bundle' => array('article'),
1522
    ))->execute(entity_metadata_wrapper('node', $article));
1523
    $this->assertTrue($result, 'Entity is of bundle condition has been met.');
1524

    
1525
    // Also test a full rule so the integrity check must work.
1526
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1527
      'name' => $this->randomName(),
1528
      'vocabulary' => 1,
1529
    ))->save();
1530
    $rule = rule(array(
1531
      'node' => array('type' => 'node'),
1532
    ));
1533
    $rule->condition('entity_is_of_bundle', array(
1534
      'entity:select' => 'node',
1535
      'bundle' => array('article'),
1536
    ));
1537
    $rule->action('data_set', array('data:select' => 'node:field_tags', 'value' => array($term_wrapped->getIdentifier())));
1538
    $rule->integrityCheck();
1539
    $rule->execute($article);
1540
    $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
1541

    
1542
    // Test again using an entity variable.
1543
    $article = $this->drupalCreateNode(array('type' => 'article'));
1544
    $rule = rule(array(
1545
      'entity' => array('type' => 'entity'),
1546
    ));
1547
    $rule->condition('entity_is_of_bundle', array(
1548
      'entity:select' => 'entity',
1549
      'type' => 'node',
1550
      'bundle' => array('article'),
1551
    ));
1552
    $rule->action('data_set', array('data:select' => 'entity:field_tags', 'value' => array($term_wrapped->getIdentifier())));
1553
    $rule->integrityCheck();
1554
    $rule->execute(entity_metadata_wrapper('node', $article));
1555
    $this->assertEqual($term_wrapped->getIdentifier(), $article->field_tags[LANGUAGE_NONE][0]['tid'], 'Entity is of bundle condition has been met.');
1556

    
1557
    // Test CRUD actions.
1558
    $action = rules_action('entity_create', array(
1559
      'type' => 'node',
1560
      'param_type' => 'page',
1561
      'param_title' => 'foo',
1562
      'param_author' => $GLOBALS['user'],
1563
    ));
1564
    $action->access();
1565
    $action->execute();
1566
    $text = RulesLog::logger()->render();
1567
    $pos = strpos($text, RulesTestCase::t('Added the provided variable %entity_created of type %node', array('entity_created', 'node')));
1568
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %entity_created of type %node.', array('entity_created', 'node')), $pos) : FALSE;
1569
    $this->assertTrue($pos !== FALSE, 'Data has been created and saved.');
1570

    
1571
    $node = $this->drupalCreateNode(array('type' => 'page', 'sticky' => 0, 'status' => 0));
1572
    $rule = rule();
1573
    $rule->action('entity_fetch', array('type' => 'node', 'id' => $node->nid, 'entity_fetched:var' => 'node'));
1574
    $rule->action('entity_save', array('data:select' => 'node', 'immediate' => TRUE));
1575
    $rule->action('entity_delete', array('data:select' => 'node'));
1576
    $rule->access();
1577
    $rule->integrityCheck()->execute();
1578

    
1579
    $text = RulesLog::logger()->render();
1580
    $pos = strpos($text, RulesTestCase::t('Evaluating the action %entity_fetch.', array('entity_fetch')));
1581
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Added the provided variable %node of type %node', array('node')), $pos) : FALSE;
1582
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Saved %node of type %node.', array('node')), $pos) : FALSE;
1583
    $pos = ($pos !== FALSE) ? strpos($text, RulesTestCase::t('Evaluating the action %entity_delete.', array('entity_delete')), $pos) : FALSE;
1584
    $this->assertTrue($pos !== FALSE, 'Data has been fetched, saved and deleted.');
1585
    //debug(RulesLog::logger()->render());
1586

    
1587

    
1588

    
1589
    $node = entity_property_values_create_entity('node', array(
1590
      'type' => 'article',
1591
      'author' => $user,
1592
      'title' => 'foo',
1593
    ))->value();
1594
    $term_wrapped = entity_property_values_create_entity('taxonomy_term', array(
1595
      'name' => $this->randomName(),
1596
      'vocabulary' => 1,
1597
    ))->save();
1598

    
1599
    // Test asserting the field and using it afterwards.
1600
    $rule = rule(array('node' => array('type' => 'node')));
1601
    $rule->condition('entity_has_field', array('entity:select' => 'node', 'field' => 'field_tags'));
1602
    $rule->condition('entity_is_new', array('entity:select' => 'node'));
1603
    $rule->action('list_add', array('list:select' => 'node:field-tags', 'item' => $term_wrapped));
1604
    $rule->integrityCheck();
1605
    $rule->execute($node);
1606

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

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

    
1615
    // Test the entity is of type condition.
1616
    $rule = rule(array('entity' => array('type' => 'entity', 'label' => 'entity')));
1617
    $rule->condition('entity_is_of_type', array('type' => 'node'));
1618
    $rule->action('data_set', array('data:select' => 'entity:title', 'value' => 'bar'));
1619
    $rule->integrityCheck();
1620
    $rule->execute(entity_metadata_wrapper('node', $node));
1621

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

    
1624

    
1625
    // Test the entity_query action.
1626
    $node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'foo2'));
1627
    $rule = rule();
1628
    $rule->action('entity_query', array('type' => 'node', 'property' => 'title', 'value' => 'foo2'))
1629
         ->action('data_set', array('data:select' => 'entity_fetched:0:title', 'value' => 'bar'));
1630
    $rule->access();
1631
    $rule->integrityCheck();
1632
    $rule->execute();
1633
    $node = node_load($node->nid);
1634
    $this->assertEqual('bar', $node->title, 'Fetched a node by title and modified it.');
1635

    
1636
    RulesLog::logger()->checkLog();
1637
  }
1638

    
1639
  /**
1640
   * Test integration for the taxonomy module.
1641
   */
1642
  function testTaxonomyIntegration() {
1643
    $term = entity_property_values_create_entity('taxonomy_term', array(
1644
      'name' => $this->randomName(),
1645
      'vocabulary' => 1,
1646
    ))->value();
1647
    $term2 = clone $term;
1648
    taxonomy_term_save($term);
1649
    taxonomy_term_save($term2);
1650

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

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

    
1664
    // Test using the taxonomy term reference field on a term object.
1665
    $field_name = drupal_strtolower($this->randomName() . '_field_name');
1666
    $field = field_create_field(array(
1667
      'field_name' => $field_name,
1668
      'type' => 'taxonomy_term_reference',
1669
      // Set cardinality to unlimited for tagging.
1670
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
1671
      'settings' => array(
1672
        'allowed_values' => array(
1673
          array(
1674
            'vocabulary' => 'tags',
1675
            'parent' => 0,
1676
          ),
1677
        ),
1678
      ),
1679
    ));
1680
    $instance = array(
1681
      'field_name' => $field_name,
1682
      'entity_type' => 'taxonomy_term',
1683
      'bundle' => 'tags', // Machine name of vocabulary.
1684
      'label' => $this->randomName() . '_label',
1685
      'description' => $this->randomName() . '_description',
1686
      'weight' => mt_rand(0, 127),
1687
      'widget' => array(
1688
        'type' => 'taxonomy_autocomplete',
1689
        'weight' => -4,
1690
      ),
1691
      'display' => array(
1692
        'default' => array(
1693
          'type' => 'taxonomy_term_reference_link',
1694
          'weight' => 10,
1695
        ),
1696
      ),
1697
    );
1698
    field_create_instance($instance);
1699

    
1700
    $term1 = entity_property_values_create_entity('taxonomy_term', array(
1701
      'name' => $this->randomName(),
1702
      'vocabulary' => 1,
1703
    ))->save();
1704
    $term2 = entity_property_values_create_entity('taxonomy_term', array(
1705
      'name' => $this->randomName(),
1706
      'vocabulary' => 1,
1707
    ))->save();
1708

    
1709
    // Test asserting the term reference field and using it afterwards.
1710
    $rule = rule(array('taxonomy_term' => array('type' => 'taxonomy_term')));
1711
    $rule->condition('entity_has_field', array('entity:select' => 'taxonomy-term', 'field' => $field_name));
1712
    // Add $term2 to $term1 using the term reference field.
1713
    $selector = str_replace('_', '-', 'taxonomy_term:' . $field_name);
1714
    $rule->action('list_add', array('list:select' => $selector, 'item' => $term2));
1715
    $rule->integrityCheck();
1716
    $rule->execute($term1);
1717

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

    
1721
    // Test an action set for merging term parents, which is provided as default
1722
    // config.
1723
    $term = entity_property_values_create_entity('taxonomy_term', array(
1724
      'name' => $this->randomName(),
1725
      'vocabulary' => 1,
1726
      'parent' => array($term1->value()),
1727
    ))->save();
1728

    
1729
    $action = rules_action('component_rules_retrieve_term_parents');
1730
    list($parents) = $action->execute(array($term->getIdentifier()));
1731
    $this->assertTrue($parents[0]->tid == $term1->getIdentifier(), 'Invoked component to retrieve term parents.');
1732
    RulesLog::logger()->checkLog();
1733
  }
1734

    
1735
  /**
1736
   * Test integration for the node module.
1737
   */
1738
  function testNodeIntegration() {
1739
    $tests = array(
1740
      array('node_unpublish', 'node_is_published', 'node_publish', 'status'),
1741
      array('node_make_unsticky', 'node_is_sticky', 'node_make_sticky', 'sticky'),
1742
      array('node_unpromote', 'node_is_promoted', 'node_promote', 'promote'),
1743
    );
1744
    $node = $this->drupalCreateNode(array('type' => 'page', 'status' => 1, 'sticky' => 1, 'promote' => 1));
1745

    
1746
    foreach ($tests as $info) {
1747
      list($action1, $condition, $action2, $property) = $info;
1748
      rules_action($action1)->execute($node);
1749

    
1750
      $node = node_load($node->nid, NULL, TRUE);
1751
      $this->assertFalse($node->$property, 'Action has permanently disabled node '. $property);
1752
      $return = rules_condition($condition)->execute($node);
1753
      $this->assertFalse($return, 'Condition determines node '. $property . ' is disabled.');
1754

    
1755
      rules_action($action2)->execute($node);
1756
      $node = node_load($node->nid, NULL, TRUE);
1757
      $this->assertTrue($node->$property, 'Action has permanently enabled node '. $property);
1758
      $return = rules_condition($condition)->execute($node);
1759
      $this->assertTrue($return, 'Condition determines node '. $property . ' is enabled.');
1760
    }
1761

    
1762
    $return = rules_condition('node_is_of_type', array('type' => array('page', 'article')))->execute($node);
1763
    $this->assertTrue($return, 'Condition determines node is of type page.');
1764
    $return = rules_condition('node_is_of_type', array('type' => array('article')))->execute($node);
1765
    $this->assertFalse($return, 'Condition determines node is not of type article.');
1766

    
1767

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

    
1780
  /**
1781
   * Test integration for the user module.
1782
   */
1783
  function testUserIntegration() {
1784
    $rid = $this->drupalCreateRole(array('administer nodes'), 'foo');
1785
    $user = $this->drupalCreateUser();
1786

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

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

    
1799
    // Test assigning a role with user_add_role action.
1800
    $rule = rule(array('user' => array('type' => 'user')));
1801
    $rule->action('user_add_role', array('account:select' => 'user', 'roles' => array($rid)));
1802
    $rule->execute($user);
1803

    
1804
    $user = user_load($user->uid, TRUE);
1805
    $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
1806
    $this->assertTrue($result, 'Role assigned to user.');
1807

    
1808
    // Test removing a role with the user_remove_role action.
1809
    $rule = rule(array('user' => array('type' => 'user')));
1810
    $rule->action('user_remove_role', array('account:select' => 'user', 'roles' => array($rid)));
1811
    $rule->execute($user);
1812

    
1813
    $user = user_load($user->uid, TRUE);
1814
    $result = rules_condition('user_has_role', array('roles' => array($rid)))->execute($user);
1815
    $this->assertFalse($result, 'Role removed from user.');
1816

    
1817
    // Test user blocking.
1818
    rules_action('user_block')->execute($user);
1819
    $user = user_load($user->uid, TRUE);
1820
    $this->assertTrue(rules_condition('user_is_blocked')->execute($user), 'User has been blocked.');
1821

    
1822
    rules_action('user_unblock')->execute($user);
1823
    $user = user_load($user->uid, TRUE);
1824
    $this->assertFalse(rules_condition('user_is_blocked')->execute($user), 'User has been unblocked.');
1825

    
1826
    RulesLog::logger()->checkLog();
1827
  }
1828

    
1829
  /**
1830
   * Test integration for the php module.
1831
   */
1832
  function testPHPIntegration() {
1833
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1834
    $rule = rule(array('var_name' => array('type' => 'node')));
1835
    $rule->condition('php_eval', array('code' => 'return TRUE;'))
1836
         ->action('php_eval', array('code' => 'drupal_set_message("Executed-" . $var_name->title);'))
1837
         ->action('drupal_message', array('message' => 'Title: <?php echo $var_name->title; ?> Token: [var_name:title]'));
1838

    
1839
    $rule->execute($node);
1840
    $rule->access();
1841
    RulesLog::logger()->checkLog();
1842
    $msg = drupal_get_messages();
1843
    $this->assertEqual(array_pop($msg['status']), "Title: foo Token: foo", 'PHP input evaluation has been applied.');
1844
    $this->assertEqual(array_pop($msg['status']), "Executed-foo", 'PHP code condition and action have been evaluated.');
1845

    
1846
    // Test PHP data processor
1847
    $rule = rule(array('var_name' => array('type' => 'node')));
1848
    $rule->action('drupal_message', array(
1849
      'message:select' => 'var_name:title',
1850
      'message:process' => array(
1851
        'php' => array('code' => 'return "Title: $value";')
1852
      ),
1853
    ));
1854
    $rule->execute($node);
1855
    $rule->access();
1856
    RulesLog::logger()->checkLog();
1857
    $msg = drupal_get_messages();
1858
    $this->assertEqual(array_pop($msg['status']), "Title: foo", 'PHP data processor has been applied.');
1859
  }
1860

    
1861
  /**
1862
   * Test the "rules_core" integration.
1863
   */
1864
  function testRulesCoreIntegration() {
1865
    // Make sure the date input evaluator evaluates properly using strtotime().
1866
    $node = $this->drupalCreateNode(array('title' => 'foo'));
1867
    $rule = rule(array('node' => array('type' => 'node')));
1868
    $rule->action('data_set', array('data:select' => 'node:created', 'value' => '+1 day'));
1869

    
1870
    $rule->execute($node);
1871
    RulesLog::logger()->checkLog();
1872
    $node = node_load($node->nid, NULL, TRUE);
1873
    $now = RulesDateInputEvaluator::gmstrtotime('now');
1874
    // Tolerate a difference of a second.
1875
    $this->assertTrue(abs($node->created - $now - 86400) <= 1, 'Date input has been evaluated.');
1876

    
1877
    // Test using a numeric offset.
1878
    $rule = rule(array('number' => array('type' => 'decimal')), array('number'));
1879
    $rule->action('data_set', array(
1880
      'data:select' => 'number',
1881
      'value:select' => 'number',
1882
      'value:process' => array(
1883
        'num_offset' => array('value' => 1),
1884
      ),
1885
    ));
1886
    $rule->integrityCheck();
1887
    list($result) = $rule->execute(10);
1888
    $this->assertTrue($result == 11, 'Numeric offset has been applied');
1889

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

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

    
1916
    // Test using a positive offset of 1 year 6 months and 30 minutes.
1917
    $set = rules_action_set(array('date' => array('type' => 'date')), array('date'));
1918
    $set->action('data_set', array(
1919
      'data:select' => 'date',
1920
      'value:select' => 'date',
1921
      'value:process' => array(
1922
        'date_offset' => array('value' => 86400 * 30 * 18 + 30 * 60),
1923
      ),
1924
    ));
1925
    $date = date_create("14 Mar 1984 10:19:23 +01:00")->format('U');
1926
    list($result) = $set->execute($date);
1927
    $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.');
1928

    
1929
    RulesLog::logger()->checkLog();
1930
  }
1931

    
1932
  /**
1933
   * Test site/system integration.
1934
   */
1935
  function testSystemIntegration() {
1936
    // Test using the 'site' variable.
1937
    $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => $GLOBALS['user']->name));
1938
    $this->assertTrue($condition->execute(), 'Retrieved the current user\'s name.');
1939
    // Another test using a token replacement.
1940
    $condition = rules_condition('data_is', array('data:select' => 'site:current-user:name', 'value' => '[site:current-user:name]'));
1941
    $this->assertTrue($condition->execute(), 'Replaced the token for the current user\'s name.');
1942

    
1943
    // Test breadcrumbs and drupal set message.
1944
    $rule = rules_reaction_rule();
1945
    $rule->event('init')
1946
         ->action('breadcrumb_set', array('titles' => array('foo'), 'paths' => array('bar')))
1947
         ->action('drupal_message', array('message' => 'A message.'));
1948
    $rule->save('test');
1949

    
1950
    $this->drupalGet('node');
1951
    $this->assertLink('foo', 0, 'Breadcrumb has been set.');
1952
    $this->assertText('A message.', 'Drupal message has been shown.');
1953

    
1954
    // Test the page redirect.
1955
    $node = $this->drupalCreateNode();
1956
    $rule = rules_reaction_rule();
1957
    $rule->event('node_view')
1958
         ->action('redirect', array('url' => 'user'));
1959
    $rule->save('test2');
1960

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

    
1964
    // Also test using a url including a fragment.
1965
    $actions = $rule->actions();
1966
    $actions[0]->settings['url'] = 'user#fragment';
1967
    $rule->save();
1968

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

    
1972

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

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

    
1982
    // Test sending mail to all users of a role. First make sure there is a
1983
    // custom role and a user for it.
1984
    $user = $this->drupalCreateUser(array('administer nodes'));
1985
    $roles = $user->roles;
1986
    // Remove the authenticate role so we only use the new role created by
1987
    // drupalCreateUser().
1988
    unset($roles[DRUPAL_AUTHENTICATED_RID]);
1989
    rules_action('mail_to_users_of_role', $settings + array('roles' => array_keys($roles)))->execute();
1990
    $this->assertMail('to', $user->mail, 'Mail to users of a role has been sent.');
1991

    
1992
    // Test reacting on new log entries and make sure the log entry is usable.
1993
    $rule = rules_reaction_rule();
1994
    $rule->event('watchdog');
1995
    $rule->action('drupal_message', array('message:select' => 'log_entry:message'));
1996
    $rule->integrityCheck()->save('test_watchdog');
1997

    
1998
    watchdog('php', 'test %message', array('%message' => 'message'));
1999
    $msg = drupal_get_messages();
2000
    $this->assertEqual(array_pop($msg['status']), t('test %message', array('%message' => 'message')), 'Watchdog event occurred and log entry properties can be used.');
2001
  }
2002

    
2003
  /**
2004
   * Tests the path module integration.
2005
   */
2006
  function testPathIntegration() {
2007
    rules_action('path_alias')->execute('foo', 'bar');
2008
    $path = path_load('foo');
2009
    $this->assertTrue($path['alias'] == 'bar', 'URL alias has been created.');
2010

    
2011
    $alias_exists = rules_condition('path_alias_exists', array('alias' => 'bar'))->execute();
2012
    $this->assertTrue($alias_exists, 'Created URL alias exists.');
2013

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

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

    
2023
    // Test term alias action.
2024
    $term = entity_property_values_create_entity('taxonomy_term', array(
2025
      'name' => $this->randomName(),
2026
      'vocabulary' => 1,
2027
    ))->value();
2028
    rules_action('taxonomy_term_path_alias')->execute($term, 'term-test');
2029
    $path = path_load("taxonomy/term/$term->tid");
2030
    $this->assertTrue($path['alias'] == 'term-test', 'Term URL alias has been created.');
2031

    
2032
    RulesLog::logger()->checkLog();
2033
  }
2034
}
2035

    
2036
/**
2037
 * Test event dispatcher functionality.
2038
 */
2039
class RulesEventDispatcherTestCase extends DrupalWebTestCase {
2040

    
2041
  static function getInfo() {
2042
    return array(
2043
      'name' => 'Rules event dispatchers',
2044
      'description' => 'Tests event dispatcher functionality.',
2045
      'group' => 'Rules',
2046
    );
2047
  }
2048

    
2049
  function setUp() {
2050
    parent::setUp('rules', 'rules_test');
2051
  }
2052

    
2053
  /**
2054
   * Tests start and stop functionality.
2055
   */
2056
  public function testStartAndStop() {
2057
    $handler = rules_get_event_handler('rules_test_event');
2058
    $rule = rules_reaction_rule();
2059
    $rule->event('rules_test_event');
2060

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

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

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

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

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

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

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

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

    
2103
/**
2104
 * Test early bootstrap Rules invocation.
2105
 */
2106
class RulesInvocationEnabledTestCase extends DrupalWebTestCase {
2107

    
2108
  /**
2109
   * {@inheritdoc}
2110
   */
2111
  public static function getInfo() {
2112
    return array(
2113
      'name' => 'Rules invocation enabled',
2114
      'description' => 'Tests that Rules events are enabled during menu item loads.',
2115
      'group' => 'Rules',
2116
    );
2117
  }
2118

    
2119
  /**
2120
   * {@inheritdoc}
2121
   */
2122
  public function setUp() {
2123
    parent::setUp('dblog', 'rules', 'rules_test', 'rules_test_invocation');
2124
  }
2125

    
2126
  /**
2127
   * Tests that a Rules event is triggered on node menu item loading.
2128
   *
2129
   * @see rules_test_invocation_node_load()
2130
   */
2131
  public function testInvocationOnNodeMenuLoading() {
2132
    // Create a test node.
2133
    $node = $this->drupalCreateNode(array('title' => 'Test'));
2134
    // Enable Rules logging on the INFO level so that entries are written to
2135
    // dblog.
2136
    variable_set('rules_log_errors', RulesLog::INFO);
2137
    // Create an empty rule that will fire in our node load hook.
2138
    $rule = rules_reaction_rule();
2139
    $rule->event('rules_test_event');
2140
    $rule->save('test_rule');
2141

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

    
2148
}