4 +--------------------------------------------------------------------+
5 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
16 * @copyright CiviCRM LLC https://civicrm.org/licensing
20 namespace api\v
4\Entity
;
23 use api\v
4\Traits\TableDropperTrait
;
24 use api\v
4\UnitTestCase
;
29 class ConformanceTest
extends UnitTestCase
{
31 use TableDropperTrait
;
32 use \api\v
4\Traits\OptionCleanupTrait
{
33 setUp
as setUpOptionCleanup
;
37 * @var \api\v4\Service\TestCreationParameterProvider
39 protected $creationParamProvider;
42 * Set up baseline for testing
44 public function setUp() {
46 'civicrm_custom_group',
47 'civicrm_custom_field',
50 'civicrm_participant',
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');
61 * Get entities to test.
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.
69 * @throws \API_Exception
70 * @throws \Civi\API\Exception\UnauthorizedException
72 public function getEntitiesHitech() {
73 return $this->toDataProviderArray(Entity
::get(FALSE)->execute()->column('name'));
77 * Get entities to test.
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.
85 public function getEntitiesLotech() {
87 $manual['remove'] = ['CustomValue'];
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));
96 array_unique(array_merge($scanned, $manual['add'])),
100 return $this->toDataProviderArray($names);
104 * Ensure that "getEntitiesLotech()" (which is the 'dataProvider') is up to date
105 * with "getEntitiesHitech()" (which is a live feed available entities).
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().");
112 * @param string $entity
114 * @dataProvider getEntitiesLotech
116 public function testConformance($entity) {
117 $entityClass = 'Civi\Api4\\' . $entity;
119 $actions = $this->checkActions($entityClass);
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 ");
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);
139 * @param string $entityClass
140 * @param string $entity
142 protected function checkFields($entityClass, $entity) {
143 $fields = $entityClass::getFields(FALSE)
144 ->setIncludeCustom(FALSE)
148 $errMsg = sprintf('%s is missing required ID field', $entity);
149 $subset = ['data_type' => 'Integer'];
151 $this->assertArraySubset($subset, $fields['id'], $errMsg);
155 * @param string $entityClass
159 protected function checkActions($entityClass) {
160 $actions = $entityClass::getActions(FALSE)
164 $this->assertNotEmpty($actions);
165 return (array) $actions;
169 * @param string $entity
170 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
174 protected function checkCreation($entity, $entityClass) {
175 $requiredParams = $this->creationParamProvider
->getRequired($entity);
176 $createResult = $entityClass::create()
177 ->setValues($requiredParams)
178 ->setCheckPermissions(FALSE)
182 $this->assertArrayHasKey('id', $createResult, "create missing ID");
183 $id = $createResult['id'];
185 $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
191 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
194 protected function checkUpdateFailsFromCreate($entityClass, $id) {
195 $exceptionThrown = '';
197 $entityClass::create(FALSE)
198 ->addValue('id', $id)
201 catch (\API_Exception
$e) {
202 $exceptionThrown = $e->getMessage();
204 $this->assertContains('id', $exceptionThrown);
208 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
210 * @param string $entity
212 protected function checkGet($entityClass, $id, $entity) {
213 $getResult = $entityClass::get(FALSE)
214 ->addWhere('id', '=', $id)
217 $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
218 $this->assertEquals($id, $getResult->first()['id'], $errMsg);
219 $this->assertEquals(1, $getResult->count(), $errMsg);
223 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
225 * @param string $entity
227 protected function checkGetCount($entityClass, $id, $entity) {
228 $getResult = $entityClass::get(FALSE)
229 ->addWhere('id', '=', $id)
232 $errMsg = sprintf('%s getCount failed', $entity);
233 $this->assertEquals(1, $getResult->count(), $errMsg);
235 $getResult = $entityClass::get(FALSE)
238 $errMsg = sprintf('%s getCount failed', $entity);
239 $this->assertGreaterThanOrEqual(1, $getResult->count(), $errMsg);
243 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
245 protected function checkDeleteWithNoId($entityClass) {
246 $exceptionThrown = '';
248 $entityClass::delete()
251 catch (\API_Exception
$e) {
252 $exceptionThrown = $e->getMessage();
254 $this->assertContains('required', $exceptionThrown);
258 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
260 protected function checkWrongParamType($entityClass) {
261 $exceptionThrown = '';
264 ->setDebug('not a bool')
267 catch (\API_Exception
$e) {
268 $exceptionThrown = $e->getMessage();
270 $this->assertContains('debug', $exceptionThrown);
271 $this->assertContains('type', $exceptionThrown);
275 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
278 protected function checkDeletion($entityClass, $id) {
279 $deleteResult = $entityClass::delete(FALSE)
280 ->addWhere('id', '=', $id)
283 // should get back an array of deleted id
284 $this->assertEquals([['id' => $id]], (array) $deleteResult);
288 * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
290 * @param string $entity
292 protected function checkPostDelete($entityClass, $id, $entity) {
293 $getDeletedResult = $entityClass::get(FALSE)
294 ->addWhere('id', '=', $id)
297 $errMsg = sprintf('Entity "%s" was not deleted', $entity);
298 $this->assertEquals(0, count($getDeletedResult), $errMsg);
302 * @param array $names
303 * List of entity names.
306 * List of data-provider arguments, one for each entity-name.
307 * Ex: ['Foo' => ['Foo'], 'Bar' => ['Bar']]
309 protected function toDataProviderArray($names) {
313 foreach ($names as $name) {
314 $result[$name] = [$name];