Merge pull request #14320 from sushantpaste/reporthook
[civicrm-core.git] / tests / phpunit / CRM / Utils / API / MatchOptionTest.php
CommitLineData
c8463688
TO
1<?php
2
c8463688
TO
3/**
4 * Test that the API accepts the 'match' and 'match-mandatory' options.
acb109b7 5 * @group headless
c8463688
TO
6 */
7class CRM_Utils_API_MatchOptionTest extends CiviUnitTestCase {
8
d0d21f46
TO
9 /**
10 * @var array
11 */
39b959db 12 public $noise;
d0d21f46 13
00be9182 14 public function setUp() {
c8463688
TO
15 parent::setUp();
16 $this->assertDBQuery(0, "SELECT count(*) FROM civicrm_contact WHERE first_name='Jeffrey' and last_name='Lebowski'");
17
18 // Create noise to ensure we don't accidentally/coincidentally match the first record
9099cab3 19 $this->noise['individual'] = $this->individualCreate([
d0d21f46
TO
20 'email' => 'ignore1@example.com',
21 // 'street_address-1' => 'Irrelevant'
9099cab3 22 'api.Address.create' => [
92915c55 23 'location_type_id' => 1,
d0d21f46
TO
24 'street_address' => '123 Irrelevant Str',
25 'supplemental_address_1' => 'Room 987',
9099cab3
CW
26 ],
27 ]);
d0d21f46
TO
28 }
29
00be9182 30 public function tearDown() {
9099cab3 31 $noise = $this->callAPISuccess('Contact', 'get', [
d0d21f46 32 'id' => $this->noise['individual'],
9099cab3 33 'return' => ['email'],
d0d21f46 34 'api.Address.get' => 1,
9099cab3 35 ]);
d0d21f46
TO
36 $this->assertEquals(1, count($noise['values']));
37 foreach ($noise['values'] as $value) {
38 $this->assertEquals('ignore1@example.com', $value['email']);
39 $this->assertEquals(1, count($value['api.Address.get']['values']));
40 }
9099cab3
CW
41 CRM_core_DAO::executeQuery('DELETE FROM civicrm_address WHERE contact_id=%1', [
42 1 => [$this->noise['individual'], 'Positive'],
43 ]);
44 $this->callAPISuccess('Contact', 'delete', [
d0d21f46 45 'id' => $this->noise['individual'],
9099cab3 46 ]);
d0d21f46 47 parent::tearDown();
c8463688
TO
48 }
49
50 /**
51 * If there's no pre-existing record, then insert a new one.
52 */
00be9182 53 public function testCreateMatch_none() {
9099cab3
CW
54 $result = $this->callAPISuccess('contact', 'create', [
55 'options' => [
56 'match' => ['first_name', 'last_name'],
57 ],
c8463688
TO
58 'contact_type' => 'Individual',
59 'first_name' => 'Jeffrey',
60 'last_name' => 'Lebowski',
61 'nick_name' => '',
62 'external_identifier' => '1',
9099cab3 63 ]);
c8463688
TO
64 $this->assertEquals('Jeffrey', $result['values'][$result['id']]['first_name']);
65 $this->assertEquals('Lebowski', $result['values'][$result['id']]['last_name']);
66 }
67
68 /**
69 * If there's no pre-existing record, then throw an error.
70 */
00be9182 71 public function testCreateMatchMandatory_none() {
9099cab3
CW
72 $this->callAPIFailure('contact', 'create', [
73 'options' => [
74 'match-mandatory' => ['first_name', 'last_name'],
75 ],
c8463688
TO
76 'contact_type' => 'Individual',
77 'first_name' => 'Jeffrey',
78 'last_name' => 'Lebowski',
79 'nick_name' => '',
80 'external_identifier' => '1',
9099cab3 81 ], 'Failed to match existing record');
c8463688
TO
82 }
83
4cbe18b8
EM
84 /**
85 * @return array
86 */
00be9182 87 public function apiOptionNames() {
9099cab3
CW
88 return [
89 ['match'],
90 ['match-mandatory'],
91 ];
c8463688
TO
92 }
93
94 /**
95 * If there's one pre-existing record, then update it.
96 *
97 * @dataProvider apiOptionNames
e16033b4
TO
98 * @param string $apiOptionName
99 * E.g. "match" or "match-mandatory".
c8463688 100 */
00be9182 101 public function testCreateMatch_one($apiOptionName) {
c8463688 102 // create basic record
9099cab3 103 $result1 = $this->callAPISuccess('contact', 'create', [
c8463688
TO
104 'contact_type' => 'Individual',
105 'first_name' => 'Jeffrey',
106 'last_name' => 'Lebowski',
107 'nick_name' => '',
108 'external_identifier' => '1',
9099cab3 109 ]);
c8463688 110
39b959db 111 // more noise!
9099cab3 112 $this->individualCreate(['email' => 'ignore2@example.com']);
c8463688
TO
113
114 // update the record by matching first/last name
9099cab3
CW
115 $result2 = $this->callAPISuccess('contact', 'create', [
116 'options' => [
117 $apiOptionName => ['first_name', 'last_name'],
118 ],
c8463688
TO
119 'contact_type' => 'Individual',
120 'first_name' => 'Jeffrey',
121 'last_name' => 'Lebowski',
122 'nick_name' => 'The Dude',
123 'external_identifier' => '2',
9099cab3 124 ]);
c8463688
TO
125
126 $this->assertEquals($result1['id'], $result2['id']);
127 $this->assertEquals('Jeffrey', $result2['values'][$result2['id']]['first_name']);
128 $this->assertEquals('Lebowski', $result2['values'][$result2['id']]['last_name']);
129 $this->assertEquals('The Dude', $result2['values'][$result2['id']]['nick_name']);
130 // Make sure it was a real update
131 $this->assertDBQuery(1, "SELECT count(*) FROM civicrm_contact WHERE first_name='Jeffrey' and last_name='Lebowski' AND nick_name = 'The Dude'");
132 }
133
134 /**
135 * If there's more than one pre-existing record, throw an error.
136 *
137 * @dataProvider apiOptionNames
e16033b4
TO
138 * @param string $apiOptionName
139 * E.g. "match" or "match-mandatory".
c8463688 140 */
00be9182 141 public function testCreateMatch_many($apiOptionName) {
c8463688 142 // create the first Lebowski
9099cab3 143 $result1 = $this->callAPISuccess('contact', 'create', [
c8463688
TO
144 'contact_type' => 'Individual',
145 'first_name' => 'Jeffrey',
146 'last_name' => 'Lebowski',
147 'nick_name' => 'The Dude',
148 'external_identifier' => '1',
9099cab3 149 ]);
c8463688
TO
150
151 // create the second Lebowski
9099cab3 152 $result2 = $this->callAPISuccess('contact', 'create', [
c8463688
TO
153 'contact_type' => 'Individual',
154 'first_name' => 'Jeffrey',
155 'last_name' => 'Lebowski',
156 'nick_name' => 'The Big Lebowski',
157 'external_identifier' => '2',
9099cab3 158 ]);
c8463688 159
39b959db 160 // more noise!
9099cab3 161 $this->individualCreate(['email' => 'ignore2@example.com']);
c8463688
TO
162
163 // Try to update - but fail due to ambiguity
9099cab3
CW
164 $result3 = $this->callAPIFailure('contact', 'create', [
165 'options' => [
166 $apiOptionName => ['first_name', 'last_name'],
167 ],
c8463688
TO
168 'contact_type' => 'Individual',
169 'first_name' => 'Jeffrey',
170 'last_name' => 'Lebowski',
171 'nick_name' => '',
172 'external_identifier' => 'new',
9099cab3 173 ], 'Ambiguous match criteria');
c8463688
TO
174 }
175
e4b4e33a
TO
176 /**
177 * When replacing one set with another set, match items within
178 * the set using a key.
179 */
00be9182 180 public function testReplaceMatch_Email() {
e4b4e33a 181 // Create contact with two emails (j1,j2)
9099cab3 182 $createResult = $this->callAPISuccess('contact', 'create', [
e4b4e33a
TO
183 'contact_type' => 'Individual',
184 'first_name' => 'Jeffrey',
185 'last_name' => 'Lebowski',
9099cab3
CW
186 'api.Email.replace' => [
187 'options' => ['match' => 'location_type_id'],
188 'values' => [
189 ['location_type_id' => 1, 'email' => 'j1-a@example.com', 'signature_text' => 'The Dude abides.'],
190 [
92915c55
TO
191 'location_type_id' => 2,
192 'email' => 'j2@example.com',
28a04ea9 193 'signature_text' => 'You know, a lotta ins, a lotta outs, a lotta what-have-yous.',
9099cab3
CW
194 ],
195 ],
196 ],
197 ]);
e4b4e33a
TO
198 $this->assertEquals(1, $createResult['count']);
199 foreach ($createResult['values'] as $value) {
200 $this->assertAPISuccess($value['api.Email.replace']);
201 $this->assertEquals(2, $value['api.Email.replace']['count']);
202 foreach ($value['api.Email.replace']['values'] as $v2) {
203 $this->assertEquals($createResult['id'], $v2['contact_id']);
204 }
205 $createEmailValues = array_values($value['api.Email.replace']['values']);
206 }
207
208 // Update contact's emails -- specifically, modify j1, delete j2, add j3
9099cab3 209 $updateResult = $this->callAPISuccess('contact', 'create', [
e4b4e33a
TO
210 'id' => $createResult['id'],
211 'nick_name' => 'The Dude',
9099cab3
CW
212 'api.Email.replace' => [
213 'options' => ['match' => 'location_type_id'],
214 'values' => [
215 ['location_type_id' => 1, 'email' => 'j1-b@example.com'],
216 ['location_type_id' => 3, 'email' => 'j3@example.com'],
217 ],
218 ],
219 ]);
e4b4e33a
TO
220 $this->assertEquals(1, $updateResult['count']);
221 foreach ($updateResult['values'] as $value) {
222 $this->assertAPISuccess($value['api.Email.replace']);
223 $this->assertEquals(2, $value['api.Email.replace']['count']);
224 foreach ($value['api.Email.replace']['values'] as $v2) {
225 $this->assertEquals($createResult['id'], $v2['contact_id']);
226 }
227 $updateEmailValues = array_values($value['api.Email.replace']['values']);
228 }
229
230 // Re-read from DB
9099cab3 231 $getResult = $this->callAPISuccess('Email', 'get', [
e4b4e33a 232 'contact_id' => $createResult['id'],
9099cab3 233 ]);
e4b4e33a
TO
234 $this->assertEquals(2, $getResult['count']);
235 $getValues = array_values($getResult['values']);
236
237 // The first email (j1@example.com) is updated (same ID#) because it matched on contact_id+location_type_id.
238 $this->assertTrue(is_numeric($createEmailValues[0]['id']));
239 $this->assertTrue(is_numeric($updateEmailValues[0]['id']));
240 $this->assertTrue(is_numeric($getValues[0]['id']));
241 $this->assertEquals($createEmailValues[0]['id'], $updateEmailValues[0]['id']);
242 $this->assertEquals($createEmailValues[0]['id'], $getValues[0]['id']);
243 $this->assertEquals('j1-b@example.com', $getValues[0]['email']);
39b959db
SL
244 // preserved from original creation; proves that we updated existing record
245 $this->assertEquals('The Dude abides.', $getValues[0]['signature_text']);
e4b4e33a
TO
246
247 // The second email (j2@example.com) is deleted because contact_id+location_type_id doesn't appear in new list.
248 // The third email (j3@example.com) is inserted (new ID#) because it doesn't match an existing contact_id+location_type_id.
249 $this->assertTrue(is_numeric($createEmailValues[1]['id']));
250 $this->assertTrue(is_numeric($updateEmailValues[1]['id']));
251 $this->assertTrue(is_numeric($getValues[1]['id']));
252 $this->assertNotEquals($createEmailValues[1]['id'], $updateEmailValues[1]['id']);
253 $this->assertEquals($updateEmailValues[1]['id'], $getValues[1]['id']);
254 $this->assertEquals('j3@example.com', $getValues[1]['email']);
255 $this->assertTrue(empty($getValues[1]['signature_text']));
256 }
257
69ce6f4d
TO
258 /**
259 * When replacing one set with another set, match items within
260 * the set using a key.
261 */
00be9182 262 public function testReplaceMatch_Address() {
69ce6f4d 263 // Create contact with two addresses (j1,j2)
9099cab3 264 $createResult = $this->callAPISuccess('contact', 'create', [
69ce6f4d
TO
265 'contact_type' => 'Individual',
266 'first_name' => 'Jeffrey',
267 'last_name' => 'Lebowski',
9099cab3
CW
268 'api.Address.replace' => [
269 'options' => ['match' => 'location_type_id'],
270 'values' => [
271 [
92915c55
TO
272 'location_type_id' => 1,
273 'street_address' => 'j1-a Example Ave',
28a04ea9 274 'supplemental_address_1' => 'The Dude abides.',
9099cab3
CW
275 ],
276 [
92915c55
TO
277 'location_type_id' => 2,
278 'street_address' => 'j2 Example Ave',
28a04ea9 279 'supplemental_address_1' => 'You know, a lotta ins, a lotta outs, a lotta what-have-yous.',
9099cab3
CW
280 ],
281 ],
282 ],
283 ]);
69ce6f4d
TO
284 $this->assertEquals(1, $createResult['count']);
285 foreach ($createResult['values'] as $value) {
286 $this->assertAPISuccess($value['api.Address.replace']);
287 $this->assertEquals(2, $value['api.Address.replace']['count']);
288 foreach ($value['api.Address.replace']['values'] as $v2) {
289 $this->assertEquals($createResult['id'], $v2['contact_id']);
290 }
291 $createAddressValues = array_values($value['api.Address.replace']['values']);
292 }
293
294 // Update contact's addresses -- specifically, modify j1, delete j2, add j3
9099cab3 295 $updateResult = $this->callAPISuccess('contact', 'create', [
69ce6f4d
TO
296 'id' => $createResult['id'],
297 'nick_name' => 'The Dude',
9099cab3
CW
298 'api.Address.replace' => [
299 'options' => ['match' => 'location_type_id'],
300 'values' => [
301 ['location_type_id' => 1, 'street_address' => 'j1-b Example Ave'],
302 ['location_type_id' => 3, 'street_address' => 'j3 Example Ave'],
303 ],
304 ],
305 ]);
69ce6f4d
TO
306 $this->assertEquals(1, $updateResult['count']);
307 foreach ($updateResult['values'] as $value) {
308 $this->assertAPISuccess($value['api.Address.replace']);
309 $this->assertEquals(2, $value['api.Address.replace']['count']);
310 foreach ($value['api.Address.replace']['values'] as $v2) {
311 $this->assertEquals($createResult['id'], $v2['contact_id']);
312 }
313 $updateAddressValues = array_values($value['api.Address.replace']['values']);
314 }
315
316 // Re-read from DB
9099cab3 317 $getResult = $this->callAPISuccess('Address', 'get', [
69ce6f4d 318 'contact_id' => $createResult['id'],
9099cab3 319 ]);
69ce6f4d
TO
320 $this->assertEquals(2, $getResult['count']);
321 $getValues = array_values($getResult['values']);
322
323 // The first street_address (j1 Example Ave) is updated (same ID#) because it matched on contact_id+location_type_id.
324 $this->assertTrue(is_numeric($createAddressValues[0]['id']));
325 $this->assertTrue(is_numeric($updateAddressValues[0]['id']));
326 $this->assertTrue(is_numeric($getValues[0]['id']));
327 $this->assertEquals($createAddressValues[0]['id'], $updateAddressValues[0]['id']);
328 $this->assertEquals($createAddressValues[0]['id'], $getValues[0]['id']);
329 $this->assertEquals('j1-b Example Ave', $getValues[0]['street_address']);
39b959db
SL
330 // preserved from original creation; proves that we updated existing record
331 $this->assertEquals('The Dude abides.', $getValues[0]['supplemental_address_1']);
69ce6f4d
TO
332
333 // The second street_address (j2 Example Ave) is deleted because contact_id+location_type_id doesn't appear in new list.
334 // The third street_address (j3 Example Ave) is inserted (new ID#) because it doesn't match an existing contact_id+location_type_id.
335 $this->assertTrue(is_numeric($createAddressValues[1]['id']));
336 $this->assertTrue(is_numeric($updateAddressValues[1]['id']));
337 $this->assertTrue(is_numeric($getValues[1]['id']));
338 $this->assertNotEquals($createAddressValues[1]['id'], $updateAddressValues[1]['id']);
339 $this->assertEquals($updateAddressValues[1]['id'], $getValues[1]['id']);
340 $this->assertEquals('j3 Example Ave', $getValues[1]['street_address']);
341 $this->assertTrue(empty($getValues[1]['supplemental_address_1']));
342 }
343
c8463688 344}