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