Merge pull request #17712 from civicrm/5.27
[civicrm-core.git] / tests / phpunit / Civi / Payment / PropertyBagTest.php
CommitLineData
47c96854
RLAR
1<?php
2namespace Civi\Payment;
3
4use Civi\Test\HeadlessInterface;
5use Civi\Test\TransactionalInterface;
aa29942d 6use PHPUnit\Framework\Error\Deprecated as DeprecatedError;
47c96854
RLAR
7
8/**
9 * @group headless
10 */
11class 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}