add: add api tests for tags and orgs, extend openapi spec, fix routes for tags plugin

pull/80/head
Luciano Righetti 2022-01-11 12:33:34 +01:00
parent 241e760ad2
commit f774f68ede
10 changed files with 657 additions and 3 deletions

View File

@ -101,5 +101,18 @@ $routes->scope('/api', function (RouteBuilder $routes) {
// Generic API route // Generic API route
$routes->connect('/{controller}/{action}/*'); $routes->connect('/{controller}/{action}/*');
// Tags plugin routes
$routes->plugin(
'tags',
['path' => '/tags'],
function ($routes) {
$routes->setRouteClass(DashedRoute::class);
$routes->connect(
'/{action}/*',
['controller' => 'Tags']
);
}
);
}); });
}); });

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class TagsTaggedsFixture extends TestFixture
{
public $connection = 'test';
public $table = 'tags_tagged';
public const TAG_RED_ID = 1;
public const TAG_GREEN_ID = 2;
public const TAG_BLUE_ID = 3;
public function init(): void
{
$faker = \Faker\Factory::create();
$this->records = [
[
'tag_id' => TagsTagsFixture::TAG_ORG_A_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_A_ID,
'fk_model' => 'Organisations',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
[
'tag_id' => TagsTagsFixture::TAG_ORG_B_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_B_ID,
'fk_model' => 'Organisations',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
];
parent::init();
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace App\Test\Fixture;
use Cake\TestSuite\Fixture\TestFixture;
class TagsTagsFixture extends TestFixture
{
public $connection = 'test';
public const TAG_RED_ID = 1;
public const TAG_GREEN_ID = 2;
public const TAG_BLUE_ID = 3;
public const TAG_ORG_A_ID = 4;
public const TAG_ORG_B_ID = 5;
public function init(): void
{
$faker = \Faker\Factory::create();
$this->records = [
[
'id' => self::TAG_RED_ID,
'name' => 'red',
'namespace' => null,
'predicate' => null,
'value' => null,
'colour' => 'FF0000',
'counter' => 0,
'text_colour' => 'red',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
[
'id' => self::TAG_GREEN_ID,
'name' => 'green',
'namespace' => null,
'predicate' => null,
'value' => null,
'colour' => '00FF00',
'counter' => 0,
'text_colour' => 'green',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
[
'id' => self::TAG_BLUE_ID,
'name' => 'blue',
'namespace' => null,
'predicate' => null,
'value' => null,
'colour' => '0000FF',
'counter' => 0,
'text_colour' => 'blue',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
[
'id' => self::TAG_ORG_A_ID,
'name' => 'org-a',
'namespace' => null,
'predicate' => null,
'value' => null,
'colour' => '000000',
'counter' => 0,
'text_colour' => 'black',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
],
[
'id' => self::TAG_ORG_B_ID,
'name' => 'org-b',
'namespace' => null,
'predicate' => null,
'value' => null,
'colour' => '000000',
'counter' => 0,
'text_colour' => 'black',
'created' => $faker->dateTime()->getTimestamp(),
'modified' => $faker->dateTime()->getTimestamp()
]
];
parent::init();
}
}

View File

@ -0,0 +1,59 @@
<?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\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
class DeleteOrganisationApiTest extends TestCase
{
use IntegrationTestTrait;
use ApiTestTrait;
protected const ENDPOINT = '/api/v1/organisations/delete';
protected $fixtures = [
'app.Organisations',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function setUp(): void
{
parent::setUp();
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
}
public function testDeleteOrganisation(): void
{
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID);
$this->delete($url);
$this->assertResponseOk();
$this->assertDbRecordNotExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'delete');
$this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.');
}
public function testDeleteOrganisationNotAllowedToRegularUser(): void
{
$this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID);
$this->delete($url);
$this->assertResponseCode(405);
$this->assertDbRecordExists('Organisations', ['id' => OrganisationsFixture::ORGANISATION_B_ID]);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'delete');
$this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.');
}
}

View File

@ -0,0 +1,45 @@
<?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\OrganisationsFixture;
use App\Test\Helper\ApiTestTrait;
class IndexOrganisationApiTest extends TestCase
{
use IntegrationTestTrait;
use ApiTestTrait;
protected const ENDPOINT = '/api/v1/organisations/index';
protected $fixtures = [
'app.Organisations',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function setUp(): void
{
parent::setUp();
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
}
public function testIndexOrganisations(): void
{
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$this->get(self::ENDPOINT);
$this->assertResponseOk();
$this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_A_ID));
$this->assertResponseContains(sprintf('"id": %d', OrganisationsFixture::ORGANISATION_B_ID));
// TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec(self::ENDPOINT);
}
}

View File

@ -0,0 +1,86 @@
<?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\OrganisationsFixture;
use App\Test\Fixture\TagsTagsFixture;
use App\Test\Helper\ApiTestTrait;
class TagOrganisationApiTest extends TestCase
{
use IntegrationTestTrait;
use ApiTestTrait;
protected const ENDPOINT = '/api/v1/organisations/tag';
protected $fixtures = [
'app.TagsTags',
'app.Organisations',
'app.TagsTaggeds',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function setUp(): void
{
parent::setUp();
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
}
public function testTagOrganisation(): void
{
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID);
$this->post(
$url,
[
'tag_list' => "[\"red\"]"
]
);
$this->assertResponseOk();
$this->assertDbRecordExists(
'TagsTagged',
[
'tag_id' => TagsTagsFixture::TAG_RED_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_A_ID,
'fk_model' => 'Organisations'
]
);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'post');
}
public function testTagOrganisationNotAllowedToRegularUser(): void
{
$this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID);
$this->post(
$url,
[
'tag_list' => "[\"green\"]"
]
);
$this->assertResponseCode(405);
$this->assertDbRecordNotExists(
'TagsTagged',
[
'tag_id' => TagsTagsFixture::TAG_GREEN_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_A_ID,
'fk_model' => 'Organisations'
]
);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'post');
}
}

