REF Update CiviCRM default PEAR Error handling to be exception rather than just PEAR_...
[civicrm-core.git] / tests / phpunit / CRM / Dedupe / DedupeFinderTest.php
1 <?php
2
3 /**
4 * Class CRM_Dedupe_DedupeFinderTest
5 * @group headless
6 */
7 class CRM_Dedupe_DedupeFinderTest extends CiviUnitTestCase {
8
9 use CRMTraits_Custom_CustomDataTrait;
10 /**
11 * IDs of created contacts.
12 *
13 * @var array
14 */
15 protected $contactIDs = [];
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.
26 *
27 * @throws \CRM_Core_Exception
28 */
29 public function tearDown() {
30
31 foreach ($this->contactIDs as $contactId) {
32 $this->contactDelete($contactId);
33 }
34 if ($this->groupID) {
35 $this->callAPISuccess('group', 'delete', ['id' => $this->groupID]);
36 }
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
41 parent::tearDown();
42 }
43
44 /**
45 * Test the unsupervised dedupe rule against a group.
46 *
47 * @throws \Exception
48 */
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();
61
62 $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Unsupervised']);
63
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
68 /**
69 * Test duplicate contact retrieval with 2 email fields.
70 *
71 * @throws \CRM_Core_Exception
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
92 /**
93 * Test that a rule set to is_reserved = 0 works.
94 *
95 * There is a different search used dependent on this variable.
96 *
97 * @throws \CRM_Core_Exception
98 */
99 public function testCustomRule() {
100 $this->setupForGroupDedupe();
101
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',
107 'rule_weight' => 4,
108 'rule_field' => $field,
109 ]);
110 }
111 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
112 $this->assertEquals(count($foundDupes), 4);
113 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
114
115 }
116
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
136 /**
137 * Test a custom rule with a non-default field.
138 *
139 * @throws \CRM_Core_Exception
140 */
141 public function testCustomRuleWithAddress() {
142 $this->setupForGroupDedupe();
143
144 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
145 'contact_type' => 'Individual',
146 'threshold' => 10,
147 'used' => 'General',
148 'name' => 'TestRule',
149 'title' => 'TestRule',
150 'is_reserved' => 0,
151 ]);
152 $rules = [];
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',
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
167 /**
168 * Test rule from Richard
169 *
170 * @throws \CRM_Core_Exception
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);
193 $this->assertCount(1, $foundDupes);
194 }
195
196 /**
197 * Test a custom rule with a non-default field.
198 *
199 * @throws \CRM_Core_Exception
200 */
201 public function testInclusiveRule() {
202 $this->setupForGroupDedupe();
203
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',
209 'rule_weight' => 4,
210 'rule_field' => $field,
211 ]);
212 }
213 $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID);
214 $this->assertCount(4, $foundDupes);
215 CRM_Dedupe_Finder::dupes($ruleGroup['id']);
216 }
217
218 /**
219 * Test the supervised dedupe rule against a group.
220 *
221 * @throws \Exception
222 */
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().');
234 }
235
236 /**
237 * Test dupesByParams function.
238 *
239 * @throws \CRM_Core_Exception
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
252
253 // contact data set
254 // FIXME: move create params to separate function
255 $params = [
256 [
257 'first_name' => 'robin',
258 'last_name' => 'hood',
259 'email' => 'robin@example.com',
260 'contact_type' => 'Individual',
261 ],
262 [
263 'first_name' => 'robin',
264 'last_name' => 'hood',
265 'email' => 'hood@example.com',
266 'contact_type' => 'Individual',
267 ],
268 [
269 'first_name' => 'robin',
270 'last_name' => 'dale',
271 'email' => 'robin@example.com',
272 'contact_type' => 'Individual',
273 ],
274 [
275 'first_name' => 'little',
276 'last_name' => 'dale',
277 'email' => 'dale@example.com',
278 'contact_type' => 'Individual',
279 ],
280 [
281 'first_name' => 'will',
282 'last_name' => 'dale',
283 'email' => 'dale@example.com',
284 'contact_type' => 'Individual',
285 ],
286 [
287 'first_name' => 'will',
288 'last_name' => 'dale',
289 'email' => 'will@example.com',
290 'contact_type' => 'Individual',
291 ],
292 [
293 'first_name' => 'will',
294 'last_name' => 'dale',
295 'email' => 'will@example.com',
296 'contact_type' => 'Individual',
297 ],
298 ];
299
300 $this->hookClass->setHook('civicrm_findDuplicates', [$this, 'hook_civicrm_findDuplicates']);
301
302 $count = 1;
303
304 foreach ($params as $param) {
305 $contact = $this->callAPISuccess('contact', 'create', $param);
306 $params = [
307 'contact_id' => $contact['id'],
308 'street_address' => 'Ambachtstraat 23',
309 'location_type_id' => 1,
310 ];
311 $this->callAPISuccess('address', 'create', $params);
312 $contactIds[$count++] = $contact['id'];
313 }
314
315 // verify that all contacts have been created separately
316 $this->assertEquals(count($contactIds), 7, 'Check for number of contacts.');
317
318 $fields = [
319 'first_name' => 'robin',
320 'last_name' => 'hood',
321 'email' => 'hood@example.com',
322 'street_address' => 'Ambachtstraat 23',
323 ];
324 $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, 'Individual', 'General', [], TRUE, NULL, ['event_id' => 1]);
325
326 // Check with default Individual-General rule
327 $this->assertEquals(count($ids), 2, 'Check Individual-General rule for dupesByParams().');
328
329 // delete all created contacts
330 foreach ($contactIds as $contactId) {
331 $this->contactDelete($contactId);
332 }
333 }
334
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
368 /**
369 * Set up a group of dedupable contacts.
370 *
371 * @throws \CRM_Core_Exception
372 */
373 protected function setupForGroupDedupe() {
374 $params = [
375 'name' => 'Dupe Group',
376 'title' => 'New Test Dupe Group',
377 'domain_id' => 1,
378 'is_active' => 1,
379 'visibility' => 'Public Pages',
380 ];
381
382 $result = $this->callAPISuccess('group', 'create', $params);
383 $this->groupID = $result['id'];
384
385 $params = [
386 [
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'],
393 ],
394 [
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'],
401 ],
402 [
403 'first_name' => 'robin',
404 'last_name' => 'dale',
405 'email' => 'robin@example.com',
406 'contact_type' => 'Individual',
407 ],
408 [
409 'first_name' => 'little',
410 'last_name' => 'dale',
411 'email' => 'dale@example.com',
412 'contact_type' => 'Individual',
413 ],
414 [
415 'first_name' => 'will',
416 'last_name' => 'dale',
417 'email' => 'dale@example.com',
418 'contact_type' => 'Individual',
419 ],
420 [
421 'first_name' => 'will',
422 'last_name' => 'dale',
423 'email' => 'will@example.com',
424 'contact_type' => 'Individual',
425 ],
426 [
427 'first_name' => 'will',
428 'last_name' => 'dale',
429 'email' => 'will@example.com',
430 'contact_type' => 'Individual',
431 ],
432 ];
433
434 $count = 1;
435 foreach ($params as $param) {
436 $contact = $this->callAPISuccess('contact', 'create', $param);
437 $this->contactIDs[$count++] = $contact['id'];
438
439 $grpParams = [
440 'contact_id' => $contact['id'],
441 'group_id' => $this->groupID,
442 ];
443 $this->callAPISuccess('group_contact', 'create', $grpParams);
444 }
445
446 // verify that all contacts have been created separately
447 $this->assertEquals(count($this->contactIDs), 7, 'Check for number of contacts.');
448 }
449
450 }