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