copyright and version fixes
[civicrm-core.git] / tests / phpunit / CRM / Core / BAO / ActionScheduleTest.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
232624b1 4 | CiviCRM version 4.4 |
6a488035
TO
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2013 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26*/
27
28
29require_once 'CiviTest/CiviUnitTestCase.php';
30class CRM_Core_BAO_ActionScheduleTest extends CiviUnitTestCase {
31 /**
32 * @var object see CiviTest/CiviMailUtils
33 */
34 var $mut;
cee19268 35
6a488035
TO
36 function get_info() {
37 return array(
38 'name' => 'Action-Schedule BAO',
39 'description' => 'Test sending of scheduled notifications.',
40 'group' => 'CiviCRM BAO Tests',
41 );
42 }
43
44 function setUp() {
45 parent::setUp();
46
47 require_once 'CiviTest/CiviMailUtils.php';
48 $this->mut = new CiviMailUtils($this, true);
49
50 $this->fixtures['rolling_membership'] = array( // createTestObject
51 'membership_type_id' => array(
52 'period_type' => 'rolling',
53 'duration_unit' => 'month',
54 'duration_interval' => '3',
55 'is_active' => 1,
56 ),
57 'join_date' => '20120315',
58 'start_date' => '20120315',
59 'end_date' => '20120615',
b29be8c2 60 'is_override' => 0,
6a488035 61 );
4b1efa1d 62
63 $this->fixtures['rolling_membership_past'] = array( // createTestObject
64 'membership_type_id' => array(
65 'period_type' => 'rolling',
66 'duration_unit' => 'month',
67 'duration_interval' => '3',
68 'is_active' => 1,
69 ),
70 'join_date' => '20100310',
71 'start_date' => '20100310',
72 'end_date' => '20100610',
73 'is_override' => 'NULL',
74 );
fe265b4e 75
6a488035
TO
76 $this->fixtures['phonecall'] = array( // createTestObject
77 'status_id' => 1,
78 'activity_type_id' => 2,
79 'activity_date_time' => '20120615100000',
80 'is_current_revision' => 1,
81 'is_deleted' => 0,
82 );
83 $this->fixtures['contact'] = array( // API
84 'version' => 3,
b29be8c2 85 'is_deceased' => 0,
6a488035
TO
86 'contact_type' => 'Individual',
87 'email' => 'test-member@example.com',
88 );
89 $this->fixtures['sched_activity_1day'] = array( // create()
90 'name' => 'One_Day_Phone_Call_Notice',
91 'title' => 'One Day Phone Call Notice',
92 'absolute_date' => NULL,
93 'body_html' => '<p>1-Day (non-repeating)</p>',
94 'body_text' => '1-Day (non-repeating)',
95 'end_action' => NULL,
96 'end_date' => NULL,
97 'end_frequency_interval' => NULL,
98 'end_frequency_unit' => NULL,
99 'entity_status' => '1',
100 'entity_value' => '2',
101 'group_id' => NULL,
102 'is_active' => '1',
103 'is_repeat' => '0',
104 'mapping_id' => '1',
105 'msg_template_id' => NULL,
106 'recipient' => '2',
107 'recipient_listing' => NULL,
108 'recipient_manual' => NULL,
109 'record_activity' => NULL,
110 'repetition_frequency_interval' => NULL,
111 'repetition_frequency_unit' => NULL,
112 'start_action_condition' => 'before',
113 'start_action_date' => 'activity_date_time',
114 'start_action_offset' => '1',
115 'start_action_unit' => 'day',
116 'subject' => '1-Day (non-repeating)',
117 );
118 $this->fixtures['sched_activity_1day_r'] = array(
119 'name' => 'One_Day_Phone_Call_Notice_R',
120 'title' => 'One Day Phone Call Notice R',
121 'absolute_date' => NULL,
122 'body_html' => '<p>1-Day (repeating)</p>',
123 'body_text' => '1-Day (repeating)',
124 'end_action' => 'after',
125 'end_date' => 'activity_date_time',
126 'end_frequency_interval' => '2',
127 'end_frequency_unit' => 'day',
128 'entity_status' => '1',
129 'entity_value' => '2',
130 'group_id' => NULL,
131 'is_active' => '1',
132 'is_repeat' => '1',
133 'mapping_id' => '1',
134 'msg_template_id' => NULL,
135 'recipient' => '2',
136 'recipient_listing' => NULL,
137 'recipient_manual' => NULL,
138 'record_activity' => NULL,
139 'repetition_frequency_interval' => '6',
140 'repetition_frequency_unit' => 'hour',
141 'start_action_condition' => 'before',
142 'start_action_date' => 'activity_date_time',
143 'start_action_offset' => '1',
144 'start_action_unit' => 'day',
145 'subject' => '1-Day (repeating)',
146 );
147 $this->fixtures['sched_membership_join_2week'] = array( // create()
148 'name' => 'sched_membership_join_2week',
149 'title' => 'sched_membership_join_2week',
150 'absolute_date' => '',
151 'body_html' => '<p>body sched_membership_join_2week</p>',
152 'body_text' => 'body sched_membership_join_2week',
153 'end_action' => '',
154 'end_date' => '',
155 'end_frequency_interval' => '',
156 'end_frequency_unit' => '',
157 'entity_status' => '',
158 'entity_value' => '',
159 'group_id' => '',
160 'is_active' => 1,
161 'is_repeat' => '0',
162 'mapping_id' => 4,
163 'msg_template_id' => '',
164 'recipient' => '',
165 'recipient_listing' => '',
166 'recipient_manual' => '',
167 'record_activity' => 1,
168 'repetition_frequency_interval' => '',
169 'repetition_frequency_unit' => '',
170 'start_action_condition' => 'after',
171 'start_action_date' => 'membership_join_date',
172 'start_action_offset' => '2',
173 'start_action_unit' => 'week',
174 'subject' => 'subject sched_membership_join_2week',
175 );
176 $this->fixtures['sched_membership_end_2week'] = array( // create()
177 'name' => 'sched_membership_end_2week',
178 'title' => 'sched_membership_end_2week',
179 'absolute_date' => '',
180 'body_html' => '<p>body sched_membership_end_2week</p>',
181 'body_text' => 'body sched_membership_end_2week',
182 'end_action' => '',
183 'end_date' => '',
184 'end_frequency_interval' => '',
185 'end_frequency_unit' => '',
186 'entity_status' => '',
187 'entity_value' => '',
188 'group_id' => '',
189 'is_active' => 1,
190 'is_repeat' => '0',
191 'mapping_id' => 4,
192 'msg_template_id' => '',
193 'recipient' => '',
194 'recipient_listing' => '',
195 'recipient_manual' => '',
196 'record_activity' => 1,
197 'repetition_frequency_interval' => '',
198 'repetition_frequency_unit' => '',
199 'start_action_condition' => 'before',
200 'start_action_date' => 'membership_end_date',
201 'start_action_offset' => '2',
202 'start_action_unit' => 'week',
203 'subject' => 'subject sched_membership_end_2week',
204 );
4b1efa1d 205
206 $this->fixtures['sched_membership_end_2month'] = array( // create()
207 'name' => 'sched_membership_end_2month',
208 'title' => 'sched_membership_end_2month',
209 'absolute_date' => '',
210 'body_html' => '<p>body sched_membership_end_2month</p>',
211 'body_text' => 'body sched_membership_end_2month',
212 'end_action' => '',
213 'end_date' => '',
214 'end_frequency_interval' => '',
215 'end_frequency_unit' => '',
216 'entity_status' => '',
217 'entity_value' => '',
218 'group_id' => '',
219 'is_active' => 1,
220 'is_repeat' => '0',
221 'mapping_id' => 4,
222 'msg_template_id' => '',
223 'recipient' => '',
224 'recipient_listing' => '',
225 'recipient_manual' => '',
226 'record_activity' => 1,
227 'repetition_frequency_interval' => '',
228 'repetition_frequency_unit' => '',
229 'start_action_condition' => 'after',
230 'start_action_date' => 'membership_end_date',
231 'start_action_offset' => '2',
232 'start_action_unit' => 'month',
233 'subject' => 'subject sched_membership_end_2month',
234 );
235
6a488035
TO
236 $this->_setUp();
237 $this->quickCleanup(array('civicrm_action_log', 'civicrm_action_schedule'));
238 }
239
240 /**
241 * Tears down the fixture, for example, closes a network connection.
242 * This method is called after a test is executed.
243 *
244 * @access protected
245 */
246 function tearDown() {
247 parent::tearDown();
248
249 $this->mut->clearMessages();
250 $this->mut->stop();
251 unset($this->mut);
252
253 $this->_tearDown();
254 }
255
256 function testActivityDateTime_Match_NonRepeatableSchedule() {
cee19268 257 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_activity_1day']);
6a488035
TO
258 $this->assertTrue(is_numeric($actionScheduleDao->id));
259
260 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']);
6a488035
TO
261 $this->assertTrue(is_numeric($activity->id));
262 $contact = civicrm_api('contact', 'create', $this->fixtures['contact']);
6a488035
TO
263 $activity->save();
264
4f20f356 265 $source['contact_id'] = $contact['id'];
266 $source['activity_id'] = $activity->id;
267 $source['record_type_id'] = 2;
268 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
269 $activityContact->save();
270
6a488035
TO
271 $this->assertCronRuns(array(
272 array( // Before the 24-hour mark, no email
273 'time' => '2012-06-14 04:00:00',
274 'recipients' => array(),
275 ),
276 array( // After the 24-hour mark, an email
277 'time' => '2012-06-14 15:00:00',
278 'recipients' => array(array('test-member@example.com')),
279 ),
280 array( // Run cron again; message already sent
281 'time' => '',
282 'recipients' => array(),
283 ),
284 ));
285 }
286
287 function testActivityDateTime_Match_RepeatableSchedule() {
cee19268 288 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_activity_1day_r']);
6a488035
TO
289 $this->assertTrue(is_numeric($actionScheduleDao->id));
290
291 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']);
292 $this->assertTrue(is_numeric($activity->id));
293 $contact = civicrm_api('contact', 'create', $this->fixtures['contact']);
6a488035
TO
294 $activity->save();
295
4f20f356 296 $source['contact_id'] = $contact['id'];
297 $source['activity_id'] = $activity->id;
298 $source['record_type_id'] =2;
299 $activityContact = $this->createTestObject('CRM_Activity_DAO_ActivityContact', $source);
300 $activityContact->save();
301
6a488035
TO
302 $this->assertCronRuns(array(
303 array( // Before the 24-hour mark, no email
304 'time' => '012-06-14 04:00:00',
305 'recipients' => array(),
306 ),
307 array( // After the 24-hour mark, an email
308 'time' => '2012-06-14 15:00:00',
309 'recipients' => array(array('test-member@example.com')),
310 ),
311 array( // Run cron 4 hours later; first message already sent
312 'time' => '2012-06-14 20:00:00',
313 'recipients' => array(),
314 ),
315 array( // Run cron 6 hours later; send second message
316 'time' => '2012-06-14 21:00:01',
317 'recipients' => array(array('test-member@example.com')),
318 ),
319 ));
320 }
321
322 /**
323 * For contacts/activities which don't match the schedule filter,
324 * an email should *not* be sent.
325 */
326 // TODO // function testActivityDateTime_NonMatch() { }
327
328 /**
329 * For contacts/members which match schedule based on join date,
330 * an email should be sent.
331 */
332 function testMembershipJoinDate_Match() {
b29be8c2 333 $membership = $this->createTestObject('CRM_Member_DAO_Membership', array_merge($this->fixtures['rolling_membership'], array('status_id' => 1)));
6a488035
TO
334 $this->assertTrue(is_numeric($membership->id));
335 $result = civicrm_api('Email', 'create', array(
336 'contact_id' => $membership->contact_id,
337 'email' => 'test-member@example.com',
338 'location_type_id' => 1,
339 'version' => 3,
340 ));
341 $this->assertAPISuccess($result);
342
b29be8c2 343 $contact = civicrm_api('contact', 'create', array_merge($this->fixtures['contact'], array('contact_id' => $membership->contact_id)));
6a488035
TO
344 $actionSchedule = $this->fixtures['sched_membership_join_2week'];
345 $actionSchedule['entity_value'] = $membership->membership_type_id;
cee19268 346 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule);
6a488035
TO
347 $this->assertTrue(is_numeric($actionScheduleDao->id));
348
349 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
350 $this->assertCronRuns(array(
351 array( // Before the 2-week mark, no email
352 'time' => '2012-03-28 01:00:00',
353 'recipients' => array(),
354 ),
355 array( // After the 2-week mark, send an email
356 'time' => '2012-03-29 01:00:00',
357 'recipients' => array(array('test-member@example.com')),
358 ),
359 ));
360 }
361
362 /**
363 * For contacts/members which match schedule based on join date,
364 * an email should be sent.
365 */
366 function testMembershipJoinDate_NonMatch() {
367 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $this->fixtures['rolling_membership']);
368 $this->assertTrue(is_numeric($membership->id));
369 $result = civicrm_api('Email', 'create', array(
370 'contact_id' => $membership->contact_id,
371 'location_type_id' => 1,
372 'email' => 'test-member@example.com',
373 'version' => 3,
374 ));
375 $this->assertAPISuccess($result);
fe265b4e 376
6a488035
TO
377 // Add an alternative membership type, and only send messages for that type
378 $extraMembershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', array());
379 $this->assertTrue(is_numeric($extraMembershipType->id));
cee19268 380 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_membership_join_2week']);
6a488035
TO
381 $this->assertTrue(is_numeric($actionScheduleDao->id));
382 $actionScheduleDao->entity_value = $extraMembershipType->id;
383 $actionScheduleDao->save();
384
385 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
386 $this->assertCronRuns(array(
387 array( // After the 2-week mark, don't send email because we have different membership type
388 'time' => '2012-03-29 01:00:00',
389 'recipients' => array(),
390 ),
391 ));
392 }
393
394 /**
395 * For contacts/members which match schedule based on end date,
396 * an email should be sent.
397 */
398 function testMembershipEndDate_Match() {
399 // creates membership with end_date = 20120615
b29be8c2 400 $membership = $this->createTestObject('CRM_Member_DAO_Membership', array_merge($this->fixtures['rolling_membership'], array('status_id' => 2)));
6a488035
TO
401 $this->assertTrue(is_numeric($membership->id));
402 $result = civicrm_api('Email', 'create', array(
403 'contact_id' => $membership->contact_id,
404 'email' => 'test-member@example.com',
405 'version' => 3,
406 ));
b29be8c2 407 $contact = civicrm_api('contact', 'create', array_merge($this->fixtures['contact'], array('contact_id' => $membership->contact_id)));
6a488035
TO
408 $this->assertAPISuccess($result);
409
410 $actionSchedule = $this->fixtures['sched_membership_end_2week'];
411 $actionSchedule['entity_value'] = $membership->membership_type_id;
cee19268 412 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule);
6a488035
TO
413 $this->assertTrue(is_numeric($actionScheduleDao->id));
414
415 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
416 $this->assertCronRuns(array(
417 array( // Before the 2-week mark, no email
418 'time' => '2012-05-31 01:00:00',
419 // 'time' => '2012-06-01 01:00:00', // FIXME: Is this the right boundary?
420 'recipients' => array(),
421 ),
422 array( // After the 2-week mark, send an email
423 'time' => '2012-06-01 01:00:00',
424 'recipients' => array(array('test-member@example.com')),
425 ),
426 ));
427 }
428
4b1efa1d 429
430 /**
431 * For contacts/members which match schedule based on end date,
432 * an email should be sent.
433 */
434 function testMembershipEndDate_NoMatch() {
435 // creates membership with end_date = 20120615
436 $membership = $this->createTestObject('CRM_Member_DAO_Membership', array_merge($this->fixtures['rolling_membership_past'], array('status_id' => 3)));
437 $this->assertTrue(is_numeric($membership->id));
438 $result = civicrm_api('Email', 'create', array(
439 'contact_id' => $membership->contact_id,
440 'email' => 'test-member@example.com',
441 'version' => 3,
442 ));
443 $contact = civicrm_api('contact', 'create', array_merge($this->fixtures['contact'], array('contact_id' => $membership->contact_id)));
444 $this->assertAPISuccess($result);
445
446 $actionSchedule = $this->fixtures['sched_membership_end_2month'];
447 $actionSchedule['entity_value'] = $membership->membership_type_id;
cee19268 448 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule);
4b1efa1d 449 $this->assertTrue(is_numeric($actionScheduleDao->id));
450
451 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
452 $this->assertCronRuns(array(
453 array( // Before the 2-week mark, no email
454 'time' => '2012-05-31 01:00:00',
455 // 'time' => '2012-06-01 01:00:00', // FIXME: Is this the right boundary?
456 'recipients' => array(),
457 ),
458 array( // After the 2-week mark, send an email
459 'time' => '2013-05-01 01:00:00',
460 'recipients' => array(),
461 ),
462 ));
463 }
464
465
466
6a488035
TO
467 // TODO // function testMembershipEndDate_NonMatch() { }
468 // TODO // function testEventTypeStartDate_Match() { }
469 // TODO // function testEventTypeEndDate_Match() { }
470 // TODO // function testEventNameStartDate_Match() { }
471 // TODO // function testEventNameEndDate_Match() { }
472
473 /**
474 * Run a series of cron jobs and make an assertion about email deliveries
475 *
476 * @param $jobSchedule array specifying when to run cron and what messages to expect; each item is an array with keys:
477 * - time: string, e.g. '2012-06-15 21:00:01'
478 * - recipients: array(array(string)), list of email addresses which should receive messages
479 */
480 function assertCronRuns($cronRuns) {
481 foreach ($cronRuns as $cronRun) {
482 CRM_Utils_Time::setTime($cronRun['time']);
483 $result = civicrm_api('job', 'send_reminder', array(
484 'version' => 3,
485 ));
486 $this->assertAPISuccess($result);
487 $this->mut->assertRecipients($cronRun['recipients']);
488 $this->mut->clearMessages();
489 }
490 }
491
492 ////////////////////////////////
493 ////////////////////////////////
494 ////////////////////////////////
495 ////////////////////////////////
496
497 /**
498 * @var array(DAO_Name => array(int)) List of items to garbage-collect during tearDown
499 */
500 private $_testObjects;
501
502 /**
503 * Sets up the fixture, for example, opens a network connection.
504 * This method is called before a test is executed.
505 *
506 * @access protected
507 */
508 protected function _setUp() {
509 $this->_testObjects = array();
510 }
511
512 /**
513 * Tears down the fixture, for example, closes a network connection.
514 * This method is called after a test is executed.
515 *
516 * @access protected
517 */
518 protected function _tearDown() {
519 parent::tearDown();
520 $this->deleteTestObjects();
521 }
522
523 /**
524 * This is a wrapper for CRM_Core_DAO::createTestObject which tracks
525 * created entities and provides for brainless clenaup.
526 *
527 * @see CRM_Core_DAO::createTestObject
528 */
529 function createTestObject($daoName, $params = array(
530 ), $numObjects = 1, $createOnly = FALSE) {
531 $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly);
532 if (is_array($objects)) {
533 $this->registerTestObjects($objects);
534 } else {
535 $this->registerTestObjects(array($objects));
536 }
537 return $objects;
538 }
539
540 /**
541 * @param $objects array(object) DAO or BAO objects
542 */
543 function registerTestObjects($objects) {
544 //if (is_object($objects)) {
545 // $objects = array($objects);
546 //}
547 foreach ($objects as $object) {
548 $daoName = preg_replace('/_BAO_/', '_DAO_', get_class($object));
549 $this->_testObjects[$daoName][] = $object->id;
550 }
551 }
552
553 function deleteTestObjects() {
554 // Note: You might argue that the FK relations between test
555 // objects could make this problematic; however, it should
556 // behave intuitively as long as we mentally split our
557 // test-objects between the "manual/primary records"
558 // and the "automatic/secondary records"
559 foreach ($this->_testObjects as $daoName => $daoIds) {
560 foreach ($daoIds as $daoId) {
561 CRM_Core_DAO::deleteTestObjects($daoName, array('id' => $daoId));
562 }
563 }
564 $this->_testObjects = array();
565 }
566
567}