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