diff --git a/tests/Fixture/IndividualsFixture.php b/tests/Fixture/IndividualsFixture.php index 9c96f8b..13aecdf 100644 --- a/tests/Fixture/IndividualsFixture.php +++ b/tests/Fixture/IndividualsFixture.php @@ -10,17 +10,11 @@ class IndividualsFixture extends TestFixture { public $connection = 'test'; - // 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 const INDIVIDUAL_A_ID = 5; public function init(): void { @@ -66,6 +60,16 @@ class IndividualsFixture extends TestFixture 'position' => 'user', 'created' => $faker->dateTime()->getTimestamp(), 'modified' => $faker->dateTime()->getTimestamp() + ], + [ + 'id' => self::INDIVIDUAL_A_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(); diff --git a/tests/Fixture/OrganisationsFixture.php b/tests/Fixture/OrganisationsFixture.php index d8b81f9..7b7439e 100644 --- a/tests/Fixture/OrganisationsFixture.php +++ b/tests/Fixture/OrganisationsFixture.php @@ -11,10 +11,7 @@ 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 diff --git a/tests/TestCase/Api/Individuals/AddIndividualApiTest.php b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php new file mode 100644 index 0000000..64855e8 --- /dev/null +++ b/tests/TestCase/Api/Individuals/AddIndividualApiTest.php @@ -0,0 +1,71 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testAddIndividual(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'email' => 'john@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'position' => 'Security Analyst' + ] + ); + + $this->assertResponseOk(); + $this->assertResponseContains('"email": "john@example.com"'); + $this->assertDbRecordExists('Individuals', ['email' => 'john@example.com']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } + + public function testAddUserNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->post( + self::ENDPOINT, + [ + 'email' => 'john@example.com', + 'first_name' => 'John', + 'last_name' => 'Doe', + 'position' => 'Security Analyst' + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Individuals', ['email' => 'john@example.com']); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); + } +} diff --git a/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php new file mode 100644 index 0000000..c493c4b --- /dev/null +++ b/tests/TestCase/Api/Individuals/DeleteIndividualApiTest.php @@ -0,0 +1,59 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testDeleteIndividual(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_A_ID); + $this->delete($url); + + $this->assertResponseOk(); + $this->assertDbRecordNotExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_A_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 testDeleteIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->delete($url); + + $this->assertResponseCode(405); + $this->assertDbRecordExists('Individuals', ['id' => IndividualsFixture::INDIVIDUAL_ADMIN_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.'); + } +} diff --git a/tests/TestCase/Api/Individuals/EditIndividualApiTest.php b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php new file mode 100644 index 0000000..fcef7fd --- /dev/null +++ b/tests/TestCase/Api/Individuals/EditIndividualApiTest.php @@ -0,0 +1,73 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testEditIndividualAsAdmin(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseOk(); + $this->assertDbRecordExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_REGULAR_USER_ID, + 'email' => 'foo@bar.com' + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } + + public function testEditAnyIndividualNotAllowedAsRegularUser(): void + { + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, IndividualsFixture::INDIVIDUAL_ADMIN_ID); + $this->put( + $url, + [ + 'email' => 'foo@bar.com', + ] + ); + + $this->assertResponseCode(405); + $this->assertDbRecordNotExists('Individuals', [ + 'id' => IndividualsFixture::INDIVIDUAL_ADMIN_ID, + 'email' => 'foo@bar.com' + ]); + //TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url, 'put'); + } +} diff --git a/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php new file mode 100644 index 0000000..55f6be1 --- /dev/null +++ b/tests/TestCase/Api/Individuals/IndexIndividualsApiTest.php @@ -0,0 +1,44 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testIndexIndividuals(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"id": %d', IndividualsFixture::INDIVIDUAL_ADMIN_ID)); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT); + } +} diff --git a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php index 4f43a09..a3f2585 100644 --- a/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/AddOrganisationApiTest.php @@ -57,7 +57,7 @@ class AddOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } - public function testAddOrganisationNotAllowedToRegularUser(): void + public function testAddOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php index 9840faf..e16f57b 100644 --- a/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/DeleteOrganisationApiTest.php @@ -44,7 +44,7 @@ class DeleteOrganisationApiTest extends TestCase $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } - public function testDeleteOrganisationNotAllowedToRegularUser(): void + public function testDeleteOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_B_ID); diff --git a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php index 61b2cab..d9cc7c6 100644 --- a/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/EditOrganisationApiTest.php @@ -55,7 +55,7 @@ class EditOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'put'); } - public function testEditOrganisationNotAllowedToRegularUser(): void + public function testEditOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php index e23fcbc..ed74c74 100644 --- a/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/TagOrganisationApiTest.php @@ -59,7 +59,7 @@ class TagOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'post'); } - public function testTagOrganisationNotAllowedToRegularUser(): void + public function testTagOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php index 0c23ca4..c8878d6 100644 --- a/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php +++ b/tests/TestCase/Api/Organisations/UntagOrganisationApiTest.php @@ -59,7 +59,7 @@ class UntagOrganisationApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'post'); } - public function testUntagOrganisationNotAllowedToRegularUser(): void + public function testUntagOrganisationNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); diff --git a/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php new file mode 100644 index 0000000..630fd92 --- /dev/null +++ b/tests/TestCase/Api/Organisations/ViewOrganisationApiTest.php @@ -0,0 +1,48 @@ +initializeValidator(APP . '../webroot/docs/openapi.yaml'); + } + + public function testViewOrganisationById(): void + { + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $url = sprintf('%s/%d', self::ENDPOINT, OrganisationsFixture::ORGANISATION_A_ID); + $this->get($url); + + $this->assertResponseOk(); + $this->assertResponseContains('"name": "Organisation A"'); + // TODO: $this->assertRequestMatchesOpenApiSpec(); + $this->assertResponseMatchesOpenApiSpec($url); + } +} diff --git a/tests/TestCase/Api/Users/AddUserApiTest.php b/tests/TestCase/Api/Users/AddUserApiTest.php index 2d23f2a..e60cf3a 100644 --- a/tests/TestCase/Api/Users/AddUserApiTest.php +++ b/tests/TestCase/Api/Users/AddUserApiTest.php @@ -55,7 +55,7 @@ class AddUserApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec(self::ENDPOINT, 'post'); } - public function testAddUserNotAllowedToRegularUser(): void + public function testAddUserNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $this->post( diff --git a/tests/TestCase/Api/Users/DeleteUserApiTest.php b/tests/TestCase/Api/Users/DeleteUserApiTest.php index 47a0580..69bd87c 100644 --- a/tests/TestCase/Api/Users/DeleteUserApiTest.php +++ b/tests/TestCase/Api/Users/DeleteUserApiTest.php @@ -46,7 +46,7 @@ class DeleteUserApiTest extends TestCase $this->addWarning('TODO: CRUDComponent::delete() sets some view variables, does not take into account `isRest()`, fix it.'); } - public function testDeleteUserNotAllowedToRegularUser(): void + public function testDeleteUserNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $url = sprintf('%s/%d', self::ENDPOINT, UsersFixture::USER_ORG_ADMIN_ID); diff --git a/tests/TestCase/Api/Users/EditUserApiTest.php b/tests/TestCase/Api/Users/EditUserApiTest.php index 6154c00..a8afa11 100644 --- a/tests/TestCase/Api/Users/EditUserApiTest.php +++ b/tests/TestCase/Api/Users/EditUserApiTest.php @@ -53,7 +53,7 @@ class EditUserApiTest extends TestCase $this->assertResponseMatchesOpenApiSpec($url, 'put'); } - public function testEditRoleNotAllowedToRegularUser(): void + public function testEditRoleNotAllowedAsRegularUser(): void { $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); $this->put( diff --git a/webroot/docs/openapi.yaml b/webroot/docs/openapi.yaml index f76a861..d74ad68 100644 --- a/webroot/docs/openapi.yaml +++ b/webroot/docs/openapi.yaml @@ -9,6 +9,8 @@ servers: - url: https://cerebrate.local tags: + - name: Individuals + description: "Individuals are natural persons. They are meant to describe the basic information about an individual that may or may not be a user of this community. Users in genral require an individual object to identify the person behind them - however, no user account is required to store information about an individual. Individuals can have affiliations to organisations and broods as well as cryptographic keys, using which their messages can be verified and which can be used to securely contact them." - name: Users description: "Users enrolled in this Cerebrate instance." - name: Organisations @@ -17,12 +19,88 @@ tags: description: "Tags can be attached to entity to quickly classify them, allowing further filtering and searches." paths: + /api/v1/individuals/index: + get: + summary: "Get individuals list" + operationId: getIndividuals + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/quickFilter" + responses: + "200": + $ref: "#/components/responses/IndividualListResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/add: + post: + summary: "Add individual" + operationId: addIndividual + tags: + - Users + requestBody: + $ref: "#/components/requestBodies/AddIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/edit/{individualId}: + put: + summary: "Edit individual" + operationId: editIndividual + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + requestBody: + $ref: "#/components/requestBodies/EditIndividualRequest" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + + /api/v1/individuals/delete/{individualId}: + delete: + summary: "Delete individual by ID" + operationId: deleteIndividualById + tags: + - Individuals + parameters: + - $ref: "#/components/parameters/individualId" + responses: + "200": + $ref: "#/components/responses/IndividualResponse" + "403": + $ref: "#/components/responses/UnauthorizedApiErrorResponse" + "405": + $ref: "#/components/responses/MethodNotAllowedApiErrorResponse" + default: + $ref: "#/components/responses/ApiErrorResponse" + /api/v1/users/index: get: summary: "Get users list" operationId: getUsers tags: - Users + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/UserListResponse" @@ -185,6 +263,8 @@ paths: operationId: getOrganisations tags: - Organisations + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/OrganisationListResponse" @@ -195,6 +275,24 @@ paths: default: $ref: "#/components/responses/ApiErrorResponse" + /api/v1/organisations/view/{organisationId}: + get: + summary: "View organisation by ID" + operationId: getOrganisationById + 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/delete/{organisationId}: delete: summary: "Delete organisation by ID" @@ -259,6 +357,8 @@ paths: operationId: getTags tags: - Tags + parameters: + - $ref: "#/components/parameters/quickFilter" responses: "200": $ref: "#/components/responses/TagListResponse" @@ -288,11 +388,64 @@ components: format: datetime example: "2022-01-05T11:19:26+00:00" + Email: + type: string + format: email + example: "user@example.com" + + # Individuals + IndividualFirstName: + type: string + example: "John" + + IndividualLastName: + type: string + example: "Doe" + + IndividualFullName: + type: string + example: "John Doe" + + IndividualPosition: + type: string + example: "Security Analyst" + + Individual: + type: object + properties: + id: + $ref: "#/components/schemas/ID" + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/Email" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + $ref: "#/components/schemas/IndividualLastName" + full_name: + $ref: "#/components/schemas/IndividualFullName" + position: + $ref: "#/components/schemas/IndividualPosition" + tags: + $ref: "#/components/schemas/TagList" + aligments: + $ref: "#/components/schemas/AligmentList" + created: + $ref: "#/components/schemas/DateTime" + modified: + $ref: "#/components/schemas/DateTime" + # Users Username: type: string example: "admin" + IndividualList: + type: array + items: + $ref: "#/components/schemas/Individual" + User: type: object properties: @@ -320,8 +473,6 @@ components: items: $ref: "#/components/schemas/User" - # Individuals - # Organisations OrganisationName: type: string @@ -533,6 +684,14 @@ components: example: 404 parameters: + individualId: + name: userId + in: path + description: "Numeric ID of the User" + required: true + schema: + $ref: "#/components/schemas/ID" + userId: name: userId in: path @@ -549,6 +708,14 @@ components: schema: $ref: "#/components/schemas/ID" + quickFilter: + name: quickFilter + in: query + description: "Quick filter used to match multiple attributes such as name, description, emails, etc." + schema: + type: string + example: "user@example.com" + securitySchemes: ApiKeyAuth: type: apiKey @@ -560,6 +727,43 @@ components: Authorization: YOUR_API_KEY requestBodies: + # Individuals + AddIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/IndividualLastName" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + type: boolean + position: + $ref: "#/components/schemas/IndividualPosition" + + EditIndividualRequest: + required: true + content: + application/json: + schema: + type: object + properties: + uuid: + $ref: "#/components/schemas/UUID" + email: + $ref: "#/components/schemas/IndividualLastName" + first_name: + $ref: "#/components/schemas/IndividualFirstName" + last_name: + type: boolean + position: + $ref: "#/components/schemas/IndividualPosition" + # Users AddUserRequest: required: true @@ -588,6 +792,8 @@ components: schema: type: object properties: + id: + $ref: "#/components/schemas/ID" individual_id: $ref: "#/components/schemas/ID" organisation_id: @@ -671,6 +877,21 @@ components: example: '["red"]' responses: + # Individuals + IndividualResponse: + description: "Individual response" + content: + application/json: + schema: + $ref: "#/components/schemas/Individual" + + IndividualListResponse: + description: "Individuals list response" + content: + application/json: + schema: + $ref: "#/components/schemas/IndividualList" + # Users UserResponse: description: "User response"