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