Commit | Line | Data |
---|---|---|
6a488035 | 1 | <?php |
0eea664b | 2 | |
aba1cd8b EM |
3 | /** |
4 | * Class CRM_Dedupe_DedupeFinderTest | |
acb109b7 | 5 | * @group headless |
aba1cd8b | 6 | */ |
36741dcf | 7 | class CRM_Dedupe_DedupeFinderTest extends CiviUnitTestCase { |
a2dd33d3 | 8 | |
9 | /** | |
10 | * IDs of created contacts. | |
11 | * | |
12 | * @var array | |
13 | */ | |
9099cab3 | 14 | protected $contactIDs = []; |
a2dd33d3 | 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) { | |
9099cab3 | 32 | $this->callAPISuccess('group', 'delete', ['id' => $this->groupID]); |
a2dd33d3 | 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() { | |
367c9a2d | 43 | // make dupe checks based on following contact sets: |
6a488035 TO |
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 | |
a2dd33d3 | 53 | $this->setupForGroupDedupe(); |
6a488035 | 54 | |
9099cab3 | 55 | $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Unsupervised']); |
87a56b12 | 56 | |
a2dd33d3 | 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 | ||
367c9a2d JP |
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 | ||
ce217e17 | 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 | */ | |
cf6f63dd AH |
88 | public function testCustomRule() { |
89 | $this->setupForGroupDedupe(); | |
90 | ||
9099cab3 | 91 | $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [ |
cf6f63dd AH |
92 | 'contact_type' => 'Individual', |
93 | 'threshold' => 8, | |
94 | 'used' => 'General', | |
95 | 'name' => 'TestRule', | |
96 | 'title' => 'TestRule', | |
97 | 'is_reserved' => 0, | |
9099cab3 | 98 | ]); |
ce217e17 | 99 | $rules = []; |
9099cab3 | 100 | foreach (['birth_date', 'first_name', 'last_name'] as $field) { |
ce217e17 | 101 | $rules[$field] = $this->callAPISuccess('Rule', 'create', [ |
69947f59 | 102 | 'dedupe_rule_group_id' => $ruleGroup['id'], |
103 | 'rule_table' => 'civicrm_contact', | |
104 | 'rule_weight' => 4, | |
105 | 'rule_field' => $field, | |
106 | ]); | |
cf6f63dd AH |
107 | } |
108 | $foundDupes = CRM_Dedupe_Finder::dupesInGroup($ruleGroup['id'], $this->groupID); | |
109 | $this->assertEquals(count($foundDupes), 4); | |
ce217e17 | 110 | CRM_Dedupe_Finder::dupes($ruleGroup['id']); |
111 | ||
cf6f63dd AH |
112 | } |
113 | ||
92eadb36 D |
114 | /** |
115 | * Test a custom rule with a non-default field. | |
116 | */ | |
117 | public function testCustomRuleWithAddress() { | |
118 | $this->setupForGroupDedupe(); | |
119 | ||
9099cab3 | 120 | $ruleGroup = $this->callAPISuccess('RuleGroup', 'create', [ |
92eadb36 D |
121 | 'contact_type' => 'Individual', |
122 | 'threshold' => 10, | |
123 | 'used' => 'General', | |
124 | 'name' => 'TestRule', | |
125 | 'title' => 'TestRule', | |
126 | 'is_reserved' => 0, | |
9099cab3 | 127 | ]); |
92eadb36 | 128 | $rules = []; |
9099cab3 | 129 | foreach (['postal_code'] as $field) { |
92eadb36 D |
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 | ||
9742d8f3 SL |
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 | ||
197e72c4 SL |
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 | ||
a2dd33d3 | 198 | /** |
199 | * Test the supervised dedupe rule against a group. | |
200 | * | |
201 | * @throws \Exception | |
202 | */ | |
203 | public function testSupervisedDupes() { | |
204 | $this->setupForGroupDedupe(); | |
9099cab3 | 205 | $ruleGroup = $this->callAPISuccessGetSingle('RuleGroup', ['is_reserved' => 1, 'contact_type' => 'Individual', 'used' => 'Supervised']); |
a2dd33d3 | 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 | |
6a488035 TO |
230 | |
231 | // contact data set | |
232 | // FIXME: move create params to separate function | |
9099cab3 CW |
233 | $params = [ |
234 | [ | |
6a488035 TO |
235 | 'first_name' => 'robin', |
236 | 'last_name' => 'hood', | |
237 | 'email' => 'robin@example.com', | |
238 | 'contact_type' => 'Individual', | |
9099cab3 CW |
239 | ], |
240 | [ | |
6a488035 TO |
241 | 'first_name' => 'robin', |
242 | 'last_name' => 'hood', | |
243 | 'email' => 'hood@example.com', | |
244 | 'contact_type' => 'Individual', | |
9099cab3 CW |
245 | ], |
246 | [ | |
6a488035 TO |
247 | 'first_name' => 'robin', |
248 | 'last_name' => 'dale', | |
249 | 'email' => 'robin@example.com', | |
250 | 'contact_type' => 'Individual', | |
9099cab3 CW |
251 | ], |
252 | [ | |
6a488035 TO |
253 | 'first_name' => 'little', |
254 | 'last_name' => 'dale', | |
255 | 'email' => 'dale@example.com', | |
256 | 'contact_type' => 'Individual', | |
9099cab3 CW |
257 | ], |
258 | [ | |
6a488035 TO |
259 | 'first_name' => 'will', |
260 | 'last_name' => 'dale', | |
261 | 'email' => 'dale@example.com', | |
262 | 'contact_type' => 'Individual', | |
9099cab3 CW |
263 | ], |
264 | [ | |
6a488035 TO |
265 | 'first_name' => 'will', |
266 | 'last_name' => 'dale', | |
267 | 'email' => 'will@example.com', | |
268 | 'contact_type' => 'Individual', | |
9099cab3 CW |
269 | ], |
270 | [ | |
6a488035 TO |
271 | 'first_name' => 'will', |
272 | 'last_name' => 'dale', | |
273 | 'email' => 'will@example.com', | |
274 | 'contact_type' => 'Individual', | |
9099cab3 CW |
275 | ], |
276 | ]; | |
6a488035 | 277 | |
9099cab3 | 278 | $this->hookClass->setHook('civicrm_findDuplicates', [$this, 'hook_civicrm_findDuplicates']); |
b8cb7e46 | 279 | |
6a488035 | 280 | $count = 1; |
a2dd33d3 | 281 | |
6a488035 | 282 | foreach ($params as $param) { |
87a56b12 | 283 | $contact = $this->callAPISuccess('contact', 'create', $param); |
9099cab3 | 284 | $params = [ |
6a488035 | 285 | 'contact_id' => $contact['id'], |
a2dd33d3 | 286 | 'street_address' => 'Ambachtstraat 23', |
287 | 'location_type_id' => 1, | |
9099cab3 | 288 | ]; |
a2dd33d3 | 289 | $this->callAPISuccess('address', 'create', $params); |
290 | $contactIds[$count++] = $contact['id']; | |
6a488035 TO |
291 | } |
292 | ||
293 | // verify that all contacts have been created separately | |
294 | $this->assertEquals(count($contactIds), 7, 'Check for number of contacts.'); | |
295 | ||
9099cab3 | 296 | $fields = [ |
a2dd33d3 | 297 | 'first_name' => 'robin', |
298 | 'last_name' => 'hood', | |
299 | 'email' => 'hood@example.com', | |
300 | 'street_address' => 'Ambachtstraat 23', | |
9099cab3 | 301 | ]; |
a2dd33d3 | 302 | CRM_Core_TemporaryErrorScope::useException(); |
b8cb7e46 | 303 | $ids = CRM_Contact_BAO_Contact::getDuplicateContacts($fields, 'Individual', 'General', [], TRUE, NULL, ['event_id' => 1]); |
6a488035 | 304 | |
a2dd33d3 | 305 | // Check with default Individual-General rule |
306 | $this->assertEquals(count($ids), 2, 'Check Individual-General rule for dupesByParams().'); | |
6a488035 | 307 | |
a2dd33d3 | 308 | // delete all created contacts |
6a488035 | 309 | foreach ($contactIds as $contactId) { |
93ac19cd | 310 | $this->contactDelete($contactId); |
6a488035 | 311 | } |
6a488035 TO |
312 | } |
313 | ||
b8cb7e46 MWMC |
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 | ||
a2dd33d3 | 347 | /** |
348 | * Set up a group of dedupable contacts. | |
349 | */ | |
350 | protected function setupForGroupDedupe() { | |
9099cab3 | 351 | $params = [ |
a2dd33d3 | 352 | 'name' => 'Dupe Group', |
353 | 'title' => 'New Test Dupe Group', | |
354 | 'domain_id' => 1, | |
355 | 'is_active' => 1, | |
356 | 'visibility' => 'Public Pages', | |
9099cab3 | 357 | ]; |
a2dd33d3 | 358 | |
359 | $result = $this->callAPISuccess('group', 'create', $params); | |
360 | $this->groupID = $result['id']; | |
6a488035 | 361 | |
9099cab3 CW |
362 | $params = [ |
363 | [ | |
6a488035 TO |
364 | 'first_name' => 'robin', |
365 | 'last_name' => 'hood', | |
366 | 'email' => 'robin@example.com', | |
367 | 'contact_type' => 'Individual', | |
ce217e17 | 368 | 'birth_date' => '2016-01-01', |
197e72c4 | 369 | 'api.Address.create' => ['street_address' => '123 Happy world', 'location_type_id' => 'Billing', 'postal_code' => '99999'], |
9099cab3 CW |
370 | ], |
371 | [ | |
6a488035 TO |
372 | 'first_name' => 'robin', |
373 | 'last_name' => 'hood', | |
374 | 'email' => 'hood@example.com', | |
375 | 'contact_type' => 'Individual', | |
ce217e17 | 376 | 'birth_date' => '2016-01-01', |
197e72c4 | 377 | 'api.Address.create' => ['street_address' => '123 Happy World', 'location_type_id' => 'Billing', 'postal_code' => '99999'], |
9099cab3 CW |
378 | ], |
379 | [ | |
6a488035 TO |
380 | 'first_name' => 'robin', |
381 | 'last_name' => 'dale', | |
382 | 'email' => 'robin@example.com', | |
383 | 'contact_type' => 'Individual', | |
9099cab3 CW |
384 | ], |
385 | [ | |
6a488035 TO |
386 | 'first_name' => 'little', |
387 | 'last_name' => 'dale', | |
388 | 'email' => 'dale@example.com', | |
389 | 'contact_type' => 'Individual', | |
9099cab3 CW |
390 | ], |
391 | [ | |
6a488035 TO |
392 | 'first_name' => 'will', |
393 | 'last_name' => 'dale', | |
394 | 'email' => 'dale@example.com', | |
395 | 'contact_type' => 'Individual', | |
9099cab3 CW |
396 | ], |
397 | [ | |
6a488035 TO |
398 | 'first_name' => 'will', |
399 | 'last_name' => 'dale', | |
400 | 'email' => 'will@example.com', | |
401 | 'contact_type' => 'Individual', | |
9099cab3 CW |
402 | ], |
403 | [ | |
6a488035 TO |
404 | 'first_name' => 'will', |
405 | 'last_name' => 'dale', | |
406 | 'email' => 'will@example.com', | |
407 | 'contact_type' => 'Individual', | |
9099cab3 CW |
408 | ], |
409 | ]; | |
6a488035 TO |
410 | |
411 | $count = 1; | |
6a488035 | 412 | foreach ($params as $param) { |
87a56b12 | 413 | $contact = $this->callAPISuccess('contact', 'create', $param); |
a2dd33d3 | 414 | $this->contactIDs[$count++] = $contact['id']; |
415 | ||
9099cab3 | 416 | $grpParams = [ |
6a488035 | 417 | 'contact_id' => $contact['id'], |
a2dd33d3 | 418 | 'group_id' => $this->groupID, |
9099cab3 | 419 | ]; |
a2dd33d3 | 420 | $this->callAPISuccess('group_contact', 'create', $grpParams); |
6a488035 TO |
421 | } |
422 | ||
423 | // verify that all contacts have been created separately | |
a2dd33d3 | 424 | $this->assertEquals(count($this->contactIDs), 7, 'Check for number of contacts.'); |
6a488035 | 425 | } |
96025800 | 426 | |
6a488035 | 427 | } |