Commit | Line | Data |
---|---|---|
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 | */ |
7 | class 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 | } |