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