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