From 32b14b03a612919d3cb52a4b067fd0ba36dcbe88 Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Tue, 16 Apr 2024 09:16:24 +0200 Subject: [PATCH 1/2] [3.x-migration] Organisation branch (#9579) * new: [Organisation] index edit unit tests * fix: [CRUD] use existing return function for edit/add * fix: [Organisations] fix ACL issue edit by org admin * new: [Organisation] addOrgApiTest * new: [Org] delte test * fix: [SG] Fixes broken SharingGroupOrg * new: [Org] more tests and validations --- src/Controller/Component/ACLComponent.php | 16 +- src/Controller/Component/CRUDComponent.php | 27 +- src/Controller/OrganisationsController.php | 176 +++++++---- src/Controller/SharingGroupsController.php | 2 +- src/Model/Behavior/UUIDBehavior.php | 50 ++- src/Model/Table/OrganisationsTable.php | 93 +++++- tests/Fixture/OrganisationsFixture.php | 8 +- tests/Fixture/UsersFixture.php | 10 +- tests/Helper/ApiTestTrait.php | 48 ++- .../Api/Cerebrates/DeleteCerebrateApiTest.php | 11 + .../Organisations/AddOrganisationsApiTest.php | 287 ++++++++++++++++++ .../DeleteOrganisationsApiTest.php | 53 ++++ .../EditOrganisationsApiTest.php | 109 +++++++ .../IndexOrganisationsApiTest.php | 57 ++++ 14 files changed, 821 insertions(+), 126 deletions(-) create mode 100644 tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php create mode 100644 tests/TestCase/Api/Organisations/DeleteOrganisationsApiTest.php create mode 100644 tests/TestCase/Api/Organisations/EditOrganisationsApiTest.php create mode 100644 tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index c168f50ef..07b861a10 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -158,15 +158,15 @@ class ACLComponent extends Component 'view' => ['perm_admin'], ], 'Organisations' => [ - 'add' => ['perm_admin'], - 'delete' => ['perm_admin'], - 'edit' => ['perm_admin'], + 'add' => ['perm_site_admin'], + 'delete' => ['perm_site_admin'], + 'edit' => ['perm_site_admin', 'perm_admin'], 'filtering' => ['*'], 'index' => ['*'], - 'tag' => ['perm_tagger'], - 'untag' => ['perm_tagger'], + 'tag' => ['AND' => ['perm_tagger', 'OR' => ['perm_site_admin', 'perm_admin']]], + 'untag' => ['AND' => ['perm_tagger', 'OR' => ['perm_site_admin', 'perm_admin']]], 'view' => ['*'], - 'viewTags' => ['*'] + 'viewTags' => ['*'], ], 'Outbox' => [ 'createEntry' => ['perm_admin'], @@ -506,7 +506,7 @@ class ACLComponent extends Component // we have to be in a publically allowed scope otherwise the Auth component will kick us out anyway. return true; } - if (!empty($this->user->Role->perm_admin)) { + if (!empty($this->user->Role->perm_site_admin)) { return true; } //$this->__checkLoggedActions($user, $controller, $action); @@ -700,7 +700,7 @@ class ACLComponent extends Component } foreach ($this->aclList as $controller => $actions) { foreach ($actions as $action => $permissions) { - if ($role['perm_admin']) { + if ($role['perm_site_admin']) { $results = $this->__formatControllerAction($results, $controller, $action, $url_mode); } elseif (in_array('*', $permissions)) { $results = $this->__formatControllerAction($results, $controller, $action, $url_mode); diff --git a/src/Controller/Component/CRUDComponent.php b/src/Controller/Component/CRUDComponent.php index 9c0a9f97b..4af7f1ccf 100644 --- a/src/Controller/Component/CRUDComponent.php +++ b/src/Controller/Component/CRUDComponent.php @@ -424,7 +424,7 @@ class CRUDComponent extends Component throw new NotFoundException(__('Could not save {0} due to the input failing to meet expectations. Your input is bad and you should feel bad.', $this->ObjectAlias)); } } - $savedData = $this->Table->save($data); + $savedData = $this->Table->save($data); // FIXME catch exceptions thrown by DB constraints, as otherwise we get 500 Internal Server Errors if ($savedData !== false) { if (isset($params['afterSave'])) { $params['afterSave']($data); @@ -458,7 +458,7 @@ class CRUDComponent extends Component ); if ($this->Controller->ParamHandler->isRest()) { $this->Controller->restResponsePayload = $this->RestResponse->viewData($message, 'json'); - } else if ($this->Controller->ParamHandler->isAjax()) { + } elseif ($this->Controller->ParamHandler->isAjax()) { $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'add', $data, $message, $validationErrors); } else { $this->Controller->Flash->error($message); @@ -715,32 +715,16 @@ class CRUDComponent extends Component $params['afterSave']($data); } $message = __('{0} `{1}` updated.', $this->ObjectAlias, $savedData->{$this->Table->getDisplayField()}); - if ($this->Controller->ParamHandler->isRest()) { - $this->Controller->restResponsePayload = $this->RestResponse->viewData($savedData, 'json'); - } else if ($this->Controller->ParamHandler->isAjax()) { - $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxSuccessResponse($this->ObjectAlias, 'edit', $savedData, $message); - } else { - $this->Controller->Flash->success($message); - if (empty($params['redirect'])) { - $this->Controller->redirect(['action' => 'view', $id]); - } else { - $this->Controller->redirect($params['redirect']); - } - } + $this->setResponseForController('edit', true, $message, $data, null, $params); } else { $validationErrors = $data->getErrors(); - $validationMessage = $this->prepareValidationError($data); + $validationMessage = $this->prepareValidationMessage($validationErrors); $message = __( '{0} could not be modified.{1}', $this->ObjectAlias, empty($validationMessage) ? '' : PHP_EOL . __('Reason: {0}', $validationMessage) ); - if ($this->Controller->ParamHandler->isRest()) { - } else if ($this->Controller->ParamHandler->isAjax()) { - $this->Controller->ajaxResponsePayload = $this->RestResponse->ajaxFailResponse($this->ObjectAlias, 'edit', $data, $message, $validationErrors); - } else { - $this->Controller->Flash->error($message); - } + $this->setResponseForController('edit', false, $message, $data, null, $params); } } if (!empty($params['fields'])) { @@ -1187,6 +1171,7 @@ class CRUDComponent extends Component } else { if ($this->Controller->ParamHandler->isRest()) { $data = $data ?? $message; + // FIXME show error in the JSON response, and return with an HTTP error code $this->Controller->restResponsePayload = $this->RestResponse->viewData($data, 'json'); } elseif ($this->Controller->ParamHandler->isAjax()) { if (!empty($additionalData['redirect'])) { // If a redirection occurs, we need to make sure the flash message gets displayed diff --git a/src/Controller/OrganisationsController.php b/src/Controller/OrganisationsController.php index 6adea95de..d1342cc58 100644 --- a/src/Controller/OrganisationsController.php +++ b/src/Controller/OrganisationsController.php @@ -1,36 +1,36 @@ true], 'uuid', 'nationality', 'sector', 'type', 'url', 'local']; public $filterFields = [ - 'name', 'uuid', 'nationality', 'sector', 'type', 'url', 'local' + 'name', 'uuid', 'nationality', 'sector', 'type', 'url', 'local', ]; public $containFields = []; public $statisticsFields = ['nationality', 'sector']; + /** + * Display the list of organizations. + * + * @return \Cake\Http\Response|null + */ public function index() { $customContextFilters = [ [ 'label' => __('Local orgs'), - 'filterCondition' => ['local' => 1] + 'filterCondition' => ['local' => 1], ], [ 'label' => __('External orgs'), - 'filterCondition' => ['local' => 0] - ] + 'filterCondition' => ['local' => 0], + ], ]; $loggedUserOrganisationNationality = $this->ACL->getUser()['Organisation']['nationality']; if (!empty($loggedUserOrganisationNationality)) { @@ -38,54 +38,79 @@ class OrganisationsController extends AppController 'label' => __('Country: {0}', $loggedUserOrganisationNationality), 'filterCondition' => [ 'nationality' => $loggedUserOrganisationNationality, - ] + ], ]; } - $this->CRUD->index([ - 'filters' => $this->filterFields, - 'quickFilters' => $this->quickFilterFields, - 'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true], - 'contextFilters' => [ - 'custom' => $customContextFilters, - ], - 'afterFind' => function($entity) { - $entity->setVirtual(['user_count']); - return $entity; - }, - 'contain' => $this->containFields, - 'statisticsFields' => $this->statisticsFields, - ]); + $this->CRUD->index( + [ + 'filters' => $this->filterFields, + 'quickFilters' => $this->quickFilterFields, + 'quickFilterForMetaField' => ['enabled' => true, 'wildcard_search' => true], + 'contextFilters' => [ + 'custom' => $customContextFilters, + ], + 'afterFind' => function ($entity) { + $entity->setVirtual(['user_count']); + + return $entity; + }, + 'contain' => $this->containFields, + 'statisticsFields' => $this->statisticsFields, + ] + ); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { return $responsePayload; } } + /** + * Filtering function. + */ public function filtering() { $this->CRUD->filtering(); } + /** + * Add a new organization. + * + * @return \Cake\Http\Response|null Redirects on successful add, renders view otherwise. + */ public function add() { - $this->CRUD->add(); + $params = [ + 'beforeSave' => function (Organisation $org) { + $org['created_by'] = $this->ACL->getUser()['id']; + + return $org; + }, + ]; + $this->CRUD->add($params); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { return $responsePayload; } - $this->set('countries', + $this->set( + 'countries', array_merge( [ - '' => __('Not specified') + '' => __('Not specified'), ], //$this->_arrayToValuesIndexArray($this->Organisation->getCountries()) - ) + ), ); $this->set('metaGroup', 'ContactDB'); } - public function view($id) + /** + * View an organization. + * + * @param int $id The ID of the organization. + * @return \Cake\Http\Response|null The response payload. + */ + public function view(int $id) { $this->CRUD->view($id); $responsePayload = $this->CRUD->getResponsePayload(); @@ -95,15 +120,24 @@ class OrganisationsController extends AppController $this->set('metaGroup', 'ContactDB'); } - public function edit($id) + /** + * Edit an organization. + * + * @param int $id The ID of the organization. + * @return \Cake\Http\Response|null The response payload. + */ + public function edit(int $id) { $currentUser = $this->ACL->getUser(); if ( - !($currentUser['Organisation']['id'] == $id && $currentUser['Role']['perm_org_admin']) && - !$currentUser['Role']['perm_admin'] + !( + $currentUser['Role']['perm_site_admin'] || + ($currentUser['Organisation']['id'] == $id && $currentUser['Role']['perm_admin']) + ) ) { throw new MethodNotAllowedException(__('You cannot modify that organisation.')); } + // FIXME prevent change of the created_by field $this->CRUD->edit($id); $responsePayload = $this->CRUD->getResponsePayload(); if (!empty($responsePayload)) { @@ -113,7 +147,13 @@ class OrganisationsController extends AppController $this->render('add'); } - public function delete($id) + /** + * Delete an organization. + * + * @param int $id The ID of the organization. + * @return \Cake\Http\Response|null The response payload. + */ + public function delete(int $id) { $this->CRUD->delete($id); $responsePayload = $this->CRUD->getResponsePayload(); @@ -123,30 +163,48 @@ class OrganisationsController extends AppController $this->set('metaGroup', 'ContactDB'); } - public function tag($id) - { - $this->CRUD->tag($id); - $responsePayload = $this->CRUD->getResponsePayload(); - if (!empty($responsePayload)) { - return $responsePayload; - } - } + // /** + // * Tag an organization. + // * + // * @param int $id The ID of the organization. + // * @return \Cake\Http\Response|null The response payload. + // */ + // public function tag(int $id) + // { + // $this->CRUD->tag($id); + // $responsePayload = $this->CRUD->getResponsePayload(); + // if (!empty($responsePayload)) { + // return $responsePayload; + // } + // } - public function untag($id) - { - $this->CRUD->untag($id); - $responsePayload = $this->CRUD->getResponsePayload(); - if (!empty($responsePayload)) { - return $responsePayload; - } - } + // /** + // * Untag an organization. + // * + // * @param int $id The ID of the organization. + // * @return \Cake\Http\Response|null The response payload. + // */ + // public function untag(int $id) + // { + // $this->CRUD->untag($id); + // $responsePayload = $this->CRUD->getResponsePayload(); + // if (!empty($responsePayload)) { + // return $responsePayload; + // } + // } - public function viewTags($id) - { - $this->CRUD->viewTags($id); - $responsePayload = $this->CRUD->getResponsePayload(); - if (!empty($responsePayload)) { - return $responsePayload; - } - } + // /** + // * View tags for an organization. + // * + // * @param int $id The ID of the organization. + // * @return \Cake\Http\Response|null The response payload. + // */ + // public function viewTags(int $id) + // { + // $this->CRUD->viewTags($id); + // $responsePayload = $this->CRUD->getResponsePayload(); + // if (!empty($responsePayload)) { + // return $responsePayload; + // } + // } } diff --git a/src/Controller/SharingGroupsController.php b/src/Controller/SharingGroupsController.php index 16c0201a8..ca91f31e7 100644 --- a/src/Controller/SharingGroupsController.php +++ b/src/Controller/SharingGroupsController.php @@ -535,7 +535,7 @@ class SharingGroupsController extends AppController return $sg; } - private function __initialiseSGQuickEditObject($id, $request, $type = 'org') + private function __initialiseSGQuickEditObject(int $id, $request, $type = 'org') { $params = [ 'org' => [ diff --git a/src/Model/Behavior/UUIDBehavior.php b/src/Model/Behavior/UUIDBehavior.php index cdb659a23..907c5df22 100644 --- a/src/Model/Behavior/UUIDBehavior.php +++ b/src/Model/Behavior/UUIDBehavior.php @@ -1,18 +1,62 @@ isNew() && empty($entity['uuid'])) { $entity['uuid'] = Text::uuid(); } } + + /** + * buildValidator + * + * @param \Cake\Event\EventInterface $event the event + * @param \Cake\Validation\Validator $validator the validator + * @param string $name the string to validate + * @return \Cake\Validation\Validator + */ + public function buildValidator(EventInterface $event, Validator $validator, string $name) + { + $validator + ->notEmptyString('uuid') + ->add( + 'uuid', + 'valid', + [ + 'rule' => 'uuid', + 'message' => 'The UUID is not valid', + ] + ) + ->add( + 'uuid', + 'unique', + [ + 'rule' => 'validateUnique', + 'provider' => 'table', + 'message' => 'The UUID name must be unique.', + ] + ); + + return $validator; + } } diff --git a/src/Model/Table/OrganisationsTable.php b/src/Model/Table/OrganisationsTable.php index a50099d39..b42954fb7 100644 --- a/src/Model/Table/OrganisationsTable.php +++ b/src/Model/Table/OrganisationsTable.php @@ -1,8 +1,8 @@ setDisplayField('name'); } + /** + * Callback method called before saving an entity. + * + * @param \Cake\Event\EventInterface $event The event instance. + * @param \Cake\Datasource\EntityInterface $entity The entity being saved. + * @param \ArrayObject $options The options passed to the save method. + * @return void + */ public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) { if ($entity->isNew()) { $entity->date_created = date('Y-m-d H:i:s'); } $entity->date_modified = date('Y-m-d H:i:s'); - return; + // $entity->created_by = $this->ACL->getUser(); // FIXME force the value in the model, see also https://github.com/usemuffin/footprint } + /** + * Default validation rules for the table. + * + * @param \Cake\Validation\Validator $validator The validator instance. + * @return \Cake\Validation\Validator The updated validator instance. + */ public function validationDefault(Validator $validator): Validator { $validator ->notEmptyString('name') - ->notEmptyString('uuid') - ->requirePresence(['name', 'uuid'], 'create'); + ->requirePresence(['name', 'uuid'], 'create') + ->add( + 'name', + [ + 'unique' => [ + 'rule' => 'validateUnique', + 'provider' => 'table', + 'message' => 'The organisation name must be unique.', + ], + 'maxLength' => [ + 'rule' => ['maxLength', 255], + 'message' => 'Name cannot be more than 255 chars.', + ], + ] + ) + ->add( + 'type', + 'maxLength', + [ + 'rule' => ['maxLength', 255], + 'message' => 'Type cannot be more than 255 chars.', + ], + ) + ->add( + 'nationality', + 'maxLength', + [ + 'rule' => ['maxLength', 255], + 'message' => 'Nationality cannot be more than 255 chars.', + ], + ) + ->add( + 'sector', + 'maxLength', + [ + 'rule' => ['maxLength', 255], + 'message' => 'Sector cannot be more than 255 chars.', + ], + ); + return $validator; } + /** + * Capture the organization. + * + * @param mixed $org The organization to capture. + * @return int|null The captured organization ID, or null if capture failed. + */ public function captureOrg($org): ?int { if (!empty($org['uuid'])) { $existingOrg = $this->find()->where( [ - 'uuid' => $org['uuid'] + 'uuid' => $org['uuid'], ] )->first(); } else { return null; } if (empty($existingOrg)) { + /** @var \App\Model\Entity\Organisation $entityToSave */ $entityToSave = $this->newEmptyEntity(); $this->patchEntity( $entityToSave, $org, [ - 'accessibleFields' => $entityToSave->getAccessibleFieldForNew() + 'accessibleFields' => $entityToSave->getAccessibleFieldForNew(), ] ); } else { @@ -70,10 +135,17 @@ class OrganisationsTable extends AppTable if (!$savedEntity) { return null; } + return $savedEntity->id; } - public function fetchOrg($id) + /** + * Fetches an organization by its ID. + * + * @param int $id The ID of the organization to fetch. + * @return mixed + */ + public function fetchOrg(int $id) { if (empty($id)) { return false; @@ -87,10 +159,11 @@ class OrganisationsTable extends AppTable $org = $this->find( 'all', [ - 'conditions' => $conditions, - 'recursive' => -1 + 'conditions' => $conditions, + 'recursive' => -1, ] )->disableHydration()->first(); - return (empty($org)) ? false : $org; + + return empty($org) ? false : $org; } } diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php index a4f7b91a1..243f97149 100644 --- a/tests/Fixture/OrganisationsFixture.php +++ b/tests/Fixture/OrganisationsFixture.php @@ -35,7 +35,7 @@ class OrganisationsFixture extends TestFixture 'contacts' => '', 'description' => 'ORGANISATION A', 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() + 'date_modified' => $faker->dateTime()->getTimestamp(), ], [ 'id' => self::ORGANISATION_B_ID, @@ -48,7 +48,7 @@ class OrganisationsFixture extends TestFixture 'contacts' => '', 'description' => 'ORGANISATION B', 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() + 'date_modified' => $faker->dateTime()->getTimestamp(), ], [ 'id' => self::ORGANISATION_C_ID, @@ -61,8 +61,8 @@ class OrganisationsFixture extends TestFixture 'contacts' => '', 'description' => 'ORGANISATION C', 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() - ] + 'date_modified' => $faker->dateTime()->getTimestamp(), + ], ]; parent::init(); } diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php index 9c6afb1e9..a3b5d3dba 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -46,7 +46,7 @@ class UsersFixture extends TestFixture 'disabled' => 0, 'org_id' => OrganisationsFixture::ORGANISATION_A_ID, 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() + 'date_modified' => $faker->dateTime()->getTimestamp(), ], [ 'id' => self::USER_SYNC_ID, @@ -57,7 +57,7 @@ class UsersFixture extends TestFixture 'disabled' => 0, 'org_id' => OrganisationsFixture::ORGANISATION_A_ID, 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() + 'date_modified' => $faker->dateTime()->getTimestamp(), ], [ 'id' => self::USER_ORG_ADMIN_ID, @@ -68,7 +68,7 @@ class UsersFixture extends TestFixture 'disabled' => 0, 'org_id' => OrganisationsFixture::ORGANISATION_A_ID, 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() + 'date_modified' => $faker->dateTime()->getTimestamp(), ], [ 'id' => self::USER_REGULAR_USER_ID, @@ -79,8 +79,8 @@ class UsersFixture extends TestFixture 'disabled' => 0, 'org_id' => OrganisationsFixture::ORGANISATION_A_ID, 'date_created' => $faker->dateTime()->getTimestamp(), - 'date_modified' => $faker->dateTime()->getTimestamp() - ] + 'date_modified' => $faker->dateTime()->getTimestamp(), + ], ]; parent::init(); } diff --git a/tests/Helper/ApiTestTrait.php b/tests/Helper/ApiTestTrait.php index 8ef4297dc..90b0941d1 100644 --- a/tests/Helper/ApiTestTrait.php +++ b/tests/Helper/ApiTestTrait.php @@ -1,5 +1,4 @@ initializeOpenApiValidator(); + + $this->configRequest( + [ + 'headers' => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + ], + ] + ); } public function setAuthToken(string $authToken): void @@ -59,8 +79,8 @@ trait ApiTestTrait 'headers' => [ 'Accept' => 'application/json', 'Authorization' => $this->_authToken, - 'Content-Type' => 'application/json' - ] + 'Content-Type' => 'application/json', + ], ] ); } @@ -89,7 +109,7 @@ trait ApiTestTrait public function initializeOpenApiValidator(): void { if (!$this->_skipOpenApiValidations) { - $this->_validator = Configure::read('App.OpenAPIValidator'); + $this->_validator = Configure::read('App.OpenAPIValidator'); if ($this->_validator === null) { throw new \Exception('OpenAPI validator is not configured'); } @@ -127,7 +147,6 @@ trait ApiTestTrait * @return void * @throws \Exception * @throws \Cake\Datasource\Exception\RecordNotFoundException - * * @see https://book.cakephp.org/4/en/orm-query-builder.html */ public function assertDbRecordExists(string $table, array $conditions): void @@ -147,7 +166,6 @@ trait ApiTestTrait * @return void * @throws \Exception * @throws \Cake\Datasource\Exception\RecordNotFoundException - * * @see https://book.cakephp.org/4/en/orm-query-builder.html */ public function assertDbRecordNotExists(string $table, array $conditions): void @@ -191,8 +209,8 @@ trait ApiTestTrait * This method intercepts IntegrationTestTrait::_buildRequest() * in the quest to get a PSR-7 request object and saves it for * later inspection, also validates it against the OpenAPI spec. - * @see \Cake\TestSuite\IntegrationTestTrait::_buildRequest() * + * @see \Cake\TestSuite\IntegrationTestTrait::_buildRequest() * @param string $url The URL * @param string $method The HTTP method * @param array|string $data The request data. @@ -229,7 +247,6 @@ trait ApiTestTrait * and validates the response against the OpenAPI spec. * * @see \Cake\TestSuite\IntegrationTestTrait::_sendRequest() - * * @param array|string $url The URL * @param string $method The HTTP method * @param array|string $data The request data. @@ -274,8 +291,8 @@ trait ApiTestTrait /** * Create a PSR-7 request from the request spec. - * @see \Cake\TestSuite\MiddlewareDispatcher::_createRequest() * + * @see \Cake\TestSuite\MiddlewareDispatcher::_createRequest() * @param array $spec The request spec. * @return \Cake\Http\ServerRequest */ @@ -292,6 +309,7 @@ trait ApiTestTrait if (strpos($environment['PHP_SELF'], 'phpunit') !== false) { $environment['PHP_SELF'] = '/'; } + return ServerRequestFactory::fromGlobals( $environment, $spec['query'], diff --git a/tests/TestCase/Api/Cerebrates/DeleteCerebrateApiTest.php b/tests/TestCase/Api/Cerebrates/DeleteCerebrateApiTest.php index 6f9a38e08..483b297e0 100644 --- a/tests/TestCase/Api/Cerebrates/DeleteCerebrateApiTest.php +++ b/tests/TestCase/Api/Cerebrates/DeleteCerebrateApiTest.php @@ -43,4 +43,15 @@ class DeleteCerebrateApiTest extends TestCase $this->assertResponseCode(405); $this->assertDbRecordExists('Cerebrates', ['id' => CerebratesFixture::SERVER_A_ID]); } + + public function testDeleteCerebrateNotAllowedAsOrgAdmin(): void + { + $this->skipOpenApiValidations(); + $this->setAuthToken(AuthKeysFixture::ORG_ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, CerebratesFixture::SERVER_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Cerebrates', ['id' => CerebratesFixture::SERVER_A_ID]); + } } diff --git a/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php new file mode 100644 index 000000000..68a8f8cb8 --- /dev/null +++ b/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php @@ -0,0 +1,287 @@ +post( + $url, + $org_data + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Organisations', + $org_data + ); + } + + private function addNotAllowed(array $org_data): void + { + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'name' => $org_data['name'], + ] + ); + } + + public function testAddOrganisationAsAdmin(): void + { + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->addOrganisation($org_data); + } + + public function testAddNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $this->addNotAllowed($org_data); + } + + public function testAddNotAllowedAsOrgAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ORG_ADMIN_API_KEY); // user from org A + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $this->addNotAllowed($org_data); + } + + public function testAddNameAlreadyExists(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => 'Organisation A', + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'DUPLICATE ENTRY', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(200); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'name' => 'Organisation A', + 'sector' => 'DUPLICATE ENTRY', + ] + ); + } + + public function testAddUuidAlreadyExists(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => OrganisationsFixture::ORGANISATION_A_UUID, + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'DUPLICATE ENTRY', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(200); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'name' => $org_data['name'], + ] + ); + } + + public function testBadUuid(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => '11111111-1111-1111-1111-111111111111', + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'DUPLICATE ENTRY', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(200); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'name' => $org_data['name'], + ] + ); + } + + public function testAddLongName(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => $faker->text(400), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + // 'created_by' => 0, + ]; + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(200); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'name' => $org_data['name'], + ] + ); + } + + public function testAddCreatedBy(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $faker = \Faker\Factory::create(); + $org_data = [ + 'uuid' => $faker->uuid(), + 'name' => $faker->text(10), + 'description' => $faker->text(10), + 'nationality' => $faker->countryCode, + 'sector' => 'IT', + 'type' => '', + 'contacts' => '', + 'local' => 1, + 'restricted_to_domain' => '', + 'landingpage' => '', + //'date_created' => $faker->dateTime()->getTimestamp(), + // 'date_modified' => $faker->dateTime()->getTimestamp(), + 'created_by' => 1, + ]; + $url = sprintf('%s', self::ENDPOINT); + $this->post( + $url, + $org_data + ); + $this->assertResponseCode(200); + $this->assertDbRecordExists( + 'Organisations', + [ + 'uuid' => $org_data['uuid'], + 'created_by' => AuthKeysFixture::ADMIN_API_ID, + ] + ); + } +} diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationsApiTest.php new file mode 100644 index 000000000..f9dd76873 --- /dev/null +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationsApiTest.php @@ -0,0 +1,53 @@ +setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_A_ID]); + } + + public function testDeleteOrganisationNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_A_ID]); + } + + public function testDeleteOrganisationNotAllowedAsOrgAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ORG_ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_A_ID]); + } +} diff --git a/tests/TestCase/Api/Organisations/EditOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationsApiTest.php new file mode 100644 index 000000000..bdb5b3a27 --- /dev/null +++ b/tests/TestCase/Api/Organisations/EditOrganisationsApiTest.php @@ -0,0 +1,109 @@ +put( + $url, + [ + 'id' => $org_id, + 'description' => 'new description', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists( + 'Organisations', + [ + 'id' => $org_id, + 'description' => 'new description', + ] + ); + } + + private function editNotAllowed(int $org_id): void + { + $url = sprintf('%s/%d', self::ENDPOINT, $org_id); + $this->put( + $url, + [ + 'id' => $org_id, + 'description' => 'new description', + ] + ); + $this->assertResponseCode(405); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'id' => $org_id, + 'description' => 'new description', + ] + ); + } + + public function testEditOrganisationAsAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->editOrganisation(OrganisationsFixture::ORGANISATION_A_ID); + } + + public function testEditOrganisationAsOrgAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ORG_ADMIN_API_KEY); // user from org A + $this->editOrganisation(OrganisationsFixture::ORGANISATION_A_ID); + } + + public function testEditNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->editNotAllowed(OrganisationsFixture::ORGANISATION_A_ID); + } + + public function testEditNotAllowedAsWrongOrgAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ORG_ADMIN_API_KEY); // user from org A + $this->editNotAllowed(OrganisationsFixture::ORGANISATION_B_ID); // edit org B not allowed + } + + public function testEditNameAlreadyExists(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->put( + $url, + [ + 'id' => OrganisationsFixture::ORGANISATION_A_ID, + 'name' => 'Organisation B', + ] + ); + $this->assertResponseCode(200); + $this->assertDbRecordNotExists( + 'Organisations', + [ + 'id' => OrganisationsFixture::ORGANISATION_A_ID, + 'description' => 'Organisation B', + ] + ); + } +} diff --git a/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php new file mode 100644 index 000000000..2d6dbc854 --- /dev/null +++ b/tests/TestCase/Api/Organisations/IndexOrganisationsApiTest.php @@ -0,0 +1,57 @@ +setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', OrganisationsFixture::ORGANISATION_A_UUID)); + $this->assertResponseContains(sprintf('"uuid": "%s"', OrganisationsFixture::ORGANISATION_B_UUID)); + } + + public function testIndexOrganisationsAsAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"uuid": "%s"', OrganisationsFixture::ORGANISATION_A_UUID)); + $this->assertResponseContains(sprintf('"uuid": "%s"', OrganisationsFixture::ORGANISATION_B_UUID)); + } + + public function testIndexOrganisationsWithInvalidAuthToken(): void + { + $this->setAuthToken('invalid_token'); + $this->get(self::ENDPOINT); + $this->assertResponseCode(405); + } + + public function testIndexOrganisationsWithNoAuthToken(): void + { + $this->skipOpenApiValidations(); + $this->get(self::ENDPOINT); + $this->assertResponseCode(405); + } +} From c8c98d15e59c688503e6650d9a7699ebd65ec06b Mon Sep 17 00:00:00 2001 From: Christophe Vandeplas Date: Tue, 16 Apr 2024 07:47:24 +0000 Subject: [PATCH 2/2] fix: [tests] Fix Organisations testAddLongName --- tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php index 68a8f8cb8..9fc88edd3 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationsApiTest.php @@ -224,7 +224,7 @@ class AddOrganisationsApiTest extends TestCase $faker = \Faker\Factory::create(); $org_data = [ 'uuid' => $faker->uuid(), - 'name' => $faker->text(400), + 'name' => 'This is a very long name that is longer than 255 characters and should not be allowed. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'description' => $faker->text(10), 'nationality' => $faker->countryCode, 'sector' => 'IT',