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