Commit | Line | Data |
---|---|---|
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 | 43 | class 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 | } |