Commit | Line | Data |
---|---|---|
47c96854 RLAR |
1 | <?php |
2 | namespace Civi\Payment; | |
3 | ||
4 | use Civi\Test\HeadlessInterface; | |
5 | use Civi\Test\TransactionalInterface; | |
aa29942d | 6 | use PHPUnit\Framework\Error\Deprecated as DeprecatedError; |
47c96854 RLAR |
7 | |
8 | /** | |
9 | * @group headless | |
10 | */ | |
11 | class PropertyBagTest extends \PHPUnit\Framework\TestCase implements HeadlessInterface, TransactionalInterface { | |
12 | ||
5b59c719 | 13 | /** |
14 | * @return \Civi\Test\CiviEnvBuilder | |
15 | */ | |
47c96854 | 16 | public function setUpHeadless() { |
22dc3ee3 RLAR |
17 | static $reset = FALSE; |
18 | $return = \Civi\Test::headless()->apply($reset); | |
19 | $reset = FALSE; | |
20 | return $return; | |
47c96854 RLAR |
21 | } |
22 | ||
47c96854 RLAR |
23 | /** |
24 | * Test we can set a contact ID. | |
25 | */ | |
26 | public function testSetContactID() { | |
27 | // Do things proper. | |
28 | $propertyBag = new PropertyBag(); | |
29 | $propertyBag->setContactID(123); | |
30 | $this->assertEquals(123, $propertyBag->getContactID()); | |
31 | ||
32 | // Same but this time set contact ID with string. | |
33 | // (php should throw its own warnings about this because of the signature) | |
34 | $propertyBag = new PropertyBag(); | |
35 | $propertyBag->setContactID('123'); | |
36 | $this->assertInternalType('int', $propertyBag->getContactID()); | |
37 | $this->assertEquals(123, $propertyBag->getContactID()); | |
38 | ||
39 | // Test we can have different labels | |
40 | $propertyBag = new PropertyBag(); | |
41 | $propertyBag->setContactID(123); | |
42 | $propertyBag->setContactID(456, 'new'); | |
43 | $this->assertEquals(123, $propertyBag->getContactID()); | |
44 | $this->assertEquals(456, $propertyBag->getContactID('new')); | |
45 | } | |
46 | ||
47 | /** | |
48 | * Test we cannot set an invalid contact ID. | |
49 | * | |
50 | * @expectedException \InvalidArgumentException | |
51 | */ | |
52 | public function testSetContactIDFailsIfInvalid() { | |
53 | $propertyBag = new PropertyBag(); | |
54 | $propertyBag->setContactID(0); | |
55 | } | |
56 | ||
57 | /** | |
58 | * Test we can set a contact ID the wrong way | |
59 | */ | |
60 | public function testSetContactIDLegacyWay() { | |
61 | $propertyBag = new PropertyBag(); | |
47c96854 | 62 | |
aa29942d RLAR |
63 | // To prevent E_USER_DEPRECATED errors during phpunit tests we take a copy |
64 | // of the existing error_reporting. | |
65 | $oldLevel = error_reporting(); | |
66 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
47c96854 | 67 | |
aa29942d RLAR |
68 | foreach (['contactID', 'contact_id'] as $prop) { |
69 | // Set by array access should cause deprecated error. | |
70 | try { | |
71 | $propertyBag[$prop] = 123; | |
72 | $this->fail("Using array access to set a property '$prop' should trigger deprecated notice."); | |
73 | } | |
74 | catch (DeprecatedError $e) { | |
75 | } | |
76 | ||
77 | // But it should still work. | |
78 | error_reporting($ignoreUserDeprecatedErrors); | |
79 | $propertyBag[$prop] = 123; | |
80 | error_reporting($oldLevel); | |
81 | $this->assertEquals(123, $propertyBag->getContactID()); | |
82 | ||
83 | // Getting by array access should also cause deprecation error. | |
84 | try { | |
85 | $_ = $propertyBag[$prop]; | |
86 | $this->fail("Using array access to get a property '$prop' should trigger deprecated notice."); | |
87 | } | |
88 | catch (DeprecatedError $e) { | |
89 | } | |
90 | ||
91 | // But again, it should work. | |
92 | error_reporting($ignoreUserDeprecatedErrors); | |
93 | $this->assertEquals(123, $propertyBag[$prop], "Getting '$prop' by array access should work"); | |
94 | error_reporting($oldLevel); | |
95 | } | |
47c96854 RLAR |
96 | } |
97 | ||
381735e2 | 98 | /** |
99 | * Test that emails set by the legacy method of 'email-5' can be retrieved with getEmail. | |
100 | */ | |
101 | public function testSetBillingEmailLegacy() { | |
aa29942d | 102 | $localPropertyBag = PropertyBag::cast(['email-' . \CRM_Core_BAO_LocationType::getBilling() => 'a@b.com']); |
381735e2 | 103 | $this->assertEquals('a@b.com', $localPropertyBag->getEmail()); |
104 | } | |
105 | ||
5b59c719 | 106 | /** |
107 | * Test that null is valid for recurring contribution ID. | |
108 | * | |
109 | * See https://github.com/civicrm/civicrm-core/pull/17292 | |
110 | */ | |
111 | public function testRecurProcessorIDNull() { | |
112 | $bag = new PropertyBag(); | |
113 | $bag->setRecurProcessorID(NULL); | |
114 | $value = $bag->getRecurProcessorID(); | |
115 | $this->assertNull($value); | |
116 | } | |
117 | ||
47c96854 RLAR |
118 | /** |
119 | */ | |
120 | public function testMergeInputs() { | |
aa29942d | 121 | $propertyBag = PropertyBag::cast([ |
47c96854 RLAR |
122 | 'contactID' => 123, |
123 | 'contributionRecurID' => 456, | |
124 | ]); | |
47c96854 RLAR |
125 | $this->assertEquals(123, $propertyBag->getContactID()); |
126 | $this->assertEquals(456, $propertyBag->getContributionRecurID()); | |
127 | } | |
128 | ||
129 | /** | |
130 | * Test we can set and access custom props. | |
131 | */ | |
132 | public function testSetCustomProp() { | |
aa29942d RLAR |
133 | $oldLevel = error_reporting(); |
134 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
135 | ||
136 | // The proper way. | |
47c96854 RLAR |
137 | $propertyBag = new PropertyBag(); |
138 | $propertyBag->setCustomProperty('customThingForMyProcessor', 'fidget'); | |
139 | $this->assertEquals('fidget', $propertyBag->getCustomProperty('customThingForMyProcessor')); | |
140 | $this->assertEquals('', $propertyBag->lastWarning); | |
141 | ||
142 | // Test we can do this with array, although we should get a warning. | |
143 | $propertyBag = new PropertyBag(); | |
aa29942d RLAR |
144 | |
145 | // Set by array access should cause deprecated error. | |
146 | try { | |
147 | $propertyBag['customThingForMyProcessor'] = 'fidget'; | |
148 | $this->fail("Using array access to set an implicitly custom property should trigger deprecated notice."); | |
149 | } | |
150 | catch (DeprecatedError $e) { | |
151 | } | |
152 | ||
153 | // But it should still work. | |
154 | error_reporting($ignoreUserDeprecatedErrors); | |
47c96854 | 155 | $propertyBag['customThingForMyProcessor'] = 'fidget'; |
aa29942d | 156 | error_reporting($oldLevel); |
47c96854 | 157 | $this->assertEquals('fidget', $propertyBag->getCustomProperty('customThingForMyProcessor')); |
aa29942d RLAR |
158 | |
159 | // Getting by array access should also cause deprecation error. | |
160 | try { | |
161 | $_ = $propertyBag['customThingForMyProcessor']; | |
162 | $this->fail("Using array access to get an implicitly custom property should trigger deprecated notice."); | |
163 | } | |
164 | catch (DeprecatedError $e) { | |
165 | } | |
166 | ||
167 | // But again, it should work. | |
168 | error_reporting($ignoreUserDeprecatedErrors); | |
22dc3ee3 | 169 | $this->assertEquals('fidget', $propertyBag['customThingForMyProcessor']); |
aa29942d RLAR |
170 | error_reporting($oldLevel); |
171 | ||
47c96854 RLAR |
172 | } |
173 | ||
174 | /** | |
175 | * Test we can't set a custom prop that we know about. | |
176 | * | |
177 | * @expectedException \InvalidArgumentException | |
178 | * @expectedExceptionMessage Attempted to set 'contactID' via setCustomProperty - must use using its setter. | |
179 | */ | |
180 | public function testSetCustomPropFails() { | |
181 | $propertyBag = new PropertyBag(); | |
182 | $propertyBag->setCustomProperty('contactID', 123); | |
183 | } | |
184 | ||
22dc3ee3 | 185 | /** |
6a78439a RLAR |
186 | * Test we get NULL for custom prop that was not set. |
187 | * | |
188 | * This is only for backward compatibility/ease of transition. One day it would be nice to throw an exception instead. | |
22dc3ee3 RLAR |
189 | * |
190 | * @expectedException \BadMethodCallException | |
191 | * @expectedExceptionMessage Property 'aCustomProp' has not been set. | |
192 | */ | |
193 | public function testGetCustomPropFails() { | |
194 | $propertyBag = new PropertyBag(); | |
aa29942d RLAR |
195 | // Tricky test. We need to ignore deprecation errors, we're testing deprecated behaviour, |
196 | // but we need to listen out for a different exception. | |
197 | $oldLevel = error_reporting(); | |
198 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
199 | error_reporting($ignoreUserDeprecatedErrors); | |
200 | ||
201 | // Do the do. | |
202 | try { | |
203 | $v = $propertyBag['aCustomProp']; | |
204 | error_reporting($oldLevel); | |
205 | $this->fail("Expected BadMethodCallException from accessing an unset custom prop."); | |
206 | } | |
207 | catch (\BadMethodCallException $e) { | |
208 | // reset error level. | |
209 | error_reporting($oldLevel); | |
210 | // rethrow for phpunit to catch. | |
211 | throw $e; | |
212 | } | |
213 | ||
22dc3ee3 RLAR |
214 | } |
215 | ||
47c96854 RLAR |
216 | /** |
217 | * | |
218 | * @dataProvider otherParamsDataProvider | |
219 | */ | |
220 | public function testOtherParams($prop, $legacy_names, $valid_values, $invalid_values) { | |
221 | $setter = 'set' . ucfirst($prop); | |
222 | $getter = 'get' . ucfirst($prop); | |
223 | ||
224 | // Using the setter and getter, check we can pass stuff in and get expected out. | |
225 | foreach ($valid_values as $_) { | |
226 | list($given, $expect) = $_; | |
227 | $propertyBag = new PropertyBag(); | |
dea0d7b8 RLAR |
228 | try { |
229 | $propertyBag->$setter($given); | |
230 | } | |
231 | catch (\Exception $e) { | |
232 | $this->fail("Expected to be able to set '$prop' to '$given' but got " . get_class($e) . ": " . $e->getMessage()); | |
233 | } | |
234 | try { | |
235 | $this->assertEquals($expect, $propertyBag->$getter()); | |
236 | } | |
237 | catch (\Exception $e) { | |
238 | $this->fail("Expected to be able to call $getter, having called $setter with '$given' but got " . get_class($e) . ": " . $e->getMessage()); | |
239 | } | |
47c96854 RLAR |
240 | } |
241 | // Using the setter and getter, check we get an error for invalid data. | |
242 | foreach ($invalid_values as $given) { | |
243 | try { | |
244 | $propertyBag = new PropertyBag(); | |
245 | $propertyBag->$setter($given); | |
246 | } | |
247 | catch (\InvalidArgumentException $e) { | |
248 | // counts this assertion. | |
249 | $this->assertTrue(TRUE); | |
250 | continue; | |
251 | } | |
252 | $this->fail("Expected an error trying to set $prop to " . json_encode($given) . " but did not get one."); | |
253 | } | |
254 | ||
aa29942d RLAR |
255 | $oldLevel = error_reporting(); |
256 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
257 | ||
47c96854 | 258 | // Check array access for the proper property name and any aliases. |
aa29942d RLAR |
259 | // This is going to throw a bunch of deprecated errors, but we know this |
260 | // (and have tested it elsewhere) so we turn those off. | |
261 | error_reporting($ignoreUserDeprecatedErrors); | |
47c96854 | 262 | foreach (array_merge([$prop], $legacy_names) as $name) { |
47c96854 RLAR |
263 | foreach ($valid_values as $_) { |
264 | list($given, $expect) = $_; | |
265 | $propertyBag = new PropertyBag(); | |
266 | $propertyBag[$name] = $given; | |
267 | $this->assertEquals($expect, $propertyBag->$getter(), "Failed to set $prop via array access on $name"); | |
268 | // Nb. I don't feel the need to repeat all the checks above for every alias. | |
269 | // We only really need to test that the array access works for each alias. | |
270 | break; | |
271 | } | |
272 | } | |
aa29942d | 273 | error_reporting($oldLevel); |
47c96854 RLAR |
274 | } |
275 | ||
276 | /** | |
277 | * Test the require method works. | |
278 | */ | |
279 | public function testRequire() { | |
280 | $propertyBag = new PropertyBag(); | |
281 | $propertyBag->setContactID(123); | |
282 | $propertyBag->setDescription('foo'); | |
283 | // This one should not error. | |
284 | $propertyBag->require(['contactID', 'description']); | |
285 | try { | |
286 | $propertyBag->require(['contactID', 'description', 'contributionID', 'somethingthatdoesntexist']); | |
287 | } | |
288 | catch (\InvalidArgumentException $e) { | |
289 | $this->assertEquals('Required properties missing: contributionID, somethingthatdoesntexist', $e->getMessage()); | |
290 | } | |
291 | } | |
292 | ||
42d24af6 | 293 | /** |
294 | * Test retrieves using CRM_Utils_Array::value still work. | |
295 | */ | |
296 | public function testUtilsArray() { | |
297 | $propertyBag = new PropertyBag(); | |
298 | $propertyBag->setContactID(123); | |
aa29942d RLAR |
299 | // This will throw deprecation notices but we don't care. |
300 | $oldLevel = error_reporting(); | |
301 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
302 | error_reporting($ignoreUserDeprecatedErrors); | |
42d24af6 | 303 | $this->assertEquals(123, \CRM_Utils_Array::value('contact_id', $propertyBag)); |
61296745 RLAR |
304 | |
305 | // Test that using utils array value to get a nonexistent property returns the default. | |
306 | $this->assertEquals(456, \CRM_Utils_Array::value('ISawAManWhoWasntThere', $propertyBag, 456)); | |
aa29942d | 307 | error_reporting($oldLevel); |
42d24af6 | 308 | } |
309 | ||
22dc3ee3 RLAR |
310 | /** |
311 | */ | |
312 | public function testEmpty() { | |
313 | $propertyBag = new PropertyBag(); | |
314 | $propertyBag->setContactID(123); | |
315 | $propertyBag->setRecurProcessorID(''); | |
316 | $propertyBag->setBillingPostalCode(NULL); | |
317 | $propertyBag->setFeeAmount(0); | |
318 | $propertyBag->setCustomProperty('custom_issue', 'black lives matter'); | |
319 | $propertyBag->setCustomProperty('custom_null', NULL); | |
320 | $propertyBag->setCustomProperty('custom_false', FALSE); | |
321 | $propertyBag->setCustomProperty('custom_zls', ''); | |
322 | $propertyBag->setCustomProperty('custom_0', 0); | |
323 | ||
aa29942d RLAR |
324 | // To prevent E_USER_DEPRECATED errors during phpunit tests we take a copy |
325 | // of the existing error_reporting. | |
326 | $oldLevel = error_reporting(); | |
327 | $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED; | |
328 | error_reporting($ignoreUserDeprecatedErrors); | |
329 | ||
22dc3ee3 RLAR |
330 | // Tests on known properties. |
331 | $v = empty($propertyBag->getContactID()); | |
332 | $this->assertFalse($v, "empty on a set, known property should return False"); | |
333 | $v = empty($propertyBag['contactID']); | |
334 | $this->assertFalse($v, "empty on a set, known property accessed by ArrayAccess with correct name should return False"); | |
335 | $v = empty($propertyBag['contact_id']); | |
336 | $this->assertFalse($v, "empty on a set, known property accessed by ArrayAccess with legacy name should return False"); | |
337 | $v = empty($propertyBag['recurProcessorID']); | |
338 | $this->assertTrue($v, "empty on an unset, known property accessed by ArrayAccess should return True"); | |
339 | $v = empty($propertyBag->getRecurProcessorID()); | |
340 | $this->assertTrue($v, "empty on a set, but '' value should return True"); | |
341 | $v = empty($propertyBag->getFeeAmount()); | |
342 | $this->assertTrue($v, "empty on a set, but 0 value should return True"); | |
343 | $v = empty($propertyBag->getBillingPostalCode()); | |
344 | $this->assertTrue($v, "empty on a set, but NULL value should return True"); | |
345 | ||
346 | // Test custom properties. | |
347 | $v = empty($propertyBag->getCustomProperty('custom_issue')); | |
348 | $this->assertFalse($v, "empty on a set custom property with non-empty value should return False"); | |
349 | foreach (['null', 'false', 'zls', '0'] as $_) { | |
350 | $v = empty($propertyBag["custom_$_"]); | |
351 | $this->assertTrue($v, "empty on a set custom property with $_ value should return TRUE"); | |
352 | } | |
353 | $v = empty($propertyBag['nonexistent_custom_field']); | |
354 | $this->assertTrue($v, "empty on a non-existent custom property should return True"); | |
355 | ||
356 | $v = empty($propertyBag['custom_issue']); | |
357 | $this->assertFalse($v, "empty on a set custom property accessed by ArrayAccess should return False"); | |
358 | ||
aa29942d | 359 | error_reporting($oldLevel); |
22dc3ee3 RLAR |
360 | } |
361 | ||
47c96854 RLAR |
362 | /** |
363 | * | |
364 | * Data provider for testOtherParams | |
365 | * | |
366 | */ | |
367 | public function otherParamsDataProvider() { | |
368 | $valid_bools = [['0' , FALSE], ['', FALSE], [0, FALSE], [FALSE, FALSE], [TRUE, TRUE], [1, TRUE], ['1', TRUE]]; | |
369 | $valid_strings = [['foo' , 'foo'], ['', '']]; | |
dea0d7b8 | 370 | $valid_strings_inc_null = [['foo' , 'foo'], ['', ''], [NULL, '']]; |
47c96854 RLAR |
371 | $valid_ints = [[123, 123], ['123', 123]]; |
372 | $invalid_ints = [-1, 0, NULL, '']; | |
373 | return [ | |
dea0d7b8 RLAR |
374 | ['billingStreetAddress', [], $valid_strings_inc_null, []], |
375 | ['billingSupplementalAddress1', [], $valid_strings_inc_null, []], | |
376 | ['billingSupplementalAddress2', [], $valid_strings_inc_null, []], | |
377 | ['billingSupplementalAddress3', [], $valid_strings_inc_null, []], | |
378 | ['billingCity', [], $valid_strings_inc_null, []], | |
379 | ['billingPostalCode', [], $valid_strings_inc_null, []], | |
380 | ['billingCounty', [], $valid_strings_inc_null, []], | |
381 | ['billingCountry', [], [['GB', 'GB'], ['NZ', 'NZ']], ['XX', '', NULL, 0]], | |
47c96854 RLAR |
382 | ['contributionID', ['contribution_id'], $valid_ints, $invalid_ints], |
383 | ['contributionRecurID', ['contribution_recur_id'], $valid_ints, $invalid_ints], | |
384 | ['description', [], [['foo' , 'foo'], ['', '']], []], | |
385 | ['feeAmount', ['fee_amount'], [[1.23, 1.23], ['4.56', 4.56]], [NULL]], | |
dea0d7b8 | 386 | ['firstName', [], $valid_strings_inc_null, []], |
47c96854 RLAR |
387 | ['invoiceID', ['invoice_id'], $valid_strings, []], |
388 | ['isBackOffice', ['is_back_office'], $valid_bools, [NULL]], | |
389 | ['isRecur', ['is_recur'], $valid_bools, [NULL]], | |
dea0d7b8 | 390 | ['lastName', [], $valid_strings_inc_null, []], |
47c96854 RLAR |
391 | ['paymentToken', [], $valid_strings, []], |
392 | ['recurFrequencyInterval', ['frequency_interval'], $valid_ints, $invalid_ints], | |
393 | ['recurFrequencyUnit', [], [['month', 'month'], ['day', 'day'], ['year', 'year']], ['', NULL, 0]], | |
5b59c719 | 394 | ['recurProcessorID', [], [['foo', 'foo']], [str_repeat('x', 256)]], |
47c96854 RLAR |
395 | ['transactionID', ['transaction_id'], $valid_strings, []], |
396 | ['trxnResultCode', [], $valid_strings, []], | |
397 | ]; | |
398 | } | |
399 | ||
a05bcbd4 RLAR |
400 | /** |
401 | * Test generic getter, setter methods. | |
402 | * | |
403 | */ | |
404 | public function testGetterAndSetter() { | |
405 | $propertyBag = new PropertyBag(); | |
406 | ||
407 | $propertyBag->setter('contactID', 123); | |
408 | $this->assertEquals(123, $propertyBag->getContactID(), "Failed testing that a valid property was set correctly"); | |
409 | ||
410 | $result = $propertyBag->getter('contactID'); | |
411 | $this->assertEquals(123, $result, "Failed testing the getter on a set property"); | |
412 | ||
413 | $result = $propertyBag->getter('contactID', TRUE, 456); | |
414 | $this->assertEquals(123, $result, "Failed testing the getter on a set property when providing a default"); | |
415 | ||
416 | $result = $propertyBag->getter('contributionRecurID', TRUE, 456); | |
417 | $this->assertEquals(456, $result, "Failed testing the getter on an unset property when providing a default"); | |
418 | ||
419 | try { | |
420 | $result = $propertyBag->getter('contributionRecurID', FALSE); | |
421 | $this->fail("getter called with unset property should throw exception but none was thrown"); | |
422 | } | |
423 | catch (\BadMethodCallException $e) { | |
424 | } | |
425 | ||
426 | $result = $propertyBag->getter('contribution_recur_id', TRUE, NULL); | |
427 | $this->assertNull($result, "Failed testing the getter on an invalid property when providing a default"); | |
428 | ||
429 | try { | |
430 | $result = $propertyBag->getter('contribution_recur_id'); | |
431 | } | |
432 | catch (\InvalidArgumentException $e) { | |
433 | $this->assertEquals("Attempted to get 'contribution_recur_id' via getCustomProperty - must use using its getter.", $e->getMessage()); | |
434 | } | |
435 | ||
6a78439a RLAR |
436 | // Nb. up to 5.26, the custom property getter did not throw an exception if the property is unset, it just returned NULL. |
437 | // Now, we return NULL for array access (legacy) but for modern access | |
438 | // (getter, getPropX(), getCustomProperty()) then we throw an exception if | |
439 | // it is not set. | |
440 | try { | |
441 | $result = $propertyBag->getter('something_custom'); | |
442 | $this->fail("Expected a BadMethodCallException when getting 'something_custom' which has not been set."); | |
443 | } | |
444 | catch (\BadMethodCallException $e) { | |
445 | } | |
a05bcbd4 RLAR |
446 | |
447 | try { | |
448 | $propertyBag->setter('some_custom_thing', 'foo'); | |
449 | $this->fail("Expected to get an exception when trying to use setter for a non-standard property."); | |
450 | } | |
451 | catch (\BadMethodCallException $e) { | |
452 | $this->assertEquals("Cannot use generic setter with non-standard properties; you must use setCustomProperty for custom properties.", $e->getMessage()); | |
453 | } | |
454 | ||
455 | // Test labels. | |
456 | $propertyBag->setter('contactID', '100', 'original'); | |
457 | $this->assertEquals(123, $propertyBag->getContactID(), "Looks like the setter did not respect the label."); | |
458 | $this->assertEquals(100, $propertyBag->getContactID('original'), "Failed to retrieve the labelled property"); | |
459 | $this->assertEquals(100, $propertyBag->getter('contactID', FALSE, NULL, 'original'), "Failed using the getter to retrieve the labelled property"); | |
460 | ||
461 | } | |
462 | ||
47c96854 | 463 | } |