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