Add ContributionSoft v4 api
[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 = [];
97 $srcDir = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
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'
121 * @dataProvider getEntitiesLotech
122 */
123 public function testConformance($entity) {
124 $entityClass = 'Civi\Api4\\' . $entity;
125
30755fcb 126 $this->checkEntityInfo($entityClass);
8868b7fc
TO
127 $actions = $this->checkActions($entityClass);
128
129 // Go no further if it's not a CRUD entity
130 if (array_diff(['get', 'create', 'update', 'delete'], array_keys($actions))) {
30755fcb 131 $this->markTestSkipped("The API \"$entity\" does not implement CRUD actions");
8868b7fc
TO
132 }
133
134 $this->checkFields($entityClass, $entity);
135 $id = $this->checkCreation($entity, $entityClass);
136 $this->checkGet($entityClass, $id, $entity);
137 $this->checkGetCount($entityClass, $id, $entity);
138 $this->checkUpdateFailsFromCreate($entityClass, $id);
139 $this->checkWrongParamType($entityClass);
140 $this->checkDeleteWithNoId($entityClass);
141 $this->checkDeletion($entityClass, $id);
142 $this->checkPostDelete($entityClass, $id, $entity);
19b53e5b
C
143 }
144
145 /**
30755fcb
CW
146 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
147 */
148 protected function checkEntityInfo($entityClass) {
149 $info = $entityClass::getInfo();
150 $this->assertNotEmpty($info['name']);
151 $this->assertNotEmpty($info['title']);
9813ae79 152 $this->assertNotEmpty($info['title_plural']);
30755fcb
CW
153 $this->assertNotEmpty($info['type']);
154 $this->assertNotEmpty($info['description']);
155 }
156
157 /**
158 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
5acc6183 159 * @param string $entity
19b53e5b
C
160 */
161 protected function checkFields($entityClass, $entity) {
fe806431 162 $fields = $entityClass::getFields(FALSE)
19b53e5b
C
163 ->setIncludeCustom(FALSE)
164 ->execute()
165 ->indexBy('name');
166
167 $errMsg = sprintf('%s is missing required ID field', $entity);
168 $subset = ['data_type' => 'Integer'];
169
170 $this->assertArraySubset($subset, $fields['id'], $errMsg);
171 }
172
173 /**
30755fcb 174 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
5acc6183 175 *
176 * @return array
19b53e5b
C
177 */
178 protected function checkActions($entityClass) {
fe806431 179 $actions = $entityClass::getActions(FALSE)
19b53e5b
C
180 ->execute()
181 ->indexBy('name');
182
183 $this->assertNotEmpty($actions);
184 return (array) $actions;
185 }
186
187 /**
188 * @param string $entity
189 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
190 *
191 * @return mixed
192 */
193 protected function checkCreation($entity, $entityClass) {
194 $requiredParams = $this->creationParamProvider->getRequired($entity);
195 $createResult = $entityClass::create()
196 ->setValues($requiredParams)
197 ->setCheckPermissions(FALSE)
198 ->execute()
199 ->first();
200
201 $this->assertArrayHasKey('id', $createResult, "create missing ID");
202 $id = $createResult['id'];
203
204 $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
205
206 return $id;
207 }
208
209 /**
210 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
211 * @param int $id
212 */
213 protected function checkUpdateFailsFromCreate($entityClass, $id) {
214 $exceptionThrown = '';
215 try {
fe806431 216 $entityClass::create(FALSE)
19b53e5b
C
217 ->addValue('id', $id)
218 ->execute();
219 }
220 catch (\API_Exception $e) {
221 $exceptionThrown = $e->getMessage();
222 }
223 $this->assertContains('id', $exceptionThrown);
224 }
225
226 /**
227 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
228 * @param int $id
229 * @param string $entity
230 */
231 protected function checkGet($entityClass, $id, $entity) {
fe806431 232 $getResult = $entityClass::get(FALSE)
19b53e5b
C
233 ->addWhere('id', '=', $id)
234 ->execute();
235
236 $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
237 $this->assertEquals($id, $getResult->first()['id'], $errMsg);
238 $this->assertEquals(1, $getResult->count(), $errMsg);
239 }
240
241 /**
242 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
243 * @param int $id
244 * @param string $entity
245 */
246 protected function checkGetCount($entityClass, $id, $entity) {
fe806431 247 $getResult = $entityClass::get(FALSE)
19b53e5b
C
248 ->addWhere('id', '=', $id)
249 ->selectRowCount()
250 ->execute();
251 $errMsg = sprintf('%s getCount failed', $entity);
252 $this->assertEquals(1, $getResult->count(), $errMsg);
253
fe806431 254 $getResult = $entityClass::get(FALSE)
19b53e5b
C
255 ->selectRowCount()
256 ->execute();
257 $errMsg = sprintf('%s getCount failed', $entity);
258 $this->assertGreaterThanOrEqual(1, $getResult->count(), $errMsg);
259 }
260
261 /**
262 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
263 */
264 protected function checkDeleteWithNoId($entityClass) {
265 $exceptionThrown = '';
266 try {
267 $entityClass::delete()
268 ->execute();
269 }
270 catch (\API_Exception $e) {
271 $exceptionThrown = $e->getMessage();
272 }
273 $this->assertContains('required', $exceptionThrown);
274 }
275
276 /**
277 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
278 */
279 protected function checkWrongParamType($entityClass) {
280 $exceptionThrown = '';
281 try {
282 $entityClass::get()
6764a9d3 283 ->setDebug('not a bool')
19b53e5b
C
284 ->execute();
285 }
286 catch (\API_Exception $e) {
287 $exceptionThrown = $e->getMessage();
288 }
6764a9d3 289 $this->assertContains('debug', $exceptionThrown);
19b53e5b
C
290 $this->assertContains('type', $exceptionThrown);
291 }
292
293 /**
294 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
295 * @param int $id
296 */
297 protected function checkDeletion($entityClass, $id) {
fe806431 298 $deleteResult = $entityClass::delete(FALSE)
19b53e5b
C
299 ->addWhere('id', '=', $id)
300 ->execute();
301
302 // should get back an array of deleted id
303 $this->assertEquals([['id' => $id]], (array) $deleteResult);
304 }
305
306 /**
307 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
308 * @param int $id
309 * @param string $entity
310 */
311 protected function checkPostDelete($entityClass, $id, $entity) {
fe806431 312 $getDeletedResult = $entityClass::get(FALSE)
19b53e5b
C
313 ->addWhere('id', '=', $id)
314 ->execute();
315
316 $errMsg = sprintf('Entity "%s" was not deleted', $entity);
317 $this->assertEquals(0, count($getDeletedResult), $errMsg);
318 }
319
8868b7fc
TO
320 /**
321 * @param array $names
322 * List of entity names.
323 * Ex: ['Foo', 'Bar']
324 * @return array
325 * List of data-provider arguments, one for each entity-name.
326 * Ex: ['Foo' => ['Foo'], 'Bar' => ['Bar']]
327 */
328 protected function toDataProviderArray($names) {
329 sort($names);
330
331 $result = [];
332 foreach ($names as $name) {
333 $result[$name] = [$name];
334 }
335 return $result;
336 }
337
19b53e5b 338}