2 namespace Civi\Payment
;
4 use Civi\Test\HeadlessInterface
;
5 use Civi\Test\TransactionalInterface
;
6 use PHPUnit\Framework\Error\Deprecated
as DeprecatedError
;
11 class PropertyBagTest
extends \PHPUnit\Framework\TestCase
implements HeadlessInterface
, TransactionalInterface
{
14 * @return \Civi\Test\CiviEnvBuilder
16 public function setUpHeadless() {
17 static $reset = FALSE;
18 $return = \Civi\Test
::headless()->apply($reset);
24 * Test we can set a contact ID.
26 public function testSetContactID() {
28 $propertyBag = new PropertyBag();
29 $propertyBag->setContactID(123);
30 $this->assertEquals(123, $propertyBag->getContactID());
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());
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'));
48 * Test we cannot set an invalid contact ID.
50 * @expectedException \InvalidArgumentException
52 public function testSetContactIDFailsIfInvalid() {
53 $propertyBag = new PropertyBag();
54 $propertyBag->setContactID(0);
58 * Test we can set a contact ID the wrong way
60 public function testSetContactIDLegacyWay() {
61 $propertyBag = new PropertyBag();
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
;
68 foreach (['contactID', 'contact_id'] as $prop) {
69 // Set by array access should cause deprecated error.
71 $propertyBag[$prop] = 123;
72 $this->fail("Using array access to set a property '$prop' should trigger deprecated notice.");
74 catch (DeprecatedError
$e) {
77 // But it should still work.
78 error_reporting($ignoreUserDeprecatedErrors);
79 $propertyBag[$prop] = 123;
80 error_reporting($oldLevel);
81 $this->assertEquals(123, $propertyBag->getContactID());
83 // Getting by array access should also cause deprecation error.
85 $_ = $propertyBag[$prop];
86 $this->fail("Using array access to get a property '$prop' should trigger deprecated notice.");
88 catch (DeprecatedError
$e) {
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);
99 * Test that emails set by the legacy method of 'email-5' can be retrieved with getEmail.
101 public function testSetBillingEmailLegacy() {
102 $localPropertyBag = PropertyBag
::cast(['email-' . \CRM_Core_BAO_LocationType
::getBilling() => 'a@b.com']);
103 $this->assertEquals('a@b.com', $localPropertyBag->getEmail());
107 * Test that null is valid for recurring contribution ID.
109 * See https://github.com/civicrm/civicrm-core/pull/17292
111 public function testRecurProcessorIDNull() {
112 $bag = new PropertyBag();
113 $bag->setRecurProcessorID(NULL);
114 $value = $bag->getRecurProcessorID();
115 $this->assertNull($value);
120 public function testMergeInputs() {
121 $propertyBag = PropertyBag
::cast([
123 'contributionRecurID' => 456,
125 $this->assertEquals(123, $propertyBag->getContactID());
126 $this->assertEquals(456, $propertyBag->getContributionRecurID());
130 * Test we can set and access custom props.
132 public function testSetCustomProp() {
133 $oldLevel = error_reporting();
134 $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED
;
137 $propertyBag = new PropertyBag();
138 $propertyBag->setCustomProperty('customThingForMyProcessor', 'fidget');
139 $this->assertEquals('fidget', $propertyBag->getCustomProperty('customThingForMyProcessor'));
140 $this->assertEquals('', $propertyBag->lastWarning
);
142 // Test we can do this with array, although we should get a warning.
143 $propertyBag = new PropertyBag();
145 // Set by array access should cause deprecated error.
147 $propertyBag['customThingForMyProcessor'] = 'fidget';
148 $this->fail("Using array access to set an implicitly custom property should trigger deprecated notice.");
150 catch (DeprecatedError
$e) {
153 // But it should still work.
154 error_reporting($ignoreUserDeprecatedErrors);
155 $propertyBag['customThingForMyProcessor'] = 'fidget';
156 error_reporting($oldLevel);
157 $this->assertEquals('fidget', $propertyBag->getCustomProperty('customThingForMyProcessor'));
159 // Getting by array access should also cause deprecation error.
161 $_ = $propertyBag['customThingForMyProcessor'];
162 $this->fail("Using array access to get an implicitly custom property should trigger deprecated notice.");
164 catch (DeprecatedError
$e) {
167 // But again, it should work.
168 error_reporting($ignoreUserDeprecatedErrors);
169 $this->assertEquals('fidget', $propertyBag['customThingForMyProcessor']);
170 error_reporting($oldLevel);
175 * Test we can't set a custom prop that we know about.
177 * @expectedException \InvalidArgumentException
178 * @expectedExceptionMessage Attempted to set 'contactID' via setCustomProperty - must use using its setter.
180 public function testSetCustomPropFails() {
181 $propertyBag = new PropertyBag();
182 $propertyBag->setCustomProperty('contactID', 123);
186 * Test we get NULL for custom prop that was not set.
188 * This is only for backward compatibility/ease of transition. One day it would be nice to throw an exception instead.
190 * @expectedException \BadMethodCallException
191 * @expectedExceptionMessage Property 'aCustomProp' has not been set.
193 public function testGetCustomPropFails() {
194 $propertyBag = new PropertyBag();
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);
203 $v = $propertyBag['aCustomProp'];
204 error_reporting($oldLevel);
205 $this->fail("Expected BadMethodCallException from accessing an unset custom prop.");
207 catch (\BadMethodCallException
$e) {
208 // reset error level.
209 error_reporting($oldLevel);
210 // rethrow for phpunit to catch.
218 * @dataProvider otherParamsDataProvider
220 public function testOtherParams($prop, $legacy_names, $valid_values, $invalid_values) {
221 $setter = 'set' . ucfirst($prop);
222 $getter = 'get' . ucfirst($prop);
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();
229 $propertyBag->$setter($given);
231 catch (\Exception
$e) {
232 $this->fail("Expected to be able to set '$prop' to '$given' but got " . get_class($e) . ": " . $e->getMessage());
235 $this->assertEquals($expect, $propertyBag->$getter());
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());
241 // Using the setter and getter, check we get an error for invalid data.
242 foreach ($invalid_values as $given) {
244 $propertyBag = new PropertyBag();
245 $propertyBag->$setter($given);
247 catch (\InvalidArgumentException
$e) {
248 // counts this assertion.
249 $this->assertTrue(TRUE);
252 $this->fail("Expected an error trying to set $prop to " . json_encode($given) . " but did not get one.");
255 $oldLevel = error_reporting();
256 $ignoreUserDeprecatedErrors = $oldLevel & ~E_USER_DEPRECATED
;
258 // Check array access for the proper property name and any aliases.
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);
262 foreach (array_merge([$prop], $legacy_names) as $name) {
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.
273 error_reporting($oldLevel);
277 * Test the require method works.
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']);
286 $propertyBag->require(['contactID', 'description', 'contributionID', 'somethingthatdoesntexist']);
288 catch (\InvalidArgumentException
$e) {
289 $this->assertEquals('Required properties missing: contributionID, somethingthatdoesntexist', $e->getMessage());
294 * Test retrieves using CRM_Utils_Array::value still work.
296 public function testUtilsArray() {
297 $propertyBag = new PropertyBag();
298 $propertyBag->setContactID(123);
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);
303 $this->assertEquals(123, \CRM_Utils_Array
::value('contact_id', $propertyBag));
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));
307 error_reporting($oldLevel);
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);
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);
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");
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");
353 $v = empty($propertyBag['nonexistent_custom_field']);
354 $this->assertTrue($v, "empty on a non-existent custom property should return True");
356 $v = empty($propertyBag['custom_issue']);
357 $this->assertFalse($v, "empty on a set custom property accessed by ArrayAccess should return False");
359 error_reporting($oldLevel);
364 * Data provider for testOtherParams
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'], ['', '']];
370 $valid_strings_inc_null = [['foo' , 'foo'], ['', ''], [NULL, '']];
371 $valid_ints = [[123, 123], ['123', 123]];
372 $invalid_ints = [-1, 0, NULL, ''];
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]],
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]],
386 ['firstName', [], $valid_strings_inc_null, []],
387 ['invoiceID', ['invoice_id'], $valid_strings, []],
388 ['isBackOffice', ['is_back_office'], $valid_bools, [NULL]],
389 ['isRecur', ['is_recur'], $valid_bools, [NULL]],
390 ['lastName', [], $valid_strings_inc_null, []],
391 ['paymentToken', [], $valid_strings, []],
392 ['recurFrequencyInterval', ['frequency_interval'], $valid_ints, $invalid_ints],
393 ['recurFrequencyUnit', [], [['month', 'month'], ['day', 'day'], ['year', 'year']], ['', NULL, 0]],
394 ['recurProcessorID', [], [['foo', 'foo']], [str_repeat('x', 256)]],
395 ['transactionID', ['transaction_id'], $valid_strings, []],
396 ['trxnResultCode', [], $valid_strings, []],
401 * Test generic getter, setter methods.
404 public function testGetterAndSetter() {
405 $propertyBag = new PropertyBag();
407 $propertyBag->setter('contactID', 123);
408 $this->assertEquals(123, $propertyBag->getContactID(), "Failed testing that a valid property was set correctly");
410 $result = $propertyBag->getter('contactID');
411 $this->assertEquals(123, $result, "Failed testing the getter on a set property");
413 $result = $propertyBag->getter('contactID', TRUE, 456);
414 $this->assertEquals(123, $result, "Failed testing the getter on a set property when providing a default");
416 $result = $propertyBag->getter('contributionRecurID', TRUE, 456);
417 $this->assertEquals(456, $result, "Failed testing the getter on an unset property when providing a default");
420 $result = $propertyBag->getter('contributionRecurID', FALSE);
421 $this->fail("getter called with unset property should throw exception but none was thrown");
423 catch (\BadMethodCallException
$e) {
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");
430 $result = $propertyBag->getter('contribution_recur_id');
432 catch (\InvalidArgumentException
$e) {
433 $this->assertEquals("Attempted to get 'contribution_recur_id' via getCustomProperty - must use using its getter.", $e->getMessage());
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
441 $result = $propertyBag->getter('something_custom');
442 $this->fail("Expected a BadMethodCallException when getting 'something_custom' which has not been set.");
444 catch (\BadMethodCallException
$e) {
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.");
451 catch (\BadMethodCallException
$e) {
452 $this->assertEquals("Cannot use generic setter with non-standard properties; you must use setCustomProperty for custom properties.", $e->getMessage());
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");