4 * Class CRM_Dedupe_DedupeMergerTest
7 class CRM_Dedupe_MergerTest
extends CiviUnitTestCase
{
10 protected $_contactIds = array();
12 public function createDupeContacts() {
13 // create a group to hold contacts, so that dupe checks don't consider any other contacts in the DB
15 'name' => 'Test Dupe Merger Group',
16 'title' => 'Test Dupe Merger Group',
19 'visibility' => 'Public Pages',
22 // TODO: This is not an API test!!
23 $result = civicrm_api('group', 'create', $params);
24 $this->_groupId
= $result['id'];
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
42 'first_name' => 'robin',
43 'last_name' => 'hood',
44 'email' => 'robin@example.com',
45 'contact_type' => 'Individual',
48 'first_name' => 'robin',
49 'last_name' => 'hood',
50 'email' => 'robin@example.com',
51 'contact_type' => 'Individual',
54 'first_name' => 'robin',
55 'last_name' => 'hood',
56 'email' => 'hood@example.com',
57 'contact_type' => 'Individual',
60 'first_name' => 'robin',
61 'last_name' => 'dale',
62 'email' => 'robin@example.com',
63 'contact_type' => 'Individual',
66 'first_name' => 'little',
67 'last_name' => 'dale',
68 'email' => 'dale@example.com',
69 'contact_type' => 'Individual',
72 'first_name' => 'little',
73 'last_name' => 'dale',
74 'email' => 'dale@example.com',
75 'contact_type' => 'Individual',
78 'first_name' => 'will',
79 'last_name' => 'dale',
80 'email' => 'dale@example.com',
81 'contact_type' => 'Individual',
84 'first_name' => 'will',
85 'last_name' => 'dale',
86 'email' => 'will@example.com',
87 'contact_type' => 'Individual',
90 'first_name' => 'will',
91 'last_name' => 'dale',
92 'email' => 'will@example.com',
93 'contact_type' => 'Individual',
98 foreach ($params as $param) {
99 $param['version'] = 3;
100 $contact = civicrm_api('contact', 'create', $param);
101 $this->_contactIds
[$count++
] = $contact['id'];
104 'contact_id' => $contact['id'],
105 'group_id' => $this->_groupId
,
108 $res = civicrm_api('group_contact', 'create', $grpParams);
113 * Delete all created contacts.
115 public function deleteDupeContacts() {
116 foreach ($this->_contactIds
as $contactId) {
117 $this->contactDelete($contactId);
121 $params = array('id' => $this->_groupId
, 'version' => 3);
122 civicrm_api('group', 'delete', $params);
126 * Test the batch merge.
128 public function testBatchMergeSelectedDuplicates() {
129 $this->createDupeContacts();
131 // verify that all contacts have been created separately
132 $this->assertEquals(count($this->_contactIds
), 9, 'Check for number of contacts.');
134 $dao = new CRM_Dedupe_DAO_RuleGroup();
135 $dao->contact_type
= 'Individual';
136 $dao->name
= 'IndividualSupervised';
137 $dao->is_default
= 1;
140 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($dao->id
, $this->_groupId
);
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().');
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
);
160 $object->setEmbedded(TRUE);
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);
168 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
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']})");
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.');
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.');
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.');
185 $this->deleteDupeContacts();
189 * Test the batch merge.
191 public function testBatchMergeAllDuplicates() {
192 $this->createDupeContacts();
194 // verify that all contacts have been created separately
195 $this->assertEquals(count($this->_contactIds
), 9, 'Check for number of contacts.');
197 $dao = new CRM_Dedupe_DAO_RuleGroup();
198 $dao->contact_type
= 'Individual';
199 $dao->name
= 'IndividualSupervised';
200 $dao->is_default
= 1;
203 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($dao->id
, $this->_groupId
);
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().');
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
);
223 $object->setEmbedded(TRUE);
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);
231 $this->assertEquals(count($foundDupes), count($pnDupePairs), 'Check number of dupe pairs in prev next cache.');
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.');
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.');
241 $this->deleteDupeContacts();
245 * The goal of this function is to test that all required tables are returned.
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());
254 * Get the list of not-really-cid-refs that are currently hacked in.
256 * This is hacked into getCIDs function.
260 public function getHackedInCIDRef() {
262 'civicrm_entity_tag' => array(
269 * Get the list of tables that refer to the CID.
271 * This is a statically maintained (in this test list).
273 * There is also a check against an automated list but having both seems to add extra stability to me. They do
276 public function getStaticCIDRefs() {
278 'civicrm_acl_cache' => array(
281 'civicrm_acl_contact_cache' => array(
285 'civicrm_action_log' => array(
288 'civicrm_activity_contact' => array(
291 'civicrm_address' => array(
294 'civicrm_batch' => array(
298 'civicrm_campaign' => array(
300 1 => 'last_modified_id',
302 'civicrm_case_contact' => array(
305 'civicrm_contact' => array(
306 0 => 'primary_contact_id',
309 'civicrm_contribution' => array(
312 'civicrm_contribution_page' => array(
315 'civicrm_contribution_recur' => array(
318 'civicrm_contribution_soft' => array(
321 'civicrm_custom_group' => array(
324 'civicrm_dashboard_contact' => array(
327 'civicrm_dedupe_exception' => array(
331 'civicrm_domain' => array(
334 'civicrm_email' => array(
337 'civicrm_event' => array(
340 'civicrm_event_carts' => array(
343 'civicrm_financial_account' => array(
346 'civicrm_financial_item' => array(
349 'civicrm_grant' => array(
352 'civicrm_group' => array(
356 'civicrm_group_contact' => array(
359 'civicrm_group_contact_cache' => array(
362 'civicrm_group_organization' => array(
363 0 => 'organization_id',
365 'civicrm_im' => array(
368 'civicrm_log' => array(
371 'civicrm_mailing' => array(
376 'civicrm_mailing_abtest' => array(
379 'civicrm_mailing_event_queue' => array(
382 'civicrm_mailing_event_subscribe' => array(
385 'civicrm_mailing_recipients' => array(
388 'civicrm_membership' => array(
391 'civicrm_membership_log' => array(
394 'civicrm_membership_type' => array(
395 0 => 'member_of_contact_id',
397 'civicrm_note' => array(
400 'civicrm_openid' => array(
403 'civicrm_participant' => array(
405 1 => 'transferred_to_contact_id', //CRM-16761
407 'civicrm_payment_token' => array(
411 'civicrm_pcp' => array(
414 'civicrm_phone' => array(
417 'civicrm_pledge' => array(
420 'civicrm_print_label' => array(
423 'civicrm_relationship' => array(
427 'civicrm_report_instance' => array(
431 'civicrm_setting' => array(
435 'civicrm_subscription_history' => array(
438 'civicrm_survey' => array(
440 1 => 'last_modified_id',
442 'civicrm_tag' => array(
445 'civicrm_uf_group' => array(
448 'civicrm_uf_match' => array(
451 'civicrm_value_testgetcidref_1' => array(
454 'civicrm_website' => array(
461 * Get a list of CIDs that is calculated off the schema.
463 * Note this is an expensive and table locking query. Should be safe in tests though.
465 public function getCalculatedCIDRefs() {
471 FROM information_schema.key_column_usage
473 referenced_table_schema = database() AND
474 referenced_table_name = 'civicrm_contact' AND
475 referenced_column_name = 'id';
477 $dao = CRM_Core_DAO
::executeQuery($sql);
478 while ($dao->fetch()) {
479 $cidRefs[$dao->table_name
][] = $dao->column_name
;
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';