Merge pull request #6515 from johanv/CRM-13725-duplicate_realtionships_check_custom_f...
[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',
92915c55
TO
95 );
96 $this->toBeImplemented['create'] = array(
1c7f266f
TO
97 'Cxn',
98 'CxnApp',
0d57e900 99 'JobLog',
92915c55
TO
100 'SurveyRespondant',
101 'OptionGroup',
102 'MailingRecipients',
103 'UFMatch',
92915c55
TO
104 'CustomSearch',
105 'Extension',
106 'ReportTemplate',
db7de9c1 107 'System',
225d474b 108 'User',
92915c55
TO
109 );
110 $this->toBeImplemented['delete'] = array(
1c7f266f
TO
111 'Cxn',
112 'CxnApp',
0d57e900 113 'JobLog',
92915c55
TO
114 'MembershipPayment',
115 'OptionGroup',
116 'SurveyRespondant',
117 'UFJoin',
118 'UFMatch',
119 'Extension',
db7de9c1 120 'System',
92915c55 121 );
567b2076
EM
122 $this->onlyIDNonZeroCount['get'] = array(
123 'ActivityType',
124 'Entity',
125 'Domain',
126 'Setting',
225d474b 127 'User',
567b2076 128 );
6a488035 129 $this->deprecatedAPI = array('Location', 'ActivityType', 'SurveyRespondant');
4a97890c 130 $this->deletableTestObjects = array();
6a488035
TO
131 }
132
00be9182 133 public function tearDown() {
4a97890c
TO
134 foreach ($this->deletableTestObjects as $entityName => $entities) {
135 foreach ($entities as $entityID) {
136 CRM_Core_DAO::deleteTestObjects($entityName, array('id' => $entityID));
137 }
138 }
139 }
6a488035 140
4cbe18b8 141 /**
db7de9c1
EM
142 * Generate list of all entities.
143 *
144 * @param array $skip
145 * Entities to skip.
4cbe18b8
EM
146 *
147 * @return array
148 */
db7de9c1 149 public static function entities($skip = array()) {
e5dd95b2
TO
150 // The order of operations in here is screwy. In the case where SYNTAX_CONFORMANCE_ENTITIES is
151 // defined, we should be able to parse+return it immediately. However, some weird dependency
152 // crept into the system where civicrm_api('Entity','get') must be called as part of entities()
153 // (even if its return value is ignored).
dcf56200 154
e5dd95b2 155 $tmp = civicrm_api('Entity', 'Get', array('version' => 3));
dcf56200 156 if (getenv('SYNTAX_CONFORMANCE_ENTITIES')) {
e5dd95b2 157 $tmp = array(
21dfd5f5 158 'values' => explode(' ', getenv('SYNTAX_CONFORMANCE_ENTITIES')),
e5dd95b2 159 );
dcf56200
TO
160 }
161
6a488035
TO
162 if (!is_array($skip)) {
163 $skip = array();
164 }
165 $tmp = array_diff($tmp['values'], $skip);
166 $entities = array();
167 foreach ($tmp as $e) {
168 $entities[] = array($e);
169 }
170 return $entities;
171 }
172
4cbe18b8 173 /**
db7de9c1
EM
174 * Get list of entities for get test.
175 *
4cbe18b8
EM
176 * @return array
177 */
6a488035
TO
178 public static function entities_get() {
179 // all the entities, beside the ones flagged
97715495 180 return static::entities(static::toBeSkipped_get(TRUE));
6a488035
TO
181 }
182
4cbe18b8 183 /**
567b2076
EM
184 * Get entities for create tests.
185 *
4cbe18b8
EM
186 * @return array
187 */
6a488035 188 public static function entities_create() {
97715495 189 return static::entities(static::toBeSkipped_create(TRUE));
6a488035
TO
190 }
191
4cbe18b8
EM
192 /**
193 * @return array
194 */
6a488035 195 public static function entities_updatesingle() {
97715495 196 return static::entities(static::toBeSkipped_updatesingle(TRUE));
6a488035
TO
197 }
198
4cbe18b8
EM
199 /**
200 * @return array
201 */
b9af4758
E
202 public static function entities_getlimit() {
203 return static::entities(static::toBeSkipped_getlimit());
204 }
205
ced9bfed
EM
206 /**
207 * Generate list of entities that can be retrieved using SQL operator syntax.
208 *
209 * @return array
210 */
dcf5b21f
EM
211 public static function entities_getSqlOperators() {
212 return static::entities(static::toBeSkipped_getSqlOperators());
213 }
92915c55 214
4cbe18b8
EM
215 /**
216 * @return array
217 */
6a488035 218 public static function entities_delete() {
97715495 219 return static::entities(static::toBeSkipped_delete(TRUE));
6a488035
TO
220 }
221
32dafeec
EM
222 /**
223 * @return array
224 */
225 public static function entities_getfields() {
226 return static::entities(static::toBeSkipped_getfields(TRUE));
227 }
92915c55 228
4cbe18b8
EM
229 /**
230 * @return array
231 */
2fc5f1e7
EM
232 public static function custom_data_entities_get() {
233 return static::custom_data_entities();
234 }
235
4cbe18b8
EM
236 /**
237 * @return array
238 */
2fc5f1e7 239 public static function custom_data_entities() {
82e1689e 240 $entities = CRM_Core_BAO_CustomQuery::$extendsMap;
84fb7424 241 $enabledComponents = Civi::settings()->get('enable_components');
82e1689e 242 $customDataEntities = array();
2fc5f1e7 243 $invalidEntities = array('Individual', 'Organization', 'Household');
2f6264b4 244 $entitiesToFix = array('Case', 'Relationship');
481a74f4 245 foreach ($entities as $entityName => $entity) {
22e263ad 246 if (!in_array($entityName, $invalidEntities)
92915c55
TO
247 && !in_array($entityName, $entitiesToFix)
248 ) {
22e263ad 249 if (!empty(self::$componentMap[$entityName]) && empty($enabledComponents[self::$componentMap[$entityName]])) {
6c6e6187
TO
250 CRM_Core_BAO_ConfigSetting::enableComponent(self::$componentMap[$entityName]);
251 }
252 $customDataEntities[] = array($entityName);
82e1689e
N
253 }
254 }
2fc5f1e7
EM
255 return $customDataEntities;
256 }
257
4cbe18b8 258 /**
567b2076
EM
259 * Get entities to be skipped on get tests.
260 *
4cbe18b8
EM
261 * @param bool $sequential
262 *
263 * @return array
264 */
6a488035 265 public static function toBeSkipped_get($sequential = FALSE) {
92915c55
TO
266 $entitiesWithoutGet = array(
267 'MailingEventSubscribe',
268 'MailingEventConfirm',
269 'MailingEventResubscribe',
270 'MailingEventUnsubscribe',
567b2076 271 'Location',
92915c55 272 );
6a488035
TO
273 if ($sequential === TRUE) {
274 return $entitiesWithoutGet;
275 }
276 $entities = array();
277 foreach ($entitiesWithoutGet as $e) {
278 $entities[] = array($e);
279 }
280 return $entities;
281 }
b7d29345 282
b14ce773 283 /**
fd786d03
EM
284 * Get entities to be skipped for get call.
285 *
b14ce773 286 * Mailing Contact Just doesn't support id. We have always insisted on finding a way to
4a2db77c 287 * support id in API but in this case the underlying tables are crying out for a restructure
225d474b
EM
288 * & it just doesn't make sense.
289 *
290 * User doesn't support get By ID because the user id is actually the CMS user ID & is not part of
291 * CiviCRM - so can only be tested through UserTest - not SyntaxConformanceTest.
b7d29345 292 *
8ab09481 293 * Entity doesn't support get By ID because it simply gives the result of string Entites in CiviCRM
294 *
4a2db77c 295 * @param bool $sequential
b7d29345 296 *
a6c01b45
CW
297 * @return array
298 * Entities that cannot be retrieved by ID
b14ce773 299 */
300 public static function toBeSkipped_getByID($sequential = FALSE) {
8ab09481 301 return array('MailingContact', 'User', 'Attachment', 'Entity');
b14ce773 302 }
6a488035 303
4cbe18b8
EM
304 /**
305 * @param bool $sequential
306 *
307 * @return array
308 */
6a488035 309 public static function toBeSkipped_create($sequential = FALSE) {
21eb0c57 310 $entitiesWithoutCreate = array('Constant', 'Entity', 'Location', 'Profile', 'MailingRecipients');
6a488035
TO
311 if ($sequential === TRUE) {
312 return $entitiesWithoutCreate;
313 }
314 $entities = array();
315 foreach ($entitiesWithoutCreate as $e) {
316 $entities[] = array($e);
317 }
318 return $entities;
319 }
320
4cbe18b8
EM
321 /**
322 * @param bool $sequential
323 *
324 * @return array
325 */
6a488035 326 public static function toBeSkipped_delete($sequential = FALSE) {
92915c55
TO
327 $entitiesWithout = array(
328 'MailingContact',
329 'MailingEventConfirm',
330 'MailingEventResubscribe',
331 'MailingEventSubscribe',
332 'MailingEventUnsubscribe',
333 'MailingRecipients',
334 'Constant',
335 'Entity',
336 'Location',
337 'Domain',
338 'Profile',
339 'CustomValue',
92c99a4a 340 'Setting',
225d474b 341 'User',
92915c55 342 );
6a488035
TO
343 if ($sequential === TRUE) {
344 return $entitiesWithout;
345 }
346 $entities = array();
347 foreach ($entitiesWithout as $e) {
348 $entities[] = array($e);
349 }
350 return $entities;
351 }
b7d29345 352
32dafeec
EM
353 /**
354 * @param bool $sequential
355 *
356 * @return array
357 * @todo add metadata for ALL these entities
358 */
359 public static function toBeSkipped_getfields($sequential = FALSE) {
17eeaef9 360 $entitiesWithMetadataNotYetFixed = array('ReportTemplate', 'CustomSearch');
32dafeec 361 if ($sequential === TRUE) {
6c6e6187 362 return $entitiesWithMetadataNotYetFixed;
32dafeec
EM
363 }
364 $entities = array();
365 foreach ($entitiesWithMetadataNotYetFixed as $e) {
366 $entities[] = array($e);
367 }
368 return $entities;
369 }
92915c55 370
6c6e6187 371 /**
eceb18cc 372 * Generate list of entities to test for get by id functions.
6c6e6187 373 * @param bool $sequential
a6c01b45
CW
374 * @return array
375 * Entities to be skipped
6c6e6187 376 */
b07a3bf9 377 public static function toBeSkipped_automock($sequential = FALSE) {
92915c55
TO
378 $entitiesWithoutGet = array(
379 'MailingContact',
380 'EntityTag',
381 'Participant',
382 'ParticipantPayment',
383 'Setting',
384 'SurveyRespondant',
385 'MailingRecipients',
386 'CustomSearch',
387 'Extension',
388 'ReportTemplate',
af9b09df 389 'System',
92915c55 390 );
b07a3bf9
TO
391 if ($sequential === TRUE) {
392 return $entitiesWithoutGet;
393 }
394 $entities = array();
395 foreach ($entitiesWithoutGet as $e) {
396 $entities[] = array($e);
397 }
398 return $entities;
399 }
400
401
b7d29345 402 /**
6c6e6187 403 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
1e1fdcf6
EM
404 * @param bool $sequential
405 * @return array
6c6e6187 406 */
6a488035
TO
407 public static function toBeSkipped_updatesingle($sequential = FALSE) {
408 $entitiesWithout = array(
92915c55
TO
409 'Attachment',
410 // pseudo-entity; testUpdateSingleValueAlter doesn't introspect properly on it. Multiple magic fields
6a488035
TO
411 'Mailing',
412 'MailingGroup',
413 'MailingJob',
414 'Address',
415 'MailingEventUnsubscribe',
416 'MailingEventSubscribe',
417 'Constant',
418 'Entity',
419 'Location',
420 'Domain',
421 'Profile',
422 'CustomValue',
423 'SurveyRespondant',
6a488035
TO
424 'UFMatch',
425 'UFJoin',
426 'UFField',
427 'OptionValue',
428 'Relationship',
429 'RelationshipType',
6a488035
TO
430 'Note',
431 'OptionGroup',
432 'Membership',
6a488035
TO
433 'Group',
434 'GroupOrganization',
435 'GroupNesting',
6a488035
TO
436 'File',
437 'EntityTag',
438 'CustomField',
439 'CustomGroup',
440 'Contribution',
6a488035
TO
441 'ActivityType',
442 'MailingEventConfirm',
443 'Case',
444 'Contact',
445 'ContactType',
446 'MailingEventResubscribe',
447 'UFGroup',
448 'Activity',
6a488035
TO
449 'Event',
450 'GroupContact',
451 'MembershipPayment',
452 'Participant',
453 'ParticipantPayment',
454 'LineItem',
6a488035
TO
455 'PledgePayment',
456 'ContributionPage',
457 'Phone',
faacb3e4 458 'PaymentProcessor',
6a488035 459 'Setting',
b14ce773 460 'MailingContact',
af9b09df 461 'SystemLog',
92915c55 462 //skip this because it doesn't make sense to update logs,
6a488035
TO
463 );
464 if ($sequential === TRUE) {
465 return $entitiesWithout;
466 }
467 $entities = array();
468 foreach ($entitiesWithout as $e) {
469 $entities[] = array(
470 $e,
471 );
472 }
473 return array('pledge');
474 return $entities;
475 }
476
b7d29345 477 /**
b9af4758 478 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
b7d29345 479 */
b9af4758
E
480 public static function toBeSkipped_getlimit() {
481 $entitiesWithout = array(
92915c55
TO
482 'Case',
483 //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
484 'EntityTag',
485 // non-standard api - has inappropriate mandatory fields & doesn't implement limit
486 'Event',
487 // failed 'check that a 5 limit returns 5' - probably is_template field is wrong or something, or could be limit doesn't work right
488 'Extension',
489 // can't handle creating 25
490 'Note',
491 // fails on 5 limit - probably a set up problem
492 'Setting',
493 //a bit of a pseudoapi - keys by domain
b9af4758
E
494 );
495 return $entitiesWithout;
496 }
497
dcf5b21f
EM
498 /**
499 * At this stage exclude the ones that don't pass & add them as we can troubleshoot them
500 */
501 public static function toBeSkipped_getSqlOperators() {
502 $entitiesWithout = array(
6c6e6187 503 'Case', //case api has non-std mandatory fields one of (case_id, contact_id, activity_id, contact_id)
0ae1863a 504 'Contact', // on the todo list!
dcf5b21f
EM
505 'EntityTag', // non-standard api - has inappropriate mandatory fields & doesn't implement limit
506 'Extension', // can't handle creating 25
507 'Note', // note has a default get that isn't implemented in createTestObject -meaning you don't 'get' them
dcf5b21f
EM
508 'Setting', //a bit of a pseudoapi - keys by domain
509 );
510 return $entitiesWithout;
511 }
512
0f583c8f
EM
513 /**
514 * @param $entity
515 * @param $key
516 *
517 * @return array
518 */
9b873358 519 public function getKnownUnworkablesUpdateSingle($entity, $key) {
6a488035
TO
520 // can't update values are values for which updates don't result in the value being changed
521 $knownFailures = array(
03fe1a00
TO
522 'ActionSchedule' => array(
523 'cant_update' => array(
524 'group_id',
525 ),
526 ),
0f583c8f
EM
527 'ActivityContact' => array(
528 'cant_update' => array(
92915c55
TO
529 'activity_id',
530 //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
531 ),
532 ),
6a488035
TO
533 'Address' => array(
534 'cant_update' => array(
535 'state_province_id', //issues with country id - need to ensure same country
6c6e6187 536 'master_id', //creates relationship
6a488035 537 ),
21dfd5f5 538 'cant_return' => array(),
6a488035 539 ),
6ead217b
E
540 'Batch' => array(
541 'cant_update' => array(
542 'entity_table', // believe this field is defined in error
543 ),
544 'cant_return' => array(
545 'entity_table',
21dfd5f5 546 ),
6ead217b 547 ),
1753bd71
TO
548 'CaseType' => array(
549 'cant_update' => array(
550 'definition',
21dfd5f5 551 ),
1753bd71 552 ),
95520636
TO
553 'MembershipBlock' => array(
554 'cant_update' => array(
555 // The fake/auto-generated values leave us unable to properly cleanup fake data
556 'entity_type',
557 'entity_id',
21dfd5f5 558 ),
95520636 559 ),
7d543448 560 'ContributionSoft' => array(
561 'cant_update' => array(
562 // can't be changed through api
563 'pcp_id',
564 ),
565 ),
7629d5a6 566 'Email' => array(
567 'cant_update' => array(
568 // This is being legitimately manipulated to always have a valid primary - skip.
569 'is_primary',
570 ),
571 ),
3523b615 572 'Navigation' => array(
573 'cant_update' => array(
574 // Weight is deliberately altered when this is changed - skip.
575 'parent_id',
576 ),
577 ),
9859f345 578 'LocationType' => array(
579 'cant_update' => array(
580 // I'm on the fence about whether the test should skip or the behaviour is wrong.
581 // display_name is set to match name if display_name is not provided. It would be more 'normal'
582 // to only calculate a default IF id is not set - but perhaps the current behaviour is kind
583 // of what someone updating the name expects..
584 'name',
585 ),
586 ),
6a488035
TO
587 'Pledge' => array(
588 'cant_update' => array(
589 'pledge_original_installment_amount',
590 'installments',
591 'original_installment_amount',
592 'next_pay_date',
af9b09df 593 'amount', // can't be changed through API,
6a488035
TO
594 ),
595 'break_return' => array(// if these are passed in they are retrieved from the wrong table
596 'honor_contact_id',
597 'cancel_date',
598 'contribution_page_id',
599 'financial_account_id',
600 'financial_type_id',
21dfd5f5 601 'currency',
6a488035
TO
602 ),
603 'cant_return' => array(// can't be retrieved from api
604 'honor_type_id', //due to uniquename missing
605 'end_date',
606 'modified_date',
607 'acknowledge_date',
608 'start_date',
609 'frequency_day',
610 'currency',
611 'max_reminders',
612 'initial_reminder_day',
613 'additional_reminder_day',
614 'frequency_unit',
615 'pledge_contribution_page_id',
616 'pledge_status_id',
617 'pledge_campaign_id',
b06d9acd 618 'pledge_financial_type_id',
21dfd5f5 619 ),
6a488035
TO
620 ),
621 'PaymentProcessorType' => array(
622 'cant_update' => array(
623 'billing_mode',
624 ),
6c6e6187
TO
625 'break_return' => array(),
626 'cant_return' => array(),
6a488035 627 ),
db232378
EM
628 'PriceFieldValue' => array(
629 'cant_update' => array(
630 'weight', //won't update as there is no 1 in the same price set
631 ),
632 ),
e6e7e540 633 'ReportInstance' => array(
634 // View mode is part of the navigation which is not retrieved by the api.
635 'cant_return' => array('view_mode'),
636 ),
5ba7b9fd
JV
637 'SavedSearch' => array(
638 // I think the fields below are generated based on form_values.
639 'cant_update' => array(
640 'search_custom_id',
641 'where_clause',
642 'select_tables',
643 'where_tables',
dafc75f8
JV
644 ),
645 ),
d47a6f4a
J
646 'StatusPreference' => array(
647 'break_return' => array(
648 'ignore_severity',
649 ),
650 ),
6a488035 651 );
9b873358 652 if (empty($knownFailures[$entity]) || empty($knownFailures[$entity][$key])) {
6a488035
TO
653 return array();
654 }
655 return $knownFailures[$entity][$key];
656 }
657
be2e0c6a 658 /* ----- testing the _get ----- */
6a488035
TO
659
660 /**
661 * @dataProvider toBeSkipped_get
be2e0c6a 662 * Entities that don't need a get action
1e1fdcf6 663 * @param $Entity
6a488035
TO
664 */
665 public function testNotImplemented_get($Entity) {
666 $result = civicrm_api($Entity, 'Get', array('version' => 3));
ba4a1892 667 $this->assertEquals(1, $result['is_error']);
311873a0 668 // $this->assertContains("API ($Entity, Get) does not exist", $result['error_message']);
6c6e6187 669 $this->assertRegExp('/API (.*) does not exist/', $result['error_message']);
6a488035
TO
670 }
671
672 /**
673 * @dataProvider entities
674 * @expectedException PHPUnit_Framework_Error
1e1fdcf6 675 * @param $Entity
6a488035
TO
676 */
677 public function testWithoutParam_get($Entity) {
678 // should get php complaining that a param is missing
679 $result = civicrm_api($Entity, 'Get');
680 }
681
682 /**
683 * @dataProvider entities
1e1fdcf6 684 * @param $Entity
6a488035
TO
685 */
686 public function testGetFields($Entity) {
bd6658bd 687 if (in_array($Entity, $this->deprecatedAPI) || $Entity == 'Entity' || $Entity == 'CustomValue') {
6a488035
TO
688 return;
689 }
690
691 $result = civicrm_api($Entity, 'getfields', array('version' => 3));
692 $this->assertTrue(is_array($result['values']), "$Entity ::get fields doesn't return values array in line " . __LINE__);
693 foreach ($result['values'] as $key => $value) {
694 $this->assertTrue(is_array($value), $Entity . "::" . $key . " is not an array in line " . __LINE__);
695 }
696 }
697
698 /**
699 * @dataProvider entities_get
1e1fdcf6 700 * @param $Entity
6a488035
TO
701 */
702 public function testEmptyParam_get($Entity) {
703
704 if (in_array($Entity, $this->toBeImplemented['get'])) {
705 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
706 return;
707 }
708 $result = civicrm_api($Entity, 'Get', array());
ba4a1892 709 $this->assertEquals(1, $result['is_error']);
6a488035
TO
710 $this->assertContains("Mandatory key(s) missing from params array", $result['error_message']);
711 }
92915c55 712
6a488035
TO
713 /**
714 * @dataProvider entities_get
1e1fdcf6 715 * @param $Entity
6a488035
TO
716 */
717 public function testEmptyParam_getString($Entity) {
718
719 if (in_array($Entity, $this->toBeImplemented['get'])) {
720 // $this->markTestIncomplete("civicrm_api3_{$Entity}_get to be implemented");
721 return;
722 }
d0e1eff2 723 $result = $this->callAPIFailure($Entity, 'Get', 'string');
6a488035
TO
724 $this->assertEquals(2000, $result['error_code']);
725 $this->assertEquals('Input variable `params` is not an array', $result['error_message']);
726 }
92915c55 727
6a488035
TO
728 /**
729 * @dataProvider entities_get
730 * @Xdepends testEmptyParam_get // no need to test the simple if the empty doesn't work/is skipped. doesn't seem to work
1e1fdcf6 731 * @param $Entity
6a488035
TO
732 */
733 public function testSimple_get($Entity) {
734 // $this->markTestSkipped("test gives core error on test server (but not on our locals). Skip until we can get server to pass");
6a488035
TO
735 if (in_array($Entity, $this->toBeImplemented['get'])) {
736 return;
737 }
738 $result = civicrm_api($Entity, 'Get', array('version' => 3));
739 // @TODO: list the get that have mandatory params
740 if ($result['is_error']) {
741 $this->assertContains("Mandatory key(s) missing from params array", $result['error_message']);
742 // either id or contact_id or entity_id is one of the field missing
743 $this->assertContains("id", $result['error_message']);
744 }
745 else {
746 $this->assertEquals(3, $result['version']);
747 $this->assertArrayHasKey('count', $result);
748 $this->assertArrayHasKey('values', $result);
749 }
750 }
751
2fc5f1e7
EM
752 /**
753 * @dataProvider custom_data_entities_get
1e1fdcf6 754 * @param $entityName
2fc5f1e7
EM
755 */
756 public function testCustomDataGet($entityName) {
757 $this->createLoggedInUser();// so subsidiary activities are created
758 $ids = $this->entityCustomGroupWithSingleFieldCreate(__FUNCTION__, $entityName . 'Test.php');
759 $customFieldName = 'custom_' . $ids['custom_field_id'];
760 $objects = $this->getMockableBAOObjects($entityName, 1);
761 $params = array('id' => $objects[0]->id, 'custom_' . $ids['custom_field_id'] => "custom string");
762 $result = $this->callAPISuccess($entityName, 'create', $params);
763
764 $getParams = array('id' => $result['id'], 'return' => array($customFieldName));
765 $check = $this->callAPISuccess($entityName, 'get', $getParams);
766 $this->assertEquals("custom string", $check['values'][$check['id']][$customFieldName]);
767
768 $this->customFieldDelete($ids['custom_field_id']);
769 $this->customGroupDelete($ids['custom_group_id']);
770 $this->callAPISuccess($entityName, 'delete', array('id' => $result['id']));
b2c4e136 771 $this->quickCleanup(array('civicrm_uf_match'));
2fc5f1e7
EM
772 }
773
6a488035
TO
774 /**
775 * @dataProvider entities_get
1e1fdcf6 776 * @param $Entity
6a488035
TO
777 */
778 public function testAcceptsOnlyID_get($Entity) {
779 // big random number. fun fact: if you multiply it by pi^e, the result is another random number, but bigger ;)
780 $nonExistantID = 30867307034;
b14ce773 781 if (in_array($Entity, $this->toBeImplemented['get'])
92915c55 782 || in_array($Entity, $this->toBeSkipped_getByID())
b14ce773 783 ) {
6a488035
TO
784 return;
785 }
786
787 // FIXME
788 // the below function returns different values and hence an early return
789 // we'll fix this once beta1 is released
790 // return;
791
c679daca 792 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
6a488035
TO
793
794 if ($result['is_error']) {
795 // just to get a clearer message in the log
796 $this->assertEquals("only id should be enough", $result['error_message']);
797 }
798 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
799 $this->assertEquals(0, $result['count']);
800 }
801 }
802
803 /**
eceb18cc 804 * Create two entities and make sure we can fetch them individually by ID.
4a97890c
TO
805 *
806 * @dataProvider entities_get
807 *
808 * limitations include the problem with avoiding loops when creating test objects -
809 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
810 * Currency - only seems to support US
1e1fdcf6 811 * @param $entityName
4a97890c
TO
812 */
813 public function testByID_get($entityName) {
b07a3bf9 814 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
4a97890c
TO
815 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
816 return;
817 }
818
18eee50e 819 $baos = $this->getMockableBAOObjects($entityName);
820 list($baoObj1, $baoObj2) = $baos;
4a97890c
TO
821
822 // fetch first by ID
6ead217b 823 $result = $this->callAPISuccess($entityName, 'get', array(
4a97890c
TO
824 'id' => $baoObj1->id,
825 ));
6ead217b 826
4a97890c
TO
827 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
828 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
829 $this->assertEquals(1, count($result['values']));
830
831 // fetch second by ID
6ead217b 832 $result = $this->callAPISuccess($entityName, 'get', array(
4a97890c
TO
833 'id' => $baoObj2->id,
834 ));
4a97890c
TO
835 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
836 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
837 $this->assertEquals(1, count($result['values']));
838 }
839
b9af4758
E
840 /**
841 * Ensure that the "get" operation accepts limiting the #result records.
842 *
843 * TODO Consider making a separate entity list ("entities_getlimit")
844 * For the moment, the "entities_updatesingle" list should give a good
845 * sense for which entities support createTestObject
846 *
847 * @dataProvider entities_getlimit
8efea814 848 *
c490a46a 849 * @param string $entityName
b9af4758 850 */
00be9182 851 public function testLimit($entityName) {
b9af4758
E
852 $cases = array(); // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
853 $cases[] = array(
854 array('options' => array('limit' => NULL)),
ebddc2d9
EM
855 30,
856 'check that a NULL limit returns unlimited',
b9af4758
E
857 );
858 $cases[] = array(
859 array('options' => array('limit' => FALSE)),
ebddc2d9
EM
860 30,
861 'check that a FALSE limit returns unlimited',
b9af4758
E
862 );
863 $cases[] = array(
864 array('options' => array('limit' => 0)),
ebddc2d9
EM
865 30,
866 'check that a 0 limit returns unlimited',
b9af4758
E
867 );
868 $cases[] = array(
869 array('options' => array('limit' => 5)),
870 5,
871 'check that a 5 limit returns 5',
872 );
873 $cases[] = array(
874 array(),
875 25,
876 'check that no limit returns 25',
877 );
878
6252a38c 879 $baoString = _civicrm_api3_get_BAO($entityName);
b9af4758
E
880 if (empty($baoString)) {
881 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
882 return;
883 }
884
885 // make 30 test items -- 30 > 25 (the default limit)
4038f8ec 886 $ids = array();
b9af4758 887 for ($i = 0; $i < 30; $i++) {
8d5544c5 888 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
4038f8ec 889 $ids[] = $baoObj->id;
b9af4758
E
890 }
891
892 // each case is array(0 => $inputtedApiOptions, 1 => $expectedResultCount)
893 foreach ($cases as $case) {
ebddc2d9 894 $this->checkLimitAgainstExpected($entityName, $case[0], $case[1], $case[2]);
a85a667f
EM
895
896 //non preferred / legacy syntax
22e263ad 897 if (isset($case[0]['options']['limit'])) {
ebddc2d9
EM
898 $this->checkLimitAgainstExpected($entityName, array('rowCount' => $case[0]['options']['limit']), $case[1], $case[2]);
899 $this->checkLimitAgainstExpected($entityName, array('option_limit' => $case[0]['options']['limit']), $case[1], $case[2]);
900 $this->checkLimitAgainstExpected($entityName, array('option.limit' => $case[0]['options']['limit']), $case[1], $case[2]);
a85a667f 901 }
b9af4758 902 }
4038f8ec
TO
903 foreach ($ids as $id) {
904 CRM_Core_DAO::deleteTestObjects($baoString, array('id' => $id));
905 }
8d5544c5 906 $baoObj->free();
b9af4758
E
907 }
908
dcf5b21f
EM
909 /**
910 * Ensure that the "get" operation accepts limiting the #result records.
911 *
912 * @dataProvider entities_getSqlOperators
913 *
c490a46a 914 * @param string $entityName
dcf5b21f 915 */
00be9182 916 public function testSqlOperators($entityName) {
8ab09481 917 $toBeIgnored = array_merge($this->toBeImplemented['get'],
918 $this->deprecatedAPI,
919 $this->toBeSkipped_get(TRUE),
920 $this->toBeSkipped_getByID()
921 );
922 if (in_array($entityName, $toBeIgnored)) {
dcf5b21f
EM
923 return;
924 }
8ab09481 925
926 $baoString = _civicrm_api3_get_BAO($entityName);
927
dcf5b21f
EM
928 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0), 'return' => 'id'));
929 $entities = array_keys($entities['values']);
930 $totalEntities = count($entities);
931 if ($totalEntities < 3) {
932 $ids = array();
933 for ($i = 0; $i < 3 - $totalEntities; $i++) {
934 $baoObj = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
935 $ids[] = $baoObj->id;
936 }
937 $totalEntities = 3;
938 }
939 $entities = $this->callAPISuccess($entityName, 'get', array('options' => array('limit' => 0)));
940 $entities = array_keys($entities['values']);
941 $this->assertGreaterThan(2, $totalEntities);
942 $this->callAPISuccess($entityName, 'getsingle', array('id' => array('IN' => array($entities[0]))));
943 $this->callAPISuccessGetCount($entityName, array('id' => array('NOT IN' => array($entities[0]))), $totalEntities - 1);
944 $this->callAPISuccessGetCount($entityName, array('id' => array('>' => $entities[0])), $totalEntities - 1);
945 }
946
ebddc2d9 947 /**
eceb18cc 948 * Check that get fetches an appropriate number of results.
ebddc2d9 949 *
e16033b4
TO
950 * @param string $entityName
951 * Name of entity to test.
dcf5b21f 952 * @param array $params
e16033b4 953 * @param int $limit
dcf5b21f 954 * @param string $message
ebddc2d9 955 */
00be9182 956 public function checkLimitAgainstExpected($entityName, $params, $limit, $message) {
ebddc2d9 957 $result = $this->callAPISuccess($entityName, 'get', $params);
22e263ad 958 if ($limit == 30) {
ebddc2d9
EM
959 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
960 $this->assertGreaterThanOrEqual($limit, $result['count'], $message);
961 }
962 else {
963 $this->assertEquals($limit, $result['count'], $message);
964 $this->assertEquals($limit, count($result['values']), $message);
965 }
966 }
92915c55 967
afb0ff51
TO
968 /**
969 * Create two entities and make sure we can fetch them individually by ID (e.g. using "contact_id=>2"
970 * or "group_id=>4")
971 *
972 * @dataProvider entities_get
973 *
974 * limitations include the problem with avoiding loops when creating test objects -
975 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
976 * Currency - only seems to support US
1e1fdcf6
EM
977 * @param $entityName
978 * @throws \PHPUnit_Framework_IncompleteTestError
afb0ff51
TO
979 */
980 public function testByIDAlias_get($entityName) {
c4de8b59 981 if (in_array($entityName, self::toBeSkipped_automock(TRUE))) {
afb0ff51
TO
982 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
983 return;
984 }
985
6252a38c 986 $baoString = _civicrm_api3_get_BAO($entityName);
afb0ff51
TO
987 if (empty($baoString)) {
988 $this->markTestIncomplete("Entity [$entityName] cannot be mocked - no known DAO");
989 return;
990 }
991
c4de8b59
TO
992 $idFieldName = _civicrm_api_get_entity_name_from_camel($entityName) . '_id';
993
afb0ff51
TO
994 // create entities
995 $baoObj1 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
a1a2a83d 996 $this->assertTrue(is_int($baoObj1->id), 'check first id');
afb0ff51
TO
997 $this->deletableTestObjects[$baoString][] = $baoObj1->id;
998 $baoObj2 = CRM_Core_DAO::createTestObject($baoString, array('currency' => 'USD'));
a1a2a83d 999 $this->assertTrue(is_int($baoObj2->id), 'check second id');
afb0ff51
TO
1000 $this->deletableTestObjects[$baoString][] = $baoObj2->id;
1001
1002 // fetch first by ID
1003 $result = civicrm_api($entityName, 'get', array(
1004 'version' => 3,
c4de8b59 1005 $idFieldName => $baoObj1->id,
afb0ff51
TO
1006 ));
1007 $this->assertAPISuccess($result);
1008 $this->assertTrue(!empty($result['values'][$baoObj1->id]), 'Should find first object by id');
1009 $this->assertEquals($baoObj1->id, $result['values'][$baoObj1->id]['id'], 'Should find id on first object');
1010 $this->assertEquals(1, count($result['values']));
1011
1012 // fetch second by ID
1013 $result = civicrm_api($entityName, 'get', array(
1014 'version' => 3,
c4de8b59 1015 $idFieldName => $baoObj2->id,
afb0ff51
TO
1016 ));
1017 $this->assertAPISuccess($result);
1018 $this->assertTrue(!empty($result['values'][$baoObj2->id]), 'Should find second object by id');
1019 $this->assertEquals($baoObj2->id, $result['values'][$baoObj2->id]['id'], 'Should find id on second object');
1020 $this->assertEquals(1, count($result['values']));
1021 }
1022
1023 /**
6a488035 1024 * @dataProvider entities_get
1e1fdcf6 1025 * @param $Entity
6a488035
TO
1026 */
1027 public function testNonExistantID_get($Entity) {
1028 // cf testAcceptsOnlyID_get
1029 $nonExistantID = 30867307034;
1030 if (in_array($Entity, $this->toBeImplemented['get'])) {
1031 return;
1032 }
1033
1034 $result = civicrm_api($Entity, 'Get', array('version' => 3, 'id' => $nonExistantID));
1035
1036 // redundant with testAcceptsOnlyID_get
1037 if ($result['is_error']) {
1038 return;
1039 }
1040
6a488035
TO
1041 $this->assertArrayHasKey('version', $result);
1042 $this->assertEquals(3, $result['version']);
1043 if (!in_array($Entity, $this->onlyIDNonZeroCount['get'])) {
1044 $this->assertEquals(0, $result['count']);
1045 }
1046 }
1047
a1a2a83d 1048 /* ---- testing the _create ---- */
6a488035
TO
1049
1050 /**
1051 * @dataProvider toBeSkipped_create
6c6e6187 1052 entities that don't need a create action
1e1fdcf6 1053 * @param $Entity
6a488035
TO
1054 */
1055 public function testNotImplemented_create($Entity) {
1056 $result = civicrm_api($Entity, 'Create', array('version' => 3));
ba4a1892 1057 $this->assertEquals(1, $result['is_error']);
311873a0 1058 $this->assertContains(strtolower("API ($Entity, Create) does not exist"), strtolower($result['error_message']));
6a488035
TO
1059 }
1060
1061 /**
1062 * @dataProvider entities
1063 * @expectedException PHPUnit_Framework_Error
1e1fdcf6 1064 * @param $Entity
6a488035
TO
1065 */
1066 public function testWithoutParam_create($Entity) {
1067 // should create php complaining that a param is missing
1068 $result = civicrm_api($Entity, 'Create');
1069 }
1070
1071 /**
1072 * @dataProvider entities_create
1e1fdcf6
EM
1073 * @param $Entity
1074 * @throws \PHPUnit_Framework_IncompleteTestError
6a488035
TO
1075 */
1076 public function testEmptyParam_create($Entity) {
e4f46be0 1077 $this->markTestIncomplete("fixing this test to test the api functions fails on numerous tests
f27f2724 1078 which will either create a completely blank entity (batch, participant status) or
18eee50e 1079 have a damn good crack at it (e.g mailing job). Marking this as incomplete beats false success");
18eee50e 1080 return;
6a488035
TO
1081 if (in_array($Entity, $this->toBeImplemented['create'])) {
1082 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1083 return;
1084 }
18eee50e 1085 $result = $this->callAPIFailure($Entity, 'Create', array());
6a488035
TO
1086 $this->assertContains("Mandatory key(s) missing from params array", $result['error_message']);
1087 }
1088
18eee50e 1089 /**
1090 * @dataProvider entities_create
1091 *
1092 * Check that create doesn't work with an invalid
1e1fdcf6
EM
1093 * @param $Entity
1094 * @throws \PHPUnit_Framework_IncompleteTestError
18eee50e 1095 */
1096 public function testInvalidID_create($Entity) {
1097 // turn test off for noew
6ead217b 1098 $this->markTestIncomplete("Entity [ $Entity ] cannot be mocked - no known DAO");
18eee50e 1099 return;
1100 if (in_array($Entity, $this->toBeImplemented['create'])) {
1101 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1102 return;
1103 }
1104 $result = $this->callAPIFailure($Entity, 'Create', array('id' => 999));
1105 }
1106
6a488035
TO
1107 /**
1108 * @dataProvider entities
1109 */
1110 public function testCreateWrongTypeParamTag_create() {
1111 $result = civicrm_api("Tag", 'Create', 'this is not a string');
ba4a1892 1112 $this->assertEquals(1, $result['is_error']);
6a488035
TO
1113 $this->assertEquals("Input variable `params` is not an array", $result['error_message']);
1114 }
1115
1116 /**
1117 * @dataProvider entities_updatesingle
1118 *
1119 * limitations include the problem with avoiding loops when creating test objects -
1120 * hence FKs only set by createTestObject when required. e.g parent_id on campaign is not being followed through
1121 * Currency - only seems to support US
1e1fdcf6 1122 * @param $entityName
6a488035
TO
1123 */
1124 public function testCreateSingleValueAlter($entityName) {
1125 if (in_array($entityName, $this->toBeImplemented['create'])) {
1126 // $this->markTestIncomplete("civicrm_api3_{$Entity}_create to be implemented");
1127 return;
1128 }
1129
6252a38c 1130 $baoString = _civicrm_api3_get_BAO($entityName);
6a488035
TO
1131 $this->assertNotEmpty($baoString, $entityName);
1132 $this->assertNotEmpty($entityName, $entityName);
905fd0e9 1133 $fieldsGet = $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'get', 'options' => array('get_options' => 'all')));
9b873358 1134 if ($entityName != 'Pledge') {
905fd0e9 1135 $fields = $this->callAPISuccess($entityName, 'getfields', array('action' => 'create', 'options' => array('get_options' => 'all')));
6a488035
TO
1136 }
1137 $fields = $fields['values'];
39bc176e 1138 $return = array_keys($fieldsGet['values']);
6a488035 1139 $valuesNotToReturn = $this->getKnownUnworkablesUpdateSingle($entityName, 'break_return');
6c6e6187 1140 // these can't be requested as return values
1fd111c8 1141 $entityValuesThatDoNotWork = array_merge(
92915c55
TO
1142 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_update'),
1143 $this->getKnownUnworkablesUpdateSingle($entityName, 'cant_return'),
1144 $valuesNotToReturn
1145 );
6a488035 1146
6c6e6187 1147 $return = array_diff($return, $valuesNotToReturn);
6a488035
TO
1148 $baoObj = new CRM_Core_DAO();
1149 $baoObj->createTestObject($baoString, array('currency' => 'USD'), 2, 0);
39bc176e
EM
1150
1151 $getEntities = $this->callAPISuccess($entityName, 'get', array(
6ead217b
E
1152 'sequential' => 1,
1153 'return' => $return,
1154 'options' => array(
1155 'sort' => 'id DESC',
1156 'limit' => 2,
1157 ),
1158 ));
a8699624 1159
6a488035 1160 // lets use first rather than assume only one exists
39bc176e
EM
1161 $entity = $getEntities['values'][0];
1162 $entity2 = $getEntities['values'][1];
1163 $this->deletableTestObjects[$baoString][] = $entity['id'];
1164 $this->deletableTestObjects[$baoString][] = $entity2['id'];
6a488035 1165 foreach ($fields as $field => $specs) {
a8699624 1166 $resetFKTo = NULL;
6a488035
TO
1167 $fieldName = $field;
1168 if (!empty($specs['uniquename'])) {
1169 $fieldName = $specs['uniquename'];
1170 }
1171 if ($field == 'currency' || $field == 'id' || $field == strtolower($entityName) . '_id'
92915c55
TO
1172 || in_array($field, $entityValuesThatDoNotWork)
1173 ) {
6a488035
TO
1174 //@todo id & entity_id are correct but we should fix currency & frequency_day
1175 continue;
1176 }
0ce6d639 1177 $this->assertArrayHasKey('type', $specs, "the _spec function for $entityName field $field does not specify the type");
6a488035
TO
1178 switch ($specs['type']) {
1179 case CRM_Utils_Type::T_DATE:
6a488035
TO
1180 $entity[$fieldName] = '2012-05-20';
1181 break;
6c6e6187 1182
b6afca8f 1183 case CRM_Utils_Type::T_TIMESTAMP:
6a488035
TO
1184 case 12:
1185 $entity[$fieldName] = '2012-05-20 03:05:20';
1186 break;
1187
1188 case CRM_Utils_Type::T_STRING:
1189 case CRM_Utils_Type::T_BLOB:
1190 case CRM_Utils_Type::T_MEDIUMBLOB:
1191 case CRM_Utils_Type::T_TEXT:
1192 case CRM_Utils_Type::T_LONGTEXT:
1193 case CRM_Utils_Type::T_EMAIL:
833478f1
JV
1194 if ($fieldName == 'form_values' && $entityName == 'SavedSearch') {
1195 // This is a hack for the SavedSearch API.
1196 // It expects form_values to be an array.
f1d23743
JV
1197 // If you want to fix this, you should definitely read this forum
1198 // post.
1199 // http://forum.civicrm.org/index.php/topic,33990.0.html
1200 // See also my question on the CiviCRM Stack Exchange:
1201 // https://civicrm.stackexchange.com/questions/3437
e29aa052 1202 $entity[$fieldName] = array('sort_name' => "SortName2");
f1d23743
JV
1203 }
1204 else {
1205 $entity[$fieldName] = substr('New String', 0, CRM_Utils_Array::Value('maxlength', $specs, 100));
7629d5a6 1206 if ($fieldName == 'email') {
1207 $entity[$fieldName] = strtolower($entity[$fieldName]);
1208 }
638c59ed
KJ
1209 // typecast with array to satisfy changes made in CRM-13160
1210 if ($entityName == 'MembershipType' && in_array($fieldName, array(
1211 'relationship_type_id',
1212 'relationship_direction',
1213 ))
1214 ) {
1215 $entity[$fieldName] = (array) $entity[$fieldName];
1216 }
f1d23743 1217 }
6a488035
TO
1218 break;
1219
1220 case CRM_Utils_Type::T_INT:
1221 // probably created with a 1
a8699624
EM
1222 if ($fieldName == 'weight') {
1223 $entity[$fieldName] = 2;
1224 }
1225 elseif (!empty($specs['FKClassName'])) {
9b873358 1226 if ($specs['FKClassName'] == $baoString) {
6a488035
TO
1227 $entity[$fieldName] = (string) $entity2['id'];
1228 }
92e4c2a5 1229 else {
0298287b 1230 $uniqueName = CRM_Utils_Array::value('uniqueName', $specs, $fieldName);
a8699624 1231 if (!empty($entity[$fieldName])) {
0298287b 1232 $resetFKTo = array($fieldName => $entity[$fieldName], $uniqueName => $entity[$fieldName]);
a8699624 1233 }
6ead217b 1234 $entity[$fieldName] = (string) empty($entity2[$field]) ? CRM_Utils_Array::value($uniqueName, $entity2) : $entity2[$field];
6c6e6187 1235 //todo - there isn't always something set here - & our checking on unset values is limited
6a488035
TO
1236 if (empty($entity[$field])) {
1237 unset($entity[$field]);
1238 }
1239 }
1240 }
a8699624
EM
1241 else {
1242 $entity[$fieldName] = '6';
1243 }
6a488035
TO
1244 break;
1245
6a488035
TO
1246 case CRM_Utils_Type::T_BOOLEAN:
1247 // probably created with a 1
1248 $entity[$fieldName] = '0';
1249 break;
1250
1251 case CRM_Utils_Type::T_FLOAT:
1252 case CRM_Utils_Type::T_MONEY:
edd31a24 1253 $entity[$field] = '22.75';
6a488035
TO
1254 break;
1255
1256 case CRM_Utils_Type::T_URL:
1257 $entity[$field] = 'warm.beer.com';
1258 }
905fd0e9
CW
1259 if (empty($specs['FKClassName']) && (!empty($specs['pseudoconstant']) || !empty($specs['options']))) {
1260 $options = CRM_Utils_Array::value('options', $specs, array());
1261 if (!$options) {
edd31a24 1262 //eg. pdf_format id doesn't ship with any
22e263ad 1263 if (isset($specs['pseudoconstant']['optionGroupName'])) {
92915c55 1264 $optionValue = $this->callAPISuccess('option_value', 'create', array(
905fd0e9 1265 'option_group_id' => $specs['pseudoconstant']['optionGroupName'],
af9b09df 1266 'label' => 'new option value',
905fd0e9 1267 'sequential' => 1,
92915c55 1268 ));
905fd0e9 1269 $optionValue = $optionValue['values'];
34e9aa63
CW
1270 $keyColumn = CRM_Utils_Array::value('keyColumn', $specs['pseudoconstant'], 'value');
1271 $options[$optionValue[0][$keyColumn]] = 'new option value';
edd31a24 1272 }
3d3ef918 1273 }
905fd0e9
CW
1274 $entity[$field] = array_rand($options);
1275 }
1276 if (!empty($specs['FKClassName']) && !empty($specs['pseudoconstant'])) {
1277 // in the weird situation where a field has both an fk and pseudoconstant defined,
1278 // e.g. campaign_id field, need to flush caches.
1279 // FIXME: Why doesn't creating a campaign clear caches?
1280 civicrm_api3($entityName, 'getfields', array('cache_clear' => 1));
6a488035
TO
1281 }
1282 $updateParams = array(
6a488035 1283 'id' => $entity['id'],
6ead217b 1284 $field => isset($entity[$field]) ? $entity[$field] : NULL,
6a488035 1285 );
22e263ad 1286 if (isset($updateParams['financial_type_id']) && in_array($entityName, array('Grant'))) {
deb562a8
EM
1287 //api has special handling on these 2 fields for backward compatibility reasons
1288 $entity['contribution_type_id'] = $updateParams['financial_type_id'];
1289 }
6a488035 1290
0298287b 1291 if (!empty($specs['uniqueName'])) {
1292 $entity[$specs['uniqueName']] = $entity[$specs['name']];
1293 }
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}