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