View File

@ -0,0 +1,86 @@
<?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\OrganisationsFixture;
use App\Test\Fixture\TagsTagsFixture;
use App\Test\Helper\ApiTestTrait;
class UntagOrganisationApiTest extends TestCase
{
use IntegrationTestTrait;
use ApiTestTrait;
protected const ENDPOINT = '/api/v1/organisations/untag';
protected $fixtures = [
'app.TagsTags',
'app.Organisations',
'app.TagsTaggeds',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function setUp(): void
{
parent::setUp();
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
}
public function testUntagOrganisation(): void
{
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID);
$this->post(
$url,
[
'tag_list' => "[\"org-a\"]"
]
);
$this->assertResponseOk();
$this->assertDbRecordNotExists(
'TagsTagged',
[
'tag_id' => TagsTagsFixture::TAG_ORG_A_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_A_ID,
'fk_model' => 'Organisations'
]
);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'post');
}
public function testUntagOrganisationNotAllowedToRegularUser(): void
{
$this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY);
$url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID);
$this->post(
$url,
[
'tag_list' => "[\"org-a\"]"
]
);
$this->assertResponseCode(405);
$this->assertDbRecordExists(
'TagsTagged',
[
'tag_id' => TagsTagsFixture::TAG_ORG_A_ID,
'fk_id' => OrganisationsFixture::ORGANISATION_A_ID,
'fk_model' => 'Organisations'
]
);
//TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec($url, 'post');
}
}

View File

@ -0,0 +1,47 @@
<?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 IndexTagsApiTest extends TestCase
{
use IntegrationTestTrait;
use ApiTestTrait;
protected const ENDPOINT = '/api/v1/tags/index';
protected $fixtures = [
'app.TagsTags',
'app.Individuals',
'app.Roles',
'app.Users',
'app.AuthKeys'
];
public function setUp(): void
{
parent::setUp();
$this->initializeValidator(APP . '../webroot/docs/openapi.yaml');
}
public function testIndexTags(): void
{
$this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY);
$this->get(self::ENDPOINT);
$this->assertResponseOk();
$this->assertResponseContains('"name": "red"');
$this->assertResponseContains('"name": "green"');
$this->assertResponseContains('"name": "blue"');
// TODO: $this->assertRequestMatchesOpenApiSpec();
$this->assertResponseMatchesOpenApiSpec(self::ENDPOINT);
}
}

View File

@ -8,7 +8,6 @@ use Cake\TestSuite\IntegrationTestTrait;
use Cake\TestSuite\TestCase; use Cake\TestSuite\TestCase;
use App\Test\Fixture\AuthKeysFixture; use App\Test\Fixture\AuthKeysFixture;
use App\Test\Fixture\UsersFixture; use App\Test\Fixture\UsersFixture;
use App\Test\Fixture\OrganisationsFixture;
use App\Test\Fixture\RolesFixture; use App\Test\Fixture\RolesFixture;
use App\Test\Helper\ApiTestTrait; use App\Test\Helper\ApiTestTrait;

View File

