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