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