4 * Class CRM_Dedupe_DedupeFinderTest
7 class CRM_Dedupe_DedupeFinderTest
extends CiviUnitTestCase
{
9 use CRMTraits_Custom_CustomDataTrait
;
11 * IDs of created contacts.
15 protected $contactIDs = [];
18 * ID of the group holding the contacts.
25 * Clean up after the test.
27 * @throws \CRM_Core_Exception
29 public function tearDown() {
31 foreach ($this->contactIDs
as $contactId) {
32 $this->contactDelete($contactId);
35 $this->callAPISuccess('group', 'delete', ['id' => $this->groupID
]);
37 $this->quickCleanup(['civicrm_contact'], TRUE);
38 CRM_Core_DAO
::executeQuery("DELETE r FROM civicrm_dedupe_rule_group rg INNER JOIN civicrm_dedupe_rule r ON rg.id = r.dedupe_rule_group_id WHERE rg.is_reserved = 0 AND used = 'General'");
39 CRM_Core_DAO
::executeQuery("DELETE FROM civicrm_dedupe_rule_group WHERE is_reserved = 0 AND used = 'General'");
45 * Test the unsupervised dedupe rule against a group.
49 public function testUnsupervisedDupes() {
50 // make dupe checks based on following contact sets:
51 // FIRST - LAST - EMAIL
52 // ---------------------------------
53 // robin - hood - robin@example.com
54 // robin - hood - hood@example.com
55 // robin - dale - robin@example.com
56 // little - dale - dale@example.com
57 // will - dale - dale@example.com
58 // will - dale - will@example.com
59 // will - dale - will@example.com
60 $this->setupForGroupDedupe();
62 $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Unsupervised']);
64 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
65 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Fuzzy dupe rule for dupesInGroup().');
69 * Test duplicate contact retrieval with 2 email fields.
71 * @throws \CRM_Core_Exception
73 public function testUnsupervisedWithTwoEmailFields() {
74 $this->setupForGroupDedupe();
76 ['hood@example.com', ''],
77 ['', 'hood@example.com'],
79 for ($i = 0; $i < 2; $i++
) {
81 'first_name' => 'robin',
82 'last_name' => 'hood',
83 'email-1' => $emails[$i][0],
84 'email-2' => $emails[$i][1],
86 $dedupeParams = CRM_Dedupe_Finder
::formatParams($fields, 'Individual');
87 $dedupeResults = CRM_Dedupe_Finder
::dupesByParams($dedupeParams, 'Individual');
88 $this->assertEquals(count($dedupeResults), 1);
93 * Test that a rule set to is_reserved = 0 works.
95 * There is a different search used dependent on this variable.
97 * @throws \CRM_Core_Exception
99 public function testCustomRule() {
100 $this->setupForGroupDedupe();
102 $ruleGroup = $this->createRuleGroup();
103 foreach (['birth_date', 'first_name', 'last_name'] as $field) {
104 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
105 'dedupe_rule_group_id' => $ruleGroup['id'],
106 'rule_table' => 'civicrm_contact',
108 'rule_field' => $field,
111 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
112 $this->assertEquals(count($foundDupes), 4);
113 CRM_Dedupe_Finder
::dupes($ruleGroup['id']);
118 * Test that we do not get a fatal error when our rule group is a custom date field.
120 * @throws \CRM_Core_Exception
122 public function testCustomRuleCustomDateField() {
124 $ruleGroup = $this->createRuleGroup();
125 $this->createCustomGroupWithFieldOfType([], 'date');
126 $this->callAPISuccess('Rule', 'create', [
127 'dedupe_rule_group_id' => $ruleGroup['id'],
128 'rule_table' => $this->getCustomGroupTable(),
130 'rule_field' => $this->getCustomFieldColumnName('date'),
133 CRM_Dedupe_Finder
::dupes($ruleGroup['id']);
137 * Test a custom rule with a non-default field.
139 * @throws \CRM_Core_Exception
141 public function testCustomRuleWithAddress() {
142 $this->setupForGroupDedupe();
144 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
145 'contact_type' => 'Individual',
148 'name' => 'TestRule',
149 'title' => 'TestRule',
153 foreach (['postal_code'] as $field) {
154 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
155 'dedupe_rule_group_id' => $ruleGroup['id'],
156 'rule_table' => 'civicrm_address',
158 'rule_field' => $field,
161 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
162 $this->assertEquals(count($foundDupes), 1);
163 CRM_Dedupe_Finder
::dupes($ruleGroup['id']);
168 * Test rule from Richard
170 * @throws \CRM_Core_Exception
172 public function testRuleThreeContactFieldsEqualWeightWIthThresholdtheTotalSumOfAllWeight() {
173 $this->setupForGroupDedupe();
175 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
176 'contact_type' => 'Individual',
179 'name' => 'TestRule',
180 'title' => 'TestRule',
184 foreach (['first_name', 'last_name', 'birth_date'] as $field) {
185 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
186 'dedupe_rule_group_id' => $ruleGroup['id'],
187 'rule_table' => 'civicrm_contact',
189 'rule_field' => $field,
192 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
193 $this->assertCount(1, $foundDupes);
197 * Test a custom rule with a non-default field.
199 * @throws \CRM_Core_Exception
201 public function testInclusiveRule() {
202 $this->setupForGroupDedupe();
204 $ruleGroup = $this->createRuleGroup();
205 foreach (['first_name', 'last_name'] as $field) {
206 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
207 'dedupe_rule_group_id' => $ruleGroup['id'],
208 'rule_table' => 'civicrm_contact',
210 'rule_field' => $field,
213 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
214 $this->assertCount(4, $foundDupes);
215 CRM_Dedupe_Finder
::dupes($ruleGroup['id']);
219 * Test the supervised dedupe rule against a group.
223 public function testSupervisedDupes() {
224 $this->setupForGroupDedupe();
225 $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Supervised']);
226 $foundDupes = CRM_Dedupe_Finder
::dupesInGroup($ruleGroup['id'], $this->groupID
);
227 // -------------------------------------------------------------------------
228 // default dedupe rule: threshold = 20 => (First + Last + Email) Matches ( 1 pair )
229 // --------------------------------------------------------------------------
230 // will - dale - will@example.com
231 // will - dale - will@example.com
232 // so 1 pair for - first + last + mail
233 $this->assertEquals(count($foundDupes), 1, 'Check Individual-Fuzzy dupe rule for dupesInGroup().');
237 * Test dupesByParams function.
239 * @throws \CRM_Core_Exception
241 public function testDupesByParams() {
242 // make dupe checks based on based on following contact sets:
243 // FIRST - LAST - EMAIL
244 // ---------------------------------
245 // robin - hood - robin@example.com
246 // robin - hood - hood@example.com
247 // robin - dale - robin@example.com
248 // little - dale - dale@example.com
249 // will - dale - dale@example.com
250 // will - dale - will@example.com
251 // will - dale - will@example.com
254 // FIXME: move create params to separate function
257 'first_name' => 'robin',
258 'last_name' => 'hood',
259 'email' => 'robin@example.com',
260 'contact_type' => 'Individual',
263 'first_name' => 'robin',
264 'last_name' => 'hood',
265 'email' => 'hood@example.com',
266 'contact_type' => 'Individual',
269 'first_name' => 'robin',
270 'last_name' => 'dale',
271 'email' => 'robin@example.com',
272 'contact_type' => 'Individual',
275 'first_name' => 'little',
276 'last_name' => 'dale',
277 'email' => 'dale@example.com',
278 'contact_type' => 'Individual',
281 'first_name' => 'will',
282 'last_name' => 'dale',
283 'email' => 'dale@example.com',
284 'contact_type' => 'Individual',
287 'first_name' => 'will',
288 'last_name' => 'dale',
289 'email' => 'will@example.com',
290 'contact_type' => 'Individual',
293 'first_name' => 'will',
294 'last_name' => 'dale',
295 'email' => 'will@example.com',
296 'contact_type' => 'Individual',
300 $this->hookClass
->setHook('civicrm_findDuplicates', [$this, 'hook_civicrm_findDuplicates']);
304 foreach ($params as $param) {
305 $contact = $this->callAPISuccess('contact', 'create', $param);
307 'contact_id' => $contact['id'],
308 'street_address' => 'Ambachtstraat 23',
309 'location_type_id' => 1,
311 $this->callAPISuccess('address', 'create', $params);
312 $contactIds[$count++
] = $contact['id'];
315 // verify that all contacts have been created separately
316 $this->assertEquals(count($contactIds), 7, 'Check for number of contacts.');
319 'first_name' => 'robin',
320 'last_name' => 'hood',
321 'email' => 'hood@example.com',
322 'street_address' => 'Ambachtstraat 23',
324 $ids = CRM_Contact_BAO_Contact
::getDuplicateContacts($fields, 'Individual', 'General', [], TRUE, NULL, ['event_id' => 1]);
326 // Check with default Individual-General rule
327 $this->assertEquals(count($ids), 2, 'Check Individual-General rule for dupesByParams().');
329 // delete all created contacts
330 foreach ($contactIds as $contactId) {
331 $this->contactDelete($contactId);
336 * Implements hook_civicrm_findDuplicates().
338 * Locks in expected params
341 public function hook_civicrm_findDuplicates($dedupeParams, &$dedupeResults, $contextParams) {
342 $expectedDedupeParams = [
343 'check_permission' => TRUE,
344 'contact_type' => 'Individual',
346 'rule_group_id' => NULL,
347 'excluded_contact_ids' => [],
349 foreach ($expectedDedupeParams as $key => $value) {
350 $this->assertEquals($value, $dedupeParams[$key]);
352 $expectedDedupeResults = [
356 foreach ($expectedDedupeResults as $key => $value) {
357 $this->assertEquals($value, $dedupeResults[$key]);
360 $expectedContext = ['event_id' => 1];
361 foreach ($expectedContext as $key => $value) {
362 $this->assertEquals($value, $contextParams[$key]);
365 return $dedupeResults;
369 * Set up a group of dedupable contacts.
371 * @throws \CRM_Core_Exception
373 protected function setupForGroupDedupe() {
375 'name' => 'Dupe Group',
376 'title' => 'New Test Dupe Group',
379 'visibility' => 'Public Pages',
382 $result = $this->callAPISuccess('group', 'create', $params);
383 $this->groupID
= $result['id'];
387 'first_name' => 'robin',
388 'last_name' => 'hood',
389 'email' => 'robin@example.com',
390 'contact_type' => 'Individual',
391 'birth_date' => '2016-01-01',
392 'api.Address.create' => ['street_address' => '123 Happy world', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
395 'first_name' => 'robin',
396 'last_name' => 'hood',
397 'email' => 'hood@example.com',
398 'contact_type' => 'Individual',
399 'birth_date' => '2016-01-01',
400 'api.Address.create' => ['street_address' => '123 Happy World', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
403 'first_name' => 'robin',
404 'last_name' => 'dale',
405 'email' => 'robin@example.com',
406 'contact_type' => 'Individual',
409 'first_name' => 'little',
410 'last_name' => 'dale',
411 'email' => 'dale@example.com',
412 'contact_type' => 'Individual',
415 'first_name' => 'will',
416 'last_name' => 'dale',
417 'email' => 'dale@example.com',
418 'contact_type' => 'Individual',
421 'first_name' => 'will',
422 'last_name' => 'dale',
423 'email' => 'will@example.com',
424 'contact_type' => 'Individual',
427 'first_name' => 'will',
428 'last_name' => 'dale',
429 'email' => 'will@example.com',
430 'contact_type' => 'Individual',
435 foreach ($params as $param) {
436 $contact = $this->callAPISuccess('contact', 'create', $param);
437 $this->contactIDs
[$count++
] = $contact['id'];
440 'contact_id' => $contact['id'],
441 'group_id' => $this->groupID
,
443 $this->callAPISuccess('group_contact', 'create', $grpParams);
446 // verify that all contacts have been created separately
447 $this->assertEquals(count($this->contactIDs
), 7, 'Check for number of contacts.');