Remove problematic serialize handing from conformance test
[civicrm-core.git] / tests / phpunit / api / v3 / SyntaxConformanceTest.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
2fe49090 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
6b83d5bd 6 | Copyright CiviCRM LLC (c) 2004-2019 |
6a488035
TO
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035 27
6a488035 28/**
e5dd95b2 29 * Test that the core actions for APIv3 entities comply with standard syntax+behavior.
6a488035 30 *
e5dd95b2
TO
31 * By default, this tests all API entities. To only test specific entities, call phpunit with
32 * environment variable SYNTAX_CONFORMANCE_ENTITIES, e.g.
33 *
34 * env SYNTAX_CONFORMANCE_ENTITIES="Contact Event" ./scripts/phpunit api_v3_SyntaxConformanceTest
35 *
36 * @package CiviCRM_APIv3
37 * @subpackage API_Core
acb109b7 38 * @group headless
6a488035 39 */
8086a0bf 40class api_v3_SyntaxConformanceTest extends CiviUnitTestCase {
18eee50e 41 protected $_apiversion = 3;
6a488035 42
4a97890c
TO
43 /**
44 * @var array e.g. $this->deletes['CRM_Contact_DAO_Contact'][] = $contactID;
45 */
46 protected $deletableTestObjects;
47
be2e0c6a
TO
48 /**
49 * This test case doesn't require DB reset.
39b959db 50 * @var bool
be2e0c6a 51 */
6a488035 52 public $DBResetRequired = FALSE;
6ead217b 53
8efea814
EM
54 protected $_entity;
55
be2e0c6a
TO
56 /**
57 * Map custom group entities to civicrm components.
39b959db 58 * @var array
be2e0c6a 59 */
39b959db 60 protected static $componentMap = array(
82e1689e
N
61 'Contribution' => 'CiviContribute',
62 'Membership' => 'CiviMember',
63 'Participant' => 'CiviEvent',
82e1689e
N
64 'Event' => 'CiviEvent',
65 'Case' => 'CiviCase',
82e1689e
N
66 'Pledge' => 'CiviPledge',
67 'Grant' => 'CiviGrant',
bf38705f 68 'Campaign' => 'CiviCampaign',
69 'Survey' => 'CiviCampaign',
82e1689e
N
70 );
71
63e9c3fd
EM
72 /**
73 * Set up function.
74 *
75 * There are two types of missing APIs:
76 * Those that are to be implemented
77 * (in some future version when someone steps in -hint hint-). List the entities in toBeImplemented[ {$action} ]
78 * Those that don't exist
79 * and that will never exist (eg an obsoleted Entity
80 * they need to be returned by the function toBeSkipped_{$action} (because it has to be a static method and therefore couldn't access a this->toBeSkipped)
81 */
00be9182 82 public function setUp() {
6a488035 83 parent::setUp();
6ead217b 84 $this->enableCiviCampaign();
92915c55 85 $this->toBeImplemented['get'] = array(
39b959db
SL
86 // CxnApp.get exists but relies on remote data outside our control; QA w/UtilsTest::testBasicArrayGet
87 'CxnApp',
92915c55
TO
88 'Profile',
89 'CustomValue',
90 'Constant',
91 'CustomSearch',
92 'Extension',
93 'ReportTemplate',
94 'System',
db7de9c1 95 'Setting',
27ed0b9a 96 'Payment',
93afbc3a 97 'Logging',
92915c55
TO
98 );
99 $this->toBeImplemented['create'] = array(
1c7f266f
TO
100 'Cxn',
101 'CxnApp',
92915c55
TO
102 'SurveyRespondant',
103 'OptionGroup',
104 'MailingRecipients',
105 'UFMatch',
92915c55
TO
106 'CustomSearch',
107 'Extension',
108 'ReportTemplate',
db7de9c1 109 'System',
225d474b 110 'User',
27ed0b9a 111 'Payment',
9422657e 112 'Order',
39b959db
SL
113 //work fine in local
114 'SavedSearch',
93afbc3a 115 'Logging',
92915c55
TO
116 );
117 $this->toBeImplemented['delete'] = array(
1c7f266f
TO
118 'Cxn',
119 'CxnApp',
92915c55
TO
120 'MembershipPayment',
121 'OptionGroup',
122 'SurveyRespondant',
123 'UFJoin',
124 'UFMatch',
125 'Extension',
db7de9c1 126 'System',
27ed0b9a 127 'Payment',
9422657e 128 'Order',
92915c55 129 );
567b2076
EM
130 $this->onlyIDNonZeroCount['get'] = array(
131 'ActivityType',
132 'Entity',
133 'Domain',
134 'Setting',
225d474b 135 'User',
567b2076 136 );
6a488035 137 $this->deprecatedAPI = array('Location', 'ActivityType', 'SurveyRespondant');
4a97890c 138 $this->deletableTestObjects = array();
6a488035
TO
139 }
140
00be9182 141 public function tearDown() {
4a97890c
TO
142 foreach ($this->deletableTestObjects as $entityName => $entities) {
143 foreach ($entities as $entityID) {
144 CRM_Core_DAO::deleteTestObjects($entityName, array('id' => $entityID));
145 }
146 }
147 }
6a488035 148
4cbe18b8 149 /**
db7de9c1
EM
150 * Generate list of all entities.
151 *
152 * @param array $skip
153 * Entities to skip.
4cbe18b8
EM
154 *
155 * @return array
156 */
db7de9c1 157 public static function entities($skip = array()) {
e5dd95b2
TO
158 // The order of operations in here is screwy. In the case where SYNTAX_CONFORMANCE_ENTITIES is
159 // defined, we should be able to parse+return it immediately. However, some weird dependency
160 // crept into the system where civicrm_api('Entity','get') must be called as part of entities()
161 // (even if its return value is ignored).
dcf56200 162
e5dd95b2 163 $tmp = civicrm_api('Entity', 'Get', array('version' => 3));
dcf56200 164 if (getenv('SYNTAX_CONFORMANCE_ENTITIES')) {
e5dd95b2 165 $tmp = array(
21dfd5f5 166 'values' => explode(' ', getenv('SYNTAX_CONFORMANCE_ENTITIES')),
e5dd95b2 167 );
dcf56200
TO
168 }
169
6a488035
TO
170 if (!is_array($skip)) {
171 $skip = array();
172 }
173 $tmp = array_diff($tmp['values'], $skip);
174 $entities = array();
175 foreach ($tmp as $e) {
176 $entities[] = array($e);
177 }
178 return $entities;
179 }
180
4cbe18b8 181 /**
db7de9c1
EM
182 * Get list of entities for get test.
183 *
4cbe18b8
EM
184 * @return array
185 */
6a488035
TO
186 public static function entities_get() {
187 // all the entities, beside the ones flagged
97715495 188 return static::entities(static::toBeSkipped_get(TRUE));
6a488035
TO
189 }
190
4cbe18b8 191 /**
567b2076
EM
192 * Get entities for create tests.
193 *
4cbe18b8
EM
194 * @return array
195 */
6a488035 196 public static function entities_create() {
97715495 197 return static::entities(static::toBeSkipped_create(TRUE));
6a488035
TO
198 }
199
4cbe18b8
EM
200 /**
201 * @return array
202 */
6a488035 203 public static function entities_updatesingle() {
97715495 204 return static::entities(static::toBeSkipped_updatesingle(TRUE));
6a488035
TO
205 }
206
4cbe18b8
EM
207 /**
208 * @return array
209 */
b9af4758
E
210 public static function entities_getlimit() {
211 return static::entities(static::toBeSkipped_getlimit());
212 }
213
ced9bfed
EM
214 /**
215 * Generate list of entities that can be retrieved using SQL operator syntax.
216 *
217 * @return array
218 */
dcf5b21f
EM
219 public static function entities_getSqlOperators() {
220 return static::entities(static::toBeSkipped_getSqlOperators());
221 }
92915c55 222
4cbe18b8
EM
223 /**
224 * @return array
225 */
6a488035 226 public static function entities_delete() {
97715495 227 return static::entities(static::toBeSkipped_delete(TRUE));
6a488035
TO
228 }
229
32dafeec
EM
230 /**
231 * @return array
232 */
233 public static function entities_getfields() {
234 return static::entities(static::toBeSkipped_getfields(TRUE));
235 }
92915c55 236
4cbe18b8
EM
237 /**
238 * @return array
239 */
2fc5f1e7
EM
240 public static function custom_data_entities_get() {
241 return static::custom_data_entities();
242 }
243
4cbe18b8
EM
244 /**
245 * @return array
246 */
2fc5f1e7 247 public static function custom_data_entities() {
82e1689e 248 $entities = CRM_Core_BAO_CustomQuery::$extendsMap;
84fb7424 249 $enabledComponents = Civi::settings()->get('enable_components');
82e1689e 250 $customDataEntities = array();
2fc5f1e7 251 $invalidEntities = array('Individual', 'Organization', 'Household');
2f6264b4 252 $entitiesToFix = array('Case', 'Relationship');
481a74f4 253 foreach ($entities as $entityName => $entity) {
22e263ad 254 if (!in_array($entityName, $invalidEntities)
92915c55
TO
255 && !in_array($entityName, $entitiesToFix)
256 ) {
22e263ad 257 if (!empty(self::$componentMap[$entityName]) && empty($enabledComponents[self::$componentMap[$entityName]])) {
6c6e6187
TO
258 CRM_Core_BAO_ConfigSetting::enableComponent(self::$componentMap[$entityName]);
259 }
260 $customDataEntities[] = array($entityName);
82e1689e
N
261 }
262 }
2fc5f1e7
EM
263 return $customDataEntities;
264 }
265
3fb8828b 266 /**
267 * Add a smattering of entities that don't normally have custom data.
268 *
269 * @return array
270 */
271 public static function custom_data_incl_non_std_entities_get() {
a25b46e9 272 return static::entities(static::toBeSkipped_custom_data_creatable(TRUE));
3fb8828b 273 }
274
4cbe18b8 275 /**
567b2076
EM
276 * Get entities to be skipped on get tests.
277 *
4cbe18b8
EM
278 * @param bool $sequential
279 *
280 * @return array
281 */
6a488035 282 public static function toBeSkipped_get($sequential = FALSE) {
92915c55
TO
283 $entitiesWithoutGet = array(
284 'MailingEventSubscribe',
285 'MailingEventConfirm',
286 'MailingEventResubscribe',
287 'MailingEventUnsubscribe',
567b2076 288 'Location',
92915c55 289 );
6a488035
TO
290 if ($sequential === TRUE) {
291 return $entitiesWithoutGet;
292 }
293 $entities = array();
294 foreach ($entitiesWithoutGet as $e) {
295 $entities[] = array($e);
296 }
297 return $entities;
298 }
b7d29345 299
b14ce773 300 /**
fd786d03
EM
301 * Get entities to be skipped for get call.
302 *
b14ce773 303 * Mailing Contact Just doesn't support id. We have always insisted on finding a way to
4a2db77c 304 * support id in API but in this case the underlying tables are crying out for a restructure
225d474b
EM
305 * & it just doesn't make sense.
306 *
307 * User doesn't support get By ID because the user id is actually the CMS user ID & is not part of
308 * CiviCRM - so can only be tested through UserTest - not SyntaxConformanceTest.
b7d29345 309 *
8ab09481 310 * Entity doesn't support get By ID because it simply gives the result of string Entites in CiviCRM
311 *
4a2db77c 312 * @param bool $sequential
b7d29345 313 *
a6c01b45
CW
314 * @return array
315 * Entities that cannot be retrieved by ID
b14ce773 316 */
317 public static function toBeSkipped_getByID($sequential = FALSE) {
8ab09481 318 return array('MailingContact', 'User', 'Attachment', 'Entity');
b14ce773 319 }
6a488035 320
4cbe18b8
EM
321 /**
322 * @param bool $sequential
323 *
324 * @return array
325 */
6a488035 326 public static function toBeSkipped_create($sequential = FALSE) {
21eb0c57 327 $entitiesWithoutCreate = array('Constant', 'Entity', 'Location', 'Profile', 'MailingRecipients');
6a488035
TO
328 if ($sequential === TRUE) {
329 return $entitiesWithoutCreate;
330 }
331 $entities = array();
332 foreach ($entitiesWithoutCreate as $e) {
333 $entities[] = array($e);
334 }
335 return $entities;
336 }
337
4cbe18b8
EM
338 /**
339 * @param bool $sequential
340 *
341 * @return array
342 */
6a488035 343 public static function toBeSkipped_delete($sequential = FALSE) {
92915c55
TO
344 $entitiesWithout = array(
345 'MailingContact',
346 'MailingEventConfirm',
347 'MailingEventResubscribe',
348 'MailingEventSubscribe',
349 'MailingEventUnsubscribe',
350 'MailingRecipients',
351 'Constant',
352 'Entity',
353 'Location',
354 'Domain',
355 'Profile',
356 'CustomValue',
92c99a4a 357 'Setting',
225d474b 358 'User',
93afbc3a 359 'Logging',
92915c55 360 );
6a488035
TO
361 if ($sequential === TRUE) {
362 return $entitiesWithout;
363 }
364 $entities = array();
365 foreach ($entitiesWithout as $e) {
366 $entities[] = array($e);
367 }
368 return $entities;
369 }
b7d29345 370
a25b46e9 371 /**
372 * @param bool $sequential
373 *
374 * @return array
375 */
376 public static function toBeSkipped_custom_data_creatable($sequential = FALSE) {
377 $entitiesWithout = array(
378 // Ones to fix.
379 'CaseContact',
380 'CustomField',
381 'CustomGroup',
382 'DashboardContact',
383 'Domain',
384 'File',
385 'FinancialType',
386 'LocBlock',
387 'MailingEventConfirm',
388 'MailingEventResubscribe',
389 'MailingEventSubscribe',
390 'MailingEventUnsubscribe',
a25b46e9 391 'MembershipPayment',
a25b46e9 392 'SavedSearch',
393 'UFJoin',
394 'UFField',
395 'PriceFieldValue',
a25b46e9 396 'GroupContact',
397 'EntityTag',
398 'PledgePayment',
a25b46e9 399 'Relationship',
a25b46e9 400
401 // ones that are not real entities hence not extendable.
402 'ActivityType',
403 'Entity',
404 'Cxn',
405 'Constant',
406 'Attachment',
407 'CustomSearch',
408 'CustomValue',
409 'CxnApp',
410 'Extension',
411 'MailingContact',
412 'User',
413 'System',
414 'Setting',
415 'SystemLog',
416 'ReportTemplate',
417 'MailingRecipients',
418 'SurveyRespondant',
419 'Profile',
420 'Payment',
421 'Order',
422 'MailingGroup',
423 'Logging',
424 );
425 if ($sequential === TRUE) {
426 return $entitiesWithout;
427 }
428 $entities = array();
429 foreach ($entitiesWithout as $e) {
430 $entities[] = array($e);
431 }
432 return $entities;
433 }
434
32dafeec
EM
435 /**
436 * @param bool $sequential
437 *
438 * @return array
439 * @todo add metadata for ALL these entities
440 */
441 public static function toBeSkipped_getfields($sequential = FALSE) {
17eeaef9 442 $entitiesWithMetadataNotYetFixed = array('ReportTemplate', 'CustomSearch');
32dafeec 443 if ($sequential === TRUE) {
6c6e6187 444 return $entitiesWithMetadataNotYetFixed;
32dafeec
EM
445 }
446 $entities = array();
447 foreach ($entitiesWithMetadataNotYetFixed as $e) {
448 $entities[] = array($e);
449 }
450 return $entities;
451 }
92915c55 452
6c6e6187 453 /**
eceb18cc 454 * Generate list of entities to test for get by id functions.
6c6e6187 455 * @param bool $sequential
a6c01b45
CW
456 * @return array
457 * Entities to be skipped
6c6e6187 458 */
b07a3bf9 459 public static function toBeSkipped_automock($sequential = FALSE) {
92915c55
TO
460 $entitiesWithoutGet = array(
461 'MailingContact',
462 'EntityTag',
463 'Participant',
92915c55
TO
464 'Setting',
465 'SurveyRespondant',
466 'MailingRecipients',
467 'CustomSearch',
468 'Extension',
469 'ReportTemplate',
af9b09df 470 'System',
93afbc3a 471 'Logging',
92915c55 472 );
b07a3bf9
TO
473 if ($sequential === TRUE) {
474 return $entitiesWithoutGet;
475 }
476 $entities = array();
477 foreach ($entitiesWithoutGet as $e) {
478 $entities[] = array($e);
479 }
480 return $entities;
481 }
482
b7d29345 483 /**
6c6e6187 484 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
1e1fdcf6
EM
485 * @param bool $sequential
486 * @return array
6c6e6187 487 */
6a488035
TO
488 public static function toBeSkipped_updatesingle($sequential = FALSE) {
489 $entitiesWithout = array(
92915c55
TO
490 'Attachment',
491 // pseudo-entity; testUpdateSingleValueAlter doesn't introspect properly on it. Multiple magic fields
6a488035 492 'Mailing',
6a488035
TO
493 'MailingEventUnsubscribe',
494 'MailingEventSubscribe',
495 'Constant',
496 'Entity',
497 'Location',
6a488035
TO
498 'Profile',
499 'CustomValue',
6a488035
TO
500 'UFJoin',
501 'UFField',
6a488035
TO
502 'Relationship',
503 'RelationshipType',
6a488035 504 'Note',
6a488035 505 'Membership',
6a488035 506 'Group',
6a488035
TO
507 'File',
508 'EntityTag',
509 'CustomField',
510 'CustomGroup',
511 'Contribution',
6a488035
TO
512 'ActivityType',
513 'MailingEventConfirm',
514 'Case',
1816ac4c 515 'CaseContact',
6a488035
TO
516 'Contact',
517 'ContactType',
518 'MailingEventResubscribe',
519 'UFGroup',
520 'Activity',
6a488035
TO
521 'Event',
522 'GroupContact',
523 'MembershipPayment',
524 'Participant',
6a488035 525 'LineItem',
6a488035
TO
526 'ContributionPage',
527 'Phone',
faacb3e4 528 'PaymentProcessor',
6a488035 529 'Setting',
b14ce773 530 'MailingContact',
af9b09df 531 'SystemLog',
92915c55 532 //skip this because it doesn't make sense to update logs,
93afbc3a 533 'Logging',
6a488035
TO
534 );
535 if ($sequential === TRUE) {
536 return $entitiesWithout;
537 }
538 $entities = array();
539 foreach ($entitiesWithout as $e) {
540 $entities[] = array(
541 $e,
542 );
543 }
544 return array('pledge');
545 return $entities;
546 }
547
b7d29345 548 /**
b9af4758 549 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
b7d29345 550 */
b9af4758
E
551 public static function toBeSkipped_getlimit() {
552 $entitiesWithout = array(
92915c55
TO
553 'Case',
554 //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
555 'EntityTag',
556 // non-standard api - has inappropriate mandatory fields & doesn't implement limit
557 'Event',
558 // failed 'check that a 5 limit returns 5' - probably is_template field is wrong or something, or could be limit doesn't work right
559 'Extension',
560 // can't handle creating 25
561 'Note',
562 // fails on 5 limit - probably a set up problem
563 'Setting',
564 //a bit of a pseudoapi - keys by domain
b9af4758
E
565 );
566 return $entitiesWithout;
567 }
568
dcf5b21f
EM
569 /**
570 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
571 */
572 public static function toBeSkipped_getSqlOperators() {
573 $entitiesWithout = array(
39b959db
SL
574 //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
575 'Case',
576 // on the todo list!
577 'Contact',
578 // non-standard api - has inappropriate mandatory fields & doesn't implement limit
579 'EntityTag',
580 // can't handle creating 25
581 'Extension',
582 // note has a default get that isn't implemented in createTestObject -meaning you don't 'get' them
583 'Note',
584 //a bit of a pseudoapi - keys by domain
585 'Setting',
dcf5b21f
EM
586 );
587 return $entitiesWithout;
588 }
589
0f583c8f
EM
590 /**
591 * @param $entity
592 * @param $key
593 *
594 * @return array
595 */
9b873358 596 public function getKnownUnworkablesUpdateSingle($entity, $key) {
6a488035
TO
597 // can't update values are values for which updates don't result in the value being changed
598 $knownFailures = array(
03fe1a00
TO
599 'ActionSchedule' => array(
600 'cant_update' => array(
601 'group_id',
602 ),
603 ),
0f583c8f
EM
604 'ActivityContact' => array(
605 'cant_update' => array(
92915c55
TO
606 'activity_id',
607 //we have an FK on activity_id + contact_id + record id so if we don't leave this one distinct we get an FK constraint error
0f583c8f
EM
608 ),
609 ),
6a488035
TO
610 'Address' => array(
611 'cant_update' => array(
39b959db
SL
612 //issues with country id - need to ensure same country
613 'state_province_id',
df4df56b 614 'world_region',
39b959db
SL
615 //creates relationship
616 'master_id',
6a488035 617 ),
df4df56b 618 'cant_return' => ['street_parsing', 'skip_geocode', 'fix_address'],
6a488035 619 ),
6ead217b
E
620 'Batch' => array(
621 'cant_update' => array(
39b959db
SL
622 // believe this field is defined in error
623 'entity_table',
6ead217b
E
624 ),
625 'cant_return' => array(
626 'entity_table',
21dfd5f5 627 ),
6ead217b 628 ),
1753bd71
TO
629 'CaseType' => array(
630 'cant_update' => array(
631 'definition',
21dfd5f5 632 ),
1753bd71 633 ),
1237d8d7 634 'Domain' => ['cant_update' => ['domain_version']],
95520636
TO
635 'MembershipBlock' => array(
636 'cant_update' => array(
637 // The fake/auto-generated values leave us unable to properly cleanup fake data
638 'entity_type',
639 'entity_id',
21dfd5f5 640 ),
95520636 641 ),
df4df56b 642 'MailingJob' => ['cant_update' => ['parent_id']],
7d543448 643 'ContributionSoft' => array(
644 'cant_update' => array(
645 // can't be changed through api
646 'pcp_id',
647 ),
648 ),
7629d5a6 649 'Email' => array(
650 'cant_update' => array(
651 // This is being legitimately manipulated to always have a valid primary - skip.
652 'is_primary',
653 ),
654 ),
3523b615 655 'Navigation' => array(
656 'cant_update' => array(
657 // Weight is deliberately altered when this is changed - skip.
658 'parent_id',
659 ),
660 ),
9859f345 661 'LocationType' => array(
662 'cant_update' => array(
663 // I'm on the fence about whether the test should skip or the behaviour is wrong.
664 // display_name is set to match name if display_name is not provided. It would be more 'normal'
665 // to only calculate a default IF id is not set - but perhaps the current behaviour is kind
666 // of what someone updating the name expects..
667 'name',
668 ),
669 ),
6a488035
TO
670 'Pledge' => array(
671 'cant_update' => array(
672 'pledge_original_installment_amount',
673 'installments',
674 'original_installment_amount',
675 'next_pay_date',
39b959db
SL
676 // can't be changed through API,
677 'amount',
6a488035 678 ),
39b959db
SL
679 // if these are passed in they are retrieved from the wrong table
680 'break_return' => array(
6a488035
TO
681 'honor_contact_id',
682 'cancel_date',
683 'contribution_page_id',
684 'financial_account_id',
685 'financial_type_id',
21dfd5f5 686 'currency',
6a488035 687 ),
39b959db
SL
688 // can't be retrieved from api
689 'cant_return' => array(
690 //due to uniquename missing
691 'honor_type_id',
6a488035
TO
692 'end_date',
693 'modified_date',
694 'acknowledge_date',
695 'start_date',
696 'frequency_day',
697 'currency',
698 'max_reminders',
699 'initial_reminder_day',
700 'additional_reminder_day',
701 'frequency_unit',
702 'pledge_contribution_page_id',
703 'pledge_status_id',
704 'pledge_campaign_id',
b06d9acd 705 'pledge_financial_type_id',
21dfd5f5 706 ),
6a488035
TO
707 ),
708 'PaymentProcessorType' => array(
709 'cant_update' => array(
710 'billing_mode',
711 ),
6c6e6187
TO
712 'break_return' => array(),
713 'cant_return' => array(),
6a488035 714 ),
db232378
EM
715 'PriceFieldValue' => array(
716 'cant_update' => array(
39b959db
SL
717 //won't update as there is no 1 in the same price set
718 'weight',
db232378
EM
719 ),
720 ),
e6e7e540 721 'ReportInstance' => array(
722 // View mode is part of the navigation which is not retrieved by the api.
723 'cant_return' => array('view_mode'),
724 ),
5ba7b9fd
JV
725 'SavedSearch' => array(
726 // I think the fields below are generated based on form_values.
727 'cant_update' => array(
728 'search_custom_id',
729 'where_clause',
730 'select_tables',
731 'where_tables',
dafc75f8
JV
732 ),
733 ),
d47a6f4a
J
734 'StatusPreference' => array(
735 'break_return' => array(
736 'ignore_severity',
737 ),
738 ),
d62b3630 739 'JobLog' => array(
740 // For better or worse triggers override.
741 'break_return' => ['run_time'],
742 'cant_update' => ['run_time'],
743 ),
6a488035 744 );
9b873358 745 if (empty($knownFailures[$entity]) || empty($knownFailures[$entity][$key])) {
6a488035
TO
746 return array();
747 }
748 return $knownFailures[$entity][$key];
749 }
750
be2e0c6a 751 /* ----- testing the _get ----- */
6a488035
TO
752
753 /**
754 * @dataProvider toBeSkipped_get
be2e0c6a 755 * Entities that don't need a get action
1e1fdcf6 756 * @param $Entity
6a488035
TO
757 */
758 public function testNotImplemented_get($Entity) {
759 $result = civicrm_api($Entity, 'Get', array('version' => 3));
ba4a1892 760 $this->assertEquals(1, $result['is_error']);
311873a0 761 // $this->assertContains("API ($Entity, Get) does not exist", $result['error_message']);
6c6e6187 762 $this->assertRegExp('/API (.*) does not exist/', $result['error_message']);
6a488035
TO
763 }
764
765 /**
766 * @dataProvider entities
1e1fdcf6 767 * @param $Entity
6a488035
TO
768 */
769 public function testWithoutParam_get($Entity) {
770 // should get php complaining that a param is missing
2a4cb975
SL
771 try {
772 $result = civicrm_api($Entity, 'Get');
773 $this->fail('Expected an exception. No exception was thrown.');
774 }
de8bb683 775 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
2a4cb975
SL
776 catch (ArgumentCountError $e) {
777 /* ok */
778 }
779 catch (PHPUnit_Framework_Error $e) {
780 /* ok */
781 }
6a488035
TO
782 }
783
784 /**
785 * @dataProvider entities
1e1fdcf6 786 * @param $Entity
6a488035
TO
787 */
788 public function testGetFields($Entity) {
bd6658bd 789 if (in_array($Entity, $this->deprecatedAPI) || $Entity == 'Entity' || $Entity == 'CustomValue') {
6a488035
TO
790 return;
791 }
792
793 $result = civicrm_api($Entity, 'getfields', array('version' => 3));
794 $this->assertTrue(is_array($result['values']), "$Entity ::get fields doesn't return values array in line " . __LINE__);
795 foreach ($result['values'] as $key => $value) {
796 $this->assertTrue(is_array($value), $Entity . "::" . $key . " is not an array in line " . __LINE__);
797 }
798 }
799
800 /**
801 * @dataProvider entities_get
1e1fdcf6 802 * @param $Entity
6a488035
TO
803 */
804 public function testEmptyParam_get($Entity) {
805
806 if (in_array($Entity, $this->toBeImplemented['get'])) {
807 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
808 return;
809 }
810 $result = civicrm_api($Entity, 'Get', array());
ba4a1892 811 $this->assertEquals(1, $result['is_error']);
34e21ce8 812 $this->assertContains("Unknown api version", $result['error_message']);
6a488035 813 }
92915c55 814
6a488035
TO
815 /**
816 * @dataProvider entities_get
1e1fdcf6 817 * @param $Entity
6a488035
TO
818 */
819 public function testEmptyParam_getString($Entity) {
820
821 if (in_array($Entity, $this->toBeImplemented['get'])) {
822 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
823 return;
824 }
d0e1eff2 825 $result = $this->callAPIFailure($Entity, 'Get', 'string');
6a488035
TO
826 $this->assertEquals(2000, $result['error_code']);
827 $this->assertEquals('Input variable `params` is not an array', $result['error_message']);
828 }
92915c55 829
6a488035
TO
830 /**
831 * @dataProvider entities_get
832 * @Xdepends testEmptyParam_get // no need to test the simple if the empty doesn't work/is skipped. doesn't seem to work
1e1fdcf6 833 * @param $Entity
6a488035
TO
834 */
835 public function testSimple_get($Entity) {
836 // $this->markTestSkipped("test gives core error on test server (but not on our locals). Skip until we can get server to pass");
6a488035
TO
837 if (in_array($Entity, $this->toBeImplemented['get'])) {
838 return;
839 }
840 $result = civicrm_api($Entity, 'Get', array('version' => 3));
841 // @TODO: list the get that have mandatory params
842 if ($result['is_error']) {
843 $this->assertContains("Mandatory key(s) missing from params array", $result['error_message']);
844 // either id or contact_id or entity_id is one of the field missing
845 $this->assertContains("id", $result['error_message']);
846 }
847 else {
848 $this->assertEquals(3, $result['version']);
849 $this->assertArrayHasKey('count', $result);
850 $this->assertArrayHasKey('values', $result);
851 }
852 }
853
2fc5f1e7 854 /**
3fb8828b 855 * @dataProvider custom_data_incl_non_std_entities_get
1e1fdcf6 856 * @param $entityName
2fc5f1e7
EM
857 */
858 public function testCustomDataGet($entityName) {
d215c0e1
MH
859 if ($entityName === 'Note') {
860 $this->markTestIncomplete('Note can not be processed here because of a vagary in the note api, it adds entity_table=contact to the get params when id is not present - which makes sense almost always but kills this test');
861 }
a25b46e9 862 $this->quickCleanup(array('civicrm_uf_match'));
39b959db
SL
863 // so subsidiary activities are created
864 $this->createLoggedInUser();
3fb8828b 865
a25b46e9 866 $entitiesWithNamingIssues = [
a25b46e9 867 'SmsProvider' => 'Provider',
868 'AclRole' => 'EntityRole',
869 'MailingEventQueue' => 'Queue',
870 ];
871
872 $usableName = !empty($entitiesWithNamingIssues[$entityName]) ? $entitiesWithNamingIssues[$entityName] : $entityName;
873 $optionName = CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getFullName($usableName));
874
3fb8828b 875 if (!isset(CRM_Core_BAO_CustomQuery::$extendsMap[$entityName])) {
876 $createdValue = $this->callAPISuccess('OptionValue', 'create', [
877 'option_group_id' => 'cg_extend_objects',
a25b46e9 878 'label' => $usableName,
879 'value' => $usableName,
880 'name' => $optionName,
3fb8828b 881 ]);
882 }
1391d5ec 883 // We are not passing 'check_permissions' so the the more limited permissions *should* be
884 // ignored but per CRM-17700 there is a history of custom data applying permissions when it shouldn't.
885 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'view my contact');
d215c0e1
MH
886 $objects = $this->getMockableBAOObjects($entityName, 1);
887
888 // simple custom field
a25b46e9 889 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $usableName . 'Test.php');
2fc5f1e7 890 $customFieldName = 'custom_' . $ids['custom_field_id'];
2fc5f1e7
EM
891 $params = array('id' => $objects[0]->id, 'custom_' . $ids['custom_field_id'] => "custom string");
892 $result = $this->callAPISuccess($entityName, 'create', $params);
a25b46e9 893 $this->assertTrue(isset($result['id']), 'no id on ' . $entityName);
2fc5f1e7
EM
894 $getParams = array('id' => $result['id'], 'return' => array($customFieldName));
895 $check = $this->callAPISuccess($entityName, 'get', $getParams);
a25b46e9 896 $this->assertTrue(!empty($check['values'][$check['id']][$customFieldName]), 'Custom data not present for ' . $entityName);
3fb8828b 897 $this->assertEquals("custom string", $check['values'][$check['id']][$customFieldName], 'Custom data not present for ' . $entityName);
2fc5f1e7
EM
898 $this->customFieldDelete($ids['custom_field_id']);
899 $this->customGroupDelete($ids['custom_group_id']);
d215c0e1
MH
900
901 $ids2 = $this->entityCustomGroupWithSingleStringMultiSelectFieldCreate(__FUNCTION__, $usableName . 'Test.php');
902 $customFieldNameMultiSelect = 'custom_' . $ids2['custom_field_id'];
903 // String custom field, Multi-Select html type
904 foreach ($ids2['custom_field_group_options'] as $option_value => $option_label) {
905 $params = ['id' => $objects[0]->id, 'custom_' . $ids2['custom_field_id'] => $option_value];
906 $result = $this->callAPISuccess($entityName, 'create', $params);
907 $getParams = [$customFieldNameMultiSelect => $option_value, 'return' => [$customFieldNameMultiSelect]];
908 $this->callAPISuccessGetCount($entityName, $getParams, 1);
909 }
910
911 // cleanup
912 $this->customFieldDelete($ids2['custom_field_id']);
913 $this->customGroupDelete($ids2['custom_group_id']);
914
2fc5f1e7 915 $this->callAPISuccess($entityName, 'delete', array('id' => $result['id']));
b2c4e136 916 $this->quickCleanup(array('civicrm_uf_match'));
3fb8828b 917 if (!empty($createdValue)) {
918 $this->callAPISuccess('OptionValue', 'delete', ['id' => $createdValue['id']]);
919 }
2fc5f1e7
EM
920 }
921
6a488035
TO
922 /**
923 * @dataProvider entities_get
1e1fdcf6 924 * @param $Entity
6a488035
TO
925 */
926 public function testAcceptsOnlyID_get($Entity) {
927 // big random number. fun fact: if you multiply it by pi^e, the result is another random number, but bigger ;)
928 $nonExistantID = 30867307034;
b14ce773 929 if (in_array($Entity, $this->toBeImplemented['get'])
92915c55 930 || in_array($Entity, $this->toBeSkipped_getByID())
b14ce773 931 ) {
6a488035
TO
932 return;
933 }
934
935 // FIXME
936 // the below function returns different values and hence an early return
937 // we'll fix this once beta1 is released
938 // return;
939
c679daca 940 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
6a488035
TO
941
942 if ($result['is_error']) {
943 // just to get a clearer message in the log
944 $this->assertEquals("only id should be enough", $result['error_message']);
945 }
946 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
947 $this->assertEquals(0, $result['count']);
948 }
949 }
950
ab24c42d
SL
951 /**
952 * Test getlist works
953 * @dataProvider entities_get
954 * @param $Entity
955 */
956 public function testGetList($Entity) {
957 if (in_array($Entity, $this->toBeImplemented['get'])
958 || in_array($Entity, $this->toBeSkipped_getByID())
959 ) {
960 return;
961 }
962 if (in_array($Entity, ['ActivityType', 'SurveyRespondant'])) {
963 $this->markTestSkipped();
964 }
965 $this->callAPISuccess($Entity, 'getlist', ['label_field' => 'id']);
966 }
967
ab24c42d
SL
968 /**
969 * Test getlist works when entity is lowercase
970 * @dataProvider entities_get
971 * @param $Entity
972 */
973 public function testGetListLowerCaseEntity($Entity) {
974 if (in_array($Entity, $this->toBeImplemented['get'])
975 || in_array($Entity, $this->toBeSkipped_getByID())
976 ) {
977 return;
978 }
979 if (in_array($Entity, ['ActivityType', 'SurveyRespondant'])) {
980 $this->markTestSkipped();
981 }
982 if ($Entity == 'UFGroup') {
983 $Entity = 'ufgroup';
984 }
985 $this->callAPISuccess($Entity, 'getlist', ['label_field' => 'id']);
986 }
987
6a488035 988 /**
eceb18cc 989 * Create two entities and make sure we can fetch them individually by ID.
4a97890c
TO
990 *
991 * @dataProvider entities_get
992 *
993 * limitations include the problem with avoiding loops when creating test objects -
994 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
995 * Currency - only seems to support US
1e1fdcf6 996 * @param $entityName
4a97890c
TO
997 */
998 public function testByID_get($entityName) {
b07a3bf9 999 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
4a97890c
TO
1000 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1001 return;
1002 }
1003
18eee50e 1004 $baos = $this->getMockableBAOObjects($entityName);
1005 list($baoObj1, $baoObj2) = $baos;
4a97890c
TO
1006
1007 // fetch first by ID
6ead217b 1008 $result = $this->callAPISuccess($entityName, 'get', array(
4a97890c
TO
1009 'id' => $baoObj1->id,
1010 ));
6ead217b 1011
4a97890c
TO
1012 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
1013 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
1014 $this->assertEquals(1, count($result['values']));
1015
1016 // fetch second by ID
6ead217b 1017 $result = $this->callAPISuccess($entityName, 'get', array(
4a97890c
TO
1018 'id' => $baoObj2->id,
1019 ));
4a97890c
TO
1020 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1021 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1022 $this->assertEquals(1, count($result['values']));
1023 }
1024
b9af4758
E
1025 /**
1026 * Ensure that the "get" operation accepts limiting the #result records.
1027 *
1028 * TODO Consider making a separate entity list ("entities_getlimit")
1029 * For the moment, the "entities_updatesingle" list should give a good
1030 * sense for which entities support createTestObject
1031 *
1032 * @dataProvider entities_getlimit
8efea814 1033 *
c490a46a 1034 * @param string $entityName
b9af4758 1035 */
00be9182 1036 public function testLimit($entityName) {
39b959db
SL
1037 // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
1038 $cases = array();
b9af4758
E
1039 $cases[] = array(
1040 array('options' => array('limit' => NULL)),
ebddc2d9
EM
1041 30,
1042 'check that a NULL limit returns unlimited',
b9af4758
E
1043 );
1044 $cases[] = array(
1045 array('options' => array('limit' => FALSE)),
ebddc2d9
EM
1046 30,
1047 'check that a FALSE limit returns unlimited',
b9af4758
E
1048 );
1049 $cases[] = array(
1050 array('options' => array('limit' => 0)),
ebddc2d9
EM
1051 30,
1052 'check that a 0 limit returns unlimited',
b9af4758
E
1053 );
1054 $cases[] = array(
1055 array('options' => array('limit' => 5)),
1056 5,
1057 'check that a 5 limit returns 5',
1058 );
1059 $cases[] = array(
1060 array(),
1061 25,
1062 'check that no limit returns 25',
1063 );
1064
6252a38c 1065 $baoString = _civicrm_api3_get_BAO($entityName);
b9af4758
E
1066 if (empty($baoString)) {
1067 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1068 return;
1069 }
1070
1071 // make 30 test items -- 30 > 25 (the default limit)
4038f8ec 1072 $ids = array();
b9af4758 1073 for ($i = 0; $i < 30; $i++) {
8d5544c5 1074 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
4038f8ec 1075 $ids[] = $baoObj->id;
b9af4758
E
1076 }
1077
1078 // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
1079 foreach ($cases as $case) {
ebddc2d9 1080 $this->checkLimitAgainstExpected($entityName, $case[0], $case[1], $case[2]);
a85a667f
EM
1081
1082 //non preferred / legacy syntax
22e263ad 1083 if (isset($case[0]['options']['limit'])) {
ebddc2d9
EM
1084 $this->checkLimitAgainstExpected($entityName, array('rowCount' => $case[0]['options']['limit']), $case[1], $case[2]);
1085 $this->checkLimitAgainstExpected($entityName, array('option_limit' => $case[0]['options']['limit']), $case[1], $case[2]);
1086 $this->checkLimitAgainstExpected($entityName, array('option.limit' => $case[0]['options']['limit']), $case[1], $case[2]);
a85a667f 1087 }
b9af4758 1088 }
4038f8ec
TO
1089 foreach ($ids as $id) {
1090 CRM_Core_DAO::deleteTestObjects($baoString, array('id' => $id));
1091 }
b9af4758
E
1092 }
1093
dcf5b21f
EM
1094 /**
1095 * Ensure that the "get" operation accepts limiting the #result records.
1096 *
1097 * @dataProvider entities_getSqlOperators
1098 *
c490a46a 1099 * @param string $entityName
dcf5b21f 1100 */
00be9182 1101 public function testSqlOperators($entityName) {
8ab09481 1102 $toBeIgnored = array_merge($this->toBeImplemented['get'],
1103 $this->deprecatedAPI,
1104 $this->toBeSkipped_get(TRUE),
1105 $this->toBeSkipped_getByID()
1106 );
1107 if (in_array($entityName, $toBeIgnored)) {
dcf5b21f
EM
1108 return;
1109 }
8ab09481 1110
1111 $baoString = _civicrm_api3_get_BAO($entityName);
1112
dcf5b21f
EM
1113 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0), 'return' => 'id'));
1114 $entities = array_keys($entities['values']);
1115 $totalEntities = count($entities);
1116 if ($totalEntities < 3) {
1117 $ids = array();
1118 for ($i = 0; $i < 3 - $totalEntities; $i++) {
1119 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1120 $ids[] = $baoObj->id;
1121 }
1122 $totalEntities = 3;
1123 }
1124 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0)));
1125 $entities = array_keys($entities['values']);
1126 $this->assertGreaterThan(2, $totalEntities);
1127 $this->callAPISuccess($entityName, 'getsingle', array('id' => array('IN' => array($entities[0]))));
1128 $this->callAPISuccessGetCount($entityName, array('id' => array('NOT IN' => array($entities[0]))), $totalEntities - 1);
1129 $this->callAPISuccessGetCount($entityName, array('id' => array('>' => $entities[0])), $totalEntities - 1);
1130 }
1131
ebddc2d9 1132 /**
eceb18cc 1133 * Check that get fetches an appropriate number of results.
ebddc2d9 1134 *
e16033b4
TO
1135 * @param string $entityName
1136 * Name of entity to test.
dcf5b21f 1137 * @param array $params
e16033b4 1138 * @param int $limit
dcf5b21f 1139 * @param string $message
ebddc2d9 1140 */
00be9182 1141 public function checkLimitAgainstExpected($entityName, $params, $limit, $message) {
ebddc2d9 1142 $result = $this->callAPISuccess($entityName, 'get', $params);
22e263ad 1143 if ($limit == 30) {
ebddc2d9
EM
1144 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1145 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1146 }
1147 else {
1148 $this->assertEquals($limit, $result['count'], $message);
1149 $this->assertEquals($limit, count($result['values']), $message);
1150 }
1151 }
92915c55 1152
afb0ff51
TO
1153 /**
1154 * Create two entities and make sure we can fetch them individually by ID (e.g. using "contact_id=>2"
1155 * or "group_id=>4")
1156 *
1157 * @dataProvider entities_get
1158 *
1159 * limitations include the problem with avoiding loops when creating test objects -
1160 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1161 * Currency - only seems to support US
1e1fdcf6
EM
1162 * @param $entityName
1163 * @throws \PHPUnit_Framework_IncompleteTestError
afb0ff51
TO
1164 */
1165 public function testByIDAlias_get($entityName) {
c4de8b59 1166 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
afb0ff51
TO
1167 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1168 return;
1169 }
1170
6252a38c 1171 $baoString = _civicrm_api3_get_BAO($entityName);
afb0ff51
TO
1172 if (empty($baoString)) {
1173 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1174 return;
1175 }
1176
c4de8b59
TO
1177 $idFieldName = _civicrm_api_get_entity_name_from_camel($entityName) . '_id';
1178
afb0ff51
TO
1179 // create entities
1180 $baoObj1 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
a1a2a83d 1181 $this->assertTrue(is_int($baoObj1->id), 'check first id');
afb0ff51
TO
1182 $this->deletableTestObjects[$baoString][] = $baoObj1->id;
1183 $baoObj2 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
a1a2a83d 1184 $this->assertTrue(is_int($baoObj2->id), 'check second id');
afb0ff51
TO
1185 $this->deletableTestObjects[$baoString][] = $baoObj2->id;
1186
1187 // fetch first by ID
1188 $result = civicrm_api($entityName, 'get', array(
1189 'version' => 3,
c4de8b59 1190 $idFieldName => $baoObj1->id,
afb0ff51
TO
1191 ));
1192 $this->assertAPISuccess($result);
1193 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
1194 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
1195 $this->assertEquals(1, count($result['values']));
1196
1197 // fetch second by ID
1198 $result = civicrm_api($entityName, 'get', array(
1199 'version' => 3,
c4de8b59 1200 $idFieldName => $baoObj2->id,
afb0ff51
TO
1201 ));
1202 $this->assertAPISuccess($result);
1203 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1204 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1205 $this->assertEquals(1, count($result['values']));
1206 }
1207
1208 /**
6a488035 1209 * @dataProvider entities_get
1e1fdcf6 1210 * @param $Entity
6a488035
TO
1211 */
1212 public function testNonExistantID_get($Entity) {
1213 // cf testAcceptsOnlyID_get
1214 $nonExistantID = 30867307034;
1215 if (in_array($Entity, $this->toBeImplemented['get'])) {
1216 return;
1217 }
1218
1219 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
1220
1221 // redundant with testAcceptsOnlyID_get
1222 if ($result['is_error']) {
1223 return;
1224 }
1225
6a488035
TO
1226 $this->assertArrayHasKey('version', $result);
1227 $this->assertEquals(3, $result['version']);
1228 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
1229 $this->assertEquals(0, $result['count']);
1230 }
1231 }
1232
a1a2a83d 1233 /* ---- testing the _create ---- */
6a488035
TO
1234
1235 /**
1236 * @dataProvider toBeSkipped_create
39b959db 1237 * entities that don't need a create action
1e1fdcf6 1238 * @param $Entity
6a488035
TO
1239 */
1240 public function testNotImplemented_create($Entity) {
1241 $result = civicrm_api($Entity, 'Create', array('version' => 3));
ba4a1892 1242 $this->assertEquals(1, $result['is_error']);
311873a0 1243 $this->assertContains(strtolower("API ($Entity, Create) does not exist"), strtolower($result['error_message']));
6a488035
TO
1244 }
1245
1246 /**
1247 * @dataProvider entities
01ee5d42 1248 * @expectedException CiviCRM_API3_Exception
1e1fdcf6 1249 * @param $Entity
6a488035
TO
1250 */
1251 public function testWithoutParam_create($Entity) {
df197a56 1252 if ($Entity === 'Setting') {
1253 $this->markTestSkipped('It seems OK for setting to skip here as it silently sips invalid params');
6a488035 1254 }
91387828
TO
1255 elseif ($Entity === 'Mailing') {
1256 $this->markTestSkipped('It seems OK for "Mailing" to skip here because you can create empty drafts');
1257 }
df197a56 1258 // should create php complaining that a param is missing
1259 civicrm_api3($Entity, 'Create');
6a488035
TO
1260 }
1261
1217e5e6
SL
1262 /**
1263 * @dataProvider entities_create
1264 *
1265 * Check that create doesn't work with an invalid
1266 * @param $Entity
1267 * @throws \PHPUnit_Framework_IncompleteTestError
1268 */
1269 public function testInvalidSort_get($Entity) {
1270 $invalidEntitys = array('ActivityType', 'Setting', 'System');
1271 if (in_array($Entity, $invalidEntitys)) {
7da14720 1272 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently sips invalid params');
1217e5e6
SL
1273 }
1274 $result = $this->callAPIFailure($Entity, 'get', array('options' => array('sort' => 'sleep(1)')));
1275 }
1276
8ff43f60
SL
1277 /**
1278 * @dataProvider entities_create
1279 *
1280 * Check that create doesn't work with an invalid
1281 * @param $Entity
1282 * @throws \PHPUnit_Framework_IncompleteTestError
1283 */
1284 public function testValidSortSingleArrayById_get($Entity) {
1285 $invalidEntitys = array('ActivityType', 'Setting', 'System');
74ec0ddb
SL
1286 $tests = array(
1287 'id' => '_id',
1288 'id desc' => '_id desc',
1289 'id DESC' => '_id DESC',
1290 'id ASC' => '_id ASC',
39b959db
SL
1291 'id asc' => '_id asc',
1292 );
74ec0ddb
SL
1293 foreach ($tests as $test => $expected) {
1294 if (in_array($Entity, $invalidEntitys)) {
1295 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently ignores passed in params');
1296 }
1297 $params = array('sort' => array($test));
1298 $result = _civicrm_api3_get_options_from_params($params, FALSE, $Entity, 'get');
1299 $lowercase_entity = _civicrm_api_get_entity_name_from_camel($Entity);
1300 $this->assertEquals($lowercase_entity . $expected, $result['sort']);
8ff43f60 1301 }
8ff43f60
SL
1302 }
1303
18eee50e 1304 /**
1305 * @dataProvider entities_create
1306 *
1307 * Check that create doesn't work with an invalid
1e1fdcf6
EM
1308 * @param $Entity
1309 * @throws \PHPUnit_Framework_IncompleteTestError
18eee50e 1310 */
1311 public function testInvalidID_create($Entity) {
1312 // turn test off for noew
6ead217b 1313 $this->markTestIncomplete("Entity [ $Entity ] cannot be mocked - no known DAO");
18eee50e 1314 return;
1315 if (in_array($Entity, $this->toBeImplemented['create'])) {
1316 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1317 return;
1318 }
1319 $result = $this->callAPIFailure($Entity, 'Create', array('id' => 999));
1320 }
1321
6a488035
TO
1322 /**
1323 * @dataProvider entities
1324 */
1325 public function testCreateWrongTypeParamTag_create() {
1326 $result = civicrm_api("Tag", 'Create', 'this is not a string');
ba4a1892 1327 $this->assertEquals(1, $result['is_error']);
6a488035
TO
1328 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1329 }
1330
1331 /**
1332 * @dataProvider entities_updatesingle
1333 *
1334 * limitations include the problem with avoiding loops when creating test objects -
1335 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1336 * Currency - only seems to support US
1e1fdcf6 1337 * @param $entityName
6a488035
TO
1338 */
1339 public function testCreateSingleValueAlter($entityName) {
1340 if (in_array($entityName, $this->toBeImplemented['create'])) {
1341 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1342 return;
1343 }
1344
6252a38c 1345 $baoString = _civicrm_api3_get_BAO($entityName);
6a488035
TO
1346 $this->assertNotEmpty($baoString, $entityName);
1347 $this->assertNotEmpty($entityName, $entityName);
905fd0e9 1348 $fieldsGet = $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'get', 'options' => array('get_options' => 'all')));
9b873358 1349 if ($entityName != 'Pledge') {
905fd0e9 1350 $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'create', 'options' => array('get_options' => 'all')));
6a488035
TO
1351 }
1352 $fields = $fields['values'];
39bc176e 1353 $return = array_keys($fieldsGet['values']);
6a488035 1354 $valuesNotToReturn = $this->getKnownUnworkablesUpdateSingle($entityName, 'break_return');
6c6e6187 1355 // these can't be requested as return values
1fd111c8 1356 $entityValuesThatDoNotWork = array_merge(
92915c55
TO
1357 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_update'),
1358 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_return'),
1359 $valuesNotToReturn
1360 );
6a488035 1361
6c6e6187 1362 $return = array_diff($return, $valuesNotToReturn);
6a488035
TO
1363 $baoObj = new CRM_Core_DAO();
1364 $baoObj->createTestObject($baoString, array('currency' => 'USD'), 2, 0);
39bc176e
EM
1365
1366 $getEntities = $this->callAPISuccess($entityName, 'get', array(
6ead217b
E
1367 'sequential' => 1,
1368 'return' => $return,
1369 'options' => array(
1370 'sort' => 'id DESC',
1371 'limit' => 2,
1372 ),
1373 ));
a8699624 1374
6a488035 1375 // lets use first rather than assume only one exists
39bc176e
EM
1376 $entity = $getEntities['values'][0];
1377 $entity2 = $getEntities['values'][1];
1378 $this->deletableTestObjects[$baoString][] = $entity['id'];
1379 $this->deletableTestObjects[$baoString][] = $entity2['id'];
df4df56b 1380 // Skip these fields that we never really expect to update well.
1381 $genericFieldsToSkip = ['currency', 'id', strtolower($entityName) . '_id', 'is_primary'];
6a488035 1382 foreach ($fields as $field => $specs) {
a8699624 1383 $resetFKTo = NULL;
6a488035 1384 $fieldName = $field;
df4df56b 1385
1386 if (in_array($field, $genericFieldsToSkip)
92915c55
TO
1387 || in_array($field, $entityValuesThatDoNotWork)
1388 ) {
6a488035
TO
1389 //@todo id & entity_id are correct but we should fix currency & frequency_day
1390 continue;
1391 }
0ce6d639 1392 $this->assertArrayHasKey('type', $specs, "the _spec function for $entityName field $field does not specify the type");
6a488035
TO
1393 switch ($specs['type']) {
1394 case CRM_Utils_Type::T_DATE:
6a488035
TO
1395 $entity[$fieldName] = '2012-05-20';
1396 break;
6c6e6187 1397
b6afca8f 1398 case CRM_Utils_Type::T_TIMESTAMP:
6a488035
TO
1399 case 12:
1400 $entity[$fieldName] = '2012-05-20 03:05:20';
1401 break;
1402
1403 case CRM_Utils_Type::T_STRING:
1404 case CRM_Utils_Type::T_BLOB:
1405 case CRM_Utils_Type::T_MEDIUMBLOB:
1406 case CRM_Utils_Type::T_TEXT:
1407 case CRM_Utils_Type::T_LONGTEXT:
1408 case CRM_Utils_Type::T_EMAIL:
833478f1
JV
1409 if ($fieldName == 'form_values' && $entityName == 'SavedSearch') {
1410 // This is a hack for the SavedSearch API.
1411 // It expects form_values to be an array.
f1d23743
JV
1412 // If you want to fix this, you should definitely read this forum
1413 // post.
1414 // http://forum.civicrm.org/index.php/topic,33990.0.html
1415 // See also my question on the CiviCRM Stack Exchange:
1416 // https://civicrm.stackexchange.com/questions/3437
e29aa052 1417 $entity[$fieldName] = array('sort_name' => "SortName2");
f1d23743
JV
1418 }
1419 else {
1420 $entity[$fieldName] = substr('New String', 0, CRM_Utils_Array::Value('maxlength', $specs, 100));
7629d5a6 1421 if ($fieldName == 'email') {
1422 $entity[$fieldName] = strtolower($entity[$fieldName]);
1423 }
638c59ed
KJ
1424 // typecast with array to satisfy changes made in CRM-13160
1425 if ($entityName == 'MembershipType' && in_array($fieldName, array(
39b959db
SL
1426 'relationship_type_id',
1427 'relationship_direction',
1428 ))) {
638c59ed
KJ
1429 $entity[$fieldName] = (array) $entity[$fieldName];
1430 }
f1d23743 1431 }
6a488035
TO
1432 break;
1433
1434 case CRM_Utils_Type::T_INT:
1435 // probably created with a 1
a8699624
EM
1436 if ($fieldName == 'weight') {
1437 $entity[$fieldName] = 2;
1438 }
1439 elseif (!empty($specs['FKClassName'])) {
9b873358 1440 if ($specs['FKClassName'] == $baoString) {
6a488035
TO
1441 $entity[$fieldName] = (string) $entity2['id'];
1442 }
92e4c2a5 1443 else {
a8699624 1444 if (!empty($entity[$fieldName])) {
1deccd7e 1445 $resetFKTo = array($fieldName => $entity[$fieldName]);
a8699624 1446 }
1deccd7e 1447 $entity[$fieldName] = (string) empty($entity2[$field]) ? '' : $entity2[$field];
6c6e6187 1448 //todo - there isn't always something set here - & our checking on unset values is limited
6a488035
TO
1449 if (empty($entity[$field])) {
1450 unset($entity[$field]);
1451 }
1452 }
1453 }
a8699624
EM
1454 else {
1455 $entity[$fieldName] = '6';
1456 }
6a488035
TO
1457 break;
1458
6a488035
TO
1459 case CRM_Utils_Type::T_BOOLEAN:
1460 // probably created with a 1
1461 $entity[$fieldName] = '0';
1462 break;
1463
1464 case CRM_Utils_Type::T_FLOAT:
1465 case CRM_Utils_Type::T_MONEY:
edd31a24 1466 $entity[$field] = '22.75';
6a488035
TO
1467 break;
1468
1469 case CRM_Utils_Type::T_URL:
1470 $entity[$field] = 'warm.beer.com';
1471 }
905fd0e9
CW
1472 if (empty($specs['FKClassName']) && (!empty($specs['pseudoconstant']) || !empty($specs['options']))) {
1473 $options = CRM_Utils_Array::value('options', $specs, array());
1474 if (!$options) {
edd31a24 1475 //eg. pdf_format id doesn't ship with any
22e263ad 1476 if (isset($specs['pseudoconstant']['optionGroupName'])) {
92915c55 1477 $optionValue = $this->callAPISuccess('option_value', 'create', array(
39b959db
SL
1478 'option_group_id' => $specs['pseudoconstant']['optionGroupName'],
1479 'label' => 'new option value',
1480 'sequential' => 1,
1481 ));
905fd0e9 1482 $optionValue = $optionValue['values'];
34e9aa63
CW
1483 $keyColumn = CRM_Utils_Array::value('keyColumn', $specs['pseudoconstant'], 'value');
1484 $options[$optionValue[0][$keyColumn]] = 'new option value';
edd31a24 1485 }
3d3ef918 1486 }
905fd0e9
CW
1487 $entity[$field] = array_rand($options);
1488 }
1489 if (!empty($specs['FKClassName']) && !empty($specs['pseudoconstant'])) {
1490 // in the weird situation where a field has both an fk and pseudoconstant defined,
1491 // e.g. campaign_id field, need to flush caches.
1492 // FIXME: Why doesn't creating a campaign clear caches?
1493 civicrm_api3($entityName, 'getfields', array('cache_clear' => 1));
6a488035
TO
1494 }
1495 $updateParams = array(
6a488035 1496 'id' => $entity['id'],
6ead217b 1497 $field => isset($entity[$field]) ? $entity[$field] : NULL,
6a488035 1498 );
22e263ad 1499 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
deb562a8
EM
1500 //api has special handling on these 2 fields for backward compatibility reasons
1501 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1502 }
258c92c6
SL
1503 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1504 //api has special handling on these 2 fields for backward compatibility reasons
1505 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1506 }
048fa8d6
MW
1507 if (isset($updateParams['image'])) {
1508 // Image field is passed through simplifyURL function so may be different, do the same here for comparison
1509 $entity['image'] = CRM_Utils_String::simplifyURL($updateParams['image'], TRUE);
1510 }
1511 if (isset($updateParams['thumbnail'])) {
1512 // Thumbnail field is passed through simplifyURL function so may be different, do the same here for comparison
1513 $entity['thumbnail'] = CRM_Utils_String::simplifyURL($updateParams['thumbnail'], TRUE);
1514 }
6a488035 1515
f27f2724 1516 $update = $this->callAPISuccess($entityName, 'create', $updateParams);
6a488035
TO
1517 $checkParams = array(
1518 'id' => $entity['id'],
6a488035
TO
1519 'sequential' => 1,
1520 'return' => $return,
1521 'options' => array(
1522 'sort' => 'id DESC',
1523 'limit' => 2,
1524 ),
1525 );
1526
f27f2724 1527 $checkEntity = $this->callAPISuccess($entityName, 'getsingle', $checkParams);
a8496135 1528 if (!empty($specs['serialize']) && !is_array($checkEntity[$field])) {
1529 // Put into serialized format for comparison if 'get' has not returned serialized.
1530 $entity[$field] = CRM_Core_DAO::serializeField($checkEntity[$field], $specs['serialize']);
1531 }
0298287b 1532
92915c55 1533 $this->assertAPIArrayComparison($entity, $checkEntity, array(), "checking if $fieldName was correctly updated\n" . print_r(array(
39b959db
SL
1534 'update-params' => $updateParams,
1535 'update-result' => $update,
1536 'getsingle-params' => $checkParams,
1537 'getsingle-result' => $checkEntity,
1538 'expected entity' => $entity,
1539 ), TRUE));
a8699624
EM
1540 if ($resetFKTo) {
1541 //reset the foreign key fields because otherwise our cleanup routine fails & some other unexpected stuff can kick in
1542 $entity = array_merge($entity, $resetFKTo);
1543 $updateParams = array_merge($updateParams, $resetFKTo);
1544 $this->callAPISuccess($entityName, 'create', $updateParams);
22e263ad 1545 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
a8699624
EM
1546 //api has special handling on these 2 fields for backward compatibility reasons
1547 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1548 }
258c92c6
SL
1549 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1550 //api has special handling on these 2 fields for backward compatibility reasons
1551 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1552 }
a8699624 1553 }
6a488035 1554 }
6a488035
TO
1555 }
1556
be2e0c6a 1557 /* ---- testing the _getFields ---- */
6a488035 1558
be2e0c6a 1559 /* ---- testing the _delete ---- */
6a488035
TO
1560
1561 /**
1562 * @dataProvider toBeSkipped_delete
39b959db 1563 * entities that don't need a delete action
1e1fdcf6 1564 * @param $Entity
6a488035
TO
1565 */
1566 public function testNotImplemented_delete($Entity) {
1567 $nonExistantID = 151416349;
1568 $result = civicrm_api($Entity, 'Delete', array('version' => 3, 'id' => $nonExistantID));
ba4a1892 1569 $this->assertEquals(1, $result['is_error']);
311873a0 1570 $this->assertContains(strtolower("API ($Entity, Delete) does not exist"), strtolower($result['error_message']));
6a488035
TO
1571 }
1572
1573 /**
1574 * @dataProvider entities
1e1fdcf6 1575 * @param $Entity
6a488035
TO
1576 */
1577 public function testWithoutParam_delete($Entity) {
1578 // should delete php complaining that a param is missing
2a4cb975
SL
1579 try {
1580 $result = civicrm_api($Entity, 'Delete');
1581 $this->fail('Expected an exception. No exception was thrown.');
1582 }
de8bb683 1583 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
2a4cb975
SL
1584 catch (ArgumentCountError $e) {
1585 /* ok */
1586 }
1587 catch (PHPUnit_Framework_Error $e) {
1588 /* ok */
1589 }
6a488035
TO
1590 }
1591
1592 /**
1593 * @dataProvider entities_delete
1e1fdcf6 1594 * @param $Entity
6a488035
TO
1595 */
1596 public function testEmptyParam_delete($Entity) {
1597 if (in_array($Entity, $this->toBeImplemented['delete'])) {
1598 // $this->markTestIncomplete("civicrm_api3_{$Entity}_delete to be implemented");
1599 return;
1600 }
1601 $result = civicrm_api($Entity, 'Delete', array());
ba4a1892 1602 $this->assertEquals(1, $result['is_error']);
34e21ce8 1603 $this->assertContains("Unknown api version", $result['error_message']);
6a488035 1604 }
92915c55 1605
18eee50e 1606 /**
1607 * @dataProvider entities_delete
1e1fdcf6
EM
1608 * @param $Entity
1609 * @throws \PHPUnit_Framework_IncompleteTestError
18eee50e 1610 */
1611 public function testInvalidID_delete($Entity) {
a25b46e9 1612 $result = $this->callAPIFailure($Entity, 'Delete', array('id' => 999999));
18eee50e 1613 }
92915c55 1614
6a488035
TO
1615 /**
1616 * @dataProvider entities
1617 */
1618 public function testDeleteWrongTypeParamTag_delete() {
1619 $result = civicrm_api("Tag", 'Delete', 'this is not a string');
ba4a1892 1620 $this->assertEquals(1, $result['is_error']);
6a488035
TO
1621 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1622 }
1623
18eee50e 1624 /**
1625 * Create two entities and make sure delete action only deletes one!
1626 *
1627 * @dataProvider entities_delete
1628 *
1629 * limitations include the problem with avoiding loops when creating test objects -
1630 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1631 * Currency - only seems to support US
1e1fdcf6
EM
1632 * @param $entityName
1633 * @throws \PHPUnit_Framework_IncompleteTestError
18eee50e 1634 */
1635 public function testByID_delete($entityName) {
1636 // turn test off for noew
1637 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1638 return;
1639
1640 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
1641 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1642 return;
1643 }
1644 $startCount = $this->callAPISuccess($entityName, 'getcount', array());
1645 $createcount = 2;
1646 $baos = $this->getMockableBAOObjects($entityName, $createcount);
1647 list($baoObj1, $baoObj2) = $baos;
1648
1649 // make sure exactly 2 exist
1650 $result = $this->callAPISuccess($entityName, 'getcount', array(),
1651 $createcount + $startCount
1652 );
1653
1654 $this->callAPISuccess($entityName, 'delete', array('id' => $baoObj2->id));
1655 //make sure 1 less exists now
1656 $result = $this->callAPISuccess($entityName, 'getcount', array(),
6c6e6187 1657 ($createcount + $startCount) - 1
18eee50e 1658 );
1659
1660 //make sure id #1 exists
1661 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj1->id),
1662 1
1663 );
1664 //make sure id #2 desn't exist
1665 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj2->id),
1666 0
1667 );
1668 }
1669
32dafeec
EM
1670 /**
1671 * Create two entities and make sure delete action only deletes one!
1672 *
1673 * @dataProvider entities_getfields
1e1fdcf6 1674 * @param $entity
32dafeec
EM
1675 */
1676 public function testGetfieldsHasTitle($entity) {
1677 $entities = $this->getEntitiesSupportingCustomFields();
1678 if (in_array($entity, $entities)) {
1679 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $entity . 'Test.php');
1680 }
6c6e6187 1681 $actions = $this->callAPISuccess($entity, 'getactions', array());
4c41ecb2 1682 foreach ($actions['values'] as $action) {
92915c55 1683 if (substr($action, -7) == '_create' || substr($action, -4) == '_get' || substr($action, -7) == '_delete') {
7385c761
EM
1684 //getactions can't distinguish between contribution_page.create & contribution_page.create
1685 continue;
1686 }
4c41ecb2
EM
1687 $fields = $this->callAPISuccess($entity, 'getfields', array('action' => $action));
1688 if (!empty($ids) && in_array($action, array('create', 'get'))) {
7385c761 1689 $this->assertArrayHasKey('custom_' . $ids['custom_field_id'], $fields['values']);
4c41ecb2 1690 }
32dafeec 1691
4c41ecb2
EM
1692 foreach ($fields['values'] as $fieldName => $fieldSpec) {
1693 $this->assertArrayHasKey('title', $fieldSpec, "no title for $entity - $fieldName on action $action");
1694 $this->assertNotEmpty($fieldSpec['title'], "empty title for $entity - $fieldName");
1695 }
32dafeec 1696 }
32dafeec
EM
1697 if (!empty($ids)) {
1698 $this->customFieldDelete($ids['custom_field_id']);
1699 $this->customGroupDelete($ids['custom_group_id']);
1700 }
1701 }
1702
1703 /**
1704 * @return array
1705 */
1706 public function getEntitiesSupportingCustomFields() {
1707 $entities = self::custom_data_entities_get();
1708 $returnEntities = array();
1709 foreach ($entities as $entityArray) {
1710 $returnEntities[] = $entityArray[0];
1711 }
1712 return $returnEntities;
1713 }
92915c55 1714
8efea814 1715 /**
100fef9d 1716 * @param string $entityName
8efea814
EM
1717 * @param int $count
1718 *
8efea814
EM
1719 * @return array
1720 */
1721 private function getMockableBAOObjects($entityName, $count = 2) {
6252a38c 1722 $baoString = _civicrm_api3_get_BAO($entityName);
18eee50e 1723 if (empty($baoString)) {
1724 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
fcb93467 1725 return array();
18eee50e 1726 }
1727 $baos = array();
6ead217b 1728 $i = 0;
22e263ad 1729 while ($i < $count) {
6c6e6187 1730 // create entities
18eee50e 1731 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
a1a2a83d 1732 $this->assertTrue(is_int($baoObj->id), 'check first id');
18eee50e 1733 $this->deletableTestObjects[$baoString][] = $baoObj->id;
1734 $baos[] = $baoObj;
2241036a 1735 $i++;
18eee50e 1736 }
1737 return $baos;
1738 }
1739
6a488035 1740 /**
fe482240 1741 * Verify that HTML metacharacters provided as inputs appear consistently.
6a488035
TO
1742 * as outputs.
1743 *
1744 * At time of writing, the encoding scheme requires (for example) that an
1745 * event title be partially-HTML-escaped before writing to DB. To provide
1746 * consistency, the API must perform extra encoding and decoding on some
1747 * fields.
1748 *
1749 * In this example, the event 'title' is subject to encoding, but the
1750 * event 'description' is not.
1751 */
1752 public function testEncodeDecodeConsistency() {
1753 // Create example
1754 $createResult = civicrm_api('Event', 'Create', array(
1755 'version' => 3,
1756 'title' => 'CiviCRM <> TheRest',
1757 'description' => 'TheRest <> CiviCRM',
1758 'event_type_id' => 1,
1759 'is_public' => 1,
1760 'start_date' => 20081021,
1761 ));
1762 $this->assertAPISuccess($createResult);
1763 $eventId = $createResult['id'];
1764 $this->assertEquals('CiviCRM <> TheRest', $createResult['values'][$eventId]['title']);
1765 $this->assertEquals('TheRest <> CiviCRM', $createResult['values'][$eventId]['description']);
1766
1767 // Verify "get" handles decoding in result value
1768 $getByIdResult = civicrm_api('Event', 'Get', array(
1769 'version' => 3,
1770 'id' => $eventId,
1771 ));
1772 $this->assertAPISuccess($getByIdResult);
1773 $this->assertEquals('CiviCRM <> TheRest', $getByIdResult['values'][$eventId]['title']);
1774 $this->assertEquals('TheRest <> CiviCRM', $getByIdResult['values'][$eventId]['description']);
1775
1776 // Verify "get" handles encoding in search value
1777 $getByTitleResult = civicrm_api('Event', 'Get', array(
1778 'version' => 3,
1779 'title' => 'CiviCRM <> TheRest',
1780 ));
1781 $this->assertAPISuccess($getByTitleResult);
1782 $this->assertEquals('CiviCRM <> TheRest', $getByTitleResult['values'][$eventId]['title']);
1783 $this->assertEquals('TheRest <> CiviCRM', $getByTitleResult['values'][$eventId]['description']);
1784
1785 // Verify that "getSingle" handles decoding
6ead217b 1786 $getSingleResult = $this->callAPISuccess('Event', 'GetSingle', array(
6a488035
TO
1787 'id' => $eventId,
1788 ));
1789
6a488035
TO
1790 $this->assertEquals('CiviCRM <> TheRest', $getSingleResult['title']);
1791 $this->assertEquals('TheRest <> CiviCRM', $getSingleResult['description']);
1792
1793 // Verify that chaining handles decoding
6ead217b 1794 $chainResult = $this->callAPISuccess('Event', 'Get', array(
6a488035 1795 'id' => $eventId,
6c6e6187 1796 'api.event.get' => array(),
6a488035
TO
1797 ));
1798 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['title']);
1799 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['description']);
1800 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['api.event.get']['values'][0]['title']);
1801 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['api.event.get']['values'][0]['description']);
1802
1803 // Verify that "setvalue" handles encoding for updates
1804 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1805 'version' => 3,
1806 'id' => $eventId,
1807 'field' => 'title',
1808 'value' => 'setValueTitle: CiviCRM <> TheRest',
1809 ));
1810 $this->assertAPISuccess($setValueTitleResult);
1811 $this->assertEquals('setValueTitle: CiviCRM <> TheRest', $setValueTitleResult['values']['title']);
1812 $setValueDescriptionResult = civicrm_api('Event', 'setvalue', array(
1813 'version' => 3,
1814 'id' => $eventId,
1815 'field' => 'description',
1816 'value' => 'setValueDescription: TheRest <> CiviCRM',
1817 ));
bc2bc079 1818 //$this->assertTrue((bool)$setValueDescriptionResult['is_error']); // not supported by setValue
1819 $this->assertEquals('setValueDescription: TheRest <> CiviCRM', $setValueDescriptionResult['values']['description']);
6c6e6187 1820 }
6a488035
TO
1821
1822 /**
1823 * Verify that write operations (create/update) use partial HTML-encoding
1824 *
1825 * In this example, the event 'title' is subject to encoding, but the
1826 * event 'description' is not.
1827 */
1828 public function testEncodeWrite() {
1829 // Create example
1830 $createResult = civicrm_api('Event', 'Create', array(
1831 'version' => 3,
1832 'title' => 'createNew: CiviCRM <> TheRest',
1833 'description' => 'createNew: TheRest <> CiviCRM',
1834 'event_type_id' => 1,
1835 'is_public' => 1,
1836 'start_date' => 20081021,
1837 ));
1838 $this->assertAPISuccess($createResult);
1839 $eventId = $createResult['id'];
1840 $this->assertDBQuery('createNew: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1841 1 => array($eventId, 'Integer'),
6a488035
TO
1842 ));
1843 $this->assertDBQuery('createNew: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1844 1 => array($eventId, 'Integer'),
6a488035
TO
1845 ));
1846
1847 // Verify that "create" handles encoding for updates
1848 $createWithIdResult = civicrm_api('Event', 'Create', array(
1849 'version' => 3,
1850 'id' => $eventId,
1851 'title' => 'createWithId: CiviCRM <> TheRest',
1852 'description' => 'createWithId: TheRest <> CiviCRM',
1853 ));
1854 $this->assertAPISuccess($createWithIdResult);
1855 $this->assertDBQuery('createWithId: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1856 1 => array($eventId, 'Integer'),
6a488035
TO
1857 ));
1858 $this->assertDBQuery('createWithId: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1859 1 => array($eventId, 'Integer'),
6a488035
TO
1860 ));
1861
1862 // Verify that "setvalue" handles encoding for updates
1863 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1864 'version' => 3,
1865 'id' => $eventId,
1866 'field' => 'title',
1867 'value' => 'setValueTitle: CiviCRM <> TheRest',
1868 ));
1869 $this->assertAPISuccess($setValueTitleResult);
1870 $this->assertDBQuery('setValueTitle: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1871 1 => array($eventId, 'Integer'),
6a488035
TO
1872 ));
1873 $setValueDescriptionResult = civicrm_api('Event', 'setvalue', array(
1874 'version' => 3,
1875 'id' => $eventId,
1876 'field' => 'description',
1877 'value' => 'setValueDescription: TheRest <> CiviCRM',
1878 ));
bc2bc079 1879 //$this->assertTrue((bool)$setValueDescriptionResult['is_error']); // not supported by setValue
1880 $this->assertAPISuccess($setValueDescriptionResult);
1881 $this->assertDBQuery('setValueDescription: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
21dfd5f5 1882 1 => array($eventId, 'Integer'),
bc2bc079 1883 ));
6a488035 1884 }
96025800 1885
6a488035 1886}