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