new: add /api openapi spec view with redoc, add faker to fixtures, validate api responses with openapi spec, add /api/v1/ prefix to api routes
parent
f45727704f
commit
a69608530c
|
@ -8,4 +8,4 @@ webroot/theme/node_modules
|
|||
.vscode
|
||||
docker/run/
|
||||
.phpunit.result.cache
|
||||
config.json
|
||||
config.json
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
"cakephp/bake": "^2.0.3",
|
||||
"cakephp/cakephp-codesniffer": "~4.0.0",
|
||||
"cakephp/debug_kit": "^4.0",
|
||||
"fzaninotto/faker": "^1.9",
|
||||
"josegonzalez/dotenv": "^3.2",
|
||||
"league/openapi-psr7-validator": "^0.16.4",
|
||||
"phpunit/phpunit": "^8.5",
|
||||
"psy/psysh": "@stable"
|
||||
},
|
||||
|
@ -44,7 +46,6 @@
|
|||
"scripts": {
|
||||
"post-install-cmd": "App\\Console\\Installer::postInstall",
|
||||
"post-create-project-cmd": "App\\Console\\Installer::postInstall",
|
||||
"post-autoload-dump": "Cake\\Composer\\Installer\\PluginInstaller::postAutoloadDump",
|
||||
"check": [
|
||||
"@test",
|
||||
"@cs-check"
|
||||
|
|
|
@ -92,14 +92,14 @@ $routes->prefix('Open', function (RouteBuilder $routes) {
|
|||
$routes->fallbacks(DashedRoute::class);
|
||||
});
|
||||
|
||||
/*
|
||||
* If you need a different set of middleware or none at all,
|
||||
* open new scope and define routes there.
|
||||
*
|
||||
* ```
|
||||
* $routes->scope('/api', function (RouteBuilder $builder) {
|
||||
* // No $builder->applyMiddleware() here.
|
||||
* // Connect API actions here.
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
// API routes
|
||||
$routes->scope('/api', function (RouteBuilder $routes) {
|
||||
// $routes->applyMiddleware('ratelimit', 'auth.api');
|
||||
$routes->scope('/v1', function (RouteBuilder $routes) {
|
||||
// $routes->applyMiddleware('v1compat');
|
||||
$routes->setExtensions(['json']);
|
||||
|
||||
// Generic API route
|
||||
$routes->connect('/{controller}/{action}/*');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Controller\AppController;
|
||||
|
||||
class ApiController extends AppController
|
||||
{
|
||||
/**
|
||||
* Controller action for displaying built-in Redoc UI
|
||||
*
|
||||
* @return \Cake\Http\Response|null|void Renders view
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$url = '/docs/openapi.yaml';
|
||||
$this->set('url', $url);
|
||||
}
|
||||
}
|
|
@ -193,6 +193,9 @@ class ACLComponent extends Component
|
|||
'getBookmarks' => ['*'],
|
||||
'saveBookmark' => ['*'],
|
||||
'deleteBookmark' => ['*']
|
||||
],
|
||||
'Api' => [
|
||||
'index' => ['*']
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<redoc spec-url='<?php echo $url ?>'></redoc>
|
||||
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
|
|
@ -11,24 +11,60 @@ class AuthKeysFixture extends TestFixture
|
|||
{
|
||||
public $connection = 'test';
|
||||
|
||||
public const ADMIN_API_KEY = '4cd687b314a3b9c4d83264e6195b9a3706ef4c2f';
|
||||
public const ADMIN_API_KEY = 'd033e22ae348aeb5660fc2140aec35850c4da997';
|
||||
public const SYNC_API_KEY = '6b387ced110858dcbcda36edb044dc18f91a0894';
|
||||
public const ORG_ADMIN_API_KEY = '1c4685d281d478dbcebd494158024bc3539004d0';
|
||||
public const USER_API_KEY = '12dea96fec20593566ab75692c9949596833adc9';
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$hasher = new DefaultPasswordHasher();
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->records = [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5',
|
||||
'uuid' => $faker->uuid(),
|
||||
'authkey' => $hasher->hash(self::ADMIN_API_KEY),
|
||||
'authkey_start' => '4cd6',
|
||||
'authkey_end' => '4c2f',
|
||||
'authkey_start' => substr(self::ADMIN_API_KEY, 0, 4),
|
||||
'authkey_end' => substr(self::ADMIN_API_KEY, -4),
|
||||
'expiration' => 0,
|
||||
'user_id' => 1,
|
||||
'user_id' => UsersFixture::USER_ADMIN_ID,
|
||||
'comment' => '',
|
||||
'created' => time(),
|
||||
'modified' => time()
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'uuid' => $faker->uuid(),
|
||||
'authkey' => $hasher->hash(self::SYNC_API_KEY),
|
||||
'authkey_start' => substr(self::SYNC_API_KEY, 0, 4),
|
||||
'authkey_end' => substr(self::SYNC_API_KEY, -4),
|
||||
'expiration' => 0,
|
||||
'user_id' => UsersFixture::USER_SYNC_ID,
|
||||
'comment' => '',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'uuid' => $faker->uuid(),
|
||||
'authkey' => $hasher->hash(self::ORG_ADMIN_API_KEY),
|
||||
'authkey_start' => substr(self::ORG_ADMIN_API_KEY, 0, 4),
|
||||
'authkey_end' => substr(self::ORG_ADMIN_API_KEY, -4),
|
||||
'expiration' => 0,
|
||||
'user_id' => UsersFixture::USER_ORG_ADMIN_ID,
|
||||
'comment' => '',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'uuid' => $faker->uuid(),
|
||||
'authkey' => $hasher->hash(self::USER_API_KEY),
|
||||
'authkey_start' => substr(self::USER_API_KEY, 0, 4),
|
||||
'authkey_end' => substr(self::USER_API_KEY, -4),
|
||||
'expiration' => 0,
|
||||
'user_id' => UsersFixture::USER_REGULAR_USER_ID,
|
||||
'comment' => '',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
]
|
||||
];
|
||||
parent::init();
|
||||
|
|
|
@ -10,16 +10,64 @@ class IndividualsFixture extends TestFixture
|
|||
{
|
||||
public $connection = 'test';
|
||||
|
||||
public $records = [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e1',
|
||||
'email' => 'admin@admin.test',
|
||||
'first_name' => 'admin',
|
||||
'last_name' => 'admin',
|
||||
'position' => 'admin',
|
||||
'created' => '2022-01-04 10:00:00',
|
||||
'modified' => '2022-01-04 10:00:00'
|
||||
]
|
||||
];
|
||||
// Admin individual
|
||||
public const INDIVIDUAL_ADMIN_ID = 1;
|
||||
|
||||
// Sync individual
|
||||
public const INDIVIDUAL_SYNC_ID = 2;
|
||||
|
||||
// Org Admin individual
|
||||
public const INDIVIDUAL_ORG_ADMIN_ID = 3;
|
||||
|
||||
// Regular User individual
|
||||
public const INDIVIDUAL_REGULAR_USER_ID = 4;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->records = [
|
||||
[
|
||||
'id' => self::INDIVIDUAL_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'email' => $faker->email(),
|
||||
'first_name' => $faker->firstName,
|
||||
'last_name' => $faker->lastName,
|
||||
'position' => 'admin',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::INDIVIDUAL_SYNC_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'email' => $faker->email(),
|
||||
'first_name' => $faker->firstName,
|
||||
'last_name' => $faker->lastName,
|
||||
'position' => 'sync',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::INDIVIDUAL_ORG_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'email' => $faker->email(),
|
||||
'first_name' => $faker->firstName,
|
||||
'last_name' => $faker->lastName,
|
||||
'position' => 'org_admin',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::INDIVIDUAL_REGULAR_USER_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'email' => $faker->email(),
|
||||
'first_name' => $faker->firstName,
|
||||
'last_name' => $faker->lastName,
|
||||
'position' => 'user',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
]
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Test\Fixture;
|
||||
|
||||
use Cake\TestSuite\Fixture\TestFixture;
|
||||
use Authentication\PasswordHasher\DefaultPasswordHasher;
|
||||
|
||||
class OrganisationsFixture extends TestFixture
|
||||
{
|
||||
public $connection = 'test';
|
||||
|
||||
// Organisation A
|
||||
public const ORGANISATION_A_ID = 1;
|
||||
|
||||
// Organisation B
|
||||
public const ORGANISATION_B_ID = 2;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->records = [
|
||||
[
|
||||
'id' => self::ORGANISATION_A_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'Organisation A',
|
||||
'url' => $faker->url,
|
||||
'nationality' => $faker->countryCode,
|
||||
'sector' => 'IT',
|
||||
'type' => '',
|
||||
'contacts' => '',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::ORGANISATION_B_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'Organisation B',
|
||||
'url' => $faker->url,
|
||||
'nationality' => $faker->countryCode,
|
||||
'sector' => 'IT',
|
||||
'type' => '',
|
||||
'contacts' => '',
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
]
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
|
@ -10,15 +10,53 @@ class RolesFixture extends TestFixture
|
|||
{
|
||||
public $connection = 'test';
|
||||
|
||||
public $records = [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e4',
|
||||
'name' => 'admin',
|
||||
'is_default' => true,
|
||||
'perm_admin' => true,
|
||||
'perm_sync' => true,
|
||||
'perm_org_admin' => true
|
||||
]
|
||||
];
|
||||
public const ROLE_ADMIN_ID = 1;
|
||||
public const ROLE_SYNC_ID = 2;
|
||||
public const ROLE_ORG_ADMIN_ID = 3;
|
||||
public const ROLE_REGULAR_USER_ID = 4;
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->records = [
|
||||
[
|
||||
'id' => self::ROLE_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'admin',
|
||||
'is_default' => false,
|
||||
'perm_admin' => true,
|
||||
'perm_sync' => false,
|
||||
'perm_org_admin' => false
|
||||
],
|
||||
[
|
||||
'id' => self::ROLE_SYNC_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'sync',
|
||||
'is_default' => false,
|
||||
'perm_admin' => false,
|
||||
'perm_sync' => true,
|
||||
'perm_org_admin' => false
|
||||
],
|
||||
[
|
||||
'id' => self::ROLE_ORG_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'org_admin',
|
||||
'is_default' => false,
|
||||
'perm_admin' => false,
|
||||
'perm_sync' => false,
|
||||
'perm_org_admin' => true
|
||||
],
|
||||
[
|
||||
'id' => self::ROLE_REGULAR_USER_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'name' => 'user',
|
||||
'is_default' => true,
|
||||
'perm_admin' => false,
|
||||
'perm_sync' => false,
|
||||
'perm_org_admin' => false
|
||||
]
|
||||
];
|
||||
parent::init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,26 +11,80 @@ class UsersFixture extends TestFixture
|
|||
{
|
||||
public $connection = 'test';
|
||||
|
||||
public const ADMIN_USER = 'admin';
|
||||
public const ADMIN_PASSWORD = 'Password1234';
|
||||
// Admin user
|
||||
public const USER_ADMIN_ID = 1;
|
||||
public const USER_ADMIN_USERNAME = 'admin';
|
||||
public const USER_ADMIN_PASSWORD = 'AdminPassword';
|
||||
|
||||
// Sync user
|
||||
public const USER_SYNC_ID = 2;
|
||||
public const USER_SYNC_USERNAME = 'sync';
|
||||
public const USER_SYNC_PASSWORD = 'SyncPassword';
|
||||
|
||||
// Org Admin user
|
||||
public const USER_ORG_ADMIN_ID = 3;
|
||||
public const USER_ORG_ADMIN_USERNAME = 'org_admin';
|
||||
public const USER_ORG_ADMIN_PASSWORD = 'OrgAdminPassword';
|
||||
|
||||
// Regular User user
|
||||
public const USER_REGULAR_USER_ID = 4;
|
||||
public const USER_REGULAR_USER_USERNAME = 'user';
|
||||
public const USER_REGULAR_USER_PASSWORD = 'UserPassword';
|
||||
|
||||
|
||||
public function init(): void
|
||||
{
|
||||
$hasher = new DefaultPasswordHasher();
|
||||
|
||||
$faker = \Faker\Factory::create();
|
||||
|
||||
$this->records = [
|
||||
[
|
||||
'id' => 1,
|
||||
'uuid' => '3ebfbe50-e7d2-406e-a092-f031e604b6e5',
|
||||
'username' => self::ADMIN_USER,
|
||||
'password' => $hasher->hash(self::ADMIN_PASSWORD),
|
||||
'role_id' => 1,
|
||||
'individual_id' => 1,
|
||||
'id' => self::USER_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'username' => self::USER_ADMIN_USERNAME,
|
||||
'password' => $hasher->hash(self::USER_ADMIN_PASSWORD),
|
||||
'role_id' => RolesFixture::ROLE_ADMIN_ID,
|
||||
'individual_id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID,
|
||||
'disabled' => 0,
|
||||
'organisation_id' => 1,
|
||||
'created' => '2022-01-04 10:00:00',
|
||||
'modified' => '2022-01-04 10:00:00'
|
||||
'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID,
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::USER_SYNC_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'username' => self::USER_SYNC_USERNAME,
|
||||
'password' => $hasher->hash(self::USER_SYNC_PASSWORD),
|
||||
'role_id' => RolesFixture::ROLE_SYNC_ID,
|
||||
'individual_id' => IndividualsFixture::INDIVIDUAL_SYNC_ID,
|
||||
'disabled' => 0,
|
||||
'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID,
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::USER_ORG_ADMIN_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'username' => self::USER_ORG_ADMIN_USERNAME,
|
||||
'password' => $hasher->hash(self::USER_ORG_ADMIN_PASSWORD),
|
||||
'role_id' => RolesFixture::ROLE_ORG_ADMIN_ID,
|
||||
'individual_id' => IndividualsFixture::INDIVIDUAL_ORG_ADMIN_ID,
|
||||
'disabled' => 0,
|
||||
'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID,
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
],
|
||||
[
|
||||
'id' => self::USER_REGULAR_USER_ID,
|
||||
'uuid' => $faker->uuid(),
|
||||
'username' => self::USER_REGULAR_USER_USERNAME,
|
||||
'password' => $hasher->hash(self::USER_REGULAR_USER_PASSWORD),
|
||||
'role_id' => RolesFixture::ROLE_REGULAR_USER_ID,
|
||||
'individual_id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID,
|
||||
'disabled' => 0,
|
||||
'organisation_id' => OrganisationsFixture::ORGANISATION_A_ID,
|
||||
'created' => $faker->dateTime()->getTimestamp(),
|
||||
'modified' => $faker->dateTime()->getTimestamp()
|
||||
]
|
||||
];
|
||||
parent::init();
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Test\Helper;
|
||||
|
||||
use \League\OpenAPIValidation\PSR7\ValidatorBuilder;
|
||||
use \League\OpenAPIValidation\PSR7\RequestValidator;
|
||||
use \League\OpenAPIValidation\PSR7\ResponseValidator;
|
||||
use \League\OpenAPIValidation\PSR7\OperationAddress;
|
||||
|
||||
trait ApiTestTrait
|
||||
{
|
||||
/** @var string */
|
||||
protected $_authToken = '';
|
||||
|
||||
/** @var ValidatorBuilder */
|
||||
private $validator;
|
||||
|
||||
/** @var RequestValidator */
|
||||
private $requestValidator;
|
||||
|
||||
/** @var ResponseValidator */
|
||||
private $responseValidator;
|
||||
|
||||
public function setAuthToken(string $authToken): void
|
||||
{
|
||||
$this->_authToken = $authToken;
|
||||
|
||||
// somehow this is not set automatically in test environment
|
||||
$_SERVER['HTTP_AUTHORIZATION'] = $authToken;
|
||||
|
||||
$this->configRequest([
|
||||
'headers' => [
|
||||
'Accept' => 'application/json',
|
||||
'Authorization' => $this->_authToken
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the OpenAPI specification and create a validator
|
||||
*
|
||||
* @param string $specFile
|
||||
* @return void
|
||||
*/
|
||||
public function initializeValidator(string $specFile): void
|
||||
{
|
||||
$this->validator = (new ValidatorBuilder)->fromYamlFile($specFile);
|
||||
$this->requestValidator = $this->validator->getRequestValidator();
|
||||
$this->responseValidator = $this->validator->getResponseValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the API request against the OpenAPI spec
|
||||
*
|
||||
* @param string $path The path to the API endpoint
|
||||
* @param string $method The HTTP method used to call the endpoint
|
||||
* @return void
|
||||
*/
|
||||
public function validateRequest(string $endpoint, string $method = 'get'): void
|
||||
{
|
||||
// TODO: find a workaround to create a PSR-7 request object for validation
|
||||
throw NotImplementedException("Unfortunately cakephp does not save the PSR-7 request object in the test context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the API response against the OpenAPI spec
|
||||
*
|
||||
* @param string $path The path to the API endpoint
|
||||
* @param string $method The HTTP method used to call the endpoint
|
||||
* @return void
|
||||
*/
|
||||
public function validateResponse(string $endpoint, string $method = 'get'): void
|
||||
{
|
||||
$address = new OperationAddress($endpoint, $method);
|
||||
$this->responseValidator->validate($address, $this->_response);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,14 @@
|
|||
# Testing
|
||||
Add a test database to your `config/app_local.php` config file and set `debug` mode to `true`.
|
||||
|
||||
1. Add a `cerebrate_test` database to the db:
|
||||
```mysql
|
||||
CREATE DATABASE cerebrate_test;
|
||||
GRANT ALL PRIVILEGES ON cerebrate_test.* to cerebrate@localhost;
|
||||
FLUSH PRIVILEGES;
|
||||
QUIT;
|
||||
```
|
||||
|
||||
2. Add a the test database to your `config/app_local.php` config file and set `debug` mode to `true`.
|
||||
```php
|
||||
'debug' => true,
|
||||
'Datasources' => [
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Test\TestCase\Api\Users;
|
||||
|
||||
use Cake\TestSuite\IntegrationTestTrait;
|
||||
use Cake\TestSuite\TestCase;
|
||||
use App\Test\Fixture\AuthKeysFixture;
|
||||
use App\Test\Fixture\UsersFixture;
|
||||
use App\Test\Helper\ApiTestTrait;
|
||||
|
||||
class UsersApiTest extends TestCase
|
||||
{
|
||||
use IntegrationTestTrait;
|
||||
use ApiTestTrait;
|
||||
|
||||
protected const ENDPOINT = '/api/v1/users/view';
|
||||
|
||||
protected $fixtures = [
|
||||
'app.Individuals',
|
||||
'app.Roles',
|
||||
'app.Users',
|
||||
'app.AuthKeys'
|
||||
];
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
|
||||
}
|
||||
|
||||
public function testViewMe(): void
|
||||
{
|
||||
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
|
||||
$this->get(self::ENDPOINT);
|
||||
|
||||
$this->assertResponseOk();
|
||||
$this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME));
|
||||
// TODO: $this->validateRequest()
|
||||
$this->validateResponse(self::ENDPOINT);
|
||||
}
|
||||
|
||||
public function testViewById(): void
|
||||
{
|
||||
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
|
||||
$url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ADMIN_ID);
|
||||
$this->get($url);
|
||||
|
||||
$this->assertResponseOk();
|
||||
$this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::USER_ADMIN_USERNAME));
|
||||
// TODO: $this->validateRequest()
|
||||
$this->validateResponse($url);
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Test\TestCase\Api;
|
||||
|
||||
use Cake\TestSuite\IntegrationTestTrait;
|
||||
use Cake\TestSuite\TestCase;
|
||||
use App\Test\Fixture\AuthKeysFixture;
|
||||
use App\Test\Fixture\UsersFixture;
|
||||
|
||||
class UsersApiTest extends TestCase
|
||||
{
|
||||
use IntegrationTestTrait;
|
||||
|
||||
protected $fixtures = [
|
||||
'app.Individuals',
|
||||
'app.Roles',
|
||||
'app.Users',
|
||||
'app.AuthKeys'
|
||||
];
|
||||
|
||||
public function testViewMe(): void
|
||||
{
|
||||
// ugly hack, $_SERVER['HTTP_AUTHORIZATION'] is not set automatically in test environment
|
||||
$_SERVER['HTTP_AUTHORIZATION'] = AuthKeysFixture::ADMIN_API_KEY;
|
||||
$this->configRequest([
|
||||
'headers' => [
|
||||
// this does not work: https://book.cakephp.org/4/en/development/testing.html#testing-stateless-authentication-and-apis
|
||||
// 'Authorization' => AuthKeysFixture::ADMIN_API_KEY,
|
||||
'Accept' => 'application/json'
|
||||
]
|
||||
]);
|
||||
|
||||
$this->get('/users/view');
|
||||
|
||||
$this->assertResponseOk();
|
||||
$this->assertResponseContains(sprintf('"username": "%s"', UsersFixture::ADMIN_USER));
|
||||
}
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Test\TestCase\Controller;
|
||||
namespace App\Test\TestCase\Controller\Users;
|
||||
|
||||
use Cake\TestSuite\IntegrationTestTrait;
|
||||
use Cake\TestSuite\TestCase;
|
|
@ -66,4 +66,6 @@ if (!in_array('skip-migrations', $_SERVER['argv'])) {
|
|||
['plugin' => 'Tags', 'connection' => 'test', 'skip' => ['*']],
|
||||
['plugin' => 'ADmad/SocialAuth', 'connection' => 'test', 'skip' => ['*']]
|
||||
]);
|
||||
}else{
|
||||
echo "[ * ] Skipping migrations ...\n";
|
||||
}
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 1.3.0
|
||||
title: Cerebrate Project API
|
||||
description: |
|
||||
|
||||
TODO: markdown description
|
||||
|
||||
servers:
|
||||
- url: https://cerebrate.local
|
||||
|
||||
tags:
|
||||
- name: Users
|
||||
description: "TODO: users resource descriptions"
|
||||
|
||||
paths:
|
||||
/api/v1/users/view:
|
||||
get:
|
||||
summary: "Get information about the current user"
|
||||
operationId: viewUserMe
|
||||
tags:
|
||||
- Users
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/ViewUserResponse"
|
||||
"403":
|
||||
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ApiErrorResponse"
|
||||
|
||||
/api/v1/users/view/{userId}:
|
||||
get:
|
||||
summary: "Get information of a user by id"
|
||||
operationId: viewUserById
|
||||
tags:
|
||||
- Users
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/userId"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/ViewUserResponse"
|
||||
"403":
|
||||
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
|
||||
default:
|
||||
$ref: "#/components/responses/ApiErrorResponse"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
# General
|
||||
UUID:
|
||||
type: string
|
||||
format: uuid
|
||||
maxLength: 36
|
||||
example: "c99506a6-1255-4b71-afa5-7b8ba48c3b1b"
|
||||
|
||||
ID:
|
||||
type: integer
|
||||
format: int32
|
||||
example: 1
|
||||
|
||||
DateTime:
|
||||
type: string
|
||||
format: datetime
|
||||
example: "2022-01-05T11:19:26+00:00"
|
||||
|
||||
# Users
|
||||
Username:
|
||||
type: string
|
||||
example: "admin"
|
||||
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/ID"
|
||||
uuid:
|
||||
$ref: "#/components/schemas/UUID"
|
||||
username:
|
||||
$ref: "#/components/schemas/Username"
|
||||
role_id:
|
||||
$ref: "#/components/schemas/ID"
|
||||
individual_id:
|
||||
$ref: "#/components/schemas/ID"
|
||||
disabled:
|
||||
type: boolean
|
||||
created:
|
||||
$ref: "#/components/schemas/DateTime"
|
||||
modified:
|
||||
$ref: "#/components/schemas/DateTime"
|
||||
organisation_id:
|
||||
$ref: "#/components/schemas/ID"
|
||||
|
||||
# Individuals
|
||||
|
||||
# Organisations
|
||||
|
||||
# Roles
|
||||
RoleName:
|
||||
type: string
|
||||
maxLength: 255
|
||||
example: "admin"
|
||||
|
||||
Role:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
$ref: "#/components/schemas/ID"
|
||||
name:
|
||||
$ref: "#/components/schemas/RoleName"
|
||||
is_default:
|
||||
type: boolean
|
||||
perm_admin:
|
||||
type: boolean
|
||||
perm_sync:
|
||||
type: boolean
|
||||
perm_org_admin:
|
||||
type: boolean
|
||||
|
||||
# Errors
|
||||
ApiError:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- message
|
||||
- url
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
url:
|
||||
type: string
|
||||
example: "/users"
|
||||
|
||||
UnauthorizedApiError:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- message
|
||||
- url
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header."
|
||||
message:
|
||||
type: string
|
||||
example: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header."
|
||||
url:
|
||||
type: string
|
||||
example: "/users"
|
||||
|
||||
NotFoundApiError:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- message
|
||||
- url
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: "Invalid user"
|
||||
message:
|
||||
type: string
|
||||
example: "Invalid user"
|
||||
url:
|
||||
type: string
|
||||
example: "/users/1234"
|
||||
|
||||
parameters:
|
||||
userId:
|
||||
name: userId
|
||||
in: path
|
||||
description: "Numeric ID of the User"
|
||||
required: true
|
||||
schema:
|
||||
$ref: "#/components/schemas/ID"
|
||||
|
||||
securitySchemes:
|
||||
ApiKeyAuth:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: Authorization
|
||||
description: |
|
||||
The authorization is performed by using the following header in the HTTP requests:
|
||||
|
||||
Authorization: YOUR_API_KEY
|
||||
|
||||
# requestBodies:
|
||||
|
||||
responses:
|
||||
# User
|
||||
ViewUserResponse:
|
||||
description: "User response"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/User"
|
||||
|
||||
# Errors
|
||||
ApiErrorResponse:
|
||||
description: "Unexpected API error"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiError"
|
||||
|
||||
UnauthorizedApiErrorResponse:
|
||||
description: "Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UnauthorizedApiError"
|
||||
|
||||
security:
|
||||
- ApiKeyAuth: []
|
Loading…
Reference in New Issue