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