REF Update CiviCRM default PEAR Error handling to be exception rather than just PEAR_...
[civicrm-core.git] / tests / phpunit / CRM / Dedupe / DedupeFinderTest.php
CommitLineData
6a488035 1<?php
0eea664b 2
aba1cd8b
EM
3/**
4 * Class CRM_Dedupe_DedupeFinderTest
acb109b7 5 * @group headless
aba1cd8b 6 */
36741dcf 7class CRM_Dedupe_DedupeFinderTest extends CiviUnitTestCase {
a2dd33d3 8
692e041d 9 use CRMTraits_Custom_CustomDataTrait;
a2dd33d3 10 /**
11 * IDs of created contacts.
12 *
13 * @var array
14 */
9099cab3 15 protected $contactIDs = [];
a2dd33d3 16
17 /**
18 * ID of the group holding the contacts.
19 *
20 * @var int
21 */
22 protected $groupID;
23
24 /**
25 * Clean up after the test.
2cbe6e87 26 *
27 * @throws \CRM_Core_Exception
a2dd33d3 28 */
29 public function tearDown() {
30
31 foreach ($this->contactIDs as $contactId) {
32 $this->contactDelete($contactId);
33 }
34 if ($this->groupID) {
9099cab3 35 $this->callAPISuccess('group', 'delete', ['id' => $this->groupID]);
a2dd33d3 36 }
692e041d 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'");
40
a2dd33d3 41 parent::tearDown();
42 }
43
44 /**
45 * Test the unsupervised dedupe rule against a group.
46 *
47 * @throws \Exception
48 */
49 public function testUnsupervisedDupes() {
367c9a2d 50 // make dupe checks based on following contact sets:
6a488035
TO
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
a2dd33d3 60 $this->setupForGroupDedupe();
6a488035 61
9099cab3 62 $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Unsupervised']);
87a56b12 63
a2dd33d3 64 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
65 $this->assertEquals(count($foundDupes), 3, 'Check Individual-Fuzzy dupe rule for dupesInGroup().');
66 }
67
367c9a2d
JP
68 /**
69 * Test duplicate contact retrieval with 2 email fields.
2cbe6e87 70 *
71 * @throws \CRM_Core_Exception
367c9a2d
JP
72 */
73 public function testUnsupervisedWithTwoEmailFields() {
74 $this->setupForGroupDedupe();
75 $emails = [
76 ['hood@example.com', ''],
77 ['', 'hood@example.com'],
78 ];
79 for ($i = 0; $i < 2; $i++) {
80 $fields = [
81 'first_name' => 'robin',
82 'last_name' => 'hood',
83 'email-1' => $emails[$i][0],
84 'email-2' => $emails[$i][1],
85 ];
86 $dedupeParams = CRM_Dedupe_Finder::formatParams($fields, 'Individual');
87 $dedupeResults = CRM_Dedupe_Finder::dupesByParams($dedupeParams, 'Individual');
88 $this->assertEquals(count($dedupeResults), 1);
89 }
90 }
91
ce217e17 92 /**
93 * Test that a rule set to is_reserved = 0 works.
94 *
95 * There is a different search used dependent on this variable.
2cbe6e87 96 *
97 * @throws \CRM_Core_Exception
ce217e17 98 */
cf6f63dd
AH
99 public function testCustomRule() {
100 $this->setupForGroupDedupe();
101
692e041d 102 $ruleGroup = $this->createRuleGroup();
9099cab3 103 foreach (['birth_date', 'first_name', 'last_name'] as $field) {
ce217e17 104 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
69947f59 105 'dedupe_rule_group_id' => $ruleGroup['id'],
106 'rule_table' => 'civicrm_contact',
107 'rule_weight' => 4,
108 'rule_field' => $field,
109 ]);
cf6f63dd
AH
110 }
111 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
112 $this->assertEquals(count($foundDupes), 4);
ce217e17 113 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
114
cf6f63dd
AH
115 }
116
692e041d 117 /**
118 * Test that we do not get a fatal error when our rule group is a custom date field.
119 *
120 * @throws \CRM_Core_Exception
121 */
122 public function testCustomRuleCustomDateField() {
123
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(),
129 'rule_weight' => 4,
130 'rule_field' => $this->getCustomFieldColumnName('date'),
131 ]);
132
133 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
134 }
135
92eadb36
D
136 /**
137 * Test a custom rule with a non-default field.
2cbe6e87 138 *
139 * @throws \CRM_Core_Exception
92eadb36
D
140 */
141 public function testCustomRuleWithAddress() {
142 $this->setupForGroupDedupe();
143
9099cab3 144 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
92eadb36
D
145 'contact_type' => 'Individual',
146 'threshold' => 10,
147 'used' => 'General',
148 'name' => 'TestRule',
149 'title' => 'TestRule',
150 'is_reserved' => 0,
9099cab3 151 ]);
92eadb36 152 $rules = [];
9099cab3 153 foreach (['postal_code'] as $field) {
92eadb36
D
154 $rules[$field] = $this->callAPISuccess('Rule', 'create', [
155 'dedupe_rule_group_id' => $ruleGroup['id'],
156 'rule_table' => 'civicrm_address',
157 'rule_weight' => 10,
158 'rule_field' => $field,
159 ]);
160 }
161 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
162 $this->assertEquals(count($foundDupes), 1);
163 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
164
165 }
166
9742d8f3
SL
167 /**
168 * Test rule from Richard
2cbe6e87 169 *
170 * @throws \CRM_Core_Exception
9742d8f3
SL
171 */
172 public function testRuleThreeContactFieldsEqualWeightWIthThresholdtheTotalSumOfAllWeight() {
173 $this->setupForGroupDedupe();
174
175 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
176 'contact_type' => 'Individual',
177 'threshold' => 30,
178 'used' => 'General',
179 'name' => 'TestRule',
180 'title' => 'TestRule',
181 'is_reserved' => 0,
182 ]);
183
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',
188 'rule_weight' => 10,
189 'rule_field' => $field,
190 ]);
191 }
192 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
2cbe6e87 193 $this->assertCount(1, $foundDupes);
9742d8f3
SL
194 }
195
197e72c4
SL
196 /**
197 * Test a custom rule with a non-default field.
2cbe6e87 198 *
199 * @throws \CRM_Core_Exception
197e72c4
SL
200 */
201 public function testInclusiveRule() {
202 $this->setupForGroupDedupe();
203
692e041d 204 $ruleGroup = $this->createRuleGroup();
197e72c4
SL
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',
209 'rule_weight' => 4,
210 'rule_field' => $field,
211 ]);
212 }
213 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
2cbe6e87 214 $this->assertCount(4, $foundDupes);
197e72c4
SL
215 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
216 }
217
a2dd33d3 218 /**
219 * Test the supervised dedupe rule against a group.
220 *
221 * @throws \Exception
222 */
223 public function testSupervisedDupes() {
224 $this->setupForGroupDedupe();
9099cab3 225 $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Supervised']);
a2dd33d3 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().');
234 }
235
236 /**
237 * Test dupesByParams function.
2cbe6e87 238 *
239 * @throws \CRM_Core_Exception
a2dd33d3 240 */
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
6a488035
TO
252
253 // contact data set
254 // FIXME: move create params to separate function
9099cab3
CW
255 $params = [
256 [
6a488035
TO
257 'first_name' => 'robin',
258 'last_name' => 'hood',
259 'email' => 'robin@example.com',
260 'contact_type' => 'Individual',
9099cab3
CW
261 ],
262 [
6a488035
TO
263 'first_name' => 'robin',
264 'last_name' => 'hood',
265 'email' => 'hood@example.com',
266 'contact_type' => 'Individual',
9099cab3
CW
267 ],
268 [
6a488035
TO
269 'first_name' => 'robin',
270 'last_name' => 'dale',
271 'email' => 'robin@example.com',
272 'contact_type' => 'Individual',
9099cab3
CW
273 ],
274 [
6a488035
TO
275 'first_name' => 'little',
276 'last_name' => 'dale',
277 'email' => 'dale@example.com',
278 'contact_type' => 'Individual',
9099cab3
CW
279 ],
280 [
6a488035
TO
281 'first_name' => 'will',
282 'last_name' => 'dale',
283 'email' => 'dale@example.com',
284 'contact_type' => 'Individual',
9099cab3
CW
285 ],
286 [
6a488035
TO
287 'first_name' => 'will',
288 'last_name' => 'dale',
289 'email' => 'will@example.com',
290 'contact_type' => 'Individual',
9099cab3
CW
291 ],
292 [
6a488035
TO
293 'first_name' => 'will',
294 'last_name' => 'dale',
295 'email' => 'will@example.com',
296 'contact_type' => 'Individual',
9099cab3
CW
297 ],
298 ];
6a488035 299
9099cab3 300 $this->hookClass->setHook('civicrm_findDuplicates', [$this, 'hook_civicrm_findDuplicates']);
b8cb7e46 301
6a488035 302 $count = 1;
a2dd33d3 303
6a488035 304 foreach ($params as $param) {
87a56b12 305 $contact = $this->callAPISuccess('contact', 'create', $param);
9099cab3 306 $params = [
6a488035 307 'contact_id' => $contact['id'],
a2dd33d3 308 'street_address' => 'Ambachtstraat 23',
309 'location_type_id' => 1,
9099cab3 310 ];
a2dd33d3 311 $this->callAPISuccess('address', 'create', $params);
312 $contactIds[$count++] = $contact['id'];
6a488035
TO
313 }
314
315 // verify that all contacts have been created separately
316 $this->assertEquals(count($contactIds), 7, 'Check for number of contacts.');
317
9099cab3 318 $fields = [
a2dd33d3 319 'first_name' => 'robin',
320 'last_name' => 'hood',
321 'email' => 'hood@example.com',
322 'street_address' => 'Ambachtstraat 23',
9099cab3 323 ];
b8cb7e46 324 $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, 'Individual', 'General', [], TRUE, NULL, ['event_id' => 1]);
6a488035 325
a2dd33d3 326 // Check with default Individual-General rule
327 $this->assertEquals(count($ids), 2, 'Check Individual-General rule for dupesByParams().');
6a488035 328
a2dd33d3 329 // delete all created contacts
6a488035 330 foreach ($contactIds as $contactId) {
93ac19cd 331 $this->contactDelete($contactId);
6a488035 332 }
6a488035
TO
333 }
334
b8cb7e46
MWMC
335 /**
336 * Implements hook_civicrm_findDuplicates().
337 *
338 * Locks in expected params
339 *
340 */
341 public function hook_civicrm_findDuplicates($dedupeParams, &$dedupeResults, $contextParams) {
342 $expectedDedupeParams = [
343 'check_permission' => TRUE,
344 'contact_type' => 'Individual',
345 'rule' => 'General',
346 'rule_group_id' => NULL,
347 'excluded_contact_ids' => [],
348 ];
349 foreach ($expectedDedupeParams as $key => $value) {
350 $this->assertEquals($value, $dedupeParams[$key]);
351 }
352 $expectedDedupeResults = [
353 'ids' => [],
354 'handled' => FALSE,
355 ];
356 foreach ($expectedDedupeResults as $key => $value) {
357 $this->assertEquals($value, $dedupeResults[$key]);
358 }
359
360 $expectedContext = ['event_id' => 1];
361 foreach ($expectedContext as $key => $value) {
362 $this->assertEquals($value, $contextParams[$key]);
363 }
364
365 return $dedupeResults;
366 }
367
a2dd33d3 368 /**
369 * Set up a group of dedupable contacts.
2cbe6e87 370 *
371 * @throws \CRM_Core_Exception
a2dd33d3 372 */
373 protected function setupForGroupDedupe() {
9099cab3 374 $params = [
a2dd33d3 375 'name' => 'Dupe Group',
376 'title' => 'New Test Dupe Group',
377 'domain_id' => 1,
378 'is_active' => 1,
379 'visibility' => 'Public Pages',
9099cab3 380 ];
a2dd33d3 381
382 $result = $this->callAPISuccess('group', 'create', $params);
383 $this->groupID = $result['id'];
6a488035 384
9099cab3
CW
385 $params = [
386 [
6a488035
TO
387 'first_name' => 'robin',
388 'last_name' => 'hood',
389 'email' => 'robin@example.com',
390 'contact_type' => 'Individual',
ce217e17 391 'birth_date' => '2016-01-01',
197e72c4 392 'api.Address.create' => ['street_address' => '123 Happy world', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
9099cab3
CW
393 ],
394 [
6a488035
TO
395 'first_name' => 'robin',
396 'last_name' => 'hood',
397 'email' => 'hood@example.com',
398 'contact_type' => 'Individual',
ce217e17 399 'birth_date' => '2016-01-01',
197e72c4 400 'api.Address.create' => ['street_address' => '123 Happy World', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
9099cab3
CW
401 ],
402 [
6a488035
TO
403 'first_name' => 'robin',
404 'last_name' => 'dale',
405 'email' => 'robin@example.com',
406 'contact_type' => 'Individual',
9099cab3
CW
407 ],
408 [
6a488035
TO
409 'first_name' => 'little',
410 'last_name' => 'dale',
411 'email' => 'dale@example.com',
412 'contact_type' => 'Individual',
9099cab3
CW
413 ],
414 [
6a488035
TO
415 'first_name' => 'will',
416 'last_name' => 'dale',
417 'email' => 'dale@example.com',
418 'contact_type' => 'Individual',
9099cab3
CW
419 ],
420 [
6a488035
TO
421 'first_name' => 'will',
422 'last_name' => 'dale',
423 'email' => 'will@example.com',
424 'contact_type' => 'Individual',
9099cab3
CW
425 ],
426 [
6a488035
TO
427 'first_name' => 'will',
428 'last_name' => 'dale',
429 'email' => 'will@example.com',
430 'contact_type' => 'Individual',
9099cab3
CW
431 ],
432 ];
6a488035
TO
433
434 $count = 1;
6a488035 435 foreach ($params as $param) {
87a56b12 436 $contact = $this->callAPISuccess('contact', 'create', $param);
a2dd33d3 437 $this->contactIDs[$count++] = $contact['id'];
438
9099cab3 439 $grpParams = [
6a488035 440 'contact_id' => $contact['id'],
a2dd33d3 441 'group_id' => $this->groupID,
9099cab3 442 ];
a2dd33d3 443 $this->callAPISuccess('group_contact', 'create', $grpParams);
6a488035
TO
444 }
445
446 // verify that all contacts have been created separately
a2dd33d3 447 $this->assertEquals(count($this->contactIDs), 7, 'Check for number of contacts.');
6a488035 448 }
96025800 449
6a488035 450}