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