Import from SVN (r45945, r596)
[civicrm-core.git] / tests / phpunit / CRM / Core / BAO / ActionScheduleTest.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.3 |
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;
35
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',
60 );
61 $this->fixtures['phonecall'] = array( // createTestObject
62 'status_id' => 1,
63 'activity_type_id' => 2,
64 'activity_date_time' => '20120615100000',
65 'is_current_revision' => 1,
66 'is_deleted' => 0,
67 );
68 $this->fixtures['contact'] = array( // API
69 'version' => 3,
70 'contact_type' => 'Individual',
71 'email' => 'test-member@example.com',
72 );
73 $this->fixtures['sched_activity_1day'] = array( // create()
74 'name' => 'One_Day_Phone_Call_Notice',
75 'title' => 'One Day Phone Call Notice',
76 'absolute_date' => NULL,
77 'body_html' => '<p>1-Day (non-repeating)</p>',
78 'body_text' => '1-Day (non-repeating)',
79 'end_action' => NULL,
80 'end_date' => NULL,
81 'end_frequency_interval' => NULL,
82 'end_frequency_unit' => NULL,
83 'entity_status' => '1',
84 'entity_value' => '2',
85 'group_id' => NULL,
86 'is_active' => '1',
87 'is_repeat' => '0',
88 'mapping_id' => '1',
89 'msg_template_id' => NULL,
90 'recipient' => '2',
91 'recipient_listing' => NULL,
92 'recipient_manual' => NULL,
93 'record_activity' => NULL,
94 'repetition_frequency_interval' => NULL,
95 'repetition_frequency_unit' => NULL,
96 'start_action_condition' => 'before',
97 'start_action_date' => 'activity_date_time',
98 'start_action_offset' => '1',
99 'start_action_unit' => 'day',
100 'subject' => '1-Day (non-repeating)',
101 );
102 $this->fixtures['sched_activity_1day_r'] = array(
103 'name' => 'One_Day_Phone_Call_Notice_R',
104 'title' => 'One Day Phone Call Notice R',
105 'absolute_date' => NULL,
106 'body_html' => '<p>1-Day (repeating)</p>',
107 'body_text' => '1-Day (repeating)',
108 'end_action' => 'after',
109 'end_date' => 'activity_date_time',
110 'end_frequency_interval' => '2',
111 'end_frequency_unit' => 'day',
112 'entity_status' => '1',
113 'entity_value' => '2',
114 'group_id' => NULL,
115 'is_active' => '1',
116 'is_repeat' => '1',
117 'mapping_id' => '1',
118 'msg_template_id' => NULL,
119 'recipient' => '2',
120 'recipient_listing' => NULL,
121 'recipient_manual' => NULL,
122 'record_activity' => NULL,
123 'repetition_frequency_interval' => '6',
124 'repetition_frequency_unit' => 'hour',
125 'start_action_condition' => 'before',
126 'start_action_date' => 'activity_date_time',
127 'start_action_offset' => '1',
128 'start_action_unit' => 'day',
129 'subject' => '1-Day (repeating)',
130 );
131 $this->fixtures['sched_membership_join_2week'] = array( // create()
132 'name' => 'sched_membership_join_2week',
133 'title' => 'sched_membership_join_2week',
134 'absolute_date' => '',
135 'body_html' => '<p>body sched_membership_join_2week</p>',
136 'body_text' => 'body sched_membership_join_2week',
137 'end_action' => '',
138 'end_date' => '',
139 'end_frequency_interval' => '',
140 'end_frequency_unit' => '',
141 'entity_status' => '',
142 'entity_value' => '',
143 'group_id' => '',
144 'is_active' => 1,
145 'is_repeat' => '0',
146 'mapping_id' => 4,
147 'msg_template_id' => '',
148 'recipient' => '',
149 'recipient_listing' => '',
150 'recipient_manual' => '',
151 'record_activity' => 1,
152 'repetition_frequency_interval' => '',
153 'repetition_frequency_unit' => '',
154 'start_action_condition' => 'after',
155 'start_action_date' => 'membership_join_date',
156 'start_action_offset' => '2',
157 'start_action_unit' => 'week',
158 'subject' => 'subject sched_membership_join_2week',
159 );
160 $this->fixtures['sched_membership_end_2week'] = array( // create()
161 'name' => 'sched_membership_end_2week',
162 'title' => 'sched_membership_end_2week',
163 'absolute_date' => '',
164 'body_html' => '<p>body sched_membership_end_2week</p>',
165 'body_text' => 'body sched_membership_end_2week',
166 'end_action' => '',
167 'end_date' => '',
168 'end_frequency_interval' => '',
169 'end_frequency_unit' => '',
170 'entity_status' => '',
171 'entity_value' => '',
172 'group_id' => '',
173 'is_active' => 1,
174 'is_repeat' => '0',
175 'mapping_id' => 4,
176 'msg_template_id' => '',
177 'recipient' => '',
178 'recipient_listing' => '',
179 'recipient_manual' => '',
180 'record_activity' => 1,
181 'repetition_frequency_interval' => '',
182 'repetition_frequency_unit' => '',
183 'start_action_condition' => 'before',
184 'start_action_date' => 'membership_end_date',
185 'start_action_offset' => '2',
186 'start_action_unit' => 'week',
187 'subject' => 'subject sched_membership_end_2week',
188 );
189 $this->_setUp();
190 $this->quickCleanup(array('civicrm_action_log', 'civicrm_action_schedule'));
191 }
192
193 /**
194 * Tears down the fixture, for example, closes a network connection.
195 * This method is called after a test is executed.
196 *
197 * @access protected
198 */
199 function tearDown() {
200 parent::tearDown();
201
202 $this->mut->clearMessages();
203 $this->mut->stop();
204 unset($this->mut);
205
206 $this->_tearDown();
207 }
208
209 function testActivityDateTime_Match_NonRepeatableSchedule() {
210 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_activity_1day'], $ids);
211 $this->assertTrue(is_numeric($actionScheduleDao->id));
212
213 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']);
214 // $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']);
215 $this->assertTrue(is_numeric($activity->id));
216 $contact = civicrm_api('contact', 'create', $this->fixtures['contact']);
217 $activity->source_contact_id = $contact['id'];
218 $activity->save();
219
220 $this->assertCronRuns(array(
221 array( // Before the 24-hour mark, no email
222 'time' => '2012-06-14 04:00:00',
223 'recipients' => array(),
224 ),
225 array( // After the 24-hour mark, an email
226 'time' => '2012-06-14 15:00:00',
227 'recipients' => array(array('test-member@example.com')),
228 ),
229 array( // Run cron again; message already sent
230 'time' => '',
231 'recipients' => array(),
232 ),
233 ));
234 }
235
236 function testActivityDateTime_Match_RepeatableSchedule() {
237 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_activity_1day_r'], $ids);
238 $this->assertTrue(is_numeric($actionScheduleDao->id));
239
240 $activity = $this->createTestObject('CRM_Activity_DAO_Activity', $this->fixtures['phonecall']);
241 $this->assertTrue(is_numeric($activity->id));
242 $contact = civicrm_api('contact', 'create', $this->fixtures['contact']);
243 $activity->source_contact_id = $contact['id'];
244 $activity->save();
245
246 $this->assertCronRuns(array(
247 array( // Before the 24-hour mark, no email
248 'time' => '012-06-14 04:00:00',
249 'recipients' => array(),
250 ),
251 array( // After the 24-hour mark, an email
252 'time' => '2012-06-14 15:00:00',
253 'recipients' => array(array('test-member@example.com')),
254 ),
255 array( // Run cron 4 hours later; first message already sent
256 'time' => '2012-06-14 20:00:00',
257 'recipients' => array(),
258 ),
259 array( // Run cron 6 hours later; send second message
260 'time' => '2012-06-14 21:00:01',
261 'recipients' => array(array('test-member@example.com')),
262 ),
263 ));
264 }
265
266 /**
267 * For contacts/activities which don't match the schedule filter,
268 * an email should *not* be sent.
269 */
270 // TODO // function testActivityDateTime_NonMatch() { }
271
272 /**
273 * For contacts/members which match schedule based on join date,
274 * an email should be sent.
275 */
276 function testMembershipJoinDate_Match() {
277 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $this->fixtures['rolling_membership']);
278 $this->assertTrue(is_numeric($membership->id));
279 $result = civicrm_api('Email', 'create', array(
280 'contact_id' => $membership->contact_id,
281 'email' => 'test-member@example.com',
282 'location_type_id' => 1,
283 'version' => 3,
284 ));
285 $this->assertAPISuccess($result);
286
287 $actionSchedule = $this->fixtures['sched_membership_join_2week'];
288 $actionSchedule['entity_value'] = $membership->membership_type_id;
289 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule, $ids);
290 $this->assertTrue(is_numeric($actionScheduleDao->id));
291
292 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
293 $this->assertCronRuns(array(
294 array( // Before the 2-week mark, no email
295 'time' => '2012-03-28 01:00:00',
296 'recipients' => array(),
297 ),
298 array( // After the 2-week mark, send an email
299 'time' => '2012-03-29 01:00:00',
300 'recipients' => array(array('test-member@example.com')),
301 ),
302 ));
303 }
304
305 /**
306 * For contacts/members which match schedule based on join date,
307 * an email should be sent.
308 */
309 function testMembershipJoinDate_NonMatch() {
310 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $this->fixtures['rolling_membership']);
311 $this->assertTrue(is_numeric($membership->id));
312 $result = civicrm_api('Email', 'create', array(
313 'contact_id' => $membership->contact_id,
314 'location_type_id' => 1,
315 'email' => 'test-member@example.com',
316 'version' => 3,
317 ));
318 $this->assertAPISuccess($result);
319
320 // Add an alternative membership type, and only send messages for that type
321 $extraMembershipType = $this->createTestObject('CRM_Member_DAO_MembershipType', array());
322 $this->assertTrue(is_numeric($extraMembershipType->id));
323 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($this->fixtures['sched_membership_join_2week'], $ids);
324 $this->assertTrue(is_numeric($actionScheduleDao->id));
325 $actionScheduleDao->entity_value = $extraMembershipType->id;
326 $actionScheduleDao->save();
327
328 // start_date=2012-03-15 ; schedule is 2 weeks after start_date
329 $this->assertCronRuns(array(
330 array( // After the 2-week mark, don't send email because we have different membership type
331 'time' => '2012-03-29 01:00:00',
332 'recipients' => array(),
333 ),
334 ));
335 }
336
337 /**
338 * For contacts/members which match schedule based on end date,
339 * an email should be sent.
340 */
341 function testMembershipEndDate_Match() {
342 // creates membership with end_date = 20120615
343 $membership = $this->createTestObject('CRM_Member_DAO_Membership', $this->fixtures['rolling_membership']);
344 $this->assertTrue(is_numeric($membership->id));
345 $result = civicrm_api('Email', 'create', array(
346 'contact_id' => $membership->contact_id,
347 'email' => 'test-member@example.com',
348 'version' => 3,
349 ));
350 $this->assertAPISuccess($result);
351
352 $actionSchedule = $this->fixtures['sched_membership_end_2week'];
353 $actionSchedule['entity_value'] = $membership->membership_type_id;
354 $actionScheduleDao = CRM_Core_BAO_ActionSchedule::add($actionSchedule, $ids);
355 $this->assertTrue(is_numeric($actionScheduleDao->id));
356
357 // end_date=2012-06-15 ; schedule is 2 weeks before end_date
358 $this->assertCronRuns(array(
359 array( // Before the 2-week mark, no email
360 'time' => '2012-05-31 01:00:00',
361 // 'time' => '2012-06-01 01:00:00', // FIXME: Is this the right boundary?
362 'recipients' => array(),
363 ),
364 array( // After the 2-week mark, send an email
365 'time' => '2012-06-01 01:00:00',
366 'recipients' => array(array('test-member@example.com')),
367 ),
368 ));
369 }
370
371 // TODO // function testMembershipEndDate_NonMatch() { }
372 // TODO // function testEventTypeStartDate_Match() { }
373 // TODO // function testEventTypeEndDate_Match() { }
374 // TODO // function testEventNameStartDate_Match() { }
375 // TODO // function testEventNameEndDate_Match() { }
376
377 /**
378 * Run a series of cron jobs and make an assertion about email deliveries
379 *
380 * @param $jobSchedule array specifying when to run cron and what messages to expect; each item is an array with keys:
381 * - time: string, e.g. '2012-06-15 21:00:01'
382 * - recipients: array(array(string)), list of email addresses which should receive messages
383 */
384 function assertCronRuns($cronRuns) {
385 foreach ($cronRuns as $cronRun) {
386 CRM_Utils_Time::setTime($cronRun['time']);
387 $result = civicrm_api('job', 'send_reminder', array(
388 'version' => 3,
389 ));
390 $this->assertAPISuccess($result);
391 $this->mut->assertRecipients($cronRun['recipients']);
392 $this->mut->clearMessages();
393 }
394 }
395
396 ////////////////////////////////
397 ////////////////////////////////
398 ////////////////////////////////
399 ////////////////////////////////
400
401 /**
402 * @var array(DAO_Name => array(int)) List of items to garbage-collect during tearDown
403 */
404 private $_testObjects;
405
406 /**
407 * Sets up the fixture, for example, opens a network connection.
408 * This method is called before a test is executed.
409 *
410 * @access protected
411 */
412 protected function _setUp() {
413 $this->_testObjects = array();
414 }
415
416 /**
417 * Tears down the fixture, for example, closes a network connection.
418 * This method is called after a test is executed.
419 *
420 * @access protected
421 */
422 protected function _tearDown() {
423 parent::tearDown();
424 $this->deleteTestObjects();
425 }
426
427 /**
428 * This is a wrapper for CRM_Core_DAO::createTestObject which tracks
429 * created entities and provides for brainless clenaup.
430 *
431 * @see CRM_Core_DAO::createTestObject
432 */
433 function createTestObject($daoName, $params = array(
434 ), $numObjects = 1, $createOnly = FALSE) {
435 $objects = CRM_Core_DAO::createTestObject($daoName, $params, $numObjects, $createOnly);
436 if (is_array($objects)) {
437 $this->registerTestObjects($objects);
438 } else {
439 $this->registerTestObjects(array($objects));
440 }
441 return $objects;
442 }
443
444 /**
445 * @param $objects array(object) DAO or BAO objects
446 */
447 function registerTestObjects($objects) {
448 //if (is_object($objects)) {
449 // $objects = array($objects);
450 //}
451 foreach ($objects as $object) {
452 $daoName = preg_replace('/_BAO_/', '_DAO_', get_class($object));
453 $this->_testObjects[$daoName][] = $object->id;
454 }
455 }
456
457 function deleteTestObjects() {
458 // Note: You might argue that the FK relations between test
459 // objects could make this problematic; however, it should
460 // behave intuitively as long as we mentally split our
461 // test-objects between the "manual/primary records"
462 // and the "automatic/secondary records"
463 foreach ($this->_testObjects as $daoName => $daoIds) {
464 foreach ($daoIds as $daoId) {
465 CRM_Core_DAO::deleteTestObjects($daoName, array('id' => $daoId));
466 }
467 }
468 $this->_testObjects = array();
469 }
470
471}