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