tests/phpunit - Declare `@group headless`
[civicrm-core.git] / tests / phpunit / CRM / Dedupe / MergerTest.php
CommitLineData
0622d221 1<?php
0622d221 2
3/**
4 * Class CRM_Dedupe_DedupeMergerTest
acb109b7 5 * @group headless
0622d221 6 */
7class CRM_Dedupe_MergerTest extends CiviUnitTestCase {
8
9 protected $_groupId;
10 protected $_contactIds = array();
11
12 public function createDupeContacts() {
13 // create a group to hold contacts, so that dupe checks don't consider any other contacts in the DB
14 $params = array(
15 'name' => 'Test Dupe Merger Group',
16 'title' => 'Test Dupe Merger Group',
17 'domain_id' => 1,
18 'is_active' => 1,
19 'visibility' => 'Public Pages',
20 'version' => 3,
21 );
22 // TODO: This is not an API test!!
23 $result = civicrm_api('group', 'create', $params);
24 $this->_groupId = $result['id'];
25
26 // contact data set
27
28 // make dupe checks based on based on following contact sets:
29 // FIRST - LAST - EMAIL
30 // ---------------------------------
31 // robin - hood - robin@example.com
32 // robin - hood - robin@example.com
33 // robin - hood - hood@example.com
34 // robin - dale - robin@example.com
35 // little - dale - dale@example.com
36 // little - dale - dale@example.com
37 // will - dale - dale@example.com
38 // will - dale - will@example.com
39 // will - dale - will@example.com
40 $params = array(
41 array(
42 'first_name' => 'robin',
43 'last_name' => 'hood',
44 'email' => 'robin@example.com',
45 'contact_type' => 'Individual',
46 ),
47 array(
48 'first_name' => 'robin',
49 'last_name' => 'hood',
50 'email' => 'robin@example.com',
51 'contact_type' => 'Individual',
52 ),
53 array(
54 'first_name' => 'robin',
55 'last_name' => 'hood',
56 'email' => 'hood@example.com',
57 'contact_type' => 'Individual',
58 ),
59 array(
60 'first_name' => 'robin',
61 'last_name' => 'dale',
62 'email' => 'robin@example.com',
63 'contact_type' => 'Individual',
64 ),
65 array(
66 'first_name' => 'little',
67 'last_name' => 'dale',
68 'email' => 'dale@example.com',
69 'contact_type' => 'Individual',
70 ),
71 array(
72 'first_name' => 'little',
73 'last_name' => 'dale',
74 'email' => 'dale@example.com',
75 'contact_type' => 'Individual',
76 ),
77 array(
78 'first_name' => 'will',
79 'last_name' => 'dale',
80 'email' => 'dale@example.com',
81 'contact_type' => 'Individual',
82 ),
83 array(
84 'first_name' => 'will',
85 'last_name' => 'dale',
86 'email' => 'will@example.com',
87 'contact_type' => 'Individual',
88 ),
89 array(
90 'first_name' => 'will',
91 'last_name' => 'dale',
92 'email' => 'will@example.com',
93 'contact_type' => 'Individual',
94 ),
95 );
96
97 $count = 1;
98 foreach ($params as $param) {
99 $param['version'] = 3;
100 $contact = civicrm_api('contact', 'create', $param);
101 $this->_contactIds[$count++] = $contact['id'];
102
103 $grpParams = array(
104 'contact_id' => $contact['id'],
105 'group_id' => $this->_groupId,
106 'version' => 3,
107 );
108 $res = civicrm_api('group_contact', 'create', $grpParams);
109 }
110 }
111
112 public function deleteDupeContacts() {
113 // delete all created contacts
114 foreach ($this->_contactIds as $contactId) {
115 Contact::delete($contactId);
116 }
117
118 // delete dupe group
119 $params = array('id' => $this->_groupId, 'version' => 3);
120 civicrm_api('group', 'delete', $params);
121 }
122
123 public function testBatchMergeSelectedDuplicates() {
124 $this->createDupeContacts();
125
126 // verify that all contacts have been created separately
127 $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.');
128
129 $dao = new CRM_Dedupe_DAO_RuleGroup();
130 $dao->contact_type = 'Individual';
131 $dao->name = 'IndividualSupervised';
132 $dao->is_default = 1;
133 $dao->find(TRUE);
134
135 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId);
136
137 // -------------------------------------------------------------------------
138 // Name and Email (reserved) Matches ( 3 pairs )
139 // --------------------------------------------------------------------------
140 // robin - hood - robin@example.com
141 // robin - hood - robin@example.com
142 // little - dale - dale@example.com
143 // little - dale - dale@example.com
144 // will - dale - will@example.com
145 // will - dale - will@example.com
146 // so 3 pairs for - first + last + mail
147 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
148
149 // Run dedupe finder as the browser would
150 $_SERVER['REQUEST_METHOD'] = 'GET'; //avoid invalid key error
151 $object = new CRM_Contact_Page_DedupeFind();
152 $object->set('gid', $this->_groupId);
153 $object->set('rgid', $dao->id);
154 $object->set('action', CRM_Core_Action::UPDATE);
155 @$object->run();
156
157 // Retrieve pairs from prev next cache table
158 $select = array('pn.is_selected' => 'is_selected');
159 $cacheKeyString = "merge Individual_{$dao->id}_{$this->_groupId}";
160 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
161
162 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
163
164 // mark first two pairs as selected
165 CRM_Core_DAO::singleValueQuery("UPDATE civicrm_prevnext_cache SET is_selected = 1 WHERE id IN ({$pnDupePairs[0]['prevnext_id']}, {$pnDupePairs[1]['prevnext_id']})");
166
167 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
168 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if first record in dupe pairs is marked as selected.');
169 $this->assertEquals($pnDupePairs[0]['is_selected'], 1, 'Check if second record in dupe pairs is marked as selected.');
170
171 // batch merge selected dupes
172 $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', TRUE, 5, 1);
173 $this->assertEquals(count($result['merged']), 2, 'Check number of merged pairs.');
174
175 // retrieve pairs from prev next cache table
176 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
177 $this->assertEquals(count($pnDupePairs), 1, 'Check number of remaining dupe pairs in prev next cache.');
178
179 $this->deleteDupeContacts();
180 }
181
182 public function testBatchMergeAllDuplicates() {
183 $this->createDupeContacts();
184
185 // verify that all contacts have been created separately
186 $this->assertEquals(count($this->_contactIds), 9, 'Check for number of contacts.');
187
188 $dao = new CRM_Dedupe_DAO_RuleGroup();
189 $dao->contact_type = 'Individual';
190 $dao->name = 'IndividualSupervised';
191 $dao->is_default = 1;
192 $dao->find(TRUE);
193
194 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($dao->id, $this->_groupId);
195
196 // -------------------------------------------------------------------------
197 // Name and Email (reserved) Matches ( 3 pairs )
198 // --------------------------------------------------------------------------
199 // robin - hood - robin@example.com
200 // robin - hood - robin@example.com
201 // little - dale - dale@example.com
202 // little - dale - dale@example.com
203 // will - dale - will@example.com
204 // will - dale - will@example.com
205 // so 3 pairs for - first + last + mail
206 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Supervised dupe rule for dupesInGroup().');
207
208 // Run dedupe finder as the browser would
209 $_SERVER['REQUEST_METHOD'] = 'GET'; //avoid invalid key error
210 $object = new CRM_Contact_Page_DedupeFind();
211 $object->set('gid', $this->_groupId);
212 $object->set('rgid', $dao->id);
213 $object->set('action', CRM_Core_Action::UPDATE);
214 @$object->run();
215
216 // Retrieve pairs from prev next cache table
217 $select = array('pn.is_selected' => 'is_selected');
218 $cacheKeyString = "merge Individual_{$dao->id}_{$this->_groupId}";
219 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
220
221 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
222
223 // batch merge all dupes
224 $result = CRM_Dedupe_Merger::batchMerge($dao->id, $this->_groupId, 'safe', TRUE, 5, 2);
225 $this->assertEquals(count($result['merged']), 3, 'Check number of merged pairs.');
226
227 // retrieve pairs from prev next cache table
228 $pnDupePairs = CRM_Core_BAO_PrevNextCache::retrieve($cacheKeyString, NULL, NULL, 0, 0, $select);
229 $this->assertEquals(count($pnDupePairs), 0, 'Check number of remaining dupe pairs in prev next cache.');
230
231 $this->deleteDupeContacts();
232 }
233
bf17fa88 234 /**
235 * The goal of this function is to test that all required tables are returned.
236 */
237 public function testGetCidRefs() {
238 $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, 'Contacts');
239 $this->assertEquals(array_merge($this->getStaticCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs());
240 $this->assertEquals(array_merge($this->getCalculatedCIDRefs(), $this->getHackedInCIDRef()), CRM_Dedupe_Merger::cidRefs());
241 }
242
243 /**
244 * Get the list of not-really-cid-refs that are currently hacked in.
245 *
246 * This is hacked into getCIDs function.
247 *
248 * @return array
249 */
250 public function getHackedInCIDRef() {
251 return array(
252 'civicrm_entity_tag' => array(
253 0 => 'entity_id',
254 ),
255 );
256 }
257
258 /**
259 * Get the list of tables that refer to the CID.
260 *
261 * This is a statically maintained (in this test list).
262 *
263 * There is also a check against an automated list but having both seems to add extra stability to me. They do
264 * not change often.
265 */
266 public function getStaticCIDRefs() {
267 return array(
268 'civicrm_acl_cache' => array(
269 0 => 'contact_id',
270 ),
271 'civicrm_acl_contact_cache' => array(
272 0 => 'user_id',
273 1 => 'contact_id',
274 ),
275 'civicrm_action_log' => array(
276 0 => 'contact_id',
277 ),
278 'civicrm_activity_contact' => array(
279 0 => 'contact_id',
280 ),
281 'civicrm_address' => array(
282 0 => 'contact_id',
283 ),
284 'civicrm_batch' => array(
285 0 => 'created_id',
286 1 => 'modified_id',
287 ),
288 'civicrm_campaign' => array(
289 0 => 'created_id',
290 1 => 'last_modified_id',
291 ),
292 'civicrm_case_contact' => array(
293 0 => 'contact_id',
294 ),
295 'civicrm_contact' => array(
296 0 => 'primary_contact_id',
297 1 => 'employer_id',
298 ),
299 'civicrm_contribution' => array(
300 0 => 'contact_id',
301 ),
302 'civicrm_contribution_page' => array(
303 0 => 'created_id',
304 ),
305 'civicrm_contribution_recur' => array(
306 0 => 'contact_id',
307 ),
308 'civicrm_contribution_soft' => array(
309 0 => 'contact_id',
310 ),
311 'civicrm_custom_group' => array(
312 0 => 'created_id',
313 ),
314 'civicrm_dashboard_contact' => array(
315 0 => 'contact_id',
316 ),
317 'civicrm_dedupe_exception' => array(
318 0 => 'contact_id1',
319 1 => 'contact_id2',
320 ),
321 'civicrm_domain' => array(
322 0 => 'contact_id',
323 ),
324 'civicrm_email' => array(
325 0 => 'contact_id',
326 ),
327 'civicrm_event' => array(
328 0 => 'created_id',
329 ),
330 'civicrm_event_carts' => array(
331 0 => 'user_id',
332 ),
333 'civicrm_financial_account' => array(
334 0 => 'contact_id',
335 ),
336 'civicrm_financial_item' => array(
337 0 => 'contact_id',
338 ),
339 'civicrm_grant' => array(
340 0 => 'contact_id',
341 ),
342 'civicrm_group' => array(
343 0 => 'created_id',
344 1 => 'modified_id',
345 ),
346 'civicrm_group_contact' => array(
347 0 => 'contact_id',
348 ),
349 'civicrm_group_contact_cache' => array(
350 0 => 'contact_id',
351 ),
352 'civicrm_group_organization' => array(
353 0 => 'organization_id',
354 ),
355 'civicrm_im' => array(
356 0 => 'contact_id',
357 ),
358 'civicrm_log' => array(
359 0 => 'modified_id',
360 ),
361 'civicrm_mailing' => array(
362 0 => 'created_id',
363 1 => 'scheduled_id',
364 2 => 'approver_id',
365 ),
366 'civicrm_mailing_abtest' => array(
367 0 => 'created_id',
368 ),
369 'civicrm_mailing_event_queue' => array(
370 0 => 'contact_id',
371 ),
372 'civicrm_mailing_event_subscribe' => array(
373 0 => 'contact_id',
374 ),
375 'civicrm_mailing_recipients' => array(
376 0 => 'contact_id',
377 ),
378 'civicrm_membership' => array(
379 0 => 'contact_id',
380 ),
381 'civicrm_membership_log' => array(
382 0 => 'modified_id',
383 ),
384 'civicrm_membership_type' => array(
385 0 => 'member_of_contact_id',
386 ),
387 'civicrm_note' => array(
388 0 => 'contact_id',
389 ),
390 'civicrm_openid' => array(
391 0 => 'contact_id',
392 ),
393 'civicrm_participant' => array(
394 0 => 'contact_id',
101d0fef 395 1 => 'transferred_to_contact_id', //CRM-16761
bf17fa88 396 ),
397 'civicrm_payment_token' => array(
398 0 => 'contact_id',
399 1 => 'created_id',
400 ),
401 'civicrm_pcp' => array(
402 0 => 'contact_id',
403 ),
404 'civicrm_phone' => array(
405 0 => 'contact_id',
406 ),
407 'civicrm_pledge' => array(
408 0 => 'contact_id',
409 ),
410 'civicrm_print_label' => array(
411 0 => 'created_id',
412 ),
413 'civicrm_relationship' => array(
414 0 => 'contact_id_a',
415 1 => 'contact_id_b',
416 ),
417 'civicrm_report_instance' => array(
418 0 => 'created_id',
419 1 => 'owner_id',
420 ),
421 'civicrm_setting' => array(
422 0 => 'contact_id',
423 1 => 'created_id',
424 ),
425 'civicrm_subscription_history' => array(
426 0 => 'contact_id',
427 ),
428 'civicrm_survey' => array(
429 0 => 'created_id',
430 1 => 'last_modified_id',
431 ),
432 'civicrm_tag' => array(
433 0 => 'created_id',
434 ),
435 'civicrm_uf_group' => array(
436 0 => 'created_id',
437 ),
438 'civicrm_uf_match' => array(
439 0 => 'contact_id',
440 ),
441 'civicrm_value_testgetcidref_1' => array(
442 0 => 'entity_id',
443 ),
444 'civicrm_website' => array(
445 0 => 'contact_id',
446 ),
447 );
448 }
449
450 /**
451 * Get a list of CIDs that is calculated off the schema.
452 *
453 * Note this is an expensive and table locking query. Should be safe in tests though.
454 */
455 public function getCalculatedCIDRefs() {
456 $cidRefs = array();
457 $sql = "
458SELECT
459 table_name,
460 column_name
461FROM information_schema.key_column_usage
462WHERE
463 referenced_table_schema = database() AND
464 referenced_table_name = 'civicrm_contact' AND
465 referenced_column_name = 'id';
466 ";
467 $dao = CRM_Core_DAO::executeQuery($sql);
468 while ($dao->fetch()) {
469 $cidRefs[$dao->table_name][] = $dao->column_name;
470 }
471 // Do specific re-ordering changes to make this the same as the ref validated one.
472 // The above query orders by FK alphabetically.
473 // There might be cleverer ways to do this but it shouldn't change much.
474 $cidRefs['civicrm_contact'][0] = 'primary_contact_id';
475 $cidRefs['civicrm_contact'][1] = 'employer_id';
476 $cidRefs['civicrm_acl_contact_cache'][0] = 'user_id';
477 $cidRefs['civicrm_acl_contact_cache'][1] = 'contact_id';
478 $cidRefs['civicrm_mailing'][0] = 'created_id';
479 $cidRefs['civicrm_mailing'][1] = 'scheduled_id';
480 $cidRefs['civicrm_mailing'][2] = 'approver_id';
481 return $cidRefs;
482 }
483
0622d221 484}