Merge pull request #17688 from artfulrobot/artfulrobot-trxn-id-and-date
[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 ];
a2dd33d3 324 CRM_Core_TemporaryErrorScope::useException();
b8cb7e46 325 $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, 'Individual', 'General', [], TRUE, NULL, ['event_id' => 1]);
6a488035 326
a2dd33d3 327 // Check with default Individual-General rule
328 $this->assertEquals(count($ids), 2, 'Check Individual-General rule for dupesByParams().');
6a488035 329
a2dd33d3 330 // delete all created contacts
6a488035 331 foreach ($contactIds as $contactId) {
93ac19cd 332 $this->contactDelete($contactId);
6a488035 333 }
6a488035
TO
334 }
335
b8cb7e46
MWMC
336 /**
337 * Implements hook_civicrm_findDuplicates().
338 *
339 * Locks in expected params
340 *
341 */
342 public function hook_civicrm_findDuplicates($dedupeParams, &$dedupeResults, $contextParams) {
343 $expectedDedupeParams = [
344 'check_permission' => TRUE,
345 'contact_type' => 'Individual',
346 'rule' => 'General',
347 'rule_group_id' => NULL,
348 'excluded_contact_ids' => [],
349 ];
350 foreach ($expectedDedupeParams as $key => $value) {
351 $this->assertEquals($value, $dedupeParams[$key]);
352 }
353 $expectedDedupeResults = [
354 'ids' => [],
355 'handled' => FALSE,
356 ];
357 foreach ($expectedDedupeResults as $key => $value) {
358 $this->assertEquals($value, $dedupeResults[$key]);
359 }
360
361 $expectedContext = ['event_id' => 1];
362 foreach ($expectedContext as $key => $value) {
363 $this->assertEquals($value, $contextParams[$key]);
364 }
365
366 return $dedupeResults;
367 }
368
a2dd33d3 369 /**
370 * Set up a group of dedupable contacts.
2cbe6e87 371 *
372 * @throws \CRM_Core_Exception
a2dd33d3 373 */
374 protected function setupForGroupDedupe() {
9099cab3 375 $params = [
a2dd33d3 376 'name' => 'Dupe Group',
377 'title' => 'New Test Dupe Group',
378 'domain_id' => 1,
379 'is_active' => 1,
380 'visibility' => 'Public Pages',
9099cab3 381 ];
a2dd33d3 382
383 $result = $this->callAPISuccess('group', 'create', $params);
384 $this->groupID = $result['id'];
6a488035 385
9099cab3
CW
386 $params = [
387 [
6a488035
TO
388 'first_name' => 'robin',
389 'last_name' => 'hood',
390 'email' => 'robin@example.com',
391 'contact_type' => 'Individual',
ce217e17 392 'birth_date' => '2016-01-01',
197e72c4 393 'api.Address.create' => ['street_address' => '123 Happy world', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
9099cab3
CW
394 ],
395 [
6a488035
TO
396 'first_name' => 'robin',
397 'last_name' => 'hood',
398 'email' => 'hood@example.com',
399 'contact_type' => 'Individual',
ce217e17 400 'birth_date' => '2016-01-01',
197e72c4 401 'api.Address.create' => ['street_address' => '123 Happy World', 'location_type_id' => 'Billing', 'postal_code' => '99999'],
9099cab3
CW
402 ],
403 [
6a488035
TO
404 'first_name' => 'robin',
405 'last_name' => 'dale',
406 'email' => 'robin@example.com',
407 'contact_type' => 'Individual',
9099cab3
CW
408 ],
409 [
6a488035
TO
410 'first_name' => 'little',
411 'last_name' => 'dale',
412 'email' => 'dale@example.com',
413 'contact_type' => 'Individual',
9099cab3
CW
414 ],
415 [
6a488035
TO
416 'first_name' => 'will',
417 'last_name' => 'dale',
418 'email' => 'dale@example.com',
419 'contact_type' => 'Individual',
9099cab3
CW
420 ],
421 [
6a488035
TO
422 'first_name' => 'will',
423 'last_name' => 'dale',
424 'email' => 'will@example.com',
425 'contact_type' => 'Individual',
9099cab3
CW
426 ],
427 [
6a488035
TO
428 'first_name' => 'will',
429 'last_name' => 'dale',
430 'email' => 'will@example.com',
431 'contact_type' => 'Individual',
9099cab3
CW
432 ],
433 ];
6a488035
TO
434
435 $count = 1;
6a488035 436 foreach ($params as $param) {
87a56b12 437 $contact = $this->callAPISuccess('contact', 'create', $param);
a2dd33d3 438 $this->contactIDs[$count++] = $contact['id'];
439
9099cab3 440 $grpParams = [
6a488035 441 'contact_id' => $contact['id'],
a2dd33d3 442 'group_id' => $this->groupID,
9099cab3 443 ];
a2dd33d3 444 $this->callAPISuccess('group_contact', 'create', $grpParams);
6a488035
TO
445 }
446
447 // verify that all contacts have been created separately
a2dd33d3 448 $this->assertEquals(count($this->contactIDs), 7, 'Check for number of contacts.');
6a488035 449 }
96025800 450
692e041d 451 /**
452 * @return array|int
453 * @throws \CRM_Core_Exception
454 */
455 protected function createRuleGroup() {
456 $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [
457 'contact_type' => 'Individual',
458 'threshold' => 8,
459 'used' => 'General',
460 'name' => 'TestRule',
461 'title' => 'TestRule',
462 'is_reserved' => 0,
463 ]);
464 return $ruleGroup;
465 }
466
6a488035 467}