Merge branch '3.x' into feature/3.x_HttpTool

feature/3.x_HttpTool
Christophe Vandeplas 2024-04-16 08:44:08 +00:00
commit 3927609cb6
14 changed files with 821 additions and 126 deletions

View File

@ -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);

View File

@ -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

View File

@ -1,36 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Controller\AppController;
use Cake\Utility\Hash;
use Cake\Utility\Text;
use Cake\Database\Expression\QueryExpression;
use Cake\Http\Exception\NotFoundException;
use App\Model\Entity\Organisation;
use Cake\Http\Exception\MethodNotAllowedException;
use Cake\Http\Exception\ForbiddenException;
class OrganisationsController extends AppController
{
public $quickFilterFields = [['name' => 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;
// }
// }
}

View File

@ -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' => [

View File

@ -1,18 +1,62 @@
<?php
declare(strict_types=1);
namespace App\Model\Behavior;
use Cake\ORM\Behavior;
use Cake\Event\EventInterface;
use Cake\Datasource\EntityInterface;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
use Cake\ORM\Behavior;
use Cake\Utility\Text;
use Cake\Validation\Validator;
class UUIDBehavior extends Behavior
{
/**
* beforeSave
*
* @param \Cake\Event\EventInterface $event the efent
* @param \Cake\Datasource\EntityInterface; $entity the entity
* @param array $options extra options
* @return void
*/
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
if ($entity->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;
}
}

View File

@ -1,8 +1,8 @@
<?php
declare(strict_types=1);
namespace App\Model\Table;
use App\Model\Table\AppTable;
use ArrayObject;
use Cake\Datasource\EntityInterface;
use Cake\Event\EventInterface;
@ -11,6 +11,12 @@ use Cake\Validation\Validator;
class OrganisationsTable extends AppTable
{
/**
* Initialize method.
*
* @param array $config The configuration for the table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
@ -23,42 +29,101 @@ class OrganisationsTable extends AppTable
$this->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;
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -1,5 +1,4 @@
<?php
declare(strict_types=1);
namespace App\Test\Helper;
@ -23,28 +22,49 @@ trait ApiTestTrait
IntegrationTestTrait::_sendRequest as _sendRequestOriginal;
}
/** @var string */
/**
* @var string
*/
protected $_authToken = '';
/** @var ValidatorBuilder */
/**
* @var ValidatorBuilder
*/
private $_validator;
/** @var RequestValidator */
/**
* @var RequestValidator
*/
private $_requestValidator;
/** @var ResponseValidator */
/**
* @var ResponseValidator
*/
private $_responseValidator;
/** @var ServerRequest */
/**
* @var ServerRequest
*/
protected $_psrRequest;
/** @var boolean */
/**
* @var bool
*/
protected $_skipOpenApiValidations = false;
public function setUp(): void
{
parent::setUp();
$this->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<string, mixed> $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'],

View File

@ -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]);
}
}

View File

@ -0,0 +1,287 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api\Organisations;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class AddOrganisationsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/organisations/add';
protected $fixtures = [
'app.Organisations',
'app.Roles',
'app.Users',
'app.AuthKeys',
];
private function addOrganisation(array $org_data): void
{
$url = sprintf('%s', self::ENDPOINT);
$this->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' => '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',
'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,
]
);
}
}

View File

@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api\Organisations;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class DeleteOrganisationsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/organisations/delete';
protected $fixtures = [
'app.Organisations',
'app.Roles',
'app.Users',
'app.AuthKeys',
];
public function testDeleteOrganisation(): void
{
$this->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]);
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api\Organisations;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class EditOrganisationsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/organisations/edit';
protected $fixtures = [
'app.Organisations',
'app.Roles',
'app.Users',
'app.AuthKeys',
];
private function editOrganisation(int $org_id): void
{
$url = sprintf('%s/%d', self::ENDPOINT, $org_id);
$this->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',
]
);
}
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace App\Test\TestCase\Api\Organisations;
use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
use Cake\TestSuite\TestCase;
class IndexOrganisationsApiTest extends TestCase
{
use ApiTestTrait;
protected const ENDPOINT = '/organisations/index';
protected $fixtures = [
'app.Organisations',
'app.Roles',
'app.Users',
'app.AuthKeys',
];
public function testIndexOrganisationsAsUser(): void
{
$this->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);
}
}