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