Merge pull request #19856 from colemanw/customFieldLabels
[civicrm-core.git] / tests / phpunit / api / v4 / Entity / ConformanceTest.php
CommitLineData
19b53e5b
C
1<?php
2
380f3545
TO
3/*
4 +--------------------------------------------------------------------+
7d61e75f 5 | Copyright CiviCRM LLC. All rights reserved. |
380f3545 6 | |
7d61e75f
TO
7 | This work is published under the GNU AGPLv3 license with some |
8 | permitted exceptions and without any warranty. For full license |
9 | and copyright information, see https://civicrm.org/licensing |
380f3545
TO
10 +--------------------------------------------------------------------+
11 */
12
13/**
14 *
15 * @package CRM
ca5cec67 16 * @copyright CiviCRM LLC https://civicrm.org/licensing
380f3545
TO
17 */
18
19
19b53e5b
C
20namespace api\v4\Entity;
21
22use Civi\Api4\Entity;
19b53e5b
C
23use api\v4\UnitTestCase;
24
25/**
26 * @group headless
27 */
28class ConformanceTest extends UnitTestCase {
29
30755fcb 30 use \api\v4\Traits\TableDropperTrait;
19b53e5b
C
31 use \api\v4\Traits\OptionCleanupTrait {
32 setUp as setUpOptionCleanup;
33 }
34
35 /**
36 * @var \api\v4\Service\TestCreationParameterProvider
37 */
38 protected $creationParamProvider;
39
40 /**
41 * Set up baseline for testing
42 */
43 public function setUp() {
44 $tablesToTruncate = [
45 'civicrm_custom_group',
46 'civicrm_custom_field',
47 'civicrm_group',
48 'civicrm_event',
49 'civicrm_participant',
50 ];
51 $this->dropByPrefix('civicrm_value_myfavorite');
52 $this->cleanup(['tablesToTruncate' => $tablesToTruncate]);
53 $this->setUpOptionCleanup();
54 $this->loadDataSet('ConformanceTest');
55 $this->creationParamProvider = \Civi::container()->get('test.param_provider');
56 parent::setUp();
19b53e5b
C
57 }
58
5acc6183 59 /**
60 * Get entities to test.
61 *
8868b7fc
TO
62 * This is the hi-tech list as generated via Civi's runtime services. It
63 * is canonical, but relies on services that may not be available during
64 * early parts of PHPUnit lifecycle.
65 *
5acc6183 66 * @return array
67 *
68 * @throws \API_Exception
69 * @throws \Civi\API\Exception\UnauthorizedException
70 */
8868b7fc 71 public function getEntitiesHitech() {
d31fb4e3
CW
72 // Ensure all components are enabled so their entities show up
73 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviEvent');
74 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviGrant');
75 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviCase');
76 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviContribute');
77 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviCampaign');
78 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviPledge');
79 \CRM_Core_BAO_ConfigSetting::enableComponent('CiviReport');
fe806431 80 return $this->toDataProviderArray(Entity::get(FALSE)->execute()->column('name'));
19b53e5b
C
81 }
82
83 /**
8868b7fc
TO
84 * Get entities to test.
85 *
86 * This is the low-tech list as generated by manual-overrides and direct inspection.
87 * It may be summoned at any time during PHPUnit lifecycle, but it may require
88 * occasional twiddling to give correct results.
89 *
90 * @return array
19b53e5b 91 */
8868b7fc
TO
92 public function getEntitiesLotech() {
93 $manual['add'] = [];
94 $manual['remove'] = ['CustomValue'];
95
96 $scanned = [];
155ea29a 97 $srcDir = dirname(__DIR__, 5);
8868b7fc
TO
98 foreach ((array) glob("$srcDir/Civi/Api4/*.php") as $name) {
99 $scanned[] = preg_replace('/\.php/', '', basename($name));
19b53e5b 100 }
8868b7fc
TO
101
102 $names = array_diff(
103 array_unique(array_merge($scanned, $manual['add'])),
104 $manual['remove']
105 );
106
107 return $this->toDataProviderArray($names);
108 }
109
110 /**
111 * Ensure that "getEntitiesLotech()" (which is the 'dataProvider') is up to date
112 * with "getEntitiesHitech()" (which is a live feed available entities).
113 */
114 public function testEntitiesProvider() {
115 $this->assertEquals($this->getEntitiesHitech(), $this->getEntitiesLotech(), "The lo-tech list of entities does not match the hi-tech list. You probably need to update getEntitiesLotech().");
116 }
117
118 /**
119 * @param string $entity
120 * Ex: 'Contact'
155ea29a 121 *
8868b7fc 122 * @dataProvider getEntitiesLotech
155ea29a 123 *
124 * @throws \API_Exception
8868b7fc 125 */
155ea29a 126 public function testConformance($entity): void {
8868b7fc
TO
127 $entityClass = 'Civi\Api4\\' . $entity;
128
30755fcb 129 $this->checkEntityInfo($entityClass);
8868b7fc
TO
130 $actions = $this->checkActions($entityClass);
131
132 // Go no further if it's not a CRUD entity
133 if (array_diff(['get', 'create', 'update', 'delete'], array_keys($actions))) {
30755fcb 134 $this->markTestSkipped("The API \"$entity\" does not implement CRUD actions");
8868b7fc
TO
135 }
136
137 $this->checkFields($entityClass, $entity);
138 $id = $this->checkCreation($entity, $entityClass);
139 $this->checkGet($entityClass, $id, $entity);
140 $this->checkGetCount($entityClass, $id, $entity);
141 $this->checkUpdateFailsFromCreate($entityClass, $id);
142 $this->checkWrongParamType($entityClass);
143 $this->checkDeleteWithNoId($entityClass);
144 $this->checkDeletion($entityClass, $id);
145 $this->checkPostDelete($entityClass, $id, $entity);
19b53e5b
C
146 }
147
148 /**
30755fcb
CW
149 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
150 */
155ea29a 151 protected function checkEntityInfo($entityClass): void {
30755fcb
CW
152 $info = $entityClass::getInfo();
153 $this->assertNotEmpty($info['name']);
154 $this->assertNotEmpty($info['title']);
9813ae79 155 $this->assertNotEmpty($info['title_plural']);
30755fcb
CW
156 $this->assertNotEmpty($info['type']);
157 $this->assertNotEmpty($info['description']);
158 }
159
160 /**
161 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
5acc6183 162 * @param string $entity
155ea29a 163 *
164 * @throws \API_Exception
19b53e5b
C
165 */
166 protected function checkFields($entityClass, $entity) {
fe806431 167 $fields = $entityClass::getFields(FALSE)
19b53e5b
C
168 ->setIncludeCustom(FALSE)
169 ->execute()
170 ->indexBy('name');
171
172 $errMsg = sprintf('%s is missing required ID field', $entity);
173 $subset = ['data_type' => 'Integer'];
174
175 $this->assertArraySubset($subset, $fields['id'], $errMsg);
176 }
177
178 /**
30755fcb 179 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
5acc6183 180 *
181 * @return array
155ea29a 182 *
183 * @throws \API_Exception
19b53e5b 184 */
155ea29a 185 protected function checkActions($entityClass): array {
fe806431 186 $actions = $entityClass::getActions(FALSE)
19b53e5b
C
187 ->execute()
188 ->indexBy('name');
189
190 $this->assertNotEmpty($actions);
191 return (array) $actions;
192 }
193
194 /**
195 * @param string $entity
196 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
197 *
198 * @return mixed
199 */
200 protected function checkCreation($entity, $entityClass) {
201 $requiredParams = $this->creationParamProvider->getRequired($entity);
202 $createResult = $entityClass::create()
203 ->setValues($requiredParams)
204 ->setCheckPermissions(FALSE)
205 ->execute()
206 ->first();
207
208 $this->assertArrayHasKey('id', $createResult, "create missing ID");
209 $id = $createResult['id'];
210
211 $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
212
213 return $id;
214 }
215
216 /**
217 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
218 * @param int $id
219 */
155ea29a 220 protected function checkUpdateFailsFromCreate($entityClass, $id): void {
19b53e5b
C
221 $exceptionThrown = '';
222 try {
fe806431 223 $entityClass::create(FALSE)
19b53e5b
C
224 ->addValue('id', $id)
225 ->execute();
226 }
227 catch (\API_Exception $e) {
228 $exceptionThrown = $e->getMessage();
229 }
230 $this->assertContains('id', $exceptionThrown);
231 }
232
233 /**
234 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
235 * @param int $id
236 * @param string $entity
237 */
238 protected function checkGet($entityClass, $id, $entity) {
fe806431 239 $getResult = $entityClass::get(FALSE)
19b53e5b
C
240 ->addWhere('id', '=', $id)
241 ->execute();
242
243 $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
244 $this->assertEquals($id, $getResult->first()['id'], $errMsg);
245 $this->assertEquals(1, $getResult->count(), $errMsg);
246 }
247
248 /**
249 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
250 * @param int $id
251 * @param string $entity
252 */
155ea29a 253 protected function checkGetCount($entityClass, $id, $entity): void {
fe806431 254 $getResult = $entityClass::get(FALSE)
19b53e5b
C
255 ->addWhere('id', '=', $id)
256 ->selectRowCount()
257 ->execute();
258 $errMsg = sprintf('%s getCount failed', $entity);
259 $this->assertEquals(1, $getResult->count(), $errMsg);
260
fe806431 261 $getResult = $entityClass::get(FALSE)
19b53e5b
C
262 ->selectRowCount()
263 ->execute();
264 $errMsg = sprintf('%s getCount failed', $entity);
265 $this->assertGreaterThanOrEqual(1, $getResult->count(), $errMsg);
266 }
267
268 /**
269 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
270 */
271 protected function checkDeleteWithNoId($entityClass) {
272 $exceptionThrown = '';
273 try {
274 $entityClass::delete()
275 ->execute();
276 }
277 catch (\API_Exception $e) {
278 $exceptionThrown = $e->getMessage();
279 }
280 $this->assertContains('required', $exceptionThrown);
281 }
282
283 /**
284 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
285 */
286 protected function checkWrongParamType($entityClass) {
287 $exceptionThrown = '';
288 try {
289 $entityClass::get()
6764a9d3 290 ->setDebug('not a bool')
19b53e5b
C
291 ->execute();
292 }
293 catch (\API_Exception $e) {
294 $exceptionThrown = $e->getMessage();
295 }
6764a9d3 296 $this->assertContains('debug', $exceptionThrown);
19b53e5b
C
297 $this->assertContains('type', $exceptionThrown);
298 }
299
300 /**
301 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
302 * @param int $id
303 */
304 protected function checkDeletion($entityClass, $id) {
fe806431 305 $deleteResult = $entityClass::delete(FALSE)
19b53e5b
C
306 ->addWhere('id', '=', $id)
307 ->execute();
308
309 // should get back an array of deleted id
310 $this->assertEquals([['id' => $id]], (array) $deleteResult);
311 }
312
313 /**
314 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
315 * @param int $id
316 * @param string $entity
317 */
318 protected function checkPostDelete($entityClass, $id, $entity) {
fe806431 319 $getDeletedResult = $entityClass::get(FALSE)
19b53e5b
C
320 ->addWhere('id', '=', $id)
321 ->execute();
322
323 $errMsg = sprintf('Entity "%s" was not deleted', $entity);
324 $this->assertEquals(0, count($getDeletedResult), $errMsg);
325 }
326
8868b7fc
TO
327 /**
328 * @param array $names
329 * List of entity names.
330 * Ex: ['Foo', 'Bar']
331 * @return array
332 * List of data-provider arguments, one for each entity-name.
333 * Ex: ['Foo' => ['Foo'], 'Bar' => ['Bar']]
334 */
335 protected function toDataProviderArray($names) {
336 sort($names);
337
338 $result = [];
339 foreach ($names as $name) {
340 $result[$name] = [$name];
341 }
342 return $result;
343 }
344
19b53e5b 345}