CRM-16259, added api test for update action
[civicrm-core.git] / tests / phpunit / api / v3 / JobProcessMailingTest.php
CommitLineData
07c09ae4
EM
1<?php
2/*
3 +--------------------------------------------------------------------+
81621fee 4 | CiviCRM version 4.7 |
07c09ae4 5 +--------------------------------------------------------------------+
e7112fa7 6 | Copyright CiviCRM LLC (c) 2004-2015 |
07c09ae4
EM
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/**
29 * File for the CiviCRM APIv3 job functions
30 *
31 * @package CiviCRM_APIv3
32 * @subpackage API_Job
33 *
e7112fa7 34 * @copyright CiviCRM LLC (c) 2004-2015
07c09ae4
EM
35 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
36 *
37 */
38require_once 'CiviTest/CiviUnitTestCase.php';
39//@todo - why doesn't class loader find these (I tried renaming)
40require_once 'CiviTest/CiviMailUtils.php';
92915c55 41
07c09ae4
EM
42/**
43 * Class api_v3_JobTest
44 */
45class api_v3_JobProcessMailingTest extends CiviUnitTestCase {
46 protected $_apiversion = 3;
47
48 public $DBResetRequired = FALSE;
49 public $_entity = 'Job';
50 public $_params = array();
51 private $_groupID;
52 private $_email;
53
d667a9ba
TO
54 protected $defaultSettings;
55
07c09ae4
EM
56 /**
57 * @var CiviMailUtils
58 */
59 private $_mut;
60
00be9182 61 public function setUp() {
d667a9ba 62 $this->cleanupMailingTest();
07c09ae4 63 parent::setUp();
97b7d4a0 64 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0; // DGW
07c09ae4
EM
65 $this->_groupID = $this->groupCreate();
66 $this->_email = 'test@test.test';
67 $this->_params = array(
68 'subject' => 'Accidents in cars cause children',
21b09c13 69 'body_text' => 'BEWARE children need regular infusions of toys. Santa knows your {domain.address}. There is no {action.optOutUrl}.',
07c09ae4
EM
70 'name' => 'mailing name',
71 'created_id' => 1,
72 'groups' => array('include' => array($this->_groupID)),
5f445749 73 'scheduled_date' => 'now',
07c09ae4 74 );
d667a9ba
TO
75 $this->defaultSettings = array(
76 'recipients' => 20, // int, #contacts to receive mailing
77 'workers' => 1, // int, #concurrent cron jobs
78 'iterations' => 1, // int, #times to spawn all the workers
79 'lockHold' => 0, // int, #extra seconds each cron job should hold lock
80 'mailerBatchLimit' => 0, // int, max# recipients to send in a given cron run
81 'mailerJobsMax' => 0, // int, max# concurrent jobs
82 'mailerJobSize' => 0, // int, max# recipients in each job
83 'mailThrottleTime' => 0, // int, microseconds separating messages
84 );
481a74f4 85 $this->_mut = new CiviMailUtils($this, TRUE);
07c09ae4
EM
86 $this->callAPISuccess('mail_settings', 'get', array('api.mail_settings.create' => array('domain' => 'chaos.org')));
87 }
88
89 /**
07c09ae4 90 */
00be9182 91 public function tearDown() {
d667a9ba 92 //$this->_mut->clearMessages();
07c09ae4 93 $this->_mut->stop();
07c09ae4 94 CRM_Utils_Hook::singleton()->reset();
97b7d4a0 95 CRM_Mailing_BAO_MailingJob::$mailsProcessed = 0; // DGW
d667a9ba 96 //$this->cleanupMailingTest();
07c09ae4 97 parent::tearDown();
07c09ae4
EM
98 }
99
d667a9ba 100 public function testBasic() {
07c09ae4 101 $this->createContactsInGroup(10, $this->_groupID);
dc00ac6d 102 Civi::settings()->add(array(
d667a9ba
TO
103 'mailerBatchLimit' => 2,
104 ));
07c09ae4 105 $this->callAPISuccess('mailing', 'create', $this->_params);
d667a9ba 106 $this->_mut->assertRecipients(array());
07c09ae4
EM
107 $this->callAPISuccess('job', 'process_mailing', array());
108 $this->_mut->assertRecipients($this->getRecipients(1, 2));
109 }
110
d667a9ba
TO
111 public function concurrencyExamples() {
112 $es = array();
113
114 // Launch 3 workers, but mailerJobsMax limits us to 1 worker.
115 $es[0] = array(
116 array(
117 'recipients' => 20,
118 'workers' => 3,
a2341a99
TO
119 // FIXME: lockHold is unrealistic/unrepresentative. In reality, this situation fails because
120 // the data.* locks trample the worker.* locks. However, setting lockHold allows us to
121 // approximate the behavior of what would happen *if* the lock-implementation didn't suffer
122 // trampling effects.
d667a9ba
TO
123 'lockHold' => 10,
124 'mailerBatchLimit' => 4,
125 'mailerJobsMax' => 1,
126 ),
127 array(
128 0 => 2, // 2 jobs which produce 0 messages
129 4 => 1, // 1 job which produces 4 messages
130 ),
131 4,
132 );
133
134 // Launch 3 workers, but mailerJobsMax limits us to 2 workers.
135 $es[1] = array(
136 array(// Settings.
137 'recipients' => 20,
138 'workers' => 3,
a2341a99
TO
139 // FIXME: lockHold is unrealistic/unrepresentative. In reality, this situation fails because
140 // the data.* locks trample the worker.* locks. However, setting lockHold allows us to
141 // approximate the behavior of what would happen *if* the lock-implementation didn't suffer
142 // trampling effects.
d667a9ba
TO
143 'lockHold' => 10,
144 'mailerBatchLimit' => 5,
145 'mailerJobsMax' => 2,
146 ),
147 array(// Tallies.
148 0 => 1, // 1 job which produce 0 messages
149 5 => 2, // 2 jobs which produce 5 messages
150 ),
151 10, // Total sent.
152 );
153
154 // Launch 3 workers and saturate them (mailerJobsMax=3)
155 $es[2] = array(
156 array(// Settings.
157 'recipients' => 20,
158 'workers' => 3,
d667a9ba
TO
159 'mailerBatchLimit' => 6,
160 'mailerJobsMax' => 3,
161 ),
162 array(// Tallies.
163 6 => 3, // 3 jobs which produce 6 messages
164 ),
165 18, // Total sent.
166 );
167
168 // Launch 4 workers and saturate them (mailerJobsMax=0)
169 $es[3] = array(
170 array(// Settings.
171 'recipients' => 20,
172 'workers' => 4,
d667a9ba
TO
173 'mailerBatchLimit' => 6,
174 'mailerJobsMax' => 0,
175 ),
176 array(// Tallies.
177 6 => 3, // 3 jobs which produce 6 messages
178 2 => 1, // 1 job which produces 2 messages
179 ),
180 20, // Total sent.
181 );
182
183 // Launch 1 worker, 3 times in a row. Deliver everything.
184 $es[4] = array(
185 array(// Settings.
186 'recipients' => 10,
187 'workers' => 1,
188 'iterations' => 3,
189 'mailerBatchLimit' => 7,
190 ),
191 array(// Tallies.
192 7 => 1, // 1 job which produces 7 messages
193 3 => 1, // 1 job which produces 3 messages
194 0 => 1, // 1 job which produces 0 messages
195 ),
196 10, // Total sent.
197 );
198
199 // Launch 2 worker, 3 times in a row. Deliver everything.
200 $es[5] = array(
201 array(// Settings.
202 'recipients' => 10,
203 'workers' => 2,
204 'iterations' => 3,
205 'mailerBatchLimit' => 3,
206 ),
207 array(// Tallies.
208 3 => 3, // 3 jobs which produce 3 messages
209 1 => 1, // 1 job which produces 1 messages
210 0 => 2, // 2 jobs which produce 0 messages
211 ),
212 10, // Total sent.
213 );
214
215 return $es;
216 }
217
218 /**
219 * Setup various mail configuration options (eg $mailerBatchLimit,
220 * $mailerJobMax) and spawn multiple worker threads ($workers).
221 * Allow the threads to complete. (Optionally, repeat the above
222 * process.) Finally, check to see if the right number of
223 * jobs delivered the right number of messages.
224 *
225 * @param array $settings
226 * An array of settings (eg mailerBatchLimit, workers). See comments
227 * for $this->defaultSettings.
228 * @param array $expectedTallies
229 * A listing of the number cron-runs keyed by their size.
230 * For example, array(10=>2) means that there 2 cron-runs
231 * which delivered 10 messages each.
232 * @param int $expectedTotal
233 * The total number of contacts for whom messages should have
234 * been sent.
235 * @dataProvider concurrencyExamples
236 */
237 public function testConcurrency($settings, $expectedTallies, $expectedTotal) {
238 $settings = array_merge($this->defaultSettings, $settings);
239
240 $this->createContactsInGroup($settings['recipients'], $this->_groupID);
dc00ac6d 241 Civi::settings()->add(CRM_Utils_Array::subset($settings, array(
d667a9ba
TO
242 'mailerBatchLimit',
243 'mailerJobsMax',
244 'mailThrottleTime',
245 )));
246
247 $this->callAPISuccess('mailing', 'create', $this->_params);
248
249 $this->_mut->assertRecipients(array());
250
251 $allApiResults = array();
252 for ($iterationId = 0; $iterationId < $settings['iterations']; $iterationId++) {
253 $apiCalls = $this->createExternalAPI();
254 $apiCalls->addEnv(array('CIVICRM_CRON_HOLD' => $settings['lockHold']));
255 for ($workerId = 0; $workerId < $settings['workers']; $workerId++) {
256 $apiCalls->addCall('job', 'process_mailing', array());
257 }
258 $apiCalls->start();
259 $this->assertEquals($settings['workers'], $apiCalls->getRunningCount());
260
261 $apiCalls->wait();
262 $allApiResults = array_merge($allApiResults, $apiCalls->getResults());
263 }
264
265 $actualTallies = $this->tallyApiResults($allApiResults);
266 $this->assertEquals($expectedTallies, $actualTallies, 'API tallies should match.' . print_r(array(
267 'expectedTallies' => $expectedTallies,
268 'actualTallies' => $actualTallies,
269 'apiResults' => $allApiResults,
270 ), TRUE));
271 $this->_mut->assertRecipients($this->getRecipients(1, $expectedTotal));
272 $this->assertEquals(0, $apiCalls->getRunningCount());
273 }
274
07c09ae4 275 /**
54957108 276 * Create contacts in group.
277 *
e16033b4
TO
278 * @param int $count
279 * @param int $groupID
54957108 280 * @param string $domain
07c09ae4 281 */
d667a9ba 282 public function createContactsInGroup($count, $groupID, $domain = 'nul.example.com') {
481a74f4 283 for ($i = 1; $i <= $count; $i++) {
d667a9ba 284 $contactID = $this->individualCreate(array('first_name' => $count, 'email' => 'mail' . $i . '@' . $domain));
92915c55 285 $this->callAPISuccess('group_contact', 'create', array(
d667a9ba
TO
286 'contact_id' => $contactID,
287 'group_id' => $groupID,
288 'status' => 'Added',
289 ));
07c09ae4
EM
290 }
291 }
292
293 /**
d667a9ba
TO
294 * Construct the list of email addresses for $count recipients.
295 *
e16033b4
TO
296 * @param int $start
297 * @param int $count
07c09ae4
EM
298 *
299 * @return array
300 */
d667a9ba 301 public function getRecipients($start, $count, $domain = 'nul.example.com') {
07c09ae4 302 $recipients = array();
481a74f4 303 for ($i = $start; $i < ($start + $count); $i++) {
d667a9ba 304 $recipients[][0] = 'mail' . $i . '@' . $domain;
07c09ae4
EM
305 }
306 return $recipients;
307 }
96025800 308
d667a9ba
TO
309 protected function cleanupMailingTest() {
310 $this->quickCleanup(array(
311 'civicrm_mailing',
312 'civicrm_mailing_job',
313 'civicrm_mailing_spool',
314 'civicrm_mailing_group',
315 'civicrm_mailing_recipients',
316 'civicrm_mailing_event_queue',
317 'civicrm_mailing_event_bounce',
318 'civicrm_mailing_event_delivered',
319 'civicrm_group',
320 'civicrm_group_contact',
321 'civicrm_contact',
322 ));
323 }
324
325 /**
326 * Categorize results based on (a) whether they succeeded
327 * and (b) the number of messages sent.
328 *
329 * @param array $apiResults
330 * @return array
331 * One key 'error' for all failures.
332 * A separate key for each distinct quantity.
333 */
334 protected function tallyApiResults($apiResults) {
335 $ret = array();
336 foreach ($apiResults as $apiResult) {
337 $key = !empty($apiResult['is_error']) ? 'error' : $apiResult['values']['processed'];
338 $ret[$key] = !empty($ret[$key]) ? 1 + $ret[$key] : 1;
339 }
340 return $ret;
341 }
342
07c09ae4 343}