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