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