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