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