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