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