Merge pull request #12178 from jitendrapurohit/membership-4
[civicrm-core.git] / tests / phpunit / api / v3 / SyntaxConformanceTest.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2018 |
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 'MailingGroup',
490 'Address',
491 'MailingEventUnsubscribe',
492 'MailingEventSubscribe',
493 'Constant',
494 'Entity',
495 'Location',
496 'Profile',
497 'CustomValue',
498 'SurveyRespondant',
499 'UFMatch',
500 'UFJoin',
501 'UFField',
502 'OptionValue',
503 'Relationship',
504 'RelationshipType',
505 'Note',
506 'OptionGroup',
507 'Membership',
508 'Group',
509 'File',
510 'EntityTag',
511 'CustomField',
512 'CustomGroup',
513 'Contribution',
514 'ActivityType',
515 'MailingEventConfirm',
516 'Case',
517 'CaseContact',
518 'Contact',
519 'ContactType',
520 'MailingEventResubscribe',
521 'UFGroup',
522 'Activity',
523 'Event',
524 'GroupContact',
525 'MembershipPayment',
526 'Participant',
527 'LineItem',
528 'PledgePayment',
529 'ContributionPage',
530 'Phone',
531 'PaymentProcessor',
532 'Setting',
533 'MailingContact',
534 'SystemLog',
535 //skip this because it doesn't make sense to update logs,
536 'Logging',
537 );
538 if ($sequential === TRUE) {
539 return $entitiesWithout;
540 }
541 $entities = array();
542 foreach ($entitiesWithout as $e) {
543 $entities[] = array(
544 $e,
545 );
546 }
547 return array('pledge');
548 return $entities;
549 }
550
551 /**
552 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
553 */
554 public static function toBeSkipped_getlimit() {
555 $entitiesWithout = array(
556 'Case',
557 //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
558 'EntityTag',
559 // non-standard api - has inappropriate mandatory fields & doesn't implement limit
560 'Event',
561 // failed 'check that a 5 limit returns 5' - probably is_template field is wrong or something, or could be limit doesn't work right
562 'Extension',
563 // can't handle creating 25
564 'Note',
565 // fails on 5 limit - probably a set up problem
566 'Setting',
567 //a bit of a pseudoapi - keys by domain
568 );
569 return $entitiesWithout;
570 }
571
572 /**
573 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
574 */
575 public static function toBeSkipped_getSqlOperators() {
576 $entitiesWithout = array(
577 'Case', //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
578 'Contact', // on the todo list!
579 'EntityTag', // non-standard api - has inappropriate mandatory fields & doesn't implement limit
580 'Extension', // can't handle creating 25
581 'Note', // note has a default get that isn't implemented in createTestObject -meaning you don't 'get' them
582 'Setting', //a bit of a pseudoapi - keys by domain
583 );
584 return $entitiesWithout;
585 }
586
587 /**
588 * @param $entity
589 * @param $key
590 *
591 * @return array
592 */
593 public function getKnownUnworkablesUpdateSingle($entity, $key) {
594 // can't update values are values for which updates don't result in the value being changed
595 $knownFailures = array(
596 'ActionSchedule' => array(
597 'cant_update' => array(
598 'group_id',
599 ),
600 ),
601 'ActivityContact' => array(
602 'cant_update' => array(
603 'activity_id',
604 //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
605 ),
606 ),
607 'Address' => array(
608 'cant_update' => array(
609 'state_province_id', //issues with country id - need to ensure same country
610 'master_id', //creates relationship
611 ),
612 'cant_return' => array(),
613 ),
614 'Batch' => array(
615 'cant_update' => array(
616 'entity_table', // believe this field is defined in error
617 ),
618 'cant_return' => array(
619 'entity_table',
620 ),
621 ),
622 'CaseType' => array(
623 'cant_update' => array(
624 'definition',
625 ),
626 ),
627 'Domain' => ['cant_update' => ['domain_version']],
628 'MembershipBlock' => array(
629 'cant_update' => array(
630 // The fake/auto-generated values leave us unable to properly cleanup fake data
631 'entity_type',
632 'entity_id',
633 ),
634 ),
635 'ContributionSoft' => array(
636 'cant_update' => array(
637 // can't be changed through api
638 'pcp_id',
639 ),
640 ),
641 'Email' => array(
642 'cant_update' => array(
643 // This is being legitimately manipulated to always have a valid primary - skip.
644 'is_primary',
645 ),
646 ),
647 'Navigation' => array(
648 'cant_update' => array(
649 // Weight is deliberately altered when this is changed - skip.
650 'parent_id',
651 ),
652 ),
653 'LocationType' => array(
654 'cant_update' => array(
655 // I'm on the fence about whether the test should skip or the behaviour is wrong.
656 // display_name is set to match name if display_name is not provided. It would be more 'normal'
657 // to only calculate a default IF id is not set - but perhaps the current behaviour is kind
658 // of what someone updating the name expects..
659 'name',
660 ),
661 ),
662 'Pledge' => array(
663 'cant_update' => array(
664 'pledge_original_installment_amount',
665 'installments',
666 'original_installment_amount',
667 'next_pay_date',
668 'amount', // can't be changed through API,
669 ),
670 'break_return' => array(// if these are passed in they are retrieved from the wrong table
671 'honor_contact_id',
672 'cancel_date',
673 'contribution_page_id',
674 'financial_account_id',
675 'financial_type_id',
676 'currency',
677 ),
678 'cant_return' => array(// can't be retrieved from api
679 'honor_type_id', //due to uniquename missing
680 'end_date',
681 'modified_date',
682 'acknowledge_date',
683 'start_date',
684 'frequency_day',
685 'currency',
686 'max_reminders',
687 'initial_reminder_day',
688 'additional_reminder_day',
689 'frequency_unit',
690 'pledge_contribution_page_id',
691 'pledge_status_id',
692 'pledge_campaign_id',
693 'pledge_financial_type_id',
694 ),
695 ),
696 'PaymentProcessorType' => array(
697 'cant_update' => array(
698 'billing_mode',
699 ),
700 'break_return' => array(),
701 'cant_return' => array(),
702 ),
703 'PriceFieldValue' => array(
704 'cant_update' => array(
705 'weight', //won't update as there is no 1 in the same price set
706 ),
707 ),
708 'ReportInstance' => array(
709 // View mode is part of the navigation which is not retrieved by the api.
710 'cant_return' => array('view_mode'),
711 ),
712 'SavedSearch' => array(
713 // I think the fields below are generated based on form_values.
714 'cant_update' => array(
715 'search_custom_id',
716 'where_clause',
717 'select_tables',
718 'where_tables',
719 ),
720 ),
721 'StatusPreference' => array(
722 'break_return' => array(
723 'ignore_severity',
724 ),
725 ),
726 'JobLog' => array(
727 // For better or worse triggers override.
728 'break_return' => ['run_time'],
729 'cant_update' => ['run_time'],
730 ),
731 );
732 if (empty($knownFailures[$entity]) || empty($knownFailures[$entity][$key])) {
733 return array();
734 }
735 return $knownFailures[$entity][$key];
736 }
737
738 /* ----- testing the _get ----- */
739
740 /**
741 * @dataProvider toBeSkipped_get
742 * Entities that don't need a get action
743 * @param $Entity
744 */
745 public function testNotImplemented_get($Entity) {
746 $result = civicrm_api($Entity, 'Get', array('version' => 3));
747 $this->assertEquals(1, $result['is_error']);
748 // $this->assertContains("API ($Entity, Get) does not exist", $result['error_message']);
749 $this->assertRegExp('/API (.*) does not exist/', $result['error_message']);
750 }
751
752 /**
753 * @dataProvider entities
754 * @param $Entity
755 */
756 public function testWithoutParam_get($Entity) {
757 // should get php complaining that a param is missing
758 try {
759 $result = civicrm_api($Entity, 'Get');
760 $this->fail('Expected an exception. No exception was thrown.');
761 }
762 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
763 catch (ArgumentCountError $e) {
764 /* ok */
765 }
766 catch (PHPUnit_Framework_Error $e) {
767 /* ok */
768 }
769 }
770
771 /**
772 * @dataProvider entities
773 * @param $Entity
774 */
775 public function testGetFields($Entity) {
776 if (in_array($Entity, $this->deprecatedAPI) || $Entity == 'Entity' || $Entity == 'CustomValue') {
777 return;
778 }
779
780 $result = civicrm_api($Entity, 'getfields', array('version' => 3));
781 $this->assertTrue(is_array($result['values']), "$Entity ::get fields doesn't return values array in line " . __LINE__);
782 foreach ($result['values'] as $key => $value) {
783 $this->assertTrue(is_array($value), $Entity . "::" . $key . " is not an array in line " . __LINE__);
784 }
785 }
786
787 /**
788 * @dataProvider entities_get
789 * @param $Entity
790 */
791 public function testEmptyParam_get($Entity) {
792
793 if (in_array($Entity, $this->toBeImplemented['get'])) {
794 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
795 return;
796 }
797 $result = civicrm_api($Entity, 'Get', array());
798 $this->assertEquals(1, $result['is_error']);
799 $this->assertContains("Unknown api version", $result['error_message']);
800 }
801
802 /**
803 * @dataProvider entities_get
804 * @param $Entity
805 */
806 public function testEmptyParam_getString($Entity) {
807
808 if (in_array($Entity, $this->toBeImplemented['get'])) {
809 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
810 return;
811 }
812 $result = $this->callAPIFailure($Entity, 'Get', 'string');
813 $this->assertEquals(2000, $result['error_code']);
814 $this->assertEquals('Input variable `params` is not an array', $result['error_message']);
815 }
816
817 /**
818 * @dataProvider entities_get
819 * @Xdepends testEmptyParam_get // no need to test the simple if the empty doesn't work/is skipped. doesn't seem to work
820 * @param $Entity
821 */
822 public function testSimple_get($Entity) {
823 // $this->markTestSkipped("test gives core error on test server (but not on our locals). Skip until we can get server to pass");
824 if (in_array($Entity, $this->toBeImplemented['get'])) {
825 return;
826 }
827 $result = civicrm_api($Entity, 'Get', array('version' => 3));
828 // @TODO: list the get that have mandatory params
829 if ($result['is_error']) {
830 $this->assertContains("Mandatory key(s) missing from params array", $result['error_message']);
831 // either id or contact_id or entity_id is one of the field missing
832 $this->assertContains("id", $result['error_message']);
833 }
834 else {
835 $this->assertEquals(3, $result['version']);
836 $this->assertArrayHasKey('count', $result);
837 $this->assertArrayHasKey('values', $result);
838 }
839 }
840
841 /**
842 * @dataProvider custom_data_incl_non_std_entities_get
843 * @param $entityName
844 */
845 public function testCustomDataGet($entityName) {
846 $this->quickCleanup(array('civicrm_uf_match'));
847 $this->createLoggedInUser();// so subsidiary activities are created
848
849 $entitiesWithNamingIssues = [
850 'MailingComponent' => 'Component',
851 'SmsProvider' => 'Provider',
852 'AclRole' => 'EntityRole',
853 'MailingEventQueue' => 'Queue',
854 ];
855
856 $usableName = !empty($entitiesWithNamingIssues[$entityName]) ? $entitiesWithNamingIssues[$entityName] : $entityName;
857 $optionName = CRM_Core_DAO_AllCoreTables::getTableForClass(CRM_Core_DAO_AllCoreTables::getFullName($usableName));
858
859 if (!isset(CRM_Core_BAO_CustomQuery::$extendsMap[$entityName])) {
860 $createdValue = $this->callAPISuccess('OptionValue', 'create', [
861 'option_group_id' => 'cg_extend_objects',
862 'label' => $usableName,
863 'value' => $usableName,
864 'name' => $optionName,
865 ]);
866 }
867 // We are not passing 'check_permissions' so the the more limited permissions *should* be
868 // ignored but per CRM-17700 there is a history of custom data applying permissions when it shouldn't.
869 CRM_Core_Config::singleton()->userPermissionClass->permissions = array('access CiviCRM', 'view my contact');
870 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $usableName . 'Test.php');
871 $customFieldName = 'custom_' . $ids['custom_field_id'];
872 $objects = $this->getMockableBAOObjects($entityName, 1);
873 $params = array('id' => $objects[0]->id, 'custom_' . $ids['custom_field_id'] => "custom string");
874 $result = $this->callAPISuccess($entityName, 'create', $params);
875
876 $this->assertTrue(isset($result['id']), 'no id on ' . $entityName);
877 $getParams = array('id' => $result['id'], 'return' => array($customFieldName));
878 $check = $this->callAPISuccess($entityName, 'get', $getParams);
879 $this->assertTrue(!empty($check['values'][$check['id']][$customFieldName]), 'Custom data not present for ' . $entityName);
880 $this->assertEquals("custom string", $check['values'][$check['id']][$customFieldName], 'Custom data not present for ' . $entityName);
881
882 $this->customFieldDelete($ids['custom_field_id']);
883 $this->customGroupDelete($ids['custom_group_id']);
884 $this->callAPISuccess($entityName, 'delete', array('id' => $result['id']));
885 $this->quickCleanup(array('civicrm_uf_match'));
886 if (!empty($createdValue)) {
887 $this->callAPISuccess('OptionValue', 'delete', ['id' => $createdValue['id']]);
888 }
889 }
890
891 /**
892 * @dataProvider entities_get
893 * @param $Entity
894 */
895 public function testAcceptsOnlyID_get($Entity) {
896 // big random number. fun fact: if you multiply it by pi^e, the result is another random number, but bigger ;)
897 $nonExistantID = 30867307034;
898 if (in_array($Entity, $this->toBeImplemented['get'])
899 || in_array($Entity, $this->toBeSkipped_getByID())
900 ) {
901 return;
902 }
903
904 // FIXME
905 // the below function returns different values and hence an early return
906 // we'll fix this once beta1 is released
907 // return;
908
909 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
910
911 if ($result['is_error']) {
912 // just to get a clearer message in the log
913 $this->assertEquals("only id should be enough", $result['error_message']);
914 }
915 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
916 $this->assertEquals(0, $result['count']);
917 }
918 }
919
920 /**
921 * Create two entities and make sure we can fetch them individually by ID.
922 *
923 * @dataProvider entities_get
924 *
925 * limitations include the problem with avoiding loops when creating test objects -
926 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
927 * Currency - only seems to support US
928 * @param $entityName
929 */
930 public function testByID_get($entityName) {
931 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
932 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
933 return;
934 }
935
936 $baos = $this->getMockableBAOObjects($entityName);
937 list($baoObj1, $baoObj2) = $baos;
938
939 // fetch first by ID
940 $result = $this->callAPISuccess($entityName, 'get', array(
941 'id' => $baoObj1->id,
942 ));
943
944 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
945 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
946 $this->assertEquals(1, count($result['values']));
947
948 // fetch second by ID
949 $result = $this->callAPISuccess($entityName, 'get', array(
950 'id' => $baoObj2->id,
951 ));
952 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
953 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
954 $this->assertEquals(1, count($result['values']));
955 }
956
957 /**
958 * Ensure that the "get" operation accepts limiting the #result records.
959 *
960 * TODO Consider making a separate entity list ("entities_getlimit")
961 * For the moment, the "entities_updatesingle" list should give a good
962 * sense for which entities support createTestObject
963 *
964 * @dataProvider entities_getlimit
965 *
966 * @param string $entityName
967 */
968 public function testLimit($entityName) {
969 $cases = array(); // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
970 $cases[] = array(
971 array('options' => array('limit' => NULL)),
972 30,
973 'check that a NULL limit returns unlimited',
974 );
975 $cases[] = array(
976 array('options' => array('limit' => FALSE)),
977 30,
978 'check that a FALSE limit returns unlimited',
979 );
980 $cases[] = array(
981 array('options' => array('limit' => 0)),
982 30,
983 'check that a 0 limit returns unlimited',
984 );
985 $cases[] = array(
986 array('options' => array('limit' => 5)),
987 5,
988 'check that a 5 limit returns 5',
989 );
990 $cases[] = array(
991 array(),
992 25,
993 'check that no limit returns 25',
994 );
995
996 $baoString = _civicrm_api3_get_BAO($entityName);
997 if (empty($baoString)) {
998 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
999 return;
1000 }
1001
1002 // make 30 test items -- 30 > 25 (the default limit)
1003 $ids = array();
1004 for ($i = 0; $i < 30; $i++) {
1005 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1006 $ids[] = $baoObj->id;
1007 $baoObj->free();
1008 }
1009
1010 // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
1011 foreach ($cases as $case) {
1012 $this->checkLimitAgainstExpected($entityName, $case[0], $case[1], $case[2]);
1013
1014 //non preferred / legacy syntax
1015 if (isset($case[0]['options']['limit'])) {
1016 $this->checkLimitAgainstExpected($entityName, array('rowCount' => $case[0]['options']['limit']), $case[1], $case[2]);
1017 $this->checkLimitAgainstExpected($entityName, array('option_limit' => $case[0]['options']['limit']), $case[1], $case[2]);
1018 $this->checkLimitAgainstExpected($entityName, array('option.limit' => $case[0]['options']['limit']), $case[1], $case[2]);
1019 }
1020 }
1021 foreach ($ids as $id) {
1022 CRM_Core_DAO::deleteTestObjects($baoString, array('id' => $id));
1023 }
1024 $baoObj->free();
1025 }
1026
1027 /**
1028 * Ensure that the "get" operation accepts limiting the #result records.
1029 *
1030 * @dataProvider entities_getSqlOperators
1031 *
1032 * @param string $entityName
1033 */
1034 public function testSqlOperators($entityName) {
1035 $toBeIgnored = array_merge($this->toBeImplemented['get'],
1036 $this->deprecatedAPI,
1037 $this->toBeSkipped_get(TRUE),
1038 $this->toBeSkipped_getByID()
1039 );
1040 if (in_array($entityName, $toBeIgnored)) {
1041 return;
1042 }
1043
1044 $baoString = _civicrm_api3_get_BAO($entityName);
1045
1046 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0), 'return' => 'id'));
1047 $entities = array_keys($entities['values']);
1048 $totalEntities = count($entities);
1049 if ($totalEntities < 3) {
1050 $ids = array();
1051 for ($i = 0; $i < 3 - $totalEntities; $i++) {
1052 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1053 $ids[] = $baoObj->id;
1054 $baoObj->free();
1055 }
1056 $totalEntities = 3;
1057 }
1058 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0)));
1059 $entities = array_keys($entities['values']);
1060 $this->assertGreaterThan(2, $totalEntities);
1061 $this->callAPISuccess($entityName, 'getsingle', array('id' => array('IN' => array($entities[0]))));
1062 $this->callAPISuccessGetCount($entityName, array('id' => array('NOT IN' => array($entities[0]))), $totalEntities - 1);
1063 $this->callAPISuccessGetCount($entityName, array('id' => array('>' => $entities[0])), $totalEntities - 1);
1064 }
1065
1066 /**
1067 * Check that get fetches an appropriate number of results.
1068 *
1069 * @param string $entityName
1070 * Name of entity to test.
1071 * @param array $params
1072 * @param int $limit
1073 * @param string $message
1074 */
1075 public function checkLimitAgainstExpected($entityName, $params, $limit, $message) {
1076 $result = $this->callAPISuccess($entityName, 'get', $params);
1077 if ($limit == 30) {
1078 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1079 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
1080 }
1081 else {
1082 $this->assertEquals($limit, $result['count'], $message);
1083 $this->assertEquals($limit, count($result['values']), $message);
1084 }
1085 }
1086
1087 /**
1088 * Create two entities and make sure we can fetch them individually by ID (e.g. using "contact_id=>2"
1089 * or "group_id=>4")
1090 *
1091 * @dataProvider entities_get
1092 *
1093 * limitations include the problem with avoiding loops when creating test objects -
1094 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1095 * Currency - only seems to support US
1096 * @param $entityName
1097 * @throws \PHPUnit_Framework_IncompleteTestError
1098 */
1099 public function testByIDAlias_get($entityName) {
1100 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
1101 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1102 return;
1103 }
1104
1105 $baoString = _civicrm_api3_get_BAO($entityName);
1106 if (empty($baoString)) {
1107 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1108 return;
1109 }
1110
1111 $idFieldName = _civicrm_api_get_entity_name_from_camel($entityName) . '_id';
1112
1113 // create entities
1114 $baoObj1 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1115 $this->assertTrue(is_int($baoObj1->id), 'check first id');
1116 $this->deletableTestObjects[$baoString][] = $baoObj1->id;
1117 $baoObj2 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1118 $this->assertTrue(is_int($baoObj2->id), 'check second id');
1119 $this->deletableTestObjects[$baoString][] = $baoObj2->id;
1120
1121 // fetch first by ID
1122 $result = civicrm_api($entityName, 'get', array(
1123 'version' => 3,
1124 $idFieldName => $baoObj1->id,
1125 ));
1126 $this->assertAPISuccess($result);
1127 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
1128 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
1129 $this->assertEquals(1, count($result['values']));
1130
1131 // fetch second by ID
1132 $result = civicrm_api($entityName, 'get', array(
1133 'version' => 3,
1134 $idFieldName => $baoObj2->id,
1135 ));
1136 $this->assertAPISuccess($result);
1137 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1138 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1139 $this->assertEquals(1, count($result['values']));
1140 }
1141
1142 /**
1143 * @dataProvider entities_get
1144 * @param $Entity
1145 */
1146 public function testNonExistantID_get($Entity) {
1147 // cf testAcceptsOnlyID_get
1148 $nonExistantID = 30867307034;
1149 if (in_array($Entity, $this->toBeImplemented['get'])) {
1150 return;
1151 }
1152
1153 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
1154
1155 // redundant with testAcceptsOnlyID_get
1156 if ($result['is_error']) {
1157 return;
1158 }
1159
1160 $this->assertArrayHasKey('version', $result);
1161 $this->assertEquals(3, $result['version']);
1162 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
1163 $this->assertEquals(0, $result['count']);
1164 }
1165 }
1166
1167 /* ---- testing the _create ---- */
1168
1169 /**
1170 * @dataProvider toBeSkipped_create
1171 entities that don't need a create action
1172 * @param $Entity
1173 */
1174 public function testNotImplemented_create($Entity) {
1175 $result = civicrm_api($Entity, 'Create', array('version' => 3));
1176 $this->assertEquals(1, $result['is_error']);
1177 $this->assertContains(strtolower("API ($Entity, Create) does not exist"), strtolower($result['error_message']));
1178 }
1179
1180 /**
1181 * @dataProvider entities
1182 * @expectedException CiviCRM_API3_Exception
1183 * @param $Entity
1184 */
1185 public function testWithoutParam_create($Entity) {
1186 if ($Entity === 'Setting') {
1187 $this->markTestSkipped('It seems OK for setting to skip here as it silently sips invalid params');
1188 }
1189 elseif ($Entity === 'Mailing') {
1190 $this->markTestSkipped('It seems OK for "Mailing" to skip here because you can create empty drafts');
1191 }
1192 // should create php complaining that a param is missing
1193 civicrm_api3($Entity, 'Create');
1194 }
1195
1196 /**
1197 * @dataProvider entities_create
1198 *
1199 * Check that create doesn't work with an invalid
1200 * @param $Entity
1201 * @throws \PHPUnit_Framework_IncompleteTestError
1202 */
1203 public function testInvalidSort_get($Entity) {
1204 $invalidEntitys = array('ActivityType', 'Setting', 'System');
1205 if (in_array($Entity, $invalidEntitys)) {
1206 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently sips invalid params');
1207 }
1208 $result = $this->callAPIFailure($Entity, 'get', array('options' => array('sort' => 'sleep(1)')));
1209 }
1210
1211 /**
1212 * @dataProvider entities_create
1213 *
1214 * Check that create doesn't work with an invalid
1215 * @param $Entity
1216 * @throws \PHPUnit_Framework_IncompleteTestError
1217 */
1218 public function testValidSortSingleArrayById_get($Entity) {
1219 $invalidEntitys = array('ActivityType', 'Setting', 'System');
1220 $tests = array(
1221 'id' => '_id',
1222 'id desc' => '_id desc',
1223 'id DESC' => '_id DESC',
1224 'id ASC' => '_id ASC',
1225 'id asc' => '_id asc');
1226 foreach ($tests as $test => $expected) {
1227 if (in_array($Entity, $invalidEntitys)) {
1228 $this->markTestSkipped('It seems OK for ' . $Entity . ' to skip here as it silently ignores passed in params');
1229 }
1230 $params = array('sort' => array($test));
1231 $result = _civicrm_api3_get_options_from_params($params, FALSE, $Entity, 'get');
1232 $lowercase_entity = _civicrm_api_get_entity_name_from_camel($Entity);
1233 $this->assertEquals($lowercase_entity . $expected, $result['sort']);
1234 }
1235 }
1236
1237 /**
1238 * @dataProvider entities_create
1239 *
1240 * Check that create doesn't work with an invalid
1241 * @param $Entity
1242 * @throws \PHPUnit_Framework_IncompleteTestError
1243 */
1244 public function testInvalidID_create($Entity) {
1245 // turn test off for noew
1246 $this->markTestIncomplete("Entity [ $Entity ] cannot be mocked - no known DAO");
1247 return;
1248 if (in_array($Entity, $this->toBeImplemented['create'])) {
1249 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1250 return;
1251 }
1252 $result = $this->callAPIFailure($Entity, 'Create', array('id' => 999));
1253 }
1254
1255 /**
1256 * @dataProvider entities
1257 */
1258 public function testCreateWrongTypeParamTag_create() {
1259 $result = civicrm_api("Tag", 'Create', 'this is not a string');
1260 $this->assertEquals(1, $result['is_error']);
1261 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1262 }
1263
1264 /**
1265 * @dataProvider entities_updatesingle
1266 *
1267 * limitations include the problem with avoiding loops when creating test objects -
1268 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1269 * Currency - only seems to support US
1270 * @param $entityName
1271 */
1272 public function testCreateSingleValueAlter($entityName) {
1273 if (in_array($entityName, $this->toBeImplemented['create'])) {
1274 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1275 return;
1276 }
1277
1278 $baoString = _civicrm_api3_get_BAO($entityName);
1279 $this->assertNotEmpty($baoString, $entityName);
1280 $this->assertNotEmpty($entityName, $entityName);
1281 $fieldsGet = $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'get', 'options' => array('get_options' => 'all')));
1282 if ($entityName != 'Pledge') {
1283 $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'create', 'options' => array('get_options' => 'all')));
1284 }
1285 $fields = $fields['values'];
1286 $return = array_keys($fieldsGet['values']);
1287 $valuesNotToReturn = $this->getKnownUnworkablesUpdateSingle($entityName, 'break_return');
1288 // these can't be requested as return values
1289 $entityValuesThatDoNotWork = array_merge(
1290 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_update'),
1291 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_return'),
1292 $valuesNotToReturn
1293 );
1294
1295 $return = array_diff($return, $valuesNotToReturn);
1296 $baoObj = new CRM_Core_DAO();
1297 $baoObj->createTestObject($baoString, array('currency' => 'USD'), 2, 0);
1298
1299 $getEntities = $this->callAPISuccess($entityName, 'get', array(
1300 'sequential' => 1,
1301 'return' => $return,
1302 'options' => array(
1303 'sort' => 'id DESC',
1304 'limit' => 2,
1305 ),
1306 ));
1307
1308 // lets use first rather than assume only one exists
1309 $entity = $getEntities['values'][0];
1310 $entity2 = $getEntities['values'][1];
1311 $this->deletableTestObjects[$baoString][] = $entity['id'];
1312 $this->deletableTestObjects[$baoString][] = $entity2['id'];
1313 foreach ($fields as $field => $specs) {
1314 $resetFKTo = NULL;
1315 $fieldName = $field;
1316 if ($field == 'currency' || $field == 'id' || $field == strtolower($entityName) . '_id'
1317 || in_array($field, $entityValuesThatDoNotWork)
1318 ) {
1319 //@todo id & entity_id are correct but we should fix currency & frequency_day
1320 continue;
1321 }
1322 $this->assertArrayHasKey('type', $specs, "the _spec function for $entityName field $field does not specify the type");
1323 switch ($specs['type']) {
1324 case CRM_Utils_Type::T_DATE:
1325 $entity[$fieldName] = '2012-05-20';
1326 break;
1327
1328 case CRM_Utils_Type::T_TIMESTAMP:
1329 case 12:
1330 $entity[$fieldName] = '2012-05-20 03:05:20';
1331 break;
1332
1333 case CRM_Utils_Type::T_STRING:
1334 case CRM_Utils_Type::T_BLOB:
1335 case CRM_Utils_Type::T_MEDIUMBLOB:
1336 case CRM_Utils_Type::T_TEXT:
1337 case CRM_Utils_Type::T_LONGTEXT:
1338 case CRM_Utils_Type::T_EMAIL:
1339 if ($fieldName == 'form_values' && $entityName == 'SavedSearch') {
1340 // This is a hack for the SavedSearch API.
1341 // It expects form_values to be an array.
1342 // If you want to fix this, you should definitely read this forum
1343 // post.
1344 // http://forum.civicrm.org/index.php/topic,33990.0.html
1345 // See also my question on the CiviCRM Stack Exchange:
1346 // https://civicrm.stackexchange.com/questions/3437
1347 $entity[$fieldName] = array('sort_name' => "SortName2");
1348 }
1349 else {
1350 $entity[$fieldName] = substr('New String', 0, CRM_Utils_Array::Value('maxlength', $specs, 100));
1351 if ($fieldName == 'email') {
1352 $entity[$fieldName] = strtolower($entity[$fieldName]);
1353 }
1354 // typecast with array to satisfy changes made in CRM-13160
1355 if ($entityName == 'MembershipType' && in_array($fieldName, array(
1356 'relationship_type_id',
1357 'relationship_direction',
1358 ))
1359 ) {
1360 $entity[$fieldName] = (array) $entity[$fieldName];
1361 }
1362 }
1363 break;
1364
1365 case CRM_Utils_Type::T_INT:
1366 // probably created with a 1
1367 if ($fieldName == 'weight') {
1368 $entity[$fieldName] = 2;
1369 }
1370 elseif (!empty($specs['FKClassName'])) {
1371 if ($specs['FKClassName'] == $baoString) {
1372 $entity[$fieldName] = (string) $entity2['id'];
1373 }
1374 else {
1375 if (!empty($entity[$fieldName])) {
1376 $resetFKTo = array($fieldName => $entity[$fieldName]);
1377 }
1378 $entity[$fieldName] = (string) empty($entity2[$field]) ? '' : $entity2[$field];
1379 //todo - there isn't always something set here - & our checking on unset values is limited
1380 if (empty($entity[$field])) {
1381 unset($entity[$field]);
1382 }
1383 }
1384 }
1385 else {
1386 $entity[$fieldName] = '6';
1387 }
1388 break;
1389
1390 case CRM_Utils_Type::T_BOOLEAN:
1391 // probably created with a 1
1392 $entity[$fieldName] = '0';
1393 break;
1394
1395 case CRM_Utils_Type::T_FLOAT:
1396 case CRM_Utils_Type::T_MONEY:
1397 $entity[$field] = '22.75';
1398 break;
1399
1400 case CRM_Utils_Type::T_URL:
1401 $entity[$field] = 'warm.beer.com';
1402 }
1403 if (empty($specs['FKClassName']) && (!empty($specs['pseudoconstant']) || !empty($specs['options']))) {
1404 $options = CRM_Utils_Array::value('options', $specs, array());
1405 if (!$options) {
1406 //eg. pdf_format id doesn't ship with any
1407 if (isset($specs['pseudoconstant']['optionGroupName'])) {
1408 $optionValue = $this->callAPISuccess('option_value', 'create', array(
1409 'option_group_id' => $specs['pseudoconstant']['optionGroupName'],
1410 'label' => 'new option value',
1411 'sequential' => 1,
1412 ));
1413 $optionValue = $optionValue['values'];
1414 $keyColumn = CRM_Utils_Array::value('keyColumn', $specs['pseudoconstant'], 'value');
1415 $options[$optionValue[0][$keyColumn]] = 'new option value';
1416 }
1417 }
1418 $entity[$field] = array_rand($options);
1419 }
1420 if (!empty($specs['FKClassName']) && !empty($specs['pseudoconstant'])) {
1421 // in the weird situation where a field has both an fk and pseudoconstant defined,
1422 // e.g. campaign_id field, need to flush caches.
1423 // FIXME: Why doesn't creating a campaign clear caches?
1424 civicrm_api3($entityName, 'getfields', array('cache_clear' => 1));
1425 }
1426 $updateParams = array(
1427 'id' => $entity['id'],
1428 $field => isset($entity[$field]) ? $entity[$field] : NULL,
1429 );
1430 if (!empty($specs['serialize'])) {
1431 $updateParams[$field] = $entity[$field] = (array) $specs['serialize'];
1432 }
1433 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
1434 //api has special handling on these 2 fields for backward compatibility reasons
1435 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1436 }
1437 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1438 //api has special handling on these 2 fields for backward compatibility reasons
1439 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1440 }
1441
1442 $update = $this->callAPISuccess($entityName, 'create', $updateParams);
1443 $checkParams = array(
1444 'id' => $entity['id'],
1445 'sequential' => 1,
1446 'return' => $return,
1447 'options' => array(
1448 'sort' => 'id DESC',
1449 'limit' => 2,
1450 ),
1451 );
1452
1453 $checkEntity = $this->callAPISuccess($entityName, 'getsingle', $checkParams);
1454 if (!empty($specs['serialize']) && !is_array($checkEntity[$field])) {
1455 // Put into serialized format for comparison if 'get' has not returned serialized.
1456 $entity[$field] = CRM_Core_DAO::serializeField($checkEntity[$field], $specs['serialize']);
1457 }
1458
1459 $this->assertAPIArrayComparison($entity, $checkEntity, array(), "checking if $fieldName was correctly updated\n" . print_r(array(
1460 'update-params' => $updateParams,
1461 'update-result' => $update,
1462 'getsingle-params' => $checkParams,
1463 'getsingle-result' => $checkEntity,
1464 'expected entity' => $entity,
1465 ), TRUE));
1466 if ($resetFKTo) {
1467 //reset the foreign key fields because otherwise our cleanup routine fails & some other unexpected stuff can kick in
1468 $entity = array_merge($entity, $resetFKTo);
1469 $updateParams = array_merge($updateParams, $resetFKTo);
1470 $this->callAPISuccess($entityName, 'create', $updateParams);
1471 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
1472 //api has special handling on these 2 fields for backward compatibility reasons
1473 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1474 }
1475 if (isset($updateParams['next_sched_contribution_date']) && in_array($entityName, array('ContributionRecur'))) {
1476 //api has special handling on these 2 fields for backward compatibility reasons
1477 $entity['next_sched_contribution'] = $updateParams['next_sched_contribution_date'];
1478 }
1479 }
1480 }
1481 $baoObj->free();
1482 }
1483
1484 /* ---- testing the _getFields ---- */
1485
1486 /* ---- testing the _delete ---- */
1487
1488 /**
1489 * @dataProvider toBeSkipped_delete
1490 entities that don't need a delete action
1491 * @param $Entity
1492 */
1493 public function testNotImplemented_delete($Entity) {
1494 $nonExistantID = 151416349;
1495 $result = civicrm_api($Entity, 'Delete', array('version' => 3, 'id' => $nonExistantID));
1496 $this->assertEquals(1, $result['is_error']);
1497 $this->assertContains(strtolower("API ($Entity, Delete) does not exist"), strtolower($result['error_message']));
1498 }
1499
1500 /**
1501 * @dataProvider entities
1502 * @param $Entity
1503 */
1504 public function testWithoutParam_delete($Entity) {
1505 // should delete php complaining that a param is missing
1506 try {
1507 $result = civicrm_api($Entity, 'Delete');
1508 $this->fail('Expected an exception. No exception was thrown.');
1509 }
1510 // As of php7.1 a new Exception is thrown by PHP ArgumentCountError when not enough params are passed.
1511 catch (ArgumentCountError $e) {
1512 /* ok */
1513 }
1514 catch (PHPUnit_Framework_Error $e) {
1515 /* ok */
1516 }
1517 }
1518
1519 /**
1520 * @dataProvider entities_delete
1521 * @param $Entity
1522 */
1523 public function testEmptyParam_delete($Entity) {
1524 if (in_array($Entity, $this->toBeImplemented['delete'])) {
1525 // $this->markTestIncomplete("civicrm_api3_{$Entity}_delete to be implemented");
1526 return;
1527 }
1528 $result = civicrm_api($Entity, 'Delete', array());
1529 $this->assertEquals(1, $result['is_error']);
1530 $this->assertContains("Unknown api version", $result['error_message']);
1531 }
1532
1533 /**
1534 * @dataProvider entities_delete
1535 * @param $Entity
1536 * @throws \PHPUnit_Framework_IncompleteTestError
1537 */
1538 public function testInvalidID_delete($Entity) {
1539 $result = $this->callAPIFailure($Entity, 'Delete', array('id' => 999999));
1540 }
1541
1542 /**
1543 * @dataProvider entities
1544 */
1545 public function testDeleteWrongTypeParamTag_delete() {
1546 $result = civicrm_api("Tag", 'Delete', 'this is not a string');
1547 $this->assertEquals(1, $result['is_error']);
1548 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1549 }
1550
1551 /**
1552 * Create two entities and make sure delete action only deletes one!
1553 *
1554 * @dataProvider entities_delete
1555 *
1556 * limitations include the problem with avoiding loops when creating test objects -
1557 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1558 * Currency - only seems to support US
1559 * @param $entityName
1560 * @throws \PHPUnit_Framework_IncompleteTestError
1561 */
1562 public function testByID_delete($entityName) {
1563 // turn test off for noew
1564 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1565 return;
1566
1567 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
1568 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1569 return;
1570 }
1571 $startCount = $this->callAPISuccess($entityName, 'getcount', array());
1572 $createcount = 2;
1573 $baos = $this->getMockableBAOObjects($entityName, $createcount);
1574 list($baoObj1, $baoObj2) = $baos;
1575
1576 // make sure exactly 2 exist
1577 $result = $this->callAPISuccess($entityName, 'getcount', array(),
1578 $createcount + $startCount
1579 );
1580
1581 $this->callAPISuccess($entityName, 'delete', array('id' => $baoObj2->id));
1582 //make sure 1 less exists now
1583 $result = $this->callAPISuccess($entityName, 'getcount', array(),
1584 ($createcount + $startCount) - 1
1585 );
1586
1587 //make sure id #1 exists
1588 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj1->id),
1589 1
1590 );
1591 //make sure id #2 desn't exist
1592 $result = $this->callAPISuccess($entityName, 'getcount', array('id' => $baoObj2->id),
1593 0
1594 );
1595 }
1596
1597 /**
1598 * Create two entities and make sure delete action only deletes one!
1599 *
1600 * @dataProvider entities_getfields
1601 * @param $entity
1602 */
1603 public function testGetfieldsHasTitle($entity) {
1604 $entities = $this->getEntitiesSupportingCustomFields();
1605 if (in_array($entity, $entities)) {
1606 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $entity . 'Test.php');
1607 }
1608 $actions = $this->callAPISuccess($entity, 'getactions', array());
1609 foreach ($actions['values'] as $action) {
1610 if (substr($action, -7) == '_create' || substr($action, -4) == '_get' || substr($action, -7) == '_delete') {
1611 //getactions can't distinguish between contribution_page.create & contribution_page.create
1612 continue;
1613 }
1614 $fields = $this->callAPISuccess($entity, 'getfields', array('action' => $action));
1615 if (!empty($ids) && in_array($action, array('create', 'get'))) {
1616 $this->assertArrayHasKey('custom_' . $ids['custom_field_id'], $fields['values']);
1617 }
1618
1619 foreach ($fields['values'] as $fieldName => $fieldSpec) {
1620 $this->assertArrayHasKey('title', $fieldSpec, "no title for $entity - $fieldName on action $action");
1621 $this->assertNotEmpty($fieldSpec['title'], "empty title for $entity - $fieldName");
1622 }
1623 }
1624 if (!empty($ids)) {
1625 $this->customFieldDelete($ids['custom_field_id']);
1626 $this->customGroupDelete($ids['custom_group_id']);
1627 }
1628 }
1629
1630 /**
1631 * @return array
1632 */
1633 public function getEntitiesSupportingCustomFields() {
1634 $entities = self::custom_data_entities_get();
1635 $returnEntities = array();
1636 foreach ($entities as $entityArray) {
1637 $returnEntities[] = $entityArray[0];
1638 }
1639 return $returnEntities;
1640 }
1641
1642 /**
1643 * @param string $entityName
1644 * @param int $count
1645 *
1646 * @return array
1647 */
1648 private function getMockableBAOObjects($entityName, $count = 2) {
1649 $baoString = _civicrm_api3_get_BAO($entityName);
1650 if (empty($baoString)) {
1651 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
1652 return array();
1653 }
1654 $baos = array();
1655 $i = 0;
1656 while ($i < $count) {
1657 // create entities
1658 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
1659 $this->assertTrue(is_int($baoObj->id), 'check first id');
1660 $this->deletableTestObjects[$baoString][] = $baoObj->id;
1661 $baos[] = $baoObj;
1662 $i++;
1663 }
1664 return $baos;
1665 }
1666
1667 /**
1668 * Verify that HTML metacharacters provided as inputs appear consistently.
1669 * as outputs.
1670 *
1671 * At time of writing, the encoding scheme requires (for example) that an
1672 * event title be partially-HTML-escaped before writing to DB. To provide
1673 * consistency, the API must perform extra encoding and decoding on some
1674 * fields.
1675 *
1676 * In this example, the event 'title' is subject to encoding, but the
1677 * event 'description' is not.
1678 */
1679 public function testEncodeDecodeConsistency() {
1680 // Create example
1681 $createResult = civicrm_api('Event', 'Create', array(
1682 'version' => 3,
1683 'title' => 'CiviCRM <> TheRest',
1684 'description' => 'TheRest <> CiviCRM',
1685 'event_type_id' => 1,
1686 'is_public' => 1,
1687 'start_date' => 20081021,
1688 ));
1689 $this->assertAPISuccess($createResult);
1690 $eventId = $createResult['id'];
1691 $this->assertEquals('CiviCRM <> TheRest', $createResult['values'][$eventId]['title']);
1692 $this->assertEquals('TheRest <> CiviCRM', $createResult['values'][$eventId]['description']);
1693
1694 // Verify "get" handles decoding in result value
1695 $getByIdResult = civicrm_api('Event', 'Get', array(
1696 'version' => 3,
1697 'id' => $eventId,
1698 ));
1699 $this->assertAPISuccess($getByIdResult);
1700 $this->assertEquals('CiviCRM <> TheRest', $getByIdResult['values'][$eventId]['title']);
1701 $this->assertEquals('TheRest <> CiviCRM', $getByIdResult['values'][$eventId]['description']);
1702
1703 // Verify "get" handles encoding in search value
1704 $getByTitleResult = civicrm_api('Event', 'Get', array(
1705 'version' => 3,
1706 'title' => 'CiviCRM <> TheRest',
1707 ));
1708 $this->assertAPISuccess($getByTitleResult);
1709 $this->assertEquals('CiviCRM <> TheRest', $getByTitleResult['values'][$eventId]['title']);
1710 $this->assertEquals('TheRest <> CiviCRM', $getByTitleResult['values'][$eventId]['description']);
1711
1712 // Verify that "getSingle" handles decoding
1713 $getSingleResult = $this->callAPISuccess('Event', 'GetSingle', array(
1714 'id' => $eventId,
1715 ));
1716
1717 $this->assertEquals('CiviCRM <> TheRest', $getSingleResult['title']);
1718 $this->assertEquals('TheRest <> CiviCRM', $getSingleResult['description']);
1719
1720 // Verify that chaining handles decoding
1721 $chainResult = $this->callAPISuccess('Event', 'Get', array(
1722 'id' => $eventId,
1723 'api.event.get' => array(),
1724 ));
1725 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['title']);
1726 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['description']);
1727 $this->assertEquals('CiviCRM <> TheRest', $chainResult['values'][$eventId]['api.event.get']['values'][0]['title']);
1728 $this->assertEquals('TheRest <> CiviCRM', $chainResult['values'][$eventId]['api.event.get']['values'][0]['description']);
1729
1730 // Verify that "setvalue" handles encoding for updates
1731 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1732 'version' => 3,
1733 'id' => $eventId,
1734 'field' => 'title',
1735 'value' => 'setValueTitle: CiviCRM <> TheRest',
1736 ));
1737 $this->assertAPISuccess($setValueTitleResult);
1738 $this->assertEquals('setValueTitle: CiviCRM <> TheRest', $setValueTitleResult['values']['title']);
1739 $setValueDescriptionResult = civicrm_api('Event', 'setvalue', array(
1740 'version' => 3,
1741 'id' => $eventId,
1742 'field' => 'description',
1743 'value' => 'setValueDescription: TheRest <> CiviCRM',
1744 ));
1745 //$this->assertTrue((bool)$setValueDescriptionResult['is_error']); // not supported by setValue
1746 $this->assertEquals('setValueDescription: TheRest <> CiviCRM', $setValueDescriptionResult['values']['description']);
1747 }
1748
1749 /**
1750 * Verify that write operations (create/update) use partial HTML-encoding
1751 *
1752 * In this example, the event 'title' is subject to encoding, but the
1753 * event 'description' is not.
1754 */
1755 public function testEncodeWrite() {
1756 // Create example
1757 $createResult = civicrm_api('Event', 'Create', array(
1758 'version' => 3,
1759 'title' => 'createNew: CiviCRM <> TheRest',
1760 'description' => 'createNew: TheRest <> CiviCRM',
1761 'event_type_id' => 1,
1762 'is_public' => 1,
1763 'start_date' => 20081021,
1764 ));
1765 $this->assertAPISuccess($createResult);
1766 $eventId = $createResult['id'];
1767 $this->assertDBQuery('createNew: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1768 1 => array($eventId, 'Integer'),
1769 ));
1770 $this->assertDBQuery('createNew: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1771 1 => array($eventId, 'Integer'),
1772 ));
1773
1774 // Verify that "create" handles encoding for updates
1775 $createWithIdResult = civicrm_api('Event', 'Create', array(
1776 'version' => 3,
1777 'id' => $eventId,
1778 'title' => 'createWithId: CiviCRM <> TheRest',
1779 'description' => 'createWithId: TheRest <> CiviCRM',
1780 ));
1781 $this->assertAPISuccess($createWithIdResult);
1782 $this->assertDBQuery('createWithId: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1783 1 => array($eventId, 'Integer'),
1784 ));
1785 $this->assertDBQuery('createWithId: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1786 1 => array($eventId, 'Integer'),
1787 ));
1788
1789 // Verify that "setvalue" handles encoding for updates
1790 $setValueTitleResult = civicrm_api('Event', 'setvalue', array(
1791 'version' => 3,
1792 'id' => $eventId,
1793 'field' => 'title',
1794 'value' => 'setValueTitle: CiviCRM <> TheRest',
1795 ));
1796 $this->assertAPISuccess($setValueTitleResult);
1797 $this->assertDBQuery('setValueTitle: CiviCRM &lt;&gt; TheRest', 'SELECT title FROM civicrm_event WHERE id = %1', array(
1798 1 => array($eventId, 'Integer'),
1799 ));
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->assertAPISuccess($setValueDescriptionResult);
1808 $this->assertDBQuery('setValueDescription: TheRest <> CiviCRM', 'SELECT description FROM civicrm_event WHERE id = %1', array(
1809 1 => array($eventId, 'Integer'),
1810 ));
1811 }
1812
1813 }