Merge pull request #13926 from pradpnayak/NoticeErrorProfile
[civicrm-core.git] / tests / phpunit / api / v3 / SyntaxConformanceTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
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 'SurveyRespondant',
100 'OptionGroup',
101 'MailingRecipients',
102 'UFMatch',
103 'CustomSearch',
104 'Extension',
105 'ReportTemplate',
106 'System',
107 'User',
108 'Payment',
109 'Order',
110 'SavedSearch', //work fine in local
111 'Logging',
112 );
113 $this->toBeImplemented['delete'] = array(
114 'Cxn',
115 'CxnApp',
116 'MembershipPayment',
117 'OptionGroup',
118 'SurveyRespondant',
119 'UFJoin',
120 'UFMatch',
121 'Extension',
122 'System',
123 'Payment',
124 'Order',
125 );
126 $this->onlyIDNonZeroCount['get'] = array(
127 'ActivityType',
128 'Entity',
129 'Domain',
130 'Setting',
131 'User',
132 );
133 $this->deprecatedAPI = array('Location', 'ActivityType', 'SurveyRespondant');
134 $this->deletableTestObjects = array();
135 }
136
137 public function tearDown() {
138 foreach ($this->deletableTestObjects as $entityName => $entities) {
139 foreach ($entities as $entityID) {
140 CRM_Core_DAO::deleteTestObjects($entityName, array('id' => $entityID));
141 }
142 }
143 }
144
145 /**
146 * Generate list of all entities.
147 *
148 * @param array $skip
149 * Entities to skip.
150 *
151 * @return array
152 */
153 public static function entities($skip = array()) {
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).
158
159 $tmp = civicrm_api('Entity', 'Get', array('version' => 3));
160 if (getenv('SYNTAX_CONFORMANCE_ENTITIES')) {
161 $tmp = array(
162 'values' => explode(' ', getenv('SYNTAX_CONFORMANCE_ENTITIES')),
163 );
164 }
165
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
177 /**
178 * Get list of entities for get test.
179 *
180 * @return array
181 */
182 public static function entities_get() {
183 // all the entities, beside the ones flagged
184 return static::entities(static::toBeSkipped_get(TRUE));
185 }
186
187 /**
188 * Get entities for create tests.
189 *
190 * @return array
191 */
192 public static function entities_create() {
193 return static::entities(static::toBeSkipped_create(TRUE));
194 }
195
196 /**
197 * @return array
198 */
199 public static function entities_updatesingle() {
200 return static::entities(static::toBeSkipped_updatesingle(TRUE));
201 }
202
203 /**
204 * @return array
205 */
206 public static function entities_getlimit() {
207 return static::entities(static::toBeSkipped_getlimit());
208 }
209
210 /**
211 * Generate list of entities that can be retrieved using SQL operator syntax.
212 *
213 * @return array
214 */
215 public static function entities_getSqlOperators() {
216 return static::entities(static::toBeSkipped_getSqlOperators());
217 }
218
219 /**
220 * @return array
221 */
222 public static function entities_delete() {
223 return static::entities(static::toBeSkipped_delete(TRUE));
224 }
225
226 /**
227 * @return array
228 */
229 public static function entities_getfields() {
230 return static::entities(static::toBeSkipped_getfields(TRUE));
231 }
232
233 /**
234 * @return array
235 */
236 public static function custom_data_entities_get() {
237 return static::custom_data_entities();
238 }
239
240 /**
241 * @return array
242 */
243 public static function custom_data_entities() {
244 $entities = CRM_Core_BAO_CustomQuery::$extendsMap;
245 $enabledComponents = Civi::settings()->get('enable_components');
246 $customDataEntities = array();
247 $invalidEntities = array('Individual', 'Organization', 'Household');
248 $entitiesToFix = array('Case', 'Relationship');
249 foreach ($entities as $entityName => $entity) {
250 if (!in_array($entityName, $invalidEntities)
251 && !in_array($entityName, $entitiesToFix)
252 ) {
253 if (!empty(self::$componentMap[$entityName]) && empty($enabledComponents[self::$componentMap[$entityName]])) {
254 CRM_Core_BAO_ConfigSetting::enableComponent(self::$componentMap[$entityName]);
255 }
256 $customDataEntities[] = array($entityName);
257 }
258 }
259 return $customDataEntities;
260 }
261
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() {
268 return static::entities(static::toBeSkipped_custom_data_creatable(TRUE));
269 }
270
271 /**
272 * Get entities to be skipped on get tests.
273 *
274 * @param bool $sequential
275 *
276 * @return array
277 */
278 public static function toBeSkipped_get($sequential = FALSE) {
279 $entitiesWithoutGet = array(
280 'MailingEventSubscribe',
281 'MailingEventConfirm',
282 'MailingEventResubscribe',
283 'MailingEventUnsubscribe',
284 'Location',
285 );
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 }
295
296 /**
297 * Get entities to be skipped for get call.
298 *
299 * Mailing Contact Just doesn't support id. We have always insisted on finding a way to
300 * support id in API but in this case the underlying tables are crying out for a restructure
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.
305 *
306 * Entity doesn't support get By ID because it simply gives the result of string Entites in CiviCRM
307 *
308 * @param bool $sequential
309 *
310 * @return array
311 * Entities that cannot be retrieved by ID
312 */
313 public static function toBeSkipped_getByID($sequential = FALSE) {
314 return array('MailingContact', 'User', 'Attachment', 'Entity');
315 }
316
317 /**
318 * @param bool $sequential
319 *
320 * @return array
321 */
322 public static function toBeSkipped_create($sequential = FALSE) {
323 $entitiesWithoutCreate = array('Constant', 'Entity', 'Location', 'Profile', 'MailingRecipients');
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
334 /**
335 * @param bool $sequential
336 *
337 * @return array
338 */
339 public static function toBeSkipped_delete($sequential = FALSE) {
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',
353 'Setting',
354 'User',
355 'Logging',
356 );
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 }
366
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',
387 'MembershipPayment',
388 'SavedSearch',
389 'UFJoin',
390 'UFField',
391 'PriceFieldValue',
392 'GroupContact',
393 'EntityTag',
394 'PledgePayment',
395 'Relationship',
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
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) {
438 $entitiesWithMetadataNotYetFixed = array('ReportTemplate', 'CustomSearch');
439 if ($sequential === TRUE) {
440 return $entitiesWithMetadataNotYetFixed;
441 }
442 $entities = array();
443 foreach ($entitiesWithMetadataNotYetFixed as $e) {
444 $entities[] = array($e);
445 }
446 return $entities;
447 }
448
449 /**
450 * Generate list of entities to test for get by id functions.
451 * @param bool $sequential
452 * @return array
453 * Entities to be skipped
454 */
455 public static function toBeSkipped_automock($sequential = FALSE) {
456 $entitiesWithoutGet = array(
457 'MailingContact',
458 'EntityTag',
459 'Participant',
460 'Setting',
461 'SurveyRespondant',
462 'MailingRecipients',
463 'CustomSearch',
464 'Extension',
465 'ReportTemplate',
466 'System',
467 'Logging',
468 );
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
479 /**
480 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
481 * @param bool $sequential
482 * @return array
483 */
484 public static function toBeSkipped_updatesingle($sequential = FALSE) {
485 $entitiesWithout = array(
486 'Attachment',
487 // pseudo-entity; testUpdateSingleValueAlter doesn't introspect properly on it. Multiple magic fields
488 'Mailing',
489 'MailingEventUnsubscribe',
490 'MailingEventSubscribe',
491 'Constant',
492 'Entity',
493 'Location',
494 'Profile',
495 'CustomValue',
496 'UFJoin',
497 'UFField',
498 'Relationship',
499 'RelationshipType',
500 'Note',
501 'Membership',
502 'Group',
503 'File',
504 'EntityTag',
505 'CustomField',
506 'CustomGroup',
507 'Contribution',
508 'ActivityType',
509 'MailingEventConfirm',
510 'Case',
511 'CaseContact',
512 'Contact',
513 'ContactType',
514 'MailingEventResubscribe',
515 'UFGroup',
516 'Activity',
517 'Event',
518 'GroupContact',
519 'MembershipPayment',
520 'Participant',
521 'LineItem',
522 'ContributionPage',
523 'Phone',
524 'PaymentProcessor',
525 'Setting',
526 'MailingContact',
527 'SystemLog',
528 //skip this because it doesn't make sense to update logs,
529 'Logging',
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
544 /**
545 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
546 */
547 public static function toBeSkipped_getlimit() {
548 $entitiesWithout = array(
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
561 );
562 return $entitiesWithout;
563 }
564
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(
570 'Case', //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
571 'Contact', // on the todo list!
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
575 'Setting', //a bit of a pseudoapi - keys by domain
576 );
577 return $entitiesWithout;
578 }
579
580 /**
581 * @param $entity
582 * @param $key
583 *
584 * @return array
585 */
586 public function getKnownUnworkablesUpdateSingle($entity, $key) {
587 // can't update values are values for which updates don't result in the value being changed
588 $knownFailures = array(
589 'ActionSchedule' => array(
590 'cant_update' => array(
591 'group_id',
592 ),
593 ),
594 'ActivityContact' => array(
595 'cant_update' => array(
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
598 ),
599 ),
600 'Address' => array(
601 'cant_update' => array(
602 'state_province_id', //issues with country id - need to ensure same country
603 'world_region',
604 'master_id', //creates relationship
605 ),
606 'cant_return' => ['street_parsing', 'skip_geocode', 'fix_address'],
607 ),
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',
614 ),
615 ),
616 'CaseType' => array(
617 'cant_update' => array(
618 'definition',
619 ),
620 ),
621 'Domain' => ['cant_update' => ['domain_version']],
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',
627 ),
628 ),
629 'MailingJob' => ['cant_update' => ['parent_id']],
630 'ContributionSoft' => array(
631 'cant_update' => array(
632 // can't be changed through api
633 'pcp_id',
634 ),
635 ),
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 ),
642 'Navigation' => array(
643 'cant_update' => array(
644 // Weight is deliberately altered when this is changed - skip.
645 'parent_id',
646 ),
647 ),
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 ),
657 'Pledge' => array(
658 'cant_update' => array(
659 'pledge_original_installment_amount',
660 'installments',
661 'original_installment_amount',
662 'next_pay_date',
663 'amount', // can't be changed through API,
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',
671 'currency',
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',
688 'pledge_financial_type_id',
689 ),
690 ),
691 'PaymentProcessorType' => array(
692 'cant_update' => array(
693 'billing_mode',
694 ),
695 'break_return' => array(),
696 'cant_return' => array(),
697 ),
698 'PriceFieldValue' => array(
699 'cant_update' => array(
700 'weight', //won't update as there is no 1 in the same price set
701 ),
702 ),
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 ),
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',
714 ),
715 ),
716 'StatusPreference' => array(
717 'break_return' => array(
718 'ignore_severity',
719 ),
720 ),
721 'JobLog' => array(
722 // For better or worse triggers override.
723 'break_return' => ['run_time'],
724 'cant_update' => ['run_time'],
725 ),
726 );
727 if (empty($knownFailures[$entity]) || empty($knownFailures[$entity][$key])) {
728 return array();
729 }
730 return $knownFailures[$entity][$key];
731 }
732
733 /* ----- testing the _get ----- */
734
735 /**
736 * @dataProvider toBeSkipped_get
737 * Entities that don't need a get action
738 * @param $Entity
739 */
740 public function testNotImplemented_get($Entity) {
741 $result = civicrm_api($Entity, 'Get', array('version' => 3));
742 $this->assertEquals(1, $result['is_error']);
743 // $this->assertContains("API ($Entity, Get) does not exist", $result['error_message']);
744 $this->assertRegExp('/API (.*) does not exist/', $result['error_message']);
745 }
746
747 /**
748 * @dataProvider entities
749 * @param $Entity
750 */
751 public function testWithoutParam_get($Entity) {
752 // should get php complaining that a param is missing
753 try {
754 $result = civicrm_api($Entity, 'Get');
755 $this->fail('Expected an exception. No exception was thrown.');
756 }
757 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
758 catch (ArgumentCountError $e) {
759 /* ok */
760 }
761 catch (PHPUnit_Framework_Error $e) {
762 /* ok */
763 }
764 }
765
766 /**
767 * @dataProvider entities
768 * @param $Entity
769 */
770 public function testGetFields($Entity) {
771 if (in_array($Entity, $this->deprecatedAPI) || $Entity == 'Entity' || $Entity == 'CustomValue') {
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
784 * @param $Entity
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());
793 $this->assertEquals(1, $result['is_error']);
794 $this->assertContains("Unknown api version", $result['error_message']);
795 }
796
797 /**
798 * @dataProvider entities_get
799 * @param $Entity
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 }
807 $result = $this->callAPIFailure($Entity, 'Get', 'string');
808 $this->assertEquals(2000, $result['error_code']);
809 $this->assertEquals('Input variable `params` is not an array', $result['error_message']);
810 }
811
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
815 * @param $Entity
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");
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
836 /**
837 * @dataProvider custom_data_incl_non_std_entities_get
838 * @param $entityName
839 */
840 public function testCustomDataGet($entityName) {
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 }
844 $this->quickCleanup(array('civicrm_uf_match'));
845 $this->createLoggedInUser();// so subsidiary activities are created
846
847 $entitiesWithNamingIssues = [
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
856 if (!isset(CRM_Core_BAO_CustomQuery::$extendsMap[$entityName])) {
857 $createdValue = $this->callAPISuccess('OptionValue', 'create', [
858 'option_group_id' => 'cg_extend_objects',
859 'label' => $usableName,
860 'value' => $usableName,
861 'name' => $optionName,
862 ]);
863 }
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');
867 $objects = $this->getMockableBAOObjects($entityName, 1);
868
869 // simple custom field
870 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $usableName . 'Test.php');
871 $customFieldName = 'custom_' . $ids['custom_field_id'];
872 $params = array('id' => $objects[0]->id, 'custom_' . $ids['custom_field_id'] => "custom string");
873 $result = $this->callAPISuccess($entityName, 'create', $params);
874 $this->assertTrue(isset($result['id']), 'no id on ' . $entityName);
875 $getParams = array('id' => $result['id'], 'return' => array($customFieldName));
876 $check = $this->callAPISuccess($entityName, 'get', $getParams);
877 $this->assertTrue(!empty($check['values'][$check['id']][$customFieldName]), 'Custom data not present for ' . $entityName);
878 $this->assertEquals("custom string", $check['values'][$check['id']][$customFieldName], 'Custom data not present for ' . $entityName);
879 $this->customFieldDelete($ids['custom_field_id']);
880 $this->customGroupDelete($ids['custom_group_id']);
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
896 $this->callAPISuccess($entityName, 'delete', array('id' => $result['id']));
897 $this->quickCleanup(array('civicrm_uf_match'));
898 if (!empty($createdValue)) {
899 $this->callAPISuccess('OptionValue', 'delete', ['id' => $createdValue['id']]);
900 }
901 }
902
903 /**
904 * @dataProvider entities_get
905 * @param $Entity
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;
910 if (in_array($Entity, $this->toBeImplemented['get'])
911 || in_array($Entity, $this->toBeSkipped_getByID())
912 ) {
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
921 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
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 /**
933 * Test getlist works
934 * @dataProvider entities_get
935 * @param $Entity
936 */
937 public function testGetList($Entity) {
938 if (in_array($Entity, $this->toBeImplemented['get'])
939 || in_array($Entity, $this->toBeSkipped_getByID())
940 ) {
941 return;
942 }
943 if (in_array($Entity, ['ActivityType', 'SurveyRespondant'])) {
944 $this->markTestSkipped();
945 }
946 $this->callAPISuccess($Entity, 'getlist', ['label_field' => 'id']);
947 }
948
949
950 /**
951 * Test getlist works when entity is lowercase
952 * @dataProvider entities_get
953 * @param $Entity
954 */
955 public function testGetListLowerCaseEntity($Entity) {
956 if (in_array($Entity, $this->toBeImplemented['get'])
957 || in_array($Entity, $this->toBeSkipped_getByID())
958 ) {
959 return;
960 }
961 if (in_array($Entity, ['ActivityType', 'SurveyRespondant'])) {
962 $this->markTestSkipped();
963 }
964 if ($Entity == 'UFGroup') {
965 $Entity = 'ufgroup';
966 }
967 $this->callAPISuccess($Entity, 'getlist', ['label_field' => 'id']);
968 }
969
970 /**
971 * Create two entities and make sure we can fetch them individually by ID.
972 *
973 * @dataProvider entities_get
974 *
975 * limitations include the problem with avoiding loops when creating test objects -
976 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
977 * Currency - only seems to support US
978 * @param $entityName
979 */
980 public function testByID_get($entityName) {
981 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
982 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
983 return;
984 }
985
986 $baos = $this->getMockableBAOObjects($entityName);
987 list($baoObj1, $baoObj2) = $baos;
988
989 // fetch first by ID
990 $result = $this->callAPISuccess($entityName, 'get', array(
991 'id' => $baoObj1->id,
992 ));
993
994 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
995 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
996 $this->assertEquals(1, count($result['values']));
997
998 // fetch second by ID
999 $result = $this->callAPISuccess($entityName, 'get', array(
1000 'id' => $baoObj2->id,
1001 ));
1002 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1003 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1004 $this->assertEquals(1, count($result['values']));
1005 }
1006
1007 /**
1008 * Ensure that the "get" operation accepts limiting the #result records.
1009 *
1010 * TODO Consider making a separate entity list ("entities_getlimit")
1011 * For the moment, the "entities_updatesingle" list should give a good
1012 * sense for which entities support createTestObject
1013 *
1014 * @dataProvider entities_getlimit
1015 *
1016 * @param string $entityName
1017 */
1018 public function testLimit($entityName) {
1019 $cases = array(); // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
1020 $cases[] = array(
1021 array('options' => array('limit' => NULL)),
1022 30,
1023 'check that a NULL limit returns unlimited',
1024 );
1025 $cases[] = array(
1026 array('options' => array('limit' => FALSE)),
1027 30,
1028 'check that a FALSE limit returns unlimited',
1029 );
1030 $cases[] = array(
1031 array('options' => array('limit' => 0)),
1032 30,
1033 'check that a 0 limit returns unlimited',
1034 );
1035 $cases[] = array(
1036 array('options' => array('limit' => 5)),
1037 5,
1038 'check that a 5 limit returns 5',
1039 );
1040 $cases[] = array(
1041 array(),
1042 25,
1043 'check that no limit returns 25',
1044 );
1045
1046 $baoString = _civicrm_api3_get_BAO($entityName);
1047 if (empty($baoString)) {
1048 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1049 return;
1050 }
1051
1052 // make 30 test items -- 30 > 25 (the default limit)
1053 $ids = array();
1054 for ($i = 0; $i < 30; $i++) {
1055 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1056 $ids[] = $baoObj->id;
1057 $baoObj->free();
1058 }
1059
1060 // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
1061 foreach ($cases as $case) {
1062 $this->checkLimitAgainstExpected($entityName, $case[0], $case[1], $case[2]);
1063
1064 //non preferred / legacy syntax
1065 if (isset($case[0]['options']['limit'])) {
1066 $this->checkLimitAgainstExpected($entityName, array('rowCount' => $case[0]['options']['limit']), $case[1], $case[2]);
1067 $this->checkLimitAgainstExpected($entityName, array('option_limit' => $case[0]['options']['limit']), $case[1], $case[2]);
1068 $this->checkLimitAgainstExpected($entityName, array('option.limit' => $case[0]['options']['limit']), $case[1], $case[2]);
1069 }
1070 }
1071 foreach ($ids as $id) {
1072 CRM_Core_DAO::deleteTestObjects($baoString, array('id' => $id));
1073 }
1074 $baoObj->free();
1075 }
1076
1077 /**
1078 * Ensure that the "get" operation accepts limiting the #result records.
1079 *
1080 * @dataProvider entities_getSqlOperators
1081 *
1082 * @param string $entityName
1083 */
1084 public function testSqlOperators($entityName) {
1085 $toBeIgnored = array_merge($this->toBeImplemented['get'],
1086 $this->deprecatedAPI,
1087 $this->toBeSkipped_get(TRUE),
1088 $this->toBeSkipped_getByID()
1089 );
1090 if (in_array($entityName, $toBeIgnored)) {
1091 return;
1092 }
1093
1094 $baoString = _civicrm_api3_get_BAO($entityName);
1095
1096 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0), 'return' => 'id'));
1097 $entities = array_keys($entities['values']);
1098 $totalEntities = count($entities);
1099 if ($totalEntities < 3) {
1100 $ids = array();
1101 for ($i = 0; $i < 3 - $totalEntities; $i++) {
1102 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1103 $ids[] = $baoObj->id;
1104 $baoObj->free();
1105 }
1106 $totalEntities = 3;
1107 }
1108 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0)));
1109 $entities = array_keys($entities['values']);
1110 $this->assertGreaterThan(2, $totalEntities);
1111 $this->callAPISuccess($entityName, 'getsingle', array('id' => array('IN' => array($entities[0]))));
1112 $this->callAPISuccessGetCount($entityName, array('id' => array('NOT IN' => array($entities[0]))), $totalEntities - 1);
1113 $this->callAPISuccessGetCount($entityName, array('id' => array('>' => $entities[0])), $totalEntities - 1);
1114 }
1115
1116 /**
1117 * Check that get fetches an appropriate number of results.
1118 *
1119 * @param string $entityName
1120 * Name of entity to test.
1121 * @param array $params
1122 * @param int $limit
1123 * @param string $message
1124 */
1125 public function checkLimitAgainstExpected($entityName, $params, $limit, $message) {
1126 $result = $this->callAPISuccess($entityName, 'get', $params);
1127 if ($limit == 30) {
1128 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1129 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1130 }
1131 else {
1132 $this->assertEquals($limit, $result['count'], $message);
1133 $this->assertEquals($limit, count($result['values']), $message);
1134 }
1135 }
1136
1137 /**
1138 * Create two entities and make sure we can fetch them individually by ID (e.g. using "contact_id=>2"
1139 * or "group_id=>4")
1140 *
1141 * @dataProvider entities_get
1142 *
1143 * limitations include the problem with avoiding loops when creating test objects -
1144 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1145 * Currency - only seems to support US
1146 * @param $entityName
1147 * @throws \PHPUnit_Framework_IncompleteTestError
1148 */
1149 public function testByIDAlias_get($entityName) {
1150 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
1151 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1152 return;
1153 }
1154
1155 $baoString = _civicrm_api3_get_BAO($entityName);
1156 if (empty($baoString)) {
1157 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1158 return;
1159 }
1160
1161 $idFieldName = _civicrm_api_get_entity_name_from_camel($entityName) . '_id';
1162
1163 // create entities
1164 $baoObj1 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1165 $this->assertTrue(is_int($baoObj1->id), 'check first id');
1166 $this->deletableTestObjects[$baoString][] = $baoObj1->id;
1167 $baoObj2 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1168 $this->assertTrue(is_int($baoObj2->id), 'check second id');
1169 $this->deletableTestObjects[$baoString][] = $baoObj2->id;
1170
1171 // fetch first by ID
1172 $result = civicrm_api($entityName, 'get', array(
1173 'version' => 3,
1174 $idFieldName => $baoObj1->id,
1175 ));
1176 $this->assertAPISuccess($result);
1177 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
1178 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
1179 $this->assertEquals(1, count($result['values']));
1180
1181 // fetch second by ID
1182 $result = civicrm_api($entityName, 'get', array(
1183 'version' => 3,
1184 $idFieldName => $baoObj2->id,
1185 ));
1186 $this->assertAPISuccess($result);
1187 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1188 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1189 $this->assertEquals(1, count($result['values']));
1190 }
1191
1192 /**
1193 * @dataProvider entities_get
1194 * @param $Entity
1195 */
1196 public function testNonExistantID_get($Entity) {
1197 // cf testAcceptsOnlyID_get
1198 $nonExistantID = 30867307034;
1199 if (in_array($Entity, $this->toBeImplemented['get'])) {
1200 return;
1201 }
1202
1203 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
1204
1205 // redundant with testAcceptsOnlyID_get
1206 if ($result['is_error']) {
1207 return;
1208 }
1209
1210 $this->assertArrayHasKey('version', $result);
1211 $this->assertEquals(3, $result['version']);
1212 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
1213 $this->assertEquals(0, $result['count']);
1214 }
1215 }
1216
1217 /* ---- testing the _create ---- */
1218
1219 /**
1220 * @dataProvider toBeSkipped_create
1221 entities that don't need a create action
1222 * @param $Entity
1223 */
1224 public function testNotImplemented_create($Entity) {
1225 $result = civicrm_api($Entity, 'Create', array('version' => 3));
1226 $this->assertEquals(1, $result['is_error']);
1227 $this->assertContains(strtolower("API ($Entity, Create) does not exist"), strtolower($result['error_message']));
1228 }
1229
1230 /**
1231 * @dataProvider entities
1232 * @expectedException CiviCRM_API3_Exception
1233 * @param $Entity
1234 */
1235 public function testWithoutParam_create($Entity) {
1236 if ($Entity === 'Setting') {
1237 $this->markTestSkipped('It seems OK for setting to skip here as it silently sips invalid params');
1238 }
1239 elseif ($Entity === 'Mailing') {
1240 $this->markTestSkipped('It seems OK for "Mailing" to skip here because you can create empty drafts');
1241 }
1242 // should create php complaining that a param is missing
1243 civicrm_api3($Entity, 'Create');
1244 }
1245
1246 /**
1247 * @dataProvider entities_create
1248 *
1249 * Check that create doesn't work with an invalid
1250 * @param $Entity
1251 * @throws \PHPUnit_Framework_IncompleteTestError
1252 */
1253 public function testInvalidSort_get($Entity) {
1254 $invalidEntitys = array('ActivityType', 'Setting', 'System');
1255 if (in_array($Entity, $invalidEntitys)) {
1256 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently sips invalid params');
1257 }
1258 $result = $this->callAPIFailure($Entity, 'get', array('options' => array('sort' => 'sleep(1)')));
1259 }
1260
1261 /**
1262 * @dataProvider entities_create
1263 *
1264 * Check that create doesn't work with an invalid
1265 * @param $Entity
1266 * @throws \PHPUnit_Framework_IncompleteTestError
1267 */
1268 public function testValidSortSingleArrayById_get($Entity) {
1269 $invalidEntitys = array('ActivityType', 'Setting', 'System');
1270 $tests = array(
1271 'id' => '_id',
1272 'id desc' => '_id desc',
1273 'id DESC' => '_id DESC',
1274 'id ASC' => '_id ASC',
1275 'id asc' => '_id asc');
1276 foreach ($tests as $test => $expected) {
1277 if (in_array($Entity, $invalidEntitys)) {
1278 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently ignores passed in params');
1279 }
1280 $params = array('sort' => array($test));
1281 $result = _civicrm_api3_get_options_from_params($params, FALSE, $Entity, 'get');
1282 $lowercase_entity = _civicrm_api_get_entity_name_from_camel($Entity);
1283 $this->assertEquals($lowercase_entity . $expected, $result['sort']);
1284 }
1285 }
1286
1287 /**
1288 * @dataProvider entities_create
1289 *
1290 * Check that create doesn't work with an invalid
1291 * @param $Entity
1292 * @throws \PHPUnit_Framework_IncompleteTestError
1293 */
1294 public function testInvalidID_create($Entity) {
1295 // turn test off for noew
1296 $this->markTestIncomplete("Entity [ $Entity ] cannot be mocked - no known DAO");
1297 return;
1298 if (in_array($Entity, $this->toBeImplemented['create'])) {
1299 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1300 return;
1301 }
1302 $result = $this->callAPIFailure($Entity, 'Create', array('id' => 999));
1303 }
1304
1305 /**
1306 * @dataProvider entities
1307 */
1308 public function testCreateWrongTypeParamTag_create() {
1309 $result = civicrm_api("Tag", 'Create', 'this is not a string');
1310 $this->assertEquals(1, $result['is_error']);
1311 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1312 }
1313
1314 /**
1315 * @dataProvider entities_updatesingle
1316 *
1317 * limitations include the problem with avoiding loops when creating test objects -
1318 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1319 * Currency - only seems to support US
1320 * @param $entityName
1321 */
1322 public function testCreateSingleValueAlter($entityName) {
1323 if (in_array($entityName, $this->toBeImplemented['create'])) {
1324 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1325 return;
1326 }
1327
1328 $baoString = _civicrm_api3_get_BAO($entityName);
1329 $this->assertNotEmpty($baoString, $entityName);
1330 $this->assertNotEmpty($entityName, $entityName);
1331 $fieldsGet = $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'get', 'options' => array('get_options' => 'all')));
1332 if ($entityName != 'Pledge') {
1333 $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'create', 'options' => array('get_options' => 'all')));
1334 }
1335 $fields = $fields['values'];
1336 $return = array_keys($fieldsGet['values']);
1337 $valuesNotToReturn = $this->getKnownUnworkablesUpdateSingle($entityName, 'break_return');
1338 // these can't be requested as return values
1339 $entityValuesThatDoNotWork = array_merge(
1340 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_update'),
1341 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_return'),
1342 $valuesNotToReturn
1343 );
1344
1345 $return = array_diff($return, $valuesNotToReturn);
1346 $baoObj = new CRM_Core_DAO();
1347 $baoObj->createTestObject($baoString, array('currency' => 'USD'), 2, 0);
1348
1349 $getEntities = $this->callAPISuccess($entityName, 'get', array(
1350 'sequential' => 1,
1351 'return' => $return,
1352 'options' => array(
1353 'sort' => 'id DESC',
1354 'limit' => 2,
1355 ),
1356 ));
1357
1358 // lets use first rather than assume only one exists
1359 $entity = $getEntities['values'][0];
1360 $entity2 = $getEntities['values'][1];
1361 $this->deletableTestObjects[$baoString][] = $entity['id'];
1362 $this->deletableTestObjects[$baoString][] = $entity2['id'];
1363 // Skip these fields that we never really expect to update well.
1364 $genericFieldsToSkip = ['currency', 'id', strtolower($entityName) . '_id', 'is_primary'];
1365 foreach ($fields as $field => $specs) {
1366 $resetFKTo = NULL;
1367 $fieldName = $field;
1368
1369 if (in_array($field, $genericFieldsToSkip)
1370 || in_array($field, $entityValuesThatDoNotWork)
1371 ) {
1372 //@todo id & entity_id are correct but we should fix currency & frequency_day
1373 continue;
1374 }
1375 $this->assertArrayHasKey('type', $specs, "the _spec function for $entityName field $field does not specify the type");
1376 switch ($specs['type']) {
1377 case CRM_Utils_Type::T_DATE:
1378 $entity[$fieldName] = '2012-05-20';
1379 break;
1380
1381 case CRM_Utils_Type::T_TIMESTAMP:
1382 case 12:
1383 $entity[$fieldName] = '2012-05-20 03:05:20';
1384 break;
1385
1386 case CRM_Utils_Type::T_STRING:
1387 case CRM_Utils_Type::T_BLOB:
1388 case CRM_Utils_Type::T_MEDIUMBLOB:
1389 case CRM_Utils_Type::T_TEXT:
1390 case CRM_Utils_Type::T_LONGTEXT:
1391 case CRM_Utils_Type::T_EMAIL:
1392 if ($fieldName == 'form_values' && $entityName == 'SavedSearch') {
1393 // This is a hack for the SavedSearch API.
1394 // It expects form_values to be an array.
1395 // If you want to fix this, you should definitely read this forum
1396 // post.
1397 // http://forum.civicrm.org/index.php/topic,33990.0.html
1398 // See also my question on the CiviCRM Stack Exchange:
1399 // https://civicrm.stackexchange.com/questions/3437
1400 $entity[$fieldName] = array('sort_name' => "SortName2");
1401 }
1402 else {
1403 $entity[$fieldName] = substr('New String', 0, CRM_Utils_Array::Value('maxlength', $specs, 100));
1404 if ($fieldName == 'email') {
1405 $entity[$fieldName] = strtolower($entity[$fieldName]);
1406 }
1407 // typecast with array to satisfy changes made in CRM-13160
1408 if ($entityName == 'MembershipType' && in_array($fieldName, array(
1409 'relationship_type_id',
1410 'relationship_direction',
1411 ))
1412 ) {
1413 $entity[$fieldName] = (array) $entity[$fieldName];
1414 }
1415 }
1416 break;
1417
1418 case CRM_Utils_Type::T_INT:
1419 // probably created with a 1
1420 if ($fieldName == 'weight') {
1421 $entity[$fieldName] = 2;
1422 }
1423 elseif (!empty($specs['FKClassName'])) {
1424 if ($specs['FKClassName'] == $baoString) {
1425 $entity[$fieldName] = (string) $entity2['id'];
1426 }
1427 else {
1428 if (!empty($entity[$fieldName])) {
1429 $resetFKTo = array($fieldName => $entity[$fieldName]);
1430 }
1431 $entity[$fieldName] = (string) empty($entity2[$field]) ? '' : $entity2[$field];
1432 //todo - there isn't always something set here - & our checking on unset values is limited
1433 if (empty($entity[$field])) {
1434 unset($entity[$field]);
1435 }
1436 }
1437 }
1438 else {
1439 $entity[$fieldName] = '6';
1440 }
1441 break;
1442
1443 case CRM_Utils_Type::T_BOOLEAN:
1444 // probably created with a 1
1445 $entity[$fieldName] = '0';
1446 break;
1447
1448 case CRM_Utils_Type::T_FLOAT:
1449 case CRM_Utils_Type::T_MONEY:
1450 $entity[$field] = '22.75';
1451 break;
1452
1453 case CRM_Utils_Type::T_URL:
1454 $entity[$field] = 'warm.beer.com';
1455 }
1456 if (empty($specs['FKClassName']) && (!empty($specs['pseudoconstant']) || !empty($specs['options']))) {
1457 $options = CRM_Utils_Array::value('options', $specs, array());
1458 if (!$options) {
1459 //eg. pdf_format id doesn't ship with any
1460 if (isset($specs['pseudoconstant']['optionGroupName'])) {
1461 $optionValue = $this->callAPISuccess('option_value', 'create', array(
1462 'option_group_id' => $specs['pseudoconstant']['optionGroupName'],
1463 'label' => 'new option value',
1464 'sequential' => 1,
1465 ));
1466 $optionValue = $optionValue['values'];
1467 $keyColumn = CRM_Utils_Array::value('keyColumn', $specs['pseudoconstant'], 'value');
1468 $options[$optionValue[0][$keyColumn]] = 'new option value';
1469 }
1470 }
1471 $entity[$field] = array_rand($options);
1472 }
1473 if (!empty($specs['FKClassName']) && !empty($specs['pseudoconstant'])) {
1474 // in the weird situation where a field has both an fk and pseudoconstant defined,
1475 // e.g. campaign_id field, need to flush caches.
1476 // FIXME: Why doesn't creating a campaign clear caches?
1477 civicrm_api3($entityName, 'getfields', array('cache_clear' => 1));
1478 }
1479 $updateParams = array(
1480 'id' => $entity['id'],
1481 $field => isset($entity[$field]) ? $entity[$field] : NULL,
1482 );
1483 if (!empty($specs['serialize'])) {
1484 $updateParams[$field] = $entity[$field] = (array) $specs['serialize'];
1485 }
1486 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
1487 //api has special handling on these 2 fields for backward compatibility reasons
1488 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1489 }
1490 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1491 //api has special handling on these 2 fields for backward compatibility reasons
1492 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1493 }
1494 if (isset($updateParams['image'])) {
1495 // Image field is passed through simplifyURL function so may be different, do the same here for comparison
1496 $entity['image'] = CRM_Utils_String::simplifyURL($updateParams['image'], TRUE);
1497 }
1498 if (isset($updateParams['thumbnail'])) {
1499 // Thumbnail field is passed through simplifyURL function so may be different, do the same here for comparison
1500 $entity['thumbnail'] = CRM_Utils_String::simplifyURL($updateParams['thumbnail'], TRUE);
1501 }
1502
1503 $update = $this->callAPISuccess($entityName, 'create', $updateParams);
1504 $checkParams = array(
1505 'id' => $entity['id'],
1506 'sequential' => 1,
1507 'return' => $return,
1508 'options' => array(
1509 'sort' => 'id DESC',
1510 'limit' => 2,
1511 ),
1512 );
1513
1514 $checkEntity = $this->callAPISuccess($entityName, 'getsingle', $checkParams);
1515 if (!empty($specs['serialize']) && !is_array($checkEntity[$field])) {
1516 // Put into serialized format for comparison if 'get' has not returned serialized.
1517 $entity[$field] = CRM_Core_DAO::serializeField($checkEntity[$field], $specs['serialize']);
1518 }
1519
1520 $this->assertAPIArrayComparison($entity, $checkEntity, array(), "checking if $fieldName was correctly updated\n" . print_r(array(
1521 'update-params' => $updateParams,
1522 'update-result' => $update,
1523 'getsingle-params' => $checkParams,
1524 'getsingle-result' => $checkEntity,
1525 'expected entity' => $entity,
1526 ), TRUE));
1527 if ($resetFKTo) {
1528 //reset the foreign key fields because otherwise our cleanup routine fails & some other unexpected stuff can kick in
1529 $entity = array_merge($entity, $resetFKTo);
1530 $updateParams = array_merge($updateParams, $resetFKTo);
1531 $this->callAPISuccess($entityName, 'create', $updateParams);
1532 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
1533 //api has special handling on these 2 fields for backward compatibility reasons
1534 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1535 }
1536 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1537 //api has special handling on these 2 fields for backward compatibility reasons
1538 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1539 }
1540 }
1541 }
1542 $baoObj->free();
1543 }
1544
1545 /* ---- testing the _getFields ---- */
1546
1547 /* ---- testing the _delete ---- */
1548
1549 /**
1550 * @dataProvider toBeSkipped_delete
1551 entities that don't need a delete action
1552 * @param $Entity
1553 */
1554 public function testNotImplemented_delete($Entity) {
1555 $nonExistantID = 151416349;
1556 $result = civicrm_api($Entity, 'Delete', array('version' => 3, 'id' => $nonExistantID));
1557 $this->assertEquals(1, $result['is_error']);
1558 $this->assertContains(strtolower("API ($Entity, Delete) does not exist"), strtolower($result['error_message']));
1559 }
1560
1561 /**
1562 * @dataProvider entities
1563 * @param $Entity
1564 */
1565 public function testWithoutParam_delete($Entity) {
1566 // should delete php complaining that a param is missing
1567 try {
1568 $result = civicrm_api($Entity, 'Delete');
1569 $this->fail('Expected an exception. No exception was thrown.');
1570 }
1571 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
1572 catch (ArgumentCountError $e) {
1573 /* ok */
1574 }
1575 catch (PHPUnit_Framework_Error $e) {
1576 /* ok */
1577 }
1578 }
1579
1580 /**
1581 * @dataProvider entities_delete
1582 * @param $Entity
1583 */
1584 public function testEmptyParam_delete($Entity) {
1585 if (in_array($Entity, $this->toBeImplemented['delete'])) {
1586 // $this->markTestIncomplete("civicrm_api3_{$Entity}_delete to be implemented");
1587 return;
1588 }
1589 $result = civicrm_api($Entity, 'Delete', array());
1590 $this->assertEquals(1, $result['is_error']);
1591 $this->assertContains("Unknown api version", $result['error_message']);
1592 }
1593
1594 /**
1595 * @dataProvider entities_delete
1596 * @param $Entity
1597 * @throws \PHPUnit_Framework_IncompleteTestError
1598 */
1599 public function testInvalidID_delete($Entity) {
1600 $result = $this->callAPIFailure($Entity, 'Delete', array('id' => 999999));
1601 }
1602
1603 /**
1604 * @dataProvider entities
1605 */
1606 public function testDeleteWrongTypeParamTag_delete() {
1607 $result = civicrm_api("Tag", 'Delete', 'this is not a string');
1608 $this->assertEquals(1, $result['is_error']);
1609 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1610 }
1611
1612 /**
1613 * Create two entities and make sure delete action only deletes one!
1614 *
1615 * @dataProvider entities_delete
1616 *
1617 * limitations include the problem with avoiding loops when creating test objects -
1618 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1619 * Currency - only seems to support US
1620 * @param $entityName
1621 * @throws \PHPUnit_Framework_IncompleteTestError
1622 */
1623 public function testByID_delete($entityName) {
1624 // turn test off for noew
1625 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1626 return;
1627
1628 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
1629 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1630 return;
1631 }
1632 $startCount = $this->callAPISuccess($entityName, 'getcount', array());
1633 $createcount = 2;
1634 $baos = $this->getMockableBAOObjects($entityName, $createcount);
1635 list($baoObj1, $baoObj2) = $baos;
1636
1637 // make sure exactly 2 exist
1638 $result = $this->callAPISuccess($entityName, 'getcount', array(),
1639 $createcount + $startCount
1640 );
1641
1642 $this->callAPISuccess($entityName, 'delete', array('id' => $baoObj2->id));
1643 //make sure 1 less exists now
1644 $result = $this->callAPISuccess($entityName, 'getcount', array(),
1645 ($createcount + $startCount) - 1
1646 );
1647
1648 //make sure id #1 exists
1649 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj1->id),
1650 1
1651 );
1652 //make sure id #2 desn't exist
1653 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj2->id),
1654 0
1655 );
1656 }
1657
1658 /**
1659 * Create two entities and make sure delete action only deletes one!
1660 *
1661 * @dataProvider entities_getfields
1662 * @param $entity
1663 */
1664 public function testGetfieldsHasTitle($entity) {
1665 $entities = $this->getEntitiesSupportingCustomFields();
1666 if (in_array($entity, $entities)) {
1667 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $entity . 'Test.php');
1668 }
1669 $actions = $this->callAPISuccess($entity, 'getactions', array());
1670 foreach ($actions['values'] as $action) {
1671 if (substr($action, -7) == '_create' || substr($action, -4) == '_get' || substr($action, -7) == '_delete') {
1672 //getactions can't distinguish between contribution_page.create & contribution_page.create
1673 continue;
1674 }
1675 $fields = $this->callAPISuccess($entity, 'getfields', array('action' => $action));
1676 if (!empty($ids) && in_array($action, array('create', 'get'))) {
1677 $this->assertArrayHasKey('custom_' . $ids['custom_field_id'], $fields['values']);
1678 }
1679
1680 foreach ($fields['values'] as $fieldName => $fieldSpec) {
1681 $this->assertArrayHasKey('title', $fieldSpec, "no title for $entity - $fieldName on action $action");
1682 $this->assertNotEmpty($fieldSpec['title'], "empty title for $entity - $fieldName");
1683 }
1684 }
1685 if (!empty($ids)) {
1686 $this->customFieldDelete($ids['custom_field_id']);
1687 $this->customGroupDelete($ids['custom_group_id']);
1688 }
1689 }
1690
1691 /**
1692 * @return array
1693 */
1694 public function getEntitiesSupportingCustomFields() {
1695 $entities = self::custom_data_entities_get();
1696 $returnEntities = array();
1697 foreach ($entities as $entityArray) {
1698 $returnEntities[] = $entityArray[0];
1699 }
1700 return $returnEntities;
1701 }
1702
1703 /**
1704 * @param string $entityName
1705 * @param int $count
1706 *
1707 * @return array
1708 */
1709 private function getMockableBAOObjects($entityName, $count = 2) {
1710 $baoString = _civicrm_api3_get_BAO($entityName);
1711 if (empty($baoString)) {
1712 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1713 return array();
1714 }
1715 $baos = array();
1716 $i = 0;
1717 while ($i < $count) {
1718 // create entities
1719 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1720 $this->assertTrue(is_int($baoObj->id), 'check first id');
1721 $this->deletableTestObjects[$baoString][] = $baoObj->id;
1722 $baos[] = $baoObj;
1723 $i++;
1724 }
1725 return $baos;
1726 }
1727
1728 /**
1729 * Verify that HTML metacharacters provided as inputs appear consistently.
1730 * as outputs.
1731 *
1732 * At time of writing, the encoding scheme requires (for example) that an
1733 * event title be partially-HTML-escaped before writing to DB. To provide
1734 * consistency, the API must perform extra encoding and decoding on some
1735 * fields.
1736 *
1737 * In this example, the event 'title' is subject to encoding, but the
1738 * event 'description' is not.
1739 */
1740 public function testEncodeDecodeConsistency() {
1741 // Create example
1742 $createResult = civicrm_api('Event', 'Create', array(
1743 'version' => 3,
1744 'title' => 'CiviCRM <> TheRest',
1745 'description' => 'TheRest <> CiviCRM',
1746 'event_type_id' => 1,
1747 'is_public' => 1,
1748 'start_date' => 20081021,
1749 ));
1750 $this->assertAPISuccess($createResult);
1751 $eventId = $createResult['id'];
1752 $this->assertEquals('CiviCRM <> TheRest', $createResult['values'][$eventId]['title']);
1753 $this->assertEquals('TheRest <> CiviCRM', $createResult['values'][$eventId]['description']);
1754
1755 // Verify "get" handles decoding in result value
1756 $getByIdResult = civicrm_api('Event', 'Get', array(
1757 'version' => 3,
1758 'id' => $eventId,
1759 ));
1760 $this->assertAPISuccess($getByIdResult);
1761 $this->assertEquals('CiviCRM <> TheRest', $getByIdResult['values'][$eventId]['title']);
1762 $this->assertEquals('TheRest <> CiviCRM', $getByIdResult['values'][$eventId]['description']);
1763
1764 // Verify "get" handles encoding in search value
1765 $getByTitleResult = civicrm_api('Event', 'Get', array(
1766 'version' => 3,
1767 'title' => 'CiviCRM <> TheRest',
1768 ));
1769 $this->assertAPISuccess($getByTitleResult);
1770 $this->assertEquals('CiviCRM <> TheRest', $getByTitleResult['values'][$eventId]['title']);
1771 $this->assertEquals('TheRest <> CiviCRM', $getByTitleResult['values'][$eventId]['description']);
1772
1773 // Verify that "getSingle" handles decoding
1774 $getSingleResult = $this->callAPISuccess('Event', 'GetSingle', array(
1775 'id' => $eventId,
1776 ));
1777
1778 $this->assertEquals('CiviCRM <> TheRest', $getSingleResult['title']);
1779 $this->assertEquals('TheRest <> CiviCRM', $getSingleResult['description']);
1780
1781 // Verify that chaining handles decoding
1782 $chainResult = $this->callAPISuccess('Event', 'Get', array(
1783 'id' => $eventId,
1784 'api.event.get' => array(),
1785 ));
1786 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['title']);
1787 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['description']);
1788 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['api.event.get']['values'][0]['title']);
1789 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['api.event.get']['values'][0]['description']);
1790
1791 // Verify that "setvalue" handles encoding for updates
1792 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1793 'version' => 3,
1794 'id' => $eventId,
1795 'field' => 'title',
1796 'value' => 'setValueTitle: CiviCRM <> TheRest',
1797 ));
1798 $this->assertAPISuccess($setValueTitleResult);
1799 $this->assertEquals('setValueTitle: CiviCRM <> TheRest', $setValueTitleResult['values']['title']);
1800 $setValueDescriptionResult = civicrm_api('Event', 'setvalue', array(
1801 'version' => 3,
1802 'id' => $eventId,
1803 'field' => 'description',
1804 'value' => 'setValueDescription: TheRest <> CiviCRM',
1805 ));
1806 //$this->assertTrue((bool)$setValueDescriptionResult['is_error']); // not supported by setValue
1807 $this->assertEquals('setValueDescription: TheRest <> CiviCRM', $setValueDescriptionResult['values']['description']);
1808 }
1809
1810 /**
1811 * Verify that write operations (create/update) use partial HTML-encoding
1812 *
1813 * In this example, the event 'title' is subject to encoding, but the
1814 * event 'description' is not.
1815 */
1816 public function testEncodeWrite() {
1817 // Create example
1818 $createResult = civicrm_api('Event', 'Create', array(
1819 'version' => 3,
1820 'title' => 'createNew: CiviCRM <> TheRest',
1821 'description' => 'createNew: TheRest <> CiviCRM',
1822 'event_type_id' => 1,
1823 'is_public' => 1,
1824 'start_date' => 20081021,
1825 ));
1826 $this->assertAPISuccess($createResult);
1827 $eventId = $createResult['id'];
1828 $this->assertDBQuery('createNew: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1829 1 => array($eventId, 'Integer'),
1830 ));
1831 $this->assertDBQuery('createNew: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1832 1 => array($eventId, 'Integer'),
1833 ));
1834
1835 // Verify that "create" handles encoding for updates
1836 $createWithIdResult = civicrm_api('Event', 'Create', array(
1837 'version' => 3,
1838 'id' => $eventId,
1839 'title' => 'createWithId: CiviCRM <> TheRest',
1840 'description' => 'createWithId: TheRest <> CiviCRM',
1841 ));
1842 $this->assertAPISuccess($createWithIdResult);
1843 $this->assertDBQuery('createWithId: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1844 1 => array($eventId, 'Integer'),
1845 ));
1846 $this->assertDBQuery('createWithId: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1847 1 => array($eventId, 'Integer'),
1848 ));
1849
1850 // Verify that "setvalue" handles encoding for updates
1851 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1852 'version' => 3,
1853 'id' => $eventId,
1854 'field' => 'title',
1855 'value' => 'setValueTitle: CiviCRM <> TheRest',
1856 ));
1857 $this->assertAPISuccess($setValueTitleResult);
1858 $this->assertDBQuery('setValueTitle: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1859 1 => array($eventId, 'Integer'),
1860 ));
1861 $setValueDescriptionResult = civicrm_api('Event', 'setvalue', array(
1862 'version' => 3,
1863 'id' => $eventId,
1864 'field' => 'description',
1865 'value' => 'setValueDescription: TheRest <> CiviCRM',
1866 ));
1867 //$this->assertTrue((bool)$setValueDescriptionResult['is_error']); // not supported by setValue
1868 $this->assertAPISuccess($setValueDescriptionResult);
1869 $this->assertDBQuery('setValueDescription: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1870 1 => array($eventId, 'Integer'),
1871 ));
1872 }
1873
1874 }