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