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