Merge pull request #17872 from eileenmcnaughton/ids
[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;
23use api\v4\Traits\TableDropperTrait;
24use api\v4\UnitTestCase;
25
26/**
27 * @group headless
28 */
29class ConformanceTest extends UnitTestCase {
30
31 use 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();
19b53e5b
C
58 }
59
5acc6183 60 /**
61 * Get entities to test.
62 *
8868b7fc
TO
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 *
5acc6183 67 * @return array
68 *
69 * @throws \API_Exception
70 * @throws \Civi\API\Exception\UnauthorizedException
71 */
8868b7fc
TO
72 public function getEntitiesHitech() {
73 return $this->toDataProviderArray(Entity::get()->setCheckPermissions(FALSE)->execute()->column('name'));
19b53e5b
C
74 }
75
76 /**
8868b7fc
TO
77 * Get entities to test.
78 *
79 * This is the low-tech list as generated by manual-overrides and direct inspection.
80 * It may be summoned at any time during PHPUnit lifecycle, but it may require
81 * occasional twiddling to give correct results.
82 *
83 * @return array
19b53e5b 84 */
8868b7fc
TO
85 public function getEntitiesLotech() {
86 $manual['add'] = [];
87 $manual['remove'] = ['CustomValue'];
88
89 $scanned = [];
90 $srcDir = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
91 foreach ((array) glob("$srcDir/Civi/Api4/*.php") as $name) {
92 $scanned[] = preg_replace('/\.php/', '', basename($name));
19b53e5b 93 }
8868b7fc
TO
94
95 $names = array_diff(
96 array_unique(array_merge($scanned, $manual['add'])),
97 $manual['remove']
98 );
99
100 return $this->toDataProviderArray($names);
101 }
102
103 /**
104 * Ensure that "getEntitiesLotech()" (which is the 'dataProvider') is up to date
105 * with "getEntitiesHitech()" (which is a live feed available entities).
106 */
107 public function testEntitiesProvider() {
108 $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().");
109 }
110
111 /**
112 * @param string $entity
113 * Ex: 'Contact'
114 * @dataProvider getEntitiesLotech
115 */
116 public function testConformance($entity) {
117 $entityClass = 'Civi\Api4\\' . $entity;
118
119 $actions = $this->checkActions($entityClass);
120
121 // Go no further if it's not a CRUD entity
122 if (array_diff(['get', 'create', 'update', 'delete'], array_keys($actions))) {
123 $this->markTestSkipped("The entity \"$entity\" does not ");
124 return;
125 }
126
127 $this->checkFields($entityClass, $entity);
128 $id = $this->checkCreation($entity, $entityClass);
129 $this->checkGet($entityClass, $id, $entity);
130 $this->checkGetCount($entityClass, $id, $entity);
131 $this->checkUpdateFailsFromCreate($entityClass, $id);
132 $this->checkWrongParamType($entityClass);
133 $this->checkDeleteWithNoId($entityClass);
134 $this->checkDeletion($entityClass, $id);
135 $this->checkPostDelete($entityClass, $id, $entity);
19b53e5b
C
136 }
137
138 /**
139 * @param string $entityClass
5acc6183 140 * @param string $entity
19b53e5b
C
141 */
142 protected function checkFields($entityClass, $entity) {
143 $fields = $entityClass::getFields()
144 ->setCheckPermissions(FALSE)
145 ->setIncludeCustom(FALSE)
146 ->execute()
147 ->indexBy('name');
148
149 $errMsg = sprintf('%s is missing required ID field', $entity);
150 $subset = ['data_type' => 'Integer'];
151
152 $this->assertArraySubset($subset, $fields['id'], $errMsg);
153 }
154
155 /**
156 * @param string $entityClass
5acc6183 157 *
158 * @return array
19b53e5b
C
159 */
160 protected function checkActions($entityClass) {
161 $actions = $entityClass::getActions()
162 ->setCheckPermissions(FALSE)
163 ->execute()
164 ->indexBy('name');
165
166 $this->assertNotEmpty($actions);
167 return (array) $actions;
168 }
169
170 /**
171 * @param string $entity
172 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
173 *
174 * @return mixed
175 */
176 protected function checkCreation($entity, $entityClass) {
177 $requiredParams = $this->creationParamProvider->getRequired($entity);
178 $createResult = $entityClass::create()
179 ->setValues($requiredParams)
180 ->setCheckPermissions(FALSE)
181 ->execute()
182 ->first();
183
184 $this->assertArrayHasKey('id', $createResult, "create missing ID");
185 $id = $createResult['id'];
186
187 $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
188
189 return $id;
190 }
191
192 /**
193 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
194 * @param int $id
195 */
196 protected function checkUpdateFailsFromCreate($entityClass, $id) {
197 $exceptionThrown = '';
198 try {
199 $entityClass::create()
200 ->setCheckPermissions(FALSE)
201 ->addValue('id', $id)
202 ->execute();
203 }
204 catch (\API_Exception $e) {
205 $exceptionThrown = $e->getMessage();
206 }
207 $this->assertContains('id', $exceptionThrown);
208 }
209
210 /**
211 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
212 * @param int $id
213 * @param string $entity
214 */
215 protected function checkGet($entityClass, $id, $entity) {
216 $getResult = $entityClass::get()
217 ->setCheckPermissions(FALSE)
218 ->addWhere('id', '=', $id)
219 ->execute();
220
221 $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
222 $this->assertEquals($id, $getResult->first()['id'], $errMsg);
223 $this->assertEquals(1, $getResult->count(), $errMsg);
224 }
225
226 /**
227 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
228 * @param int $id
229 * @param string $entity
230 */
231 protected function checkGetCount($entityClass, $id, $entity) {
232 $getResult = $entityClass::get()
233 ->setCheckPermissions(FALSE)
234 ->addWhere('id', '=', $id)
235 ->selectRowCount()
236 ->execute();
237 $errMsg = sprintf('%s getCount failed', $entity);
238 $this->assertEquals(1, $getResult->count(), $errMsg);
239
240 $getResult = $entityClass::get()
241 ->setCheckPermissions(FALSE)
242 ->selectRowCount()
243 ->execute();
244 $errMsg = sprintf('%s getCount failed', $entity);
245 $this->assertGreaterThanOrEqual(1, $getResult->count(), $errMsg);
246 }
247
248 /**
249 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
250 */
251 protected function checkDeleteWithNoId($entityClass) {
252 $exceptionThrown = '';
253 try {
254 $entityClass::delete()
255 ->execute();
256 }
257 catch (\API_Exception $e) {
258 $exceptionThrown = $e->getMessage();
259 }
260 $this->assertContains('required', $exceptionThrown);
261 }
262
263 /**
264 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
265 */
266 protected function checkWrongParamType($entityClass) {
267 $exceptionThrown = '';
268 try {
269 $entityClass::get()
6764a9d3 270 ->setDebug('not a bool')
19b53e5b
C
271 ->execute();
272 }
273 catch (\API_Exception $e) {
274 $exceptionThrown = $e->getMessage();
275 }
6764a9d3 276 $this->assertContains('debug', $exceptionThrown);
19b53e5b
C
277 $this->assertContains('type', $exceptionThrown);
278 }
279
280 /**
281 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
282 * @param int $id
283 */
284 protected function checkDeletion($entityClass, $id) {
285 $deleteResult = $entityClass::delete()
286 ->setCheckPermissions(FALSE)
287 ->addWhere('id', '=', $id)
288 ->execute();
289
290 // should get back an array of deleted id
291 $this->assertEquals([['id' => $id]], (array) $deleteResult);
292 }
293
294 /**
295 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
296 * @param int $id
297 * @param string $entity
298 */
299 protected function checkPostDelete($entityClass, $id, $entity) {
300 $getDeletedResult = $entityClass::get()
301 ->setCheckPermissions(FALSE)
302 ->addWhere('id', '=', $id)
303 ->execute();
304
305 $errMsg = sprintf('Entity "%s" was not deleted', $entity);
306 $this->assertEquals(0, count($getDeletedResult), $errMsg);
307 }
308
8868b7fc
TO
309 /**
310 * @param array $names
311 * List of entity names.
312 * Ex: ['Foo', 'Bar']
313 * @return array
314 * List of data-provider arguments, one for each entity-name.
315 * Ex: ['Foo' => ['Foo'], 'Bar' => ['Bar']]
316 */
317 protected function toDataProviderArray($names) {
318 sort($names);
319
320 $result = [];
321 foreach ($names as $name) {
322 $result[$name] = [$name];
323 }
324 return $result;
325 }
326
19b53e5b 327}