CRM-19076 add test for hooks.
[civicrm-core.git] / tests / phpunit / api / v3 / JobTest.php
CommitLineData
6a488035 1<?php
6a488035
TO
2/*
3 +--------------------------------------------------------------------+
81621fee 4 | CiviCRM version 4.7 |
6a488035 5 +--------------------------------------------------------------------+
fa938177 6 | Copyright CiviCRM LLC (c) 2004-2016 |
6a488035
TO
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 *
fa938177 34 * @copyright CiviCRM LLC (c) 2004-2016
6a488035
TO
35 * @version $Id: Job.php 30879 2010-11-22 15:45:55Z shot $
36 *
37 */
4cbe18b8
EM
38
39/**
40 * Class api_v3_JobTest
acb109b7 41 * @group headless
4cbe18b8 42 */
6a488035 43class api_v3_JobTest extends CiviUnitTestCase {
f39bacdf 44 protected $_apiversion = 3;
b7c9bc4c 45
6a488035
TO
46 public $DBResetRequired = FALSE;
47 public $_entity = 'Job';
ef1672da 48 public $_params = array();
5d8b37be 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;
6a488035 57
00be9182 58 public function setUp() {
6a488035 59 parent::setUp();
5d8b37be 60 $this->membershipTypeID = $this->membershipTypeCreate(array('name' => 'General'));
6510b20a 61 $this->useTransaction(TRUE);
ef1672da 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 );
6a488035
TO
72 }
73
5d8b37be 74 public function tearDown() {
75 parent::tearDown();
76 $this->membershipTypeDelete(array('id' => $this->membershipTypeID));
77 }
78
6a488035 79 /**
6d6dc885 80 * Check with no name.
6a488035 81 */
00be9182 82 public function testCreateWithoutName() {
ef1672da 83 $params = array(
5896d037
TO
84 'is_active' => 1,
85 );
6d6dc885 86 $this->callAPIFailure('job', 'create', $params,
f39bacdf 87 'Mandatory key(s) missing from params array: run_frequency, name, api_entity, api_action'
88 );
89 }
6a488035
TO
90
91 /**
fe482240 92 * Create job with an invalid "run_frequency" value.
6a488035 93 */
00be9182 94 public function testCreateWithInvalidFrequency() {
6a488035 95 $params = array(
6a488035
TO
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 );
76ddbc8e 105 $this->callAPIFailure('job', 'create', $params);
6a488035
TO
106 }
107
108 /**
6d6dc885 109 * Create job.
6a488035 110 */
00be9182 111 public function testCreate() {
ef1672da 112 $result = $this->callAPIAndDocument('job', 'create', $this->_params, __FUNCTION__, __FILE__);
ba4a1892 113 $this->assertNotNull($result['values'][0]['id']);
6a488035
TO
114
115 // mutate $params to match expected return value
ef1672da 116 unset($this->_params['sequential']);
6a488035 117 //assertDBState compares expected values in $result to actual values in the DB
ef1672da 118 $this->assertDBState('CRM_Core_DAO_Job', $result['id'], $this->_params);
6a488035
TO
119 }
120
121 /**
6d6dc885 122 * Check with empty array.
6a488035 123 */
00be9182 124 public function testDeleteEmpty() {
6a488035 125 $params = array();
d0e1eff2 126 $result = $this->callAPIFailure('job', 'delete', $params);
6a488035
TO
127 }
128
129 /**
6d6dc885 130 * Check with No array.
6a488035 131 */
00be9182 132 public function testDeleteParamsNotArray() {
d0e1eff2 133 $result = $this->callAPIFailure('job', 'delete', 'string');
6a488035
TO
134 }
135
136 /**
6d6dc885 137 * Check if required fields are not passed.
6a488035 138 */
00be9182 139 public function testDeleteWithoutRequired() {
6a488035
TO
140 $params = array(
141 'name' => 'API_Test_PP',
142 'title' => 'API Test Payment Processor',
143 'class_name' => 'CRM_Core_Payment_APITest',
144 );
145
d0e1eff2
CW
146 $result = $this->callAPIFailure('job', 'delete', $params);
147 $this->assertEquals($result['error_message'], 'Mandatory key(s) missing from params array: id');
6a488035
TO
148 }
149
150 /**
6d6dc885 151 * Check with incorrect required fields.
6a488035 152 */
00be9182 153 public function testDeleteWithIncorrectData() {
6a488035 154 $params = array(
5896d037
TO
155 'id' => 'abcd',
156 );
d0e1eff2 157 $result = $this->callAPIFailure('job', 'delete', $params);
6a488035
TO
158 }
159
160 /**
6d6dc885 161 * Check job delete.
6a488035 162 */
00be9182 163 public function testDelete() {
ef1672da 164 $createResult = $this->callAPISuccess('job', 'create', $this->_params);
6c6e6187 165 $params = array('id' => $createResult['id']);
f39bacdf 166 $result = $this->callAPIAndDocument('job', 'delete', $params, __FUNCTION__, __FILE__);
ef1672da 167 $this->assertAPIDeleted($this->_entity, $createResult['id']);
6a488035
TO
168 }
169
170 /**
5896d037
TO
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 * /*
6c6e6187
TO
182 * Note that this test is about tesing the metadata / calling of the function & doesn't test the success of the called function
183 */
6a488035 184 public function testCallUpdateGreetingSuccess() {
5896d037 185 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array(
92915c55
TO
186 'gt' => 'postal_greeting',
187 'ct' => 'Individual',
188 ));
6c6e6187 189 }
6a488035
TO
190
191 public function testCallUpdateGreetingCommaSeparatedParamsSuccess() {
192 $gt = 'postal_greeting,email_greeting,addressee';
193 $ct = 'Individual,Household';
f39bacdf 194 $result = $this->callAPISuccess($this->_entity, 'update_greeting', array('gt' => $gt, 'ct' => $ct));
6a488035 195 }
49f8272d 196
d946b676 197 /**
fe482240
EM
198 * Test the call reminder success sends more than 25 reminders & is not incorrectly limited.
199 *
d946b676 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();
0090e3d2 209 $this->membershipStatusCreate();
d946b676 210 $createTotal = 30;
5896d037 211 for ($i = 1; $i <= $createTotal; $i++) {
d946b676 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 ));
5896d037 226 $this->callAPISuccess('group_contact', 'create', array(
92915c55
TO
227 'contact_id' => $contactID,
228 'status' => 'Added',
229 'group_id' => $groupID,
230 ));
d946b676 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
ff32c128 237 /**
fe482240
EM
238 * Test scheduled reminders respect limit to (since above identified addition_to handling issue).
239 *
ff32c128
EM
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...'));
5896d037 248 for ($i = 1; $i <= $createTotal; $i++) {
ff32c128 249 $contactID = $this->individualCreate();
5896d037 250 if ($i == 2) {
ff32c128
EM
251 $theChosenOneID = $contactID;
252 }
5896d037
TO
253 if ($i < 3) {
254 $this->callAPISuccess('group_contact', 'create', array(
92915c55
TO
255 'contact_id' => $contactID,
256 'status' => 'Added',
257 'group_id' => $groupID,
258 ));
ff32c128 259 }
5896d037 260 if ($i > 1) {
ff32c128 261 $this->callAPISuccess('membership', 'create', array(
5896d037
TO
262 'contact_id' => $contactID,
263 'membership_type_id' => $membershipTypeID,
a60ba2b3 264 'join_date' => 'now',
207a414e 265 'start_date' => '+ 1 day',
ff32c128
EM
266 )
267 );
268 }
269 }
a60ba2b3 270 $this->callAPISuccess('action_schedule', 'create', array(
ff32c128
EM
271 'title' => " remind all Texans",
272 'subject' => "drawling renewal",
273 'entity_value' => $membershipTypeID,
274 'mapping_id' => 4,
a60ba2b3 275 'start_action_date' => 'membership_start_date',
207a414e 276 'start_action_offset' => 1,
ff32c128 277 'start_action_condition' => 'before',
207a414e 278 'start_action_unit' => 'day',
ff32c128
EM
279 'group_id' => $groupID,
280 'limit_to' => TRUE,
281 ));
a60ba2b3 282 $this->callAPISuccess('job', 'send_reminder', array());
ff32c128
EM
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
49f8272d
E
289 public function testCallDisableExpiredRelationships() {
290 $individualID = $this->individualCreate();
291 $orgID = $this->organizationCreate();
292 CRM_Utils_Hook_UnitTests::singleton()->setHook('civicrm_pre', array($this, 'hookPreRelationship'));
5896d037 293 $relationshipTypeID = $this->callAPISuccess('relationship_type', 'getvalue', array(
92915c55
TO
294 'return' => 'id',
295 'name_a_b' => 'Employee of',
296 ));
49f8272d
E
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
76ddbc8e 313 /**
314 * Test the batch merge function.
a45614cc 315 *
316 * We are just checking it returns without error here.
76ddbc8e 317 */
318 public function testBatchMerge() {
319 $this->callAPISuccess('Job', 'process_batch_merge', array());
320 }
321
a45614cc 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
962f4484 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']));
92a77772 337 $result = $this->callAPISuccess('Contact', 'get', array(
338 'contact_sub_type' => 'Student',
339 'sequential' => 1,
340 'is_deceased' => array('IN' => array(0, 1)),
341 'options' => array('sort' => 'id ASC'),
342 ));
962f4484 343 $this->assertEquals(count($dataSet['expected']), $result['count']);
344 foreach ($dataSet['expected'] as $index => $contact) {
345 foreach ($contact as $key => $value) {
92a77772 346 if ($key == 'gender_id') {
347 $key = 'gender';
348 }
962f4484 349 $this->assertEquals($value, $result['values'][$index][$key]);
350 }
351 }
352 }
353
354 /**
355 * Test the batch merge function actually works!
356 *
357 * @dataProvider getMergeSets
358 *
359 * @param $dataSet
360 */
361 public function testBatchMergeConflictOnDeceased($dataSet) {
362 foreach ($dataSet['contacts'] as $params) {
363 $this->callAPISuccess('Contact', 'create', $params);
364 }
365
99140301 366 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => $dataSet['mode']));
a45614cc 367 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
368 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
5d8b37be 369 $result = $this->callAPISuccess('Contact', 'get', array(
370 'contact_sub_type' => 'Student',
371 'sequential' => 1,
372 'options' => array('sort' => 'id ASC'),
373 ));
a45614cc 374 $this->assertEquals(count($dataSet['expected']), $result['count']);
375 foreach ($dataSet['expected'] as $index => $contact) {
376 foreach ($contact as $key => $value) {
5d8b37be 377 // Handle the fact it's in a different field in the return value.
378 if ($key == 'gender_id') {
379 $key = 'gender';
380 }
a45614cc 381 $this->assertEquals($value, $result['values'][$index][$key]);
382 }
383 }
384 }
385
5d8b37be 386 /**
fc3d8f72 387<<<<<<< HEAD
5d8b37be 388 * Check that the merge carries across various related entities.
389 *
390 * Note the group combinations & expected results:
391 */
392 public function testBatchMergeWithAssets() {
393 $contactID = $this->individualCreate();
394 $contact2ID = $this->individualCreate();
395 $this->contributionCreate(array('contact_id' => $contactID));
396 $this->contributionCreate(array('contact_id' => $contact2ID, 'invoice_id' => '2', 'trxn_id' => 2));
397 $this->contactMembershipCreate(array('contact_id' => $contactID));
398 $this->contactMembershipCreate(array('contact_id' => $contact2ID));
399 $this->activityCreate(array('source_contact_id' => $contactID, 'target_contact_id' => $contactID, 'assignee_contact_id' => $contactID));
400 $this->activityCreate(array('source_contact_id' => $contact2ID, 'target_contact_id' => $contact2ID, 'assignee_contact_id' => $contact2ID));
401 $this->tagCreate(array('name' => 'Tall'));
402 $this->tagCreate(array('name' => 'Short'));
403 $this->entityTagAdd(array('contact_id' => $contactID, 'tag_id' => 'Tall'));
404 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Short'));
405 $this->entityTagAdd(array('contact_id' => $contact2ID, 'tag_id' => 'Tall'));
406 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
407 $this->assertEquals(0, count($result['values']['skipped']));
408 $this->assertEquals(1, count($result['values']['merged']));
409 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contactID), 2);
410 $this->callAPISuccessGetCount('Contribution', array('contact_id' => $contact2ID), 0);
411 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contactID), 2);
412 $this->callAPISuccessGetCount('FinancialItem', array('contact_id' => $contact2ID), 0);
413 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contactID), 2);
414 $this->callAPISuccessGetCount('Membership', array('contact_id' => $contact2ID), 0);
415 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contactID), 2);
416 $this->callAPISuccessGetCount('EntityTag', array('contact_id' => $contact2ID), 0);
417 // 12 activities is one for each contribution (2), one for each membership (+2 = 4)
418 // 3 for each of the added activities as there are 3 roles (+6 = 10
419 // 2 for the (source & target) contact merged activity (+2 = 12)
420 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contactID), 12);
421 // 2 for the connection to the deleted by merge activity (source & target)
422 $this->callAPISuccessGetCount('ActivityContact', array('contact_id' => $contact2ID), 2);
423 }
424
425 /**
426 * Check that the merge carries across various related entities.
427 *
428 * Note the group combinations 'expected' results:
429 *
430 * Group 0 Added null Added
431 * Group 1 Added Added Added
432 * Group 2 Added Removed **** Added
433 * Group 3 Removed null **** null
434 * Group 4 Removed Added **** Added
435 * Group 5 Removed Removed **** null
436 * Group 6 null Added Added
437 * Group 7 null Removed **** null
438 *
439 * The ones with **** are the ones where I think a case could be made to change the behaviour.
440 */
441 public function testBatchMergeMergesGroups() {
442 $contactID = $this->individualCreate();
443 $contact2ID = $this->individualCreate();
444 $groups = array();
445 for ($i = 0; $i < 8; $i++) {
fc3d8f72 446 $groups[] = $this->groupCreate(array(
447 'name' => 'mergeGroup' . $i,
448 'title' => 'merge group' . $i,
449 ));
5d8b37be 450 }
451
fc3d8f72 452 $this->callAPISuccess('GroupContact', 'create', array(
453 'contact_id' => $contactID,
454 'group_id' => $groups[0],
455 ));
456 $this->callAPISuccess('GroupContact', 'create', array(
457 'contact_id' => $contactID,
458 'group_id' => $groups[1],
459 ));
460 $this->callAPISuccess('GroupContact', 'create', array(
461 'contact_id' => $contactID,
462 'group_id' => $groups[2],
463 ));
464 $this->callAPISuccess('GroupContact', 'create', array(
465 'contact_id' => $contactID,
466 'group_id' => $groups[3],
467 'status' => 'Removed',
468 ));
469 $this->callAPISuccess('GroupContact', 'create', array(
470 'contact_id' => $contactID,
471 'group_id' => $groups[4],
472 'status' => 'Removed',
473 ));
474 $this->callAPISuccess('GroupContact', 'create', array(
475 'contact_id' => $contactID,
476 'group_id' => $groups[5],
477 'status' => 'Removed',
478 ));
479 $this->callAPISuccess('GroupContact', 'create', array(
480 'contact_id' => $contact2ID,
481 'group_id' => $groups[1],
482 ));
483 $this->callAPISuccess('GroupContact', 'create', array(
484 'contact_id' => $contact2ID,
485 'group_id' => $groups[2],
486 'status' => 'Removed',
487 ));
488 $this->callAPISuccess('GroupContact', 'create', array(
489 'contact_id' => $contact2ID,
490 'group_id' => $groups[4],
491 ));
492 $this->callAPISuccess('GroupContact', 'create', array(
493 'contact_id' => $contact2ID,
494 'group_id' => $groups[5],
495 'status' => 'Removed',
496 ));
497 $this->callAPISuccess('GroupContact', 'create', array(
498 'contact_id' => $contact2ID,
499 'group_id' => $groups[6],
500 ));
501 $this->callAPISuccess('GroupContact', 'create', array(
502 'contact_id' => $contact2ID,
503 'group_id' => $groups[7],
504 'status' => 'Removed',
505 ));
5d8b37be 506 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
507 $this->assertEquals(0, count($result['values']['skipped']));
508 $this->assertEquals(1, count($result['values']['merged']));
509 $groupResult = $this->callAPISuccess('GroupContact', 'get', array());
510 $this->assertEquals(5, $groupResult['count']);
fc3d8f72 511 $expectedGroups = array(
512 $groups[0],
513 $groups[1],
514 $groups[2],
515 $groups[4],
516 $groups[6],
517 );
5d8b37be 518 foreach ($groupResult['values'] as $groupValues) {
519 $this->assertEquals($contactID, $groupValues['contact_id']);
520 $this->assertEquals('Added', $groupValues['status']);
521 $this->assertTrue(in_array($groupValues['group_id'], $expectedGroups));
fc3d8f72 522
523 }
524 }
525
526 /**
527 * Test the decisions made for addresses when merging.
528 *
529 * @dataProvider getMergeAddresses
530 *
531 * Scenarios:
532 * (the ones with **** could be disputed as whether it is the best outcome).
533 * 'matching_primary' - Primary matches, including location_type_id. One contact has an additional address.
534 * - result - primary is the shared one. Additional address is retained.
535 * 'matching_primary_reverse' - Primary matches, including location_type_id. Keep both. (opposite order)
536 * - result - primary is the shared one. Additional address is retained.
537 * 'only_one_has_address' - Only one contact has addresses (retain)
538 * - the (only) address is retained
539 * 'only_one_has_address_reverse'
540 * - the (only) address is retained
541 * **** 'different_primaries_with_different_location_type' Primaries are different but do not clash due to diff type
542 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
543 * **** 'different_primaries_with_different_location_type_reverse' Primaries are different but do not clash due to diff type
544 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
545 * **** 'different_primaries_location_match_only_one_address' per previous but a second address matches the primary but is not primary
546 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
547 * **** 'different_primaries_location_match_only_one_address_reverse' per previous but a second address matches the primary but is not primary
548 * - result - both addresses kept. The one from the kept (lowest ID) contact is primary
549 * **** 'same_primaries_different_location' Primary addresses are the same but have different location type IDs
550 * - result primary kept with the lowest ID.
551 * **** 'same_primaries_different_location_reverse' Primary addresses are the same but have different location type IDs
552 * - result primary kept with the lowest ID.
553 *
554 * @param array $dataSet
555 */
556 public function testBatchMergesAddresses($dataSet) {
557 $contactID1 = $this->individualCreate();
558 $contactID2 = $this->individualCreate();
559 foreach ($dataSet['contact_1'] as $address) {
560 $this->callAPISuccess('Address', 'create', array_merge(array('contact_id' => $contactID1), $address));
561 }
562 foreach ($dataSet['contact_2'] as $address) {
563 $this->callAPISuccess('Address', 'create', array_merge(array('contact_id' => $contactID2), $address));
564 }
565
566 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
567 $this->assertEquals(1, count($result['values']['merged']));
568 $addresses = $this->callAPISuccess('Address', 'get', array('contact_id' => $contactID1, 'sequential' => 1));
569 $this->assertEquals(count($dataSet['expected']), $addresses['count']);
570 $locationTypes = $this->callAPISuccess('Address', 'getoptions', array('field' => 'location_type_id'));
571 foreach ($dataSet['expected'] as $index => $expectedAddress) {
572 foreach ($expectedAddress as $key => $value) {
573 if ($key == 'location_type_id') {
574 $this->assertEquals($locationTypes['values'][$addresses['values'][$index][$key]], $value);
575 }
576 else {
577 $this->assertEquals($addresses['values'][$index][$key], $value);
578 }
579 }
580 }
581 }
582
583 /**
584 * Test altering the address decision by hook.
585 *
586 * @dataProvider getMergeAddresses
587 *
588 * @param array $dataSet
589 */
590 public function testBatchMergesAddressesHook($dataSet) {
591 $contactID1 = $this->individualCreate();
592 $contactID2 = $this->individualCreate();
593 $this->contributionCreate(array('contact_id' => $contactID1, 'receive_date' => '2010-01-01', 'invoice_id' => 1, 'trxn_id' => 1));
594 $this->contributionCreate(array('contact_id' => $contactID2, 'receive_date' => '2012-01-01', 'invoice_id' => 2, 'trxn_id' => 2));
595 foreach ($dataSet['contact_1'] as $address) {
596 $this->callAPISuccess('Address', 'create', array_merge(array('contact_id' => $contactID1), $address));
597 }
598 foreach ($dataSet['contact_2'] as $address) {
599 $this->callAPISuccess('Address', 'create', array_merge(array('contact_id' => $contactID2), $address));
600 }
92a77772 601 $this->hookClass->setHook('civicrm_alterLocationMergeData', array($this, 'hookMostRecentDonor'));
fc3d8f72 602
603 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'safe'));
604 $this->assertEquals(1, count($result['values']['merged']));
605 $addresses = $this->callAPISuccess('Address', 'get', array('contact_id' => $contactID1, 'sequential' => 1));
606 $this->assertEquals(count($dataSet['expected_hook']), $addresses['count']);
607 $locationTypes = $this->callAPISuccess('Address', 'getoptions', array('field' => 'location_type_id'));
608 foreach ($dataSet['expected_hook'] as $index => $expectedAddress) {
609 foreach ($expectedAddress as $key => $value) {
610 if ($key == 'location_type_id') {
611 $this->assertEquals($locationTypes['values'][$addresses['values'][$index][$key]], $value);
612 }
613 else {
92a77772 614 $this->assertEquals($addresses['values'][$index][$key], $value, 'Unexpected value for ' . $key);
fc3d8f72 615 }
616 }
5d8b37be 617 }
618 }
619
620 /**
621 * Test the organization will not be matched to an individual.
622 */
623 public function testBatchMergeWillNotMergeOrganizationToIndividual() {
fc3d8f72 624 $individual = $this->callAPISuccess('Contact', 'create', array(
625 'contact_type' => 'Individual',
626 'organization_name' => 'Anon',
627 'email' => 'anonymous@hacker.com',
628 ));
629 $organization = $this->callAPISuccess('Contact', 'create', array(
630 'contact_type' => 'Organization',
631 'organization_name' => 'Anon',
632 'email' => 'anonymous@hacker.com',
633 ));
5d8b37be 634 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('mode' => 'aggressive'));
635 $this->assertEquals(0, count($result['values']['skipped']));
fc3d8f72 636 $this->assertEquals(0, ¬count($result['values']['merged']));
5d8b37be 637 $this->callAPISuccessGetSingle('Contact', array('id' => $individual['id']));
638 $this->callAPISuccessGetSingle('Contact', array('id' => $organization['id']));
fc3d8f72 639
640 }
641
642 /**
92a77772 643 * Test hook allowing modification of the data calculated for merging locations.
644 *
645 * We are testing a nuanced real life situation where the address data of the
646 * most recent donor gets priority - resulting in the primary address being set
647 * to the primary address of the most recent donor and address data on a per
648 * location type basis also being set to the most recent donor. Hook also excludes
649 * a fully matching address with a different location.
fc3d8f72 650 *
92a77772 651 * This has been added to the test suite to ensure the code supports more this
652 * type of intervention.
653 *
654 * @param array $blocksDAO
655 * Array of location DAO to be saved. These are arrays in 2 keys 'update' & 'delete'.
fc3d8f72 656 * @param int $mainId
92a77772 657 * Contact_id of the contact that survives the merge.
fc3d8f72 658 * @param int $otherId
92a77772 659 * Contact_id of the contact that will be absorbed and deleted.
660 * @param array $migrationInfo
661 * Calculated migration info, informational only.
662 *
663 * @return mixed
fc3d8f72 664 */
92a77772 665 public function hookMostRecentDonor(&$blocksDAO, $mainId, $otherId, $migrationInfo) {
666
667 $lastDonorID = $this->callAPISuccessGetValue('Contribution', array(
668 'return' => 'contact_id',
669 'contact_id' => array('IN' => array($mainId, $otherId)),
670 'options' => array('sort' => 'receive_date DESC', 'limit' => 1),
671 ));
672 // Since the last donor is not the main ID we are prioritising info from the last donor.
673 // In the test this should always be true - but keep the check in case
674 // something changes that we need to detect.
675 if ($lastDonorID != $mainId) {
676 foreach ($migrationInfo['other_details']['location_blocks'] as $blockType => $blocks) {
677 foreach ($blocks as $block) {
678 if ($block['is_primary']) {
679 $primaryAddressID = $block['id'];
680 if (!empty($migrationInfo['main_details']['location_blocks'][$blockType])) {
681 foreach ($migrationInfo['main_details']['location_blocks'][$blockType] as $mainBlock) {
682 if (empty($blocksDAO[$blockType]['update'][$block['id']]) && $mainBlock['location_type_id'] == $block['location_type_id']) {
683 // This was an address match - we just need to check the is_primary
684 // is true on the matching kept address.
685 $primaryAddressID = $mainBlock['id'];
686 $blocksDAO[$blockType]['update'][$primaryAddressID] = _civicrm_api3_load_DAO($blockType);
687 $blocksDAO[$blockType]['update'][$primaryAddressID]->id = $primaryAddressID;
688 }
689 $mainLocationTypeID = $mainBlock['location_type_id'];
690 // We also want to be more ruthless about removing matching addresses.
691 unset($mainBlock['location_type_id']);
692 if (CRM_Dedupe_Merger::addressIsSame($block, $mainBlock)
693 && (!isset($blocksDAO[$blockType]['update']) || !isset($blocksDAO[$blockType]['update'][$mainBlock['id']]))
694 && (!isset($blocksDAO[$blockType]['delete']) || !isset($blocksDAO[$blockType]['delete'][$mainBlock['id']]))
695 ) {
696 $blocksDAO[$blockType]['delete'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType);
697 $blocksDAO[$blockType]['delete'][$mainBlock['id']]->id = $mainBlock['id'];
698 }
699 // Arguably the right way to handle this is just to set is_primary for the primary
700 // and for the merge fn to call something like BAO::add & hooks to work etc.
701 // if that happens though this should keep working...
702 elseif ($mainBlock['is_primary'] && $mainLocationTypeID != $block['location_type_id']) {
703 $blocksDAO['address']['update'][$mainBlock['id']] = _civicrm_api3_load_DAO($blockType);
704 $blocksDAO['address']['update'][$mainBlock['id']]->is_primary = 0;
705 $blocksDAO['address']['update'][$mainBlock['id']]->id = $mainBlock['id'];
706 }
707
708 }
709 $blocksDAO[$blockType]['update'][$primaryAddressID]->is_primary = 1;
710 }
711 }
712 }
713 }
fc3d8f72 714 }
fc3d8f72 715 }
716
717 /**
718 * Get address combinations for the merge test.
719 *
720 * @return array
721 */
722 public function getMergeAddresses() {
723 $address1 = array('street_address' => 'Buckingham Palace', 'city' => 'London');
724 $address2 = array('street_address' => 'The Doghouse', 'supplemental_address_1' => 'under the blanket');
725 $data = array(
726 array(
727 'matching_primary' => array(
728 'contact_1' => array(
729 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
730 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
731 ),
732 'contact_2' => array(
733 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
734 ),
735 'expected' => array(
736 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
737 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
738 ),
739 'expected_hook' => array(
740 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
741 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
742 ),
743 ),
744 ),
745 array(
746 'matching_primary_reverse' => array(
747 'contact_1' => array(
748 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
749 ),
750 'contact_2' => array(
751 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
752 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
753 ),
754 'expected' => array(
755 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
756 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
757 ),
758 'expected_hook' => array(
759 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
760 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
761 ),
762 ),
763 ),
764 array(
765 'only_one_has_address' => array(
766 'contact_1' => array(
767 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
768 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
769 ),
770 'contact_2' => array(),
771 'expected' => array(
772 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
773 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
774 ),
775 'expected_hook' => array(
776 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
777 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
778 ),
779 ),
780 ),
781 array(
782 'only_one_has_address_reverse' => array(
783 'contact_1' => array(),
784 'contact_2' => array(
785 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
786 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
787 ),
788 'expected' => array(
789 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
790 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
791 ),
792 'expected_hook' => array(
793 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
794 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
795 ),
796 ),
797 ),
798 array(
799 'different_primaries_with_different_location_type' => array(
800 'contact_1' => array(
801 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
802 ),
803 'contact_2' => array(
804 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
805 ),
806 'expected' => array(
807 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
808 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
809 ),
810 'expected_hook' => array(
811 array_merge(array('location_type_id' => 'Home', 'is_primary' => 0), $address1),
812 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
813 ),
814 ),
815 ),
816 array(
817 'different_primaries_with_different_location_type_reverse' => array(
818 'contact_1' => array(
819 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
820 ),
821 'contact_2' => array(
822 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
823 ),
824 'expected' => array(
825 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
826 array_merge(array('location_type_id' => 'Home', 'is_primary' => 0), $address1),
827 ),
92a77772 828 'expected_hook' => array(
829 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
830 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
831 ),
fc3d8f72 832 ),
833 ),
834 array(
835 'different_primaries_location_match_only_one_address' => array(
836 'contact_1' => array(
837 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
838 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
839 ),
840 'contact_2' => array(
841 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
842
843 ),
844 'expected' => array(
845 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
846 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
847 ),
848 'expected_hook' => array(
849 array_merge(array('location_type_id' => 'Home', 'is_primary' => 0), $address1),
850 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
851 ),
852 ),
853 ),
854 array(
855 'different_primaries_location_match_only_one_address_reverse' => array(
856 'contact_1' => array(
857 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
858 ),
859 'contact_2' => array(
860 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
861 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
862 ),
863 'expected' => array(
864 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address2),
865 array_merge(array('location_type_id' => 'Home', 'is_primary' => 0), $address1),
866 ),
867 'expected_hook' => array(
868 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address2),
869 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
870 ),
871 ),
872 ),
873 array(
874 'same_primaries_different_location' => array(
875 'contact_1' => array(
876 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
877 ),
878 'contact_2' => array(
879 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address1),
880
881 ),
882 'expected' => array(
883 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
884 array_merge(array('location_type_id' => 'Work', 'is_primary' => 0), $address1),
885 ),
886 'expected_hook' => array(
887 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address1),
888 ),
889 ),
890 ),
891 array(
892 'same_primaries_different_location_reverse' => array(
893 'contact_1' => array(
894 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address1),
895 ),
896 'contact_2' => array(
897 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
898 ),
899 'expected' => array(
900 array_merge(array('location_type_id' => 'Work', 'is_primary' => 1), $address1),
901 array_merge(array('location_type_id' => 'Home', 'is_primary' => 0), $address1),
902 ),
903 'expected_hook' => array(
904 array_merge(array('location_type_id' => 'Home', 'is_primary' => 1), $address1),
905 ),
906 ),
907 ),
908 );
909 return $data;
5d8b37be 910 }
911
99140301 912 /**
c08fae40 913 * Test the batch merge does not create duplicate emails.
914 *
915 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
99140301 916 */
c08fae40 917 public function testBatchMergeEmailHandling() {
918 for ($x = 0; $x <= 4; $x++) {
919 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
920 }
921 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
922 $this->assertEquals(4, count($result['values']['merged']));
923 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
924 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
925 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
926 $this->callAPISuccessGetCount('Email', array(
927 'email' => 'batman@gotham.met',
928 'contact_id' => array('IN' => array_keys($contacts['values'])),
929 ), 1);
930 $this->callAPISuccessGetCount('Email', array(
931 'email' => 'batman@gotham.met',
932 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
933 ), 4);
99140301 934 }
935
6c866f0c 936 /**
937 * Test the batch merge does not fatal on an empty rule.
938 *
939 * @dataProvider getRuleSets
940 *
941 * @param string $contactType
942 * @param string $used
943 * @param bool $isReserved
944 * @param int $threshold
945 */
946 public function testBatchMergeEmptyRule($contactType, $used, $name, $isReserved, $threshold) {
947 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', array(
948 'contact_type' => $contactType,
949 'threshold' => $threshold,
950 'used' => $used,
951 'name' => $name,
952 'is_reserved' => $isReserved,
953 ));
954 $this->callAPISuccess('Job', 'process_batch_merge', array('rule_group_id' => $ruleGroup['id']));
955 $this->callAPISuccess('RuleGroup', 'delete', array('id' => $ruleGroup['id']));
956 }
957
958 /**
959 * Get the various rule combinations.
960 */
961 public function getRuleSets() {
962 $contactTypes = array('Individual', 'Organization', 'Household');
963 $useds = array('Unsupervised', 'General', 'Supervised');
964 $ruleGroups = array();
965 foreach ($contactTypes as $contactType) {
966 foreach ($useds as $used) {
967 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 0);
968 $ruleGroups[] = array($contactType, $used, 'Bob', FALSE, 10);
969 $ruleGroups[] = array($contactType, $used, 'Bob', TRUE, 10);
970 $ruleGroups[] = array($contactType, $used, $contactType . $used, FALSE, 10);
971 $ruleGroups[] = array($contactType, $used, $contactType . $used, TRUE, 10);
972 }
973 }
974 return $ruleGroups;
975 }
976
d48ad2c0 977 /**
978 * Test the batch merge does not create duplicate emails.
979 *
980 * Test CRM-18546, a 4.7 regression whereby a merged contact gets duplicate emails.
981 */
982 public function testBatchMergeMatchingAddress() {
983 for ($x = 0; $x <= 2; $x++) {
984 $this->individualCreate(array(
985 'api.address.create' => array(
986 'location_type_id' => 'Home',
987 'street_address' => 'Appt 115, The Batcave',
988 'city' => 'Gotham',
989 'postal_code' => 'Nananananana',
990 ),
991 ));
992 }
993 // Different location type, still merge, identical.
994 $this->individualCreate(array(
995 'api.address.create' => array(
996 'location_type_id' => 'Main',
997 'street_address' => 'Appt 115, The Batcave',
998 'city' => 'Gotham',
999 'postal_code' => 'Nananananana',
1000 ),
1001 ));
1002
1003 $this->individualCreate(array(
1004 'api.address.create' => array(
1005 'location_type_id' => 'Home',
1006 'street_address' => 'Appt 115, The Batcave',
1007 'city' => 'Gotham',
1008 'postal_code' => 'Batman',
1009 ),
1010 ));
1011
1012 $result = $this->callAPISuccess('Job', 'process_batch_merge', array());
1013 $this->assertEquals(3, count($result['values']['merged']));
1014 $this->assertEquals(1, count($result['values']['skipped']));
1015 $this->callAPISuccessGetCount('Contact', array('street_address' => 'Appt 115, The Batcave'), 2);
1016 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
1017 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 1));
1018 $this->callAPISuccessGetCount('Address', array(
1019 'street_address' => 'Appt 115, The Batcave',
1020 'contact_id' => array('IN' => array_keys($contacts['values'])),
1021 ), 3);
1022
1023 $this->callAPISuccessGetCount('Address', array(
1024 'street_address' => 'Appt 115, The Batcave',
1025 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
1026 ), 2);
1027 }
1028
e23e26ec 1029 /**
1030 * Test the batch merge by id range.
1031 *
1032 * We have 2 sets of 5 matches & set the merge only to merge the lower set.
1033 */
1034 public function testBatchMergeIDRange() {
1035 for ($x = 0; $x <= 4; $x++) {
1036 $id = $this->individualCreate(array('email' => 'batman@gotham.met'));
1037 }
1038 for ($x = 0; $x <= 4; $x++) {
1039 $this->individualCreate(array('email' => 'robin@gotham.met'));
1040 }
1041 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('criteria' => array('contact' => array('id' => array('<' => $id)))));
1042 $this->assertEquals(4, count($result['values']['merged']));
1043 $this->callAPISuccessGetCount('Contact', array('email' => 'batman@gotham.met'), 1);
1044 $this->callAPISuccessGetCount('Contact', array('email' => 'robin@gotham.met'), 5);
1045 $contacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
1046 $deletedContacts = $this->callAPISuccess('Contact', 'get', array('is_deleted' => 0));
1047 $this->callAPISuccessGetCount('Email', array(
1048 'email' => 'batman@gotham.met',
1049 'contact_id' => array('IN' => array_keys($contacts['values'])),
1050 ), 1);
1051 $this->callAPISuccessGetCount('Email', array(
1052 'email' => 'batman@gotham.met',
1053 'contact_id' => array('IN' => array_keys($deletedContacts['values'])),
1054 ), 1);
1055 $this->callAPISuccessGetCount('Email', array(
1056 'email' => 'robin@gotham.met',
1057 'contact_id' => array('IN' => array_keys($contacts['values'])),
1058 ), 5);
1059
1060 }
1061
3058f4d9 1062 /**
1063 * Test the batch merge function actually works!
1064 *
1065 * @dataProvider getMergeSets
1066 *
1067 * @param $dataSet
1068 */
1069 public function testBatchMergeWorksCheckPermissionsTrue($dataSet) {
1070 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'administer CiviCRM');
1071 foreach ($dataSet['contacts'] as $params) {
1072 $this->callAPISuccess('Contact', 'create', $params);
1073 }
1074
1075 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 1, 'mode' => $dataSet['mode']));
1076 $this->assertEquals(0, count($result['values']['merged']), 'User does not have permission to any contacts, so no merging');
1077 $this->assertEquals(0, count($result['values']['skipped']), 'User does not have permission to any contacts, so no skip visibility');
1078 }
1079
1080 /**
1081 * Test the batch merge function actually works!
1082 *
1083 * @dataProvider getMergeSets
1084 *
1085 * @param $dataSet
1086 */
1087 public function testBatchMergeWorksCheckPermissionsFalse($dataSet) {
1088 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'edit my contact');
1089 foreach ($dataSet['contacts'] as $params) {
1090 $this->callAPISuccess('Contact', 'create', $params);
1091 }
1092
1093 $result = $this->callAPISuccess('Job', 'process_batch_merge', array('check_permissions' => 0, 'mode' => $dataSet['mode']));
1094 $this->assertEquals($dataSet['skipped'], count($result['values']['skipped']), 'Failed to skip the right number:' . $dataSet['skipped']);
1095 $this->assertEquals($dataSet['merged'], count($result['values']['merged']));
1096 }
1097
a45614cc 1098 /**
1099 * Get data for batch merge.
1100 */
1101 public function getMergeSets() {
1102 $data = array(
1103 array(
1104 array(
99140301 1105 'mode' => 'safe',
a45614cc 1106 'contacts' => array(
1107 array(
1108 'first_name' => 'Michael',
1109 'last_name' => 'Jackson',
1110 'email' => 'michael@neverland.com',
1111 'contact_type' => 'Individual',
1112 'contact_sub_type' => 'Student',
1113 'api.Address.create' => array(
1114 'street_address' => 'big house',
1115 'location_type_id' => 'Home',
1116 ),
1117 ),
1118 array(
1119 'first_name' => 'Michael',
1120 'last_name' => 'Jackson',
1121 'email' => 'michael@neverland.com',
1122 'contact_type' => 'Individual',
1123 'contact_sub_type' => 'Student',
1124 ),
1125 ),
1126 'skipped' => 0,
1127 'merged' => 1,
1128 'expected' => array(
1129 array(
1130 'first_name' => 'Michael',
1131 'last_name' => 'Jackson',
1132 'email' => 'michael@neverland.com',
1133 'contact_type' => 'Individual',
1134 ),
1135 ),
1136 ),
1137 ),
1138 array(
1139 array(
99140301 1140 'mode' => 'safe',
a45614cc 1141 'contacts' => array(
1142 array(
1143 'first_name' => 'Michael',
1144 'last_name' => 'Jackson',
1145 'email' => 'michael@neverland.com',
1146 'contact_type' => 'Individual',
1147 'contact_sub_type' => 'Student',
1148 'api.Address.create' => array(
1149 'street_address' => 'big house',
1150 'location_type_id' => 'Home',
1151 ),
1152 ),
1153 array(
1154 'first_name' => 'Michael',
1155 'last_name' => 'Jackson',
1156 'email' => 'michael@neverland.com',
1157 'contact_type' => 'Individual',
1158 'contact_sub_type' => 'Student',
1159 'api.Address.create' => array(
1160 'street_address' => 'bigger house',
1161 'location_type_id' => 'Home',
1162 ),
1163 ),
1164 ),
1165 'skipped' => 1,
1166 'merged' => 0,
1167 'expected' => array(
1168 array(
1169 'first_name' => 'Michael',
1170 'last_name' => 'Jackson',
1171 'email' => 'michael@neverland.com',
1172 'contact_type' => 'Individual',
1173 'street_address' => 'big house',
1174 ),
1175 array(
1176 'first_name' => 'Michael',
1177 'last_name' => 'Jackson',
1178 'email' => 'michael@neverland.com',
1179 'contact_type' => 'Individual',
1180 'street_address' => 'bigger house',
1181 ),
1182 ),
1183 ),
1184 ),
99140301 1185 array(
1186 array(
1187 'mode' => 'aggressive',
1188 'contacts' => array(
1189 array(
1190 'first_name' => 'Michael',
1191 'last_name' => 'Jackson',
1192 'email' => 'michael@neverland.com',
1193 'contact_type' => 'Individual',
1194 'contact_sub_type' => 'Student',
1195 'api.Address.create' => array(
1196 'street_address' => 'big house',
1197 'location_type_id' => 'Home',
1198 ),
1199 ),
1200 array(
1201 'first_name' => 'Michael',
1202 'last_name' => 'Jackson',
1203 'email' => 'michael@neverland.com',
1204 'contact_type' => 'Individual',
1205 'contact_sub_type' => 'Student',
1206 'api.Address.create' => array(
1207 'street_address' => 'bigger house',
1208 'location_type_id' => 'Home',
1209 ),
1210 ),
1211 ),
1212 'skipped' => 0,
1213 'merged' => 1,
1214 'expected' => array(
1215 array(
1216 'first_name' => 'Michael',
1217 'last_name' => 'Jackson',
1218 'email' => 'michael@neverland.com',
1219 'contact_type' => 'Individual',
1220 'street_address' => 'big house',
1221 ),
1222 ),
35e76fc2 1223 ),
99140301 1224 ),
962f4484 1225 array(
1226 array(
1227 'mode' => 'safe',
1228 'contacts' => array(
1229 array(
1230 'first_name' => 'Michael',
1231 'last_name' => 'Jackson',
1232 'email' => 'michael@neverland.com',
1233 'contact_type' => 'Individual',
1234 'contact_sub_type' => 'Student',
1235 'api.Address.create' => array(
1236 'street_address' => 'big house',
1237 'location_type_id' => 'Home',
1238 ),
1239 ),
1240 array(
1241 'first_name' => 'Michael',
1242 'last_name' => 'Jackson',
1243 'email' => 'michael@neverland.com',
1244 'contact_type' => 'Individual',
1245 'contact_sub_type' => 'Student',
1246 'is_deceased' => 1,
1247 ),
1248 ),
1249 'skipped' => 1,
1250 'merged' => 0,
1251 'expected' => array(
1252 array(
1253 'first_name' => 'Michael',
1254 'last_name' => 'Jackson',
1255 'email' => 'michael@neverland.com',
1256 'contact_type' => 'Individual',
1257 'is_deceased' => 0,
1258 ),
1259 array(
1260 'first_name' => 'Michael',
1261 'last_name' => 'Jackson',
1262 'email' => 'michael@neverland.com',
1263 'contact_type' => 'Individual',
1264 'is_deceased' => 1,
1265 ),
1266 ),
1267 ),
1268 ),
1269 array(
1270 array(
1271 'mode' => 'safe',
1272 'contacts' => array(
1273 array(
1274 'first_name' => 'Michael',
1275 'last_name' => 'Jackson',
1276 'email' => 'michael@neverland.com',
1277 'contact_type' => 'Individual',
1278 'contact_sub_type' => 'Student',
1279 'api.Address.create' => array(
1280 'street_address' => 'big house',
1281 'location_type_id' => 'Home',
1282 ),
1283 'is_deceased' => 1,
1284 ),
1285 array(
1286 'first_name' => 'Michael',
1287 'last_name' => 'Jackson',
1288 'email' => 'michael@neverland.com',
1289 'contact_type' => 'Individual',
1290 'contact_sub_type' => 'Student',
1291 ),
1292 ),
1293 'skipped' => 1,
1294 'merged' => 0,
1295 'expected' => array(
1296 array(
1297 'first_name' => 'Michael',
1298 'last_name' => 'Jackson',
1299 'email' => 'michael@neverland.com',
1300 'contact_type' => 'Individual',
1301 'is_deceased' => 1,
1302 ),
1303 array(
1304 'first_name' => 'Michael',
1305 'last_name' => 'Jackson',
1306 'email' => 'michael@neverland.com',
1307 'contact_type' => 'Individual',
1308 'is_deceased' => 0,
1309 ),
1310 ),
1311 ),
1312 ),
a45614cc 1313 );
5d8b37be 1314
1315 $conflictPairs = array(
1316 'first_name' => 'Dianna',
1317 'last_name' => 'McAndrew',
1318 'middle_name' => 'Prancer',
1319 'birth_date' => '2015-12-25',
1320 'gender_id' => 'Female',
1321 'job_title' => 'Thriller',
1322 );
1323
1324 foreach ($conflictPairs as $key => $value) {
1325 $contactParams = array(
1326 'first_name' => 'Michael',
1327 'middle_name' => 'Dancer',
1328 'last_name' => 'Jackson',
1329 'birth_date' => '2015-02-25',
1330 'email' => 'michael@neverland.com',
1331 'contact_type' => 'Individual',
1332 'contact_sub_type' => array('Student'),
1333 'gender_id' => 'Male',
1334 'job_title' => 'Entertainer',
1335 );
1336 $contact2 = $contactParams;
1337
1338 $contact2[$key] = $value;
1339 $data[$key . '_conflict'] = array(
1340 array(
1341 'mode' => 'safe',
1342 'contacts' => array($contactParams, $contact2),
1343 'skipped' => 1,
1344 'merged' => 0,
1345 'expected' => array($contactParams, $contact2),
1346 ),
1347 );
1348 }
1349
a45614cc 1350 return $data;
1351 }
1352
4cbe18b8
EM
1353 /**
1354 * @param $op
100fef9d
CW
1355 * @param string $objectName
1356 * @param int $id
c490a46a 1357 * @param array $params
4cbe18b8 1358 */
6c6e6187 1359 public function hookPreRelationship($op, $objectName, $id, &$params) {
5896d037 1360 if ($op == 'delete') {
49f8272d
E
1361 return;
1362 }
5896d037 1363 if ($params['is_active']) {
49f8272d
E
1364 $params['description'] = 'Hooked';
1365 }
1366 else {
1367 $params['description'] = 'Go Go you good thing';
1368 }
1369 }
96025800 1370
6a488035 1371}