@ -13,6 +13,8 @@ tags:
description: "Users enrolled in this Cerebrate instance." description: "Users enrolled in this Cerebrate instance."
- name: Organisations - name: Organisations
description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles." description: "Organisations can be equivalent to legal entities or specific individual teams within such entities. Their purpose is to relate individuals to their affiliations and for release control of information using the Trust Circles."
- name: Tags
description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches."
paths: paths:
/api/v1/users/index: /api/v1/users/index:
@ -177,6 +179,96 @@ paths:
default: default:
$ref: "#/components/responses/ApiErrorResponse" $ref: "#/components/responses/ApiErrorResponse"
/api/v1/organisations/index:
get:
summary: "Get organisations"
operationId: getOrganisations
tags:
- Organisations
responses:
"200":
$ref: "#/components/responses/OrganisationListResponse"
"403":
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
"405":
$ref: "#/components/responses/MethodNotAllowedApiErrorResponse"
default:
$ref: "#/components/responses/ApiErrorResponse"
/api/v1/organisations/delete/{organisationId}:
delete:
summary: "Delete organisation by ID"
operationId: deleteOrganisationById
tags:
- Organisations
parameters:
- $ref: "#/components/parameters/organisationId"
responses:
"200":
$ref: "#/components/responses/OrganisationResponse"
"403":
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
"405":
$ref: "#/components/responses/MethodNotAllowedApiErrorResponse"
default:
$ref: "#/components/responses/ApiErrorResponse"
/api/v1/organisations/tag/{organisationId}:
post:
summary: "Tag organisation by ID"
operationId: tagOrganisationById
tags:
- Organisations
parameters:
- $ref: "#/components/parameters/organisationId"
requestBody:
$ref: "#/components/requestBodies/TagOrganisationRequest"
responses:
"200":
$ref: "#/components/responses/OrganisationResponse"
"403":
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
"405":
$ref: "#/components/responses/MethodNotAllowedApiErrorResponse"
default:
$ref: "#/components/responses/ApiErrorResponse"
/api/v1/organisations/untag/{organisationId}:
post:
summary: "Remove organisation tag by ID"
operationId: untagOrganisationById
tags:
- Organisations
parameters:
- $ref: "#/components/parameters/organisationId"
requestBody:
$ref: "#/components/requestBodies/UntagOrganisationRequest"
responses:
"200":
$ref: "#/components/responses/OrganisationResponse"
"403":
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
"405":
$ref: "#/components/responses/MethodNotAllowedApiErrorResponse"
default:
$ref: "#/components/responses/ApiErrorResponse"
/api/v1/tags/index:
get:
summary: "Get tags list"
operationId: getTags
tags:
- Tags
responses:
"200":
$ref: "#/components/responses/TagListResponse"
"403":
$ref: "#/components/responses/UnauthorizedApiErrorResponse"
"405":
$ref: "#/components/responses/MethodNotAllowedApiErrorResponse"
default:
$ref: "#/components/responses/ApiErrorResponse"
components: components:
schemas: schemas:
# General # General
@ -281,9 +373,60 @@ components:
aligments: aligments:
$ref: "#/components/schemas/AligmentList" $ref: "#/components/schemas/AligmentList"
OrganisationList:
type: array
items:
$ref: "#/components/schemas/Organisation"
# Tags # Tags
TagName:
type: string
example: "white"
TagNamespace:
type: string
nullable: true
example: "tlp"
TagPredicate:
type: string
nullable: true
TagValue:
type: string
nullable: true
TagColour:
type: string
example: "FFFFFF"
TagTextColour:
type: string
example: "white"
Tag: Tag:
type: object type: object
properties:
id:
$ref: "#/components/schemas/ID"
name:
$ref: "#/components/schemas/TagName"
namespace:
$ref: "#/components/schemas/TagNamespace"
predicate:
$ref: "#/components/schemas/TagPredicate"
value:
$ref: "#/components/schemas/TagValue"
colour:
$ref: "#/components/schemas/TagColour"
text_colour:
$ref: "#/components/schemas/TagTextColour"
counter:
type: integer
created:
$ref: "#/components/schemas/DateTime"
modified:
$ref: "#/components/schemas/DateTime"
TagList: TagList:
type: array type: array
@ -417,6 +560,7 @@ components:
Authorization: YOUR_API_KEY Authorization: YOUR_API_KEY
requestBodies: requestBodies:
# Users
AddUserRequest: AddUserRequest:
required: true required: true
content: content:
@ -457,6 +601,7 @@ components:
password: password:
type: string type: string
# Organisations
AddOrganisationRequest: AddOrganisationRequest:
required: true required: true
content: content:
@ -501,8 +646,32 @@ components:
contacts: contacts:
$ref: "#/components/schemas/OrganisationContacts" $ref: "#/components/schemas/OrganisationContacts"
TagOrganisationRequest:
required: true
content:
application/json:
schema:
type: object
properties:
tag_list:
type: string
description: "Stringified JSON array of the tag names to add."
example: '["red"]'
UntagOrganisationRequest:
required: true
content:
application/json:
schema:
type: object
properties:
tag_list:
type: string
description: "Stringified JSON array of the tag names to remove."
example: '["red"]'
responses: responses:
# User # Users
UserResponse: UserResponse:
description: "User response" description: "User response"
content: content:
@ -517,6 +686,7 @@ components:
schema: schema:
$ref: "#/components/schemas/UserList" $ref: "#/components/schemas/UserList"
# Organisations
OrganisationResponse: OrganisationResponse:
description: "Organisation response" description: "Organisation response"
content: content:
@ -524,6 +694,28 @@ components:
schema: schema:
$ref: "#/components/schemas/Organisation" $ref: "#/components/schemas/Organisation"
OrganisationListResponse:
description: "Organisations list response"
content:
application/json:
schema:
$ref: "#/components/schemas/OrganisationList"
# Tags
TagResponse:
description: "Tag response"
content:
application/json:
schema:
$ref: "#/components/schemas/Tag"
TagListResponse:
description: "Tags list response"
content:
application/json:
schema:
$ref: "#/components/schemas/TagList"
# Errors # Errors
ApiErrorResponse: ApiErrorResponse:
description: "Unexpected API error" description: "Unexpected API error"