CRM-19029 Additional tests for merge function
[civicrm-core.git] / tests / phpunit / api / v3 / JobTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.7 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2016 |
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 *
34 * @copyright CiviCRM LLC (c) 2004-2016
35 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
36 *
37 */
38
39 /**
40 * Class api_v3_JobTest
41 * @group headless
42 */
43 class api_v3_JobTest extends CiviUnitTestCase {
44 protected $_apiversion = 3;
45
46 public $DBResetRequired = FALSE;
47 public $_entity = 'Job';
48 public $_params = array();
49 /**
50 * Created membership type.
51 *
52 * Must be created outside the transaction due to it breaking the transaction.
53 *
54 * @var
55 */
56 public $membershipTypeID;
57
58 public function setUp() {
59 parent::setUp();
60 $this->membershipTypeID = $this->membershipTypeCreate(array('name' => 'General'));
61 $this->useTransaction(TRUE);
62 $this->_params = array(
63 'sequential' => 1,
64 'name' => 'API_Test_Job',
65 'description' => 'A long description written by hand in cursive',
66 'run_frequency' => 'Daily',
67 'api_entity' => 'ApiTestEntity',
68 'api_action' => 'apitestaction',
69 'parameters' => 'Semi-formal explanation of runtime job parameters',
70 'is_active' => 1,
71 );
72 }
73
74 public function tearDown() {
75 parent::tearDown();
76 $this->membershipTypeDelete(array('id' => $this->membershipTypeID));
77 }
78
79 /**
80 * Check with no name.
81 */
82 public function testCreateWithoutName() {
83 $params = array(
84 'is_active' => 1,
85 );
86 $this->callAPIFailure('job', 'create', $params,
87 'Mandatory key(s) missing from params array: run_frequency, name, api_entity, api_action'
88 );
89 }
90
91 /**
92 * Create job with an invalid "run_frequency" value.
93 */
94 public function testCreateWithInvalidFrequency() {
95 $params = array(
96 'sequential' => 1,
97 'name' => 'API_Test_Job',
98 'description' => 'A long description written by hand in cursive',
99 'run_frequency' => 'Fortnightly',
100 'api_entity' => 'ApiTestEntity',
101 'api_action' => 'apitestaction',
102 'parameters' => 'Semi-formal explanation of runtime job parameters',
103 'is_active' => 1,
104 );
105 $this->callAPIFailure('job', 'create', $params);
106 }
107
108 /**
109 * Create job.
110 */
111 public function testCreate() {
112 $result = $this->callAPIAndDocument('job', 'create', $this->_params, __FUNCTION__, __FILE__);
113 $this->assertNotNull($result['values'][0]['id']);
114
115 // mutate $params to match expected return value
116 unset($this->_params['sequential']);
117 //assertDBState compares expected values in $result to actual values in the DB
118 $this->assertDBState('CRM_Core_DAO_Job', $result['id'], $this->_params);
119 }
120
121 /**
122 * Check with empty array.
123 */
124 public function testDeleteEmpty() {
125 $params = array();
126 $result = $this->callAPIFailure('job', 'delete', $params);
127 }
128
129 /**
130 * Check with No array.
131 */
132 public function testDeleteParamsNotArray() {
133 $result = $this->callAPIFailure('job', 'delete', 'string');
134 }
135
136 /**
137 * Check if required fields are not passed.
138 */
139 public function testDeleteWithoutRequired() {
140 $params = array(
141 'name' => 'API_Test_PP',
142 'title' => 'API Test Payment Processor',
143 'class_name' => 'CRM_Core_Payment_APITest',
144 );
145
146 $result = $this->callAPIFailure('job', 'delete', $params);
147 $this->assertEquals($result['error_message'], 'Mandatory key(s) missing from params array: id');
148 }
149
150 /**
151 * Check with incorrect required fields.
152 */
153 public function testDeleteWithIncorrectData() {
154 $params = array(
155 'id' => 'abcd',
156 );
157 $result = $this->callAPIFailure('job', 'delete', $params);
158 }
159
160 /**
161 * Check job delete.
162 */
163 public function testDelete() {
164 $createResult = $this->callAPISuccess('job', 'create', $this->_params);
165 $params = array('id' => $createResult['id']);
166 $result = $this->callAPIAndDocument('job', 'delete', $params, __FUNCTION__, __FILE__);
167 $this->assertAPIDeleted($this->_entity, $createResult['id']);
168 }
169
170 /**
171 *
172 * public function testCallUpdateGreetingMissingParams() {
173 * $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => 1));
174 * $this->assertEquals('Mandatory key(s) missing from params array: ct', $result['error_message']);
175 * }
176 *
177 * public function testCallUpdateGreetingIncorrectParams() {
178 * $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => 1, 'ct' => 'djkfhdskjfhds'));
179 * $this->assertEquals('ct `djkfhdskjfhds` is not valid.', $result['error_message']);
180 * }
181 * /*
182 * Note that this test is about tesing the metadata / calling of the function & doesn't test the success of the called function
183 */
184 public function testCallUpdateGreetingSuccess() {
185 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array(
186 'gt' => 'postal_greeting',
187 'ct' => 'Individual',
188 ));
189 }
190
191 public function testCallUpdateGreetingCommaSeparatedParamsSuccess() {
192 $gt = 'postal_greeting,email_greeting,addressee';
193 $ct = 'Individual,Household';
194 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => $gt, 'ct' => $ct));
195 }
196
197 /**
198 * Test the call reminder success sends more than 25 reminders & is not incorrectly limited.
199 *
200 * Note that this particular test sends the reminders to the additional recipients only
201 * as no real reminder person is configured
202 *
203 * Also note that this is testing a 'job' api so is in this class rather than scheduled_reminder - which
204 * seems a cleaner place to build up a collection of scheduled reminder testing functions. However, it seems
205 * that the api itself would need to be moved to the scheduled_reminder fn to do that with the job wrapper being respected for legacy functions
206 */
207 public function testCallSendReminderSuccessMoreThanDefaultLimit() {
208 $membershipTypeID = $this->membershipTypeCreate();
209 $this->membershipStatusCreate();
210 $createTotal = 30;
211 for ($i = 1; $i <= $createTotal; $i++) {
212 $contactID = $this->individualCreate();
213 $groupID = $this->groupCreate(array('name' => $i, 'title' => $i));
214 $result = $this->callAPISuccess('action_schedule', 'create', array(
215 'title' => " job $i",
216 'subject' => "job $i",
217 'entity_value' => $membershipTypeID,
218 'mapping_id' => 4,
219 'start_action_date' => 'membership_join_date',
220 'start_action_offset' => 0,
221 'start_action_condition' => 'before',
222 'start_action_unit' => 'hour',
223 'group_id' => $groupID,
224 'limit_to' => FALSE,
225 ));
226 $this->callAPISuccess('group_contact', 'create', array(
227 'contact_id' => $contactID,
228 'status' => 'Added',
229 'group_id' => $groupID,
230 ));
231 }
232 $result = $this->callAPISuccess('job', 'send_reminder', array());
233 $successfulCronCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_action_log");
234 $this->assertEquals($successfulCronCount, $createTotal);
235 }
236
237 /**
238 * Test scheduled reminders respect limit to (since above identified addition_to handling issue).
239 *
240 * We create 3 contacts - 1 is in our group, 1 has our membership & the chosen one has both
241 * & check that only the chosen one got the reminder
242 */
243 public function testCallSendReminderLimitTo() {
244 $membershipTypeID = $this->membershipTypeCreate();
245 $this->membershipStatusCreate();
246 $createTotal = 3;
247 $groupID = $this->groupCreate(array('name' => 'Texan drawlers', 'title' => 'a...'));
248 for ($i = 1; $i <= $createTotal; $i++) {
249 $contactID = $this->individualCreate();
250 if ($i == 2) {
251 $theChosenOneID = $contactID;
252 }
253 if ($i < 3) {
254 $this->callAPISuccess('group_contact', 'create', array(
255 'contact_id' => $contactID,
256 'status' => 'Added',
257 'group_id' => $groupID,
258 ));
259 }
260 if ($i > 1) {
261 $this->callAPISuccess('membership', 'create', array(
262 'contact_id' => $contactID,
263 'membership_type_id' => $membershipTypeID,
264 'join_date' => 'now',
265 'start_date' => '+ 1 day',
266 )
267 );
268 }
269 }
270 $this->callAPISuccess('action_schedule', 'create', array(
271 'title' => " remind all Texans",
272 'subject' => "drawling renewal",
273 'entity_value' => $membershipTypeID,
274 'mapping_id' => 4,
275 'start_action_date' => 'membership_start_date',
276 'start_action_offset' => 1,
277 'start_action_condition' => 'before',
278 'start_action_unit' => 'day',
279 'group_id' => $groupID,
280 'limit_to' => TRUE,
281 ));
282 $this->callAPISuccess('job', 'send_reminder', array());
283 $successfulCronCount = CRM_Core_DAO::singleValueQuery("SELECT count(*) FROM civicrm_action_log");
284 $this->assertEquals($successfulCronCount, 1);
285 $sentToID = CRM_Core_DAO::singleValueQuery("SELECT contact_id FROM civicrm_action_log");
286 $this->assertEquals($sentToID, $theChosenOneID);
287 }
288
289 public function testCallDisableExpiredRelationships() {
290 $individualID = $this->individualCreate();
291 $orgID = $this->organizationCreate();
292 CRM_Utils_Hook_UnitTests::singleton()->setHook('civicrm_pre', array($this, 'hookPreRelationship'));
293 $relationshipTypeID = $this->callAPISuccess('relationship_type', 'getvalue', array(
294 'return' => 'id',
295 'name_a_b' => 'Employee of',
296 ));
297 $result = $this->callAPISuccess('relationship', 'create', array(
298 'relationship_type_id' => $relationshipTypeID,
299 'contact_id_a' => $individualID,
300 'contact_id_b' => $orgID,
301 'is_active' => 1,
302 'end_date' => 'yesterday',
303 ));
304 $relationshipID = $result['id'];
305 $this->assertEquals('Hooked', $result['values'][$relationshipID]['description']);
306 $this->callAPISuccess($this->_entity, 'disable_expired_relationships', array());
307 $result = $this->callAPISuccess('relationship', 'get', array());
308 $this->assertEquals('Go Go you good thing', $result['values'][$relationshipID]['description']);
309 $this->contactDelete($individualID);
310 $this->contactDelete($orgID);
311 }
312
313 /**
314 * Test the batch merge function.
315 *
316 * We are just checking it returns without error here.
317 */
318 public function testBatchMerge() {
319 $this->callAPISuccess('Job', 'process_batch_merge', array());
320 }
321
322 /**
323 * Test the batch merge function actually works!
324 *
325 * @dataProvider getMergeSets
326 *
327 * @param $dataSet
328 */
329 public function testBatchMergeWorks($dataSet) {
330 foreach ($dataSet['contacts'] as $params) {
331 $this->callAPISuccess('Contact', 'create', $params);
332 }
333
334 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => $dataSet['mode']));
335 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
336 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
337 $result = $this->callAPISuccess('Contact', 'get', array('contact_sub_type' => 'Student', 'sequential' => 1, 'is_deceased' => array('IN' => array(0, 1))));
338 $this->assertEquals(count($dataSet['expected']), $result['count']);
339 foreach ($dataSet['expected'] as $index => $contact) {
340 foreach ($contact as $key => $value) {
341 $this->assertEquals($value, $result['values'][$index][$key]);
342 }
343 }
344 }
345
346 /**
347 * Test the batch merge function actually works!
348 *
349 * @dataProvider getMergeSets
350 *
351 * @param $dataSet
352 */
353 public function testBatchMergeConflictOnDeceased($dataSet) {
354 foreach ($dataSet['contacts'] as $params) {
355 $this->callAPISuccess('Contact', 'create', $params);
356 }
357
358 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => $dataSet['mode']));
359 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
360 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
361 $result = $this->callAPISuccess('Contact', 'get', array(
362 'contact_sub_type' => 'Student',
363 'sequential' => 1,
364 'options' => array('sort' => 'id ASC'),
365 ));
366 $this->assertEquals(count($dataSet['expected']), $result['count']);
367 foreach ($dataSet['expected'] as $index => $contact) {
368 foreach ($contact as $key => $value) {
369 // Handle the fact it's in a different field in the return value.
370 if ($key == 'gender_id') {
371 $key = 'gender';
372 }
373 $this->assertEquals($value, $result['values'][$index][$key]);
374 }
375 }
376 }
377
378 /**
379 * Check that the merge carries across various related entities.
380 *
381 * Note the group combinations & expected results:
382 */
383 public function testBatchMergeWithAssets() {
384 $contactID = $this->individualCreate();
385 $contact2ID = $this->individualCreate();
386 $this->contributionCreate(array('contact_id' => $contactID));
387 $this->contributionCreate(array('contact_id' => $contact2ID, 'invoice_id' => '2', 'trxn_id' => 2));
388 $this->contactMembershipCreate(array('contact_id' => $contactID));
389 $this->contactMembershipCreate(array('contact_id' => $contact2ID));
390 $this->activityCreate(array('source_contact_id' => $contactID, 'target_contact_id' => $contactID, 'assignee_contact_id' => $contactID));
391 $this->activityCreate(array('source_contact_id' => $contact2ID, 'target_contact_id' => $contact2ID, 'assignee_contact_id' => $contact2ID));
392 $this->tagCreate(array('name' => 'Tall'));
393 $this->tagCreate(array('name' => 'Short'));
394 $this->entityTagAdd(array('contact_id' => $contactID, 'tag_id' => 'Tall'));
395 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Short'));
396 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Tall'));
397 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
398 $this->assertEquals(0, count($result['values']['skipped']));
399 $this->assertEquals(1, count($result['values']['merged']));
400 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contactID), 2);
401 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contact2ID), 0);
402 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contactID), 2);
403 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contact2ID), 0);
404 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contactID), 2);
405 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contact2ID), 0);
406 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contactID), 2);
407 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contact2ID), 0);
408 // 12 activities is one for each contribution (2), one for each membership (+2 = 4)
409 // 3 for each of the added activities as there are 3 roles (+6 = 10
410 // 2 for the (source & target) contact merged activity (+2 = 12)
411 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contactID), 12);
412 // 2 for the connection to the deleted by merge activity (source & target)
413 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contact2ID), 2);
414 }
415
416 /**
417 * Check that the merge carries across various related entities.
418 *
419 * Note the group combinations 'expected' results:
420 *
421 * Group 0 Added null Added
422 * Group 1 Added Added Added
423 * Group 2 Added Removed **** Added
424 * Group 3 Removed null **** null
425 * Group 4 Removed Added **** Added
426 * Group 5 Removed Removed **** null
427 * Group 6 null Added Added
428 * Group 7 null Removed **** null
429 *
430 * The ones with **** are the ones where I think a case could be made to change the behaviour.
431 */
432 public function testBatchMergeMergesGroups() {
433 $contactID = $this->individualCreate();
434 $contact2ID = $this->individualCreate();
435 $groups = array();
436 for ($i = 0; $i < 8; $i++) {
437 $groups[] = $this->groupCreate(array('name' => 'mergeGroup' . $i, 'title' => 'merge group' . $i));
438 }
439
440 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[0]));
441 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[1]));
442 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[2]));
443 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[3], 'status' => 'Removed'));
444 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[4], 'status' => 'Removed'));
445 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contactID, 'group_id' => $groups[5], 'status' => 'Removed'));
446 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[1]));
447 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[2], 'status' => 'Removed'));
448 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[4]));
449 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[5], 'status' => 'Removed'));
450 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[6]));
451 $this->callAPISuccess('GroupContact', 'create', array('contact_id' => $contact2ID, 'group_id' => $groups[7], 'status' => 'Removed'));
452 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
453 $this->assertEquals(0, count($result['values']['skipped']));
454 $this->assertEquals(1, count($result['values']['merged']));
455 $groupResult = $this->callAPISuccess('GroupContact', 'get', array());
456 $this->assertEquals(5, $groupResult['count']);
457 $expectedGroups = array($groups[0], $groups[1], $groups[2], $groups[4], $groups[6]);
458 foreach ($groupResult['values'] as $groupValues) {
459 $this->assertEquals($contactID, $groupValues['contact_id']);
460 $this->assertEquals('Added', $groupValues['status']);
461 $this->assertTrue(in_array($groupValues['group_id'], $expectedGroups));
462 }
463 }
464
465 /**
466 * Test the organization will not be matched to an individual.
467 */
468 public function testBatchMergeWillNotMergeOrganizationToIndividual() {
469 $individual = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Individual', 'organization_name' => 'Anon', 'email' => 'anonymous@hacker.com'));
470 $organization = $this->callAPISuccess('Contact', 'create', array('contact_type' => 'Organization', 'organization_name' => 'Anon', 'email' => 'anonymous@hacker.com'));
471 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'aggressive'));
472 $this->assertEquals(0, count($result['values']['skipped']));
473 $this->assertEquals(0, count($result['values']['merged']));
474 $this->callAPISuccessGetSingle('Contact', array('id' => $individual['id']));
475 $this->callAPISuccessGetSingle('Contact', array('id' => $organization['id']));
476 }
477
478 /**
479 * Test the batch merge does not create duplicate emails.
480 *
481 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
482 */
483 public function testBatchMergeEmailHandling() {
484 for ($x = 0; $x <= 4; $x++) {
485 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
486 }
487 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
488 $this->assertEquals(4, count($result['values']['merged']));
489 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
490 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
491 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
492 $this->callAPISuccessGetCount('Email', array(
493 'email' => 'batman@gotham.met',
494 'contact_id' => array('IN' => array_keys($contacts['values'])),
495 ), 1);
496 $this->callAPISuccessGetCount('Email', array(
497 'email' => 'batman@gotham.met',
498 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
499 ), 4);
500 }
501
502 /**
503 * Test the batch merge does not fatal on an empty rule.
504 *
505 * @dataProvider getRuleSets
506 *
507 * @param string $contactType
508 * @param string $used
509 * @param bool $isReserved
510 * @param int $threshold
511 */
512 public function testBatchMergeEmptyRule($contactType, $used, $name, $isReserved, $threshold) {
513 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', array(
514 'contact_type' => $contactType,
515 'threshold' => $threshold,
516 'used' => $used,
517 'name' => $name,
518 'is_reserved' => $isReserved,
519 ));
520 $this->callAPISuccess('Job', 'process_batch_merge', array('rule_group_id' => $ruleGroup['id']));
521 $this->callAPISuccess('RuleGroup', 'delete', array('id' => $ruleGroup['id']));
522 }
523
524 /**
525 * Get the various rule combinations.
526 */
527 public function getRuleSets() {
528 $contactTypes = array('Individual', 'Organization', 'Household');
529 $useds = array('Unsupervised', 'General', 'Supervised');
530 $ruleGroups = array();
531 foreach ($contactTypes as $contactType) {
532 foreach ($useds as $used) {
533 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 0);
534 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 10);
535 $ruleGroups[] = array($contactType, $used, 'Bob', TRUE, 10);
536 $ruleGroups[] = array($contactType, $used, $contactType . $used, FALSE, 10);
537 $ruleGroups[] = array($contactType, $used, $contactType . $used, TRUE, 10);
538 }
539 }
540 return $ruleGroups;
541 }
542
543 /**
544 * Test the batch merge does not create duplicate emails.
545 *
546 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
547 */
548 public function testBatchMergeMatchingAddress() {
549 for ($x = 0; $x <= 2; $x++) {
550 $this->individualCreate(array(
551 'api.address.create' => array(
552 'location_type_id' => 'Home',
553 'street_address' => 'Appt 115, The Batcave',
554 'city' => 'Gotham',
555 'postal_code' => 'Nananananana',
556 ),
557 ));
558 }
559 // Different location type, still merge, identical.
560 $this->individualCreate(array(
561 'api.address.create' => array(
562 'location_type_id' => 'Main',
563 'street_address' => 'Appt 115, The Batcave',
564 'city' => 'Gotham',
565 'postal_code' => 'Nananananana',
566 ),
567 ));
568
569 $this->individualCreate(array(
570 'api.address.create' => array(
571 'location_type_id' => 'Home',
572 'street_address' => 'Appt 115, The Batcave',
573 'city' => 'Gotham',
574 'postal_code' => 'Batman',
575 ),
576 ));
577
578 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
579 $this->assertEquals(3, count($result['values']['merged']));
580 $this->assertEquals(1, count($result['values']['skipped']));
581 $this->callAPISuccessGetCount('Contact', array('street_address' => 'Appt 115, The Batcave'), 2);
582 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
583 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
584 $this->callAPISuccessGetCount('Address', array(
585 'street_address' => 'Appt 115, The Batcave',
586 'contact_id' => array('IN' => array_keys($contacts['values'])),
587 ), 3);
588
589 $this->callAPISuccessGetCount('Address', array(
590 'street_address' => 'Appt 115, The Batcave',
591 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
592 ), 2);
593 }
594
595 /**
596 * Test the batch merge by id range.
597 *
598 * We have 2 sets of 5 matches & set the merge only to merge the lower set.
599 */
600 public function testBatchMergeIDRange() {
601 for ($x = 0; $x <= 4; $x++) {
602 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
603 }
604 for ($x = 0; $x <= 4; $x++) {
605 $this->individualCreate(array('email' => 'robin@gotham.met'));
606 }
607 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('criteria' => array('contact' => array('id' => array('<' => $id)))));
608 $this->assertEquals(4, count($result['values']['merged']));
609 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
610 $this->callAPISuccessGetCount('Contact', array('email' => 'robin@gotham.met'), 5);
611 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
612 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
613 $this->callAPISuccessGetCount('Email', array(
614 'email' => 'batman@gotham.met',
615 'contact_id' => array('IN' => array_keys($contacts['values'])),
616 ), 1);
617 $this->callAPISuccessGetCount('Email', array(
618 'email' => 'batman@gotham.met',
619 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
620 ), 1);
621 $this->callAPISuccessGetCount('Email', array(
622 'email' => 'robin@gotham.met',
623 'contact_id' => array('IN' => array_keys($contacts['values'])),
624 ), 5);
625
626 }
627
628 /**
629 * Test the batch merge function actually works!
630 *
631 * @dataProvider getMergeSets
632 *
633 * @param $dataSet
634 */
635 public function testBatchMergeWorksCheckPermissionsTrue($dataSet) {
636 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'administer CiviCRM');
637 foreach ($dataSet['contacts'] as $params) {
638 $this->callAPISuccess('Contact', 'create', $params);
639 }
640
641 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 1, 'mode' => $dataSet['mode']));
642 $this->assertEquals(0, count($result['values']['merged']), 'User does not have permission to any contacts, so no merging');
643 $this->assertEquals(0, count($result['values']['skipped']), 'User does not have permission to any contacts, so no skip visibility');
644 }
645
646 /**
647 * Test the batch merge function actually works!
648 *
649 * @dataProvider getMergeSets
650 *
651 * @param $dataSet
652 */
653 public function testBatchMergeWorksCheckPermissionsFalse($dataSet) {
654 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'edit my contact');
655 foreach ($dataSet['contacts'] as $params) {
656 $this->callAPISuccess('Contact', 'create', $params);
657 }
658
659 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 0, 'mode' => $dataSet['mode']));
660 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
661 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
662 }
663
664 /**
665 * Get data for batch merge.
666 */
667 public function getMergeSets() {
668 $data = array(
669 array(
670 array(
671 'mode' => 'safe',
672 'contacts' => array(
673 array(
674 'first_name' => 'Michael',
675 'last_name' => 'Jackson',
676 'email' => 'michael@neverland.com',
677 'contact_type' => 'Individual',
678 'contact_sub_type' => 'Student',
679 'api.Address.create' => array(
680 'street_address' => 'big house',
681 'location_type_id' => 'Home',
682 ),
683 ),
684 array(
685 'first_name' => 'Michael',
686 'last_name' => 'Jackson',
687 'email' => 'michael@neverland.com',
688 'contact_type' => 'Individual',
689 'contact_sub_type' => 'Student',
690 ),
691 ),
692 'skipped' => 0,
693 'merged' => 1,
694 'expected' => array(
695 array(
696 'first_name' => 'Michael',
697 'last_name' => 'Jackson',
698 'email' => 'michael@neverland.com',
699 'contact_type' => 'Individual',
700 ),
701 ),
702 ),
703 ),
704 array(
705 array(
706 'mode' => 'safe',
707 'contacts' => array(
708 array(
709 'first_name' => 'Michael',
710 'last_name' => 'Jackson',
711 'email' => 'michael@neverland.com',
712 'contact_type' => 'Individual',
713 'contact_sub_type' => 'Student',
714 'api.Address.create' => array(
715 'street_address' => 'big house',
716 'location_type_id' => 'Home',
717 ),
718 ),
719 array(
720 'first_name' => 'Michael',
721 'last_name' => 'Jackson',
722 'email' => 'michael@neverland.com',
723 'contact_type' => 'Individual',
724 'contact_sub_type' => 'Student',
725 'api.Address.create' => array(
726 'street_address' => 'bigger house',
727 'location_type_id' => 'Home',
728 ),
729 ),
730 ),
731 'skipped' => 1,
732 'merged' => 0,
733 'expected' => array(
734 array(
735 'first_name' => 'Michael',
736 'last_name' => 'Jackson',
737 'email' => 'michael@neverland.com',
738 'contact_type' => 'Individual',
739 'street_address' => 'big house',
740 ),
741 array(
742 'first_name' => 'Michael',
743 'last_name' => 'Jackson',
744 'email' => 'michael@neverland.com',
745 'contact_type' => 'Individual',
746 'street_address' => 'bigger house',
747 ),
748 ),
749 ),
750 ),
751 array(
752 array(
753 'mode' => 'aggressive',
754 'contacts' => array(
755 array(
756 'first_name' => 'Michael',
757 'last_name' => 'Jackson',
758 'email' => 'michael@neverland.com',
759 'contact_type' => 'Individual',
760 'contact_sub_type' => 'Student',
761 'api.Address.create' => array(
762 'street_address' => 'big house',
763 'location_type_id' => 'Home',
764 ),
765 ),
766 array(
767 'first_name' => 'Michael',
768 'last_name' => 'Jackson',
769 'email' => 'michael@neverland.com',
770 'contact_type' => 'Individual',
771 'contact_sub_type' => 'Student',
772 'api.Address.create' => array(
773 'street_address' => 'bigger house',
774 'location_type_id' => 'Home',
775 ),
776 ),
777 ),
778 'skipped' => 0,
779 'merged' => 1,
780 'expected' => array(
781 array(
782 'first_name' => 'Michael',
783 'last_name' => 'Jackson',
784 'email' => 'michael@neverland.com',
785 'contact_type' => 'Individual',
786 'street_address' => 'big house',
787 ),
788 ),
789 ),
790 ),
791 array(
792 array(
793 'mode' => 'safe',
794 'contacts' => array(
795 array(
796 'first_name' => 'Michael',
797 'last_name' => 'Jackson',
798 'email' => 'michael@neverland.com',
799 'contact_type' => 'Individual',
800 'contact_sub_type' => 'Student',
801 'api.Address.create' => array(
802 'street_address' => 'big house',
803 'location_type_id' => 'Home',
804 ),
805 ),
806 array(
807 'first_name' => 'Michael',
808 'last_name' => 'Jackson',
809 'email' => 'michael@neverland.com',
810 'contact_type' => 'Individual',
811 'contact_sub_type' => 'Student',
812 'is_deceased' => 1,
813 ),
814 ),
815 'skipped' => 1,
816 'merged' => 0,
817 'expected' => array(
818 array(
819 'first_name' => 'Michael',
820 'last_name' => 'Jackson',
821 'email' => 'michael@neverland.com',
822 'contact_type' => 'Individual',
823 'is_deceased' => 0,
824 ),
825 array(
826 'first_name' => 'Michael',
827 'last_name' => 'Jackson',
828 'email' => 'michael@neverland.com',
829 'contact_type' => 'Individual',
830 'is_deceased' => 1,
831 ),
832 ),
833 ),
834 ),
835 array(
836 array(
837 'mode' => 'safe',
838 'contacts' => array(
839 array(
840 'first_name' => 'Michael',
841 'last_name' => 'Jackson',
842 'email' => 'michael@neverland.com',
843 'contact_type' => 'Individual',
844 'contact_sub_type' => 'Student',
845 'api.Address.create' => array(
846 'street_address' => 'big house',
847 'location_type_id' => 'Home',
848 ),
849 'is_deceased' => 1,
850 ),
851 array(
852 'first_name' => 'Michael',
853 'last_name' => 'Jackson',
854 'email' => 'michael@neverland.com',
855 'contact_type' => 'Individual',
856 'contact_sub_type' => 'Student',
857 ),
858 ),
859 'skipped' => 1,
860 'merged' => 0,
861 'expected' => array(
862 array(
863 'first_name' => 'Michael',
864 'last_name' => 'Jackson',
865 'email' => 'michael@neverland.com',
866 'contact_type' => 'Individual',
867 'is_deceased' => 1,
868 ),
869 array(
870 'first_name' => 'Michael',
871 'last_name' => 'Jackson',
872 'email' => 'michael@neverland.com',
873 'contact_type' => 'Individual',
874 'is_deceased' => 0,
875 ),
876 ),
877 ),
878 ),
879 );
880
881 $conflictPairs = array(
882 'first_name' => 'Dianna',
883 'last_name' => 'McAndrew',
884 'middle_name' => 'Prancer',
885 'birth_date' => '2015-12-25',
886 'gender_id' => 'Female',
887 'job_title' => 'Thriller',
888 );
889
890 foreach ($conflictPairs as $key => $value) {
891 $contactParams = array(
892 'first_name' => 'Michael',
893 'middle_name' => 'Dancer',
894 'last_name' => 'Jackson',
895 'birth_date' => '2015-02-25',
896 'email' => 'michael@neverland.com',
897 'contact_type' => 'Individual',
898 'contact_sub_type' => array('Student'),
899 'gender_id' => 'Male',
900 'job_title' => 'Entertainer',
901 );
902 $contact2 = $contactParams;
903
904 $contact2[$key] = $value;
905 $data[$key . '_conflict'] = array(
906 array(
907 'mode' => 'safe',
908 'contacts' => array($contactParams, $contact2),
909 'skipped' => 1,
910 'merged' => 0,
911 'expected' => array($contactParams, $contact2),
912 ),
913 );
914 }
915
916 return $data;
917 }
918
919 /**
920 * @param $op
921 * @param string $objectName
922 * @param int $id
923 * @param array $params
924 */
925 public function hookPreRelationship($op, $objectName, $id, &$params) {
926 if ($op == 'delete') {
927 return;
928 }
929 if ($params['is_active']) {
930 $params['description'] = 'Hooked';
931 }
932 else {
933 $params['description'] = 'Go Go you good thing';
934 }
935 }
936
937 }