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