$this->markTestSkipped("The API \"$entityName\" does not implement CRUD actions");
}
- $this->checkFields($entityClass, $entityName);
+ $this->checkFields($entityName);
$this->checkCreationDenied($entityName, $entityClass);
- $id = $this->checkCreation($entityName, $entityClass);
- $getResult = $this->checkGet($entityName, $id);
+ $entityKeys = $this->checkCreation($entityName);
+ $getResult = $this->checkGet($entityName, $entityKeys);
// civi.api4.authorizeRecord does not work on `get` actions
// $this->checkGetAllowed($entityClass, $id, $entityName);
- $this->checkGetCount($entityClass, $id, $entityName);
- $this->checkUpdateFailsFromCreate($entityClass, $id);
- $this->checkUpdate($entityName, $getResult);
+ $this->checkGetCount($entityClass, $entityKeys, $entityName);
+ $this->checkUpdateFailsFromCreate($entityClass, $entityKeys);
+ $this->checkUpdate($entityName, $entityKeys, $getResult);
$this->checkWrongParamType($entityClass);
$this->checkDeleteWithNoId($entityClass);
- $this->checkDeletionDenied($entityClass, $id, $entityName);
- $this->checkDeletionAllowed($entityClass, $id, $entityName);
- $this->checkPostDelete($entityClass, $id, $entityName);
+ $this->checkDeletionDenied($entityClass, $entityKeys, $entityName);
+ $this->checkDeletionAllowed($entityClass, $entityKeys, $entityName);
+ $this->checkPostDelete($entityClass, $entityKeys, $entityName);
}
/**
}
/**
- * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param string $entity
+ * @param string $entityName
*
* @throws \CRM_Core_Exception
*/
- protected function checkFields($entityClass, $entity) {
- $fields = $entityClass::getFields(FALSE)
- ->addWhere('type', '=', 'Field')
- ->execute()
- ->indexBy('name');
+ protected function checkFields($entityName) {
+ $fields = civicrm_api4($entityName, 'getFields', [
+ 'checkPermissions' => FALSE,
+ 'where' => [['type', '=', 'Field']],
+ ])->indexBy('name');
- $idField = CoreUtil::getIdFieldName($entity);
+ $idField = CoreUtil::getIdFieldName($entityName);
- $errMsg = sprintf('%s getfields is missing primary key field', $entity);
+ $errMsg = sprintf('%s getfields is missing primary key field', $entityName);
$this->assertArrayHasKey($idField, $fields, $errMsg);
+ // Hmm, not true of every primary key... what about Afform.name?
$this->assertEquals('Integer', $fields[$idField]['data_type']);
// Ensure that the getFields (FieldSpec) format is generally consistent.
}
/**
- * @param string $entity
- * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
+ * @param string $entityName
*
- * @return mixed
+ * @return array
*/
- protected function checkCreation($entity, $entityClass) {
- $isReadOnly = $this->isReadOnly($entityClass);
+ protected function checkCreation(string $entityName): array {
+ $isReadOnly = $this->isReadOnly($entityName);
$hookLog = [];
$onValidate = function(ValidateValuesEvent $e) use (&$hookLog) {
$hookLog[$e->getEntityName()][$e->getActionName()] = 1 + ($hookLog[$e->getEntityName()][$e->getActionName()] ?? 0);
};
\Civi::dispatcher()->addListener('civi.api4.validate', $onValidate);
- \Civi::dispatcher()->addListener('civi.api4.validate::' . $entity, $onValidate);
+ \Civi::dispatcher()->addListener('civi.api4.validate::' . $entityName, $onValidate);
- $this->setCheckAccessGrants(["{$entity}::create" => TRUE]);
- $this->assertEquals(0, $this->checkAccessCounts["{$entity}::create"]);
+ $this->setCheckAccessGrants(["{$entityName}::create" => TRUE]);
+ $this->assertEquals(0, $this->checkAccessCounts["{$entityName}::create"]);
- $requiredParams = $this->getRequiredValuesToCreate($entity);
- $createResult = $entityClass::create()
- ->setValues($requiredParams)
- ->setCheckPermissions(!$isReadOnly)
- ->execute()
- ->first();
+ $requiredParams = $this->getRequiredValuesToCreate($entityName);
+ $createResult = civicrm_api4($entityName, 'create', [
+ 'values' => $requiredParams,
+ 'checkPermissions' => !$isReadOnly,
+ ])->single();
- $idField = CoreUtil::getIdFieldName($entity);
+ $primaryKeys = CoreUtil::getInfoItem($entityName, 'primary_key');
- $this->assertArrayHasKey($idField, $createResult, "create missing ID");
- $id = $createResult[$idField];
- $this->assertGreaterThanOrEqual(1, $id, "$entity ID not positive");
+ foreach ($primaryKeys as $idField) {
+ $this->assertArrayHasKey($idField, $createResult, "create missing $idField");
+ }
+ $id = $createResult[$primaryKeys[0]];
+ $this->assertGreaterThanOrEqual(1, $id, "$entityName ID not positive");
if (!$isReadOnly) {
- $this->assertEquals(1, $this->checkAccessCounts["{$entity}::create"]);
+ $this->assertEquals(1, $this->checkAccessCounts["{$entityName}::create"]);
}
$this->resetCheckAccess();
- $this->assertEquals(2, $hookLog[$entity]['create']);
+ $this->assertEquals(2, $hookLog[$entityName]['create']);
\Civi::dispatcher()->removeListener('civi.api4.validate', $onValidate);
- \Civi::dispatcher()->removeListener('civi.api4.validate::' . $entity, $onValidate);
+ \Civi::dispatcher()->removeListener('civi.api4.validate::' . $entityName, $onValidate);
- return $id;
+ return array_intersect_key($createResult, array_flip($primaryKeys));
}
/**
- * @param string $entity
+ * @param string $entityName
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
*/
- protected function checkCreationDenied(string $entity, $entityClass): void {
- $this->setCheckAccessGrants(["{$entity}::create" => FALSE]);
- $this->assertEquals(0, $this->checkAccessCounts["{$entity}::create"]);
+ protected function checkCreationDenied(string $entityName, $entityClass): void {
+ $this->setCheckAccessGrants(["{$entityName}::create" => FALSE]);
+ $this->assertEquals(0, $this->checkAccessCounts["{$entityName}::create"]);
- $requiredParams = $this->getRequiredValuesToCreate($entity);
+ $requiredParams = $this->getRequiredValuesToCreate($entityName);
try {
$entityClass::create()
->setValues($requiredParams)
- ->setCheckPermissions(TRUE)
- ->execute()
- ->first();
+ ->execute();
$this->fail("{$entityClass}::create() should throw an authorization failure.");
}
catch (UnauthorizedException $e) {
// OK, expected exception
}
- if (!$this->isReadOnly($entityClass)) {
- $this->assertEquals(1, $this->checkAccessCounts["{$entity}::create"]);
+ if (!$this->isReadOnly($entityName)) {
+ $this->assertEquals(1, $this->checkAccessCounts["{$entityName}::create"]);
}
$this->resetCheckAccess();
}
/**
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param int $id
+ * @param array $entityKeys
*/
- protected function checkUpdateFailsFromCreate($entityClass, int $id): void {
+ protected function checkUpdateFailsFromCreate($entityClass, array $entityKeys): void {
$exceptionThrown = '';
try {
$entityClass::create(FALSE)
- ->addValue('id', $id)
+ ->addValue('id', reset($entityKeys))
->execute();
}
catch (\CRM_Core_Exception $e) {
/**
* @param string $entityName
- * @param int $id
+ * @param array $entityKeys
*/
- protected function checkGet(string $entityName, int $id): array {
- $idField = CoreUtil::getIdFieldName($entityName);
+ protected function checkGet(string $entityName, array $entityKeys): array {
$getResult = civicrm_api4($entityName, 'get', [
'checkPermissions' => FALSE,
- 'where' => [[$idField, '=', $id]],
- ]);
- $errMsg = sprintf('Failed to fetch a %s after creation', $entityName);
- $this->assertEquals($id, $getResult->first()[$idField], $errMsg);
- return $getResult->single();
+ 'where' => self::valsToClause($entityKeys),
+ ])->single();
+ foreach ($entityKeys as $key => $val) {
+ $this->assertEquals($val, $getResult[$key]);
+ }
+ return $getResult;
}
/**
* Ensure updating an entity does not alter it
*
* @param string $entityName
+ * @param array $entityKeys
* @param array $getResult
* @throws \CRM_Core_Exception
*/
- protected function checkUpdate(string $entityName, array $getResult): void {
- $idField = CoreUtil::getIdFieldName($entityName);
+ protected function checkUpdate(string $entityName, array $entityKeys, array $getResult): void {
civicrm_api4($entityName, 'update', [
'checkPermissions' => FALSE,
- 'where' => [[$idField, '=', $getResult[$idField]]],
- 'values' => [$idField, $getResult[$idField]],
+ 'where' => self::valsToClause($entityKeys),
+ 'values' => $entityKeys,
]);
$getResult2 = civicrm_api4($entityName, 'get', [
'checkPermissions' => FALSE,
- 'where' => [[$idField, '=', $getResult[$idField]]],
+ 'where' => self::valsToClause($entityKeys),
]);
$this->assertEquals($getResult, $getResult2->single());
}
/**
* FIXME: Not working. `civi.api4.authorizeRecord` does not work on `get` actions.
*/
- protected function checkGetAllowed($entityClass, $id, $entity) {
- $this->setCheckAccessGrants(["{$entity}::get" => TRUE]);
+ protected function checkGetAllowed($entityClass, $id, $entityName) {
+ $this->setCheckAccessGrants(["{$entityName}::get" => TRUE]);
$getResult = $entityClass::get()
->addWhere('id', '=', $id)
->execute();
- $errMsg = sprintf('Failed to fetch a %s after creation', $entity);
- $idField = CoreUtil::getIdFieldName($entity);
+ $errMsg = sprintf('Failed to fetch a %s after creation', $entityName);
+ $idField = CoreUtil::getIdFieldName($entityName);
$this->assertEquals($id, $getResult->first()[$idField], $errMsg);
$this->assertEquals(1, $getResult->count(), $errMsg);
$this->resetCheckAccess();
/**
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param int $id
- * @param string $entity
+ * @param array $entityKeys
+ * @param string $entityName
*/
- protected function checkGetCount($entityClass, $id, $entity): void {
- $idField = CoreUtil::getIdFieldName($entity);
+ protected function checkGetCount(string $entityClass, array $entityKeys, string $entityName): void {
$getResult = $entityClass::get(FALSE)
- ->addWhere($idField, '=', $id)
+ ->setWhere(self::valsToClause($entityKeys))
->selectRowCount()
->execute();
- $errMsg = sprintf('%s getCount failed', $entity);
+ $errMsg = sprintf('%s getCount failed', $entityName);
$this->assertEquals(1, $getResult->count(), $errMsg);
$getResult = $entityClass::get(FALSE)
* Delete an entity - while having a targeted grant (hook_civirm_checkAccess).
*
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param int $id
- * @param string $entity
+ * @param array $entityKeys
+ * @param string $entityName
*/
- protected function checkDeletionAllowed($entityClass, $id, $entity) {
- $this->setCheckAccessGrants(["{$entity}::delete" => TRUE]);
- $this->assertEquals(0, $this->checkAccessCounts["{$entity}::delete"]);
- $isReadOnly = $this->isReadOnly($entityClass);
+ protected function checkDeletionAllowed($entityClass, $entityKeys, $entityName) {
+ $this->setCheckAccessGrants(["{$entityName}::delete" => TRUE]);
+ $this->assertEquals(0, $this->checkAccessCounts["{$entityName}::delete"]);
+ $isReadOnly = $this->isReadOnly($entityName);
- $idField = CoreUtil::getIdFieldName($entity);
$deleteAction = $entityClass::delete()
->setCheckPermissions(!$isReadOnly)
- ->addWhere($idField, '=', $id);
+ ->setWhere(self::valsToClause($entityKeys));
if (property_exists($deleteAction, 'useTrash')) {
$deleteAction->setUseTrash(FALSE);
$deleteResult = $deleteAction->execute();
});
- if (in_array('DAOEntity', CoreUtil::getInfoItem($entity, 'type'))) {
+ if (in_array('DAOEntity', CoreUtil::getInfoItem($entityName, 'type'))) {
// We should have emitted an event.
- $hookEntity = ($entity === 'Contact') ? 'Individual' : $entity;/* ooph */
- $this->assertContains("pre.{$hookEntity}.delete", $log, "$entity should emit hook_civicrm_pre() for deletions");
- $this->assertContains("post.{$hookEntity}.delete", $log, "$entity should emit hook_civicrm_post() for deletions");
+ $hookEntity = ($entityName === 'Contact') ? 'Individual' : $entityName;/* ooph */
+ $this->assertContains("pre.{$hookEntity}.delete", $log, "$entityName should emit hook_civicrm_pre() for deletions");
+ $this->assertContains("post.{$hookEntity}.delete", $log, "$entityName should emit hook_civicrm_post() for deletions");
// should get back an array of deleted id
- $this->assertEquals([['id' => $id]], (array) $deleteResult);
+ $this->assertEquals([$entityKeys], (array) $deleteResult);
if (!$isReadOnly) {
- $this->assertEquals(1, $this->checkAccessCounts["{$entity}::delete"]);
+ $this->assertEquals(1, $this->checkAccessCounts["{$entityName}::delete"]);
}
}
$this->resetCheckAccess();
* Attempt to delete an entity while having explicitly denied permission (hook_civicrm_checkAccess).
*
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param int $id
- * @param string $entity
+ * @param array $entityKeys
+ * @param string $entityName
*/
- protected function checkDeletionDenied($entityClass, $id, $entity) {
- $this->setCheckAccessGrants(["{$entity}::delete" => FALSE]);
- $this->assertEquals(0, $this->checkAccessCounts["{$entity}::delete"]);
+ protected function checkDeletionDenied($entityClass, array $entityKeys, $entityName) {
+ $this->setCheckAccessGrants(["{$entityName}::delete" => FALSE]);
+ $this->assertEquals(0, $this->checkAccessCounts["{$entityName}::delete"]);
try {
$entityClass::delete()
- ->addWhere('id', '=', $id)
+ ->setWhere(self::valsToClause($entityKeys))
->execute();
- $this->fail("{$entity}::delete should throw an authorization failure.");
+ $this->fail("{$entityName}::delete should throw an authorization failure.");
}
catch (UnauthorizedException $e) {
// OK
}
- if (!$this->isReadOnly($entityClass)) {
- $this->assertEquals(1, $this->checkAccessCounts["{$entity}::delete"]);
+ if (!$this->isReadOnly($entityName)) {
+ $this->assertEquals(1, $this->checkAccessCounts["{$entityName}::delete"]);
}
$this->resetCheckAccess();
}
/**
* @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
- * @param int $id
- * @param string $entity
+ * @param array $entityKeys
+ * @param string $entityName
*/
- protected function checkPostDelete($entityClass, $id, $entity) {
+ protected function checkPostDelete($entityClass, array $entityKeys, $entityName) {
$getDeletedResult = $entityClass::get(FALSE)
- ->addWhere('id', '=', $id)
+ ->setWhere(self::valsToClause($entityKeys))
->execute();
- $errMsg = sprintf('Entity "%s" was not deleted', $entity);
+ $errMsg = sprintf('Entity "%s" was not deleted', $entityName);
$this->assertEquals(0, count($getDeletedResult), $errMsg);
}
}
/**
- * @param \Civi\Api4\Generic\AbstractEntity|string $entityClass
+ * @param string $entityName
* @return bool
*/
- protected function isReadOnly($entityClass) {
- return in_array('ReadOnlyEntity', $entityClass::getInfo()['type'], TRUE);
+ protected function isReadOnly($entityName) {
+ return in_array('ReadOnlyEntity', CoreUtil::getInfoItem($entityName, 'type'), TRUE);
}
/**
return $log;
}
+ private static function valsToClause(array $vals) {
+ $clause = [];
+ foreach ($vals as $key => $val) {
+ $clause[] = [$key, '=', $val];
+ }
+ return $clause;
+ }
+
}