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