diff --git a/README.md b/README.md index 8fdb5adf..a74c7fe2 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,19 @@ # phalcon-api Sample API using Phalcon -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/niden/phalcon-api/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/niden/phalcon-api/?branch=master) -[![Code Coverage](https://scrutinizer-ci.com/g/niden/phalcon-api/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/niden/phalcon-api/?branch=master) -[![Build Status](https://scrutinizer-ci.com/g/niden/phalcon-api/badges/build.png?b=master)](https://scrutinizer-ci.com/g/niden/phalcon-api/build-status/master) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/phalcon/phalcon-api/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/phalcon/phalcon-api/?branch=master) +[![Code Coverage](https://scrutinizer-ci.com/g/phalcon/phalcon-api/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/phalcon/phalcon-api/?branch=master) +[![Build Status](https://scrutinizer-ci.com/g/phalcon/phalcon-api/badges/build.png?b=master)](https://scrutinizer-ci.com/g/phalcon/phalcon-api/build-status/master) -Implementation of an API application using the Phalcon Framework (https://phalconphp.com) +Implementation of an API application using the Phalcon Framework [https://phalconphp.com](https://phalconphp.com) ### Installation - Clone the project - In the project folder run `nanobox run php-server` - Hit the IP address with postman -This requires [nanobox](https://nanobox.io) to be present in your system. Visit their site for installation instructions. +**NOTE** This requires [nanobox](https://nanobox.io) to be present in your system. Visit their site for installation instructions. ### Features ##### JWT Tokens @@ -24,65 +24,106 @@ As part of the security of the API, [JWT](https://jwt.io) are used. JSON Web Tok - Stop execution as early as possible when an error occurs - Execution - NotFound - 404 when the resource requested is not found - - Payload - Check the posted JSON string if it is correct - Authentication - After a `/login` checks the `Authentication` header - TokenUser - When a token is supplied, check if it corresponds to a user in the database - TokenVerification - When a token is supplied, check if it is correctly signed - TokenValidation - When a token is supplied, check if it is valid (`issuedAt`, `notBefore`, `expires`) +##### JSONAPI +This implementation follows the [JSON API](https://jsonapi.org) standard. All responses are formatted according to the standard, which offers a uniformed way of presenting data, simple or compound documents, includes (related data), sparse fieldsets, sorting, patination and filtering. + ### Usage #### Requests -All requests to the API have be submitted using `POST`. All requests must send a JSON string with one root element `data`. Data needed for the request must be under the `data` element - -The endpoints are: - -`/login` - -| Method | Payload | -|--------|--------------------------------------------------------| -| `POST` | `{"data": {"username": "niden", "password": "12345"}}` | +The routes available are: + +| Method | Route | Parameters | Action | +|--------|--------------------|------------------------------------|----------------------------------------------------------| +| `POST` | `login` | `username`, `password` | Login - get Token | +| `POST` | `companies` | `name`, `address`, `city`, `phone` | Add a company record in the database | +| `GET` | `companies` | | Get companies. Empty resultset if no data present | +| `GET` | `companies` | Numeric Id | Get company by id. 404 if record does not exist | +| `GET` | `individuals` | | Get individuals. Empty resultset if no data present | +| `GET` | `individuals` | Numeric Id | Get individual by id. 404 if record does not exist | +| `GET` | `individual-types` | | Get individual types. Empty resultset if no data present | +| `GET` | `individual-types` | Numeric Id | Get individual type by id. 404 if record does not exist | +| `GET` | `products` | | Get products. Empty resultset if no data present | +| `GET` | `products` | Numeric Id | Get product by id. 404 if record does not exist | +| `GET` | `product-types` | | Get product types. Empty resultset if no data present | +| `GET` | `product-types` | Numeric Id | Get product type by id. 404 if record does not exist | +| `GET` | `users` | | Get users. Empty resultset if no data present | +| `GET` | `users` | Numeric Id | Get user by id. 404 if record does not exist | + +#### Relationships + +`/companies//individuals` +`/companies//products` +`/companies//individuals,products` + +`/companies//relationships/individuals` +`/companies//relationships/products` +`/companies//relationships/individuals,products` + +`individuals//companies` +`individuals//individual-types` +`individuals//companies,individual-types` + +`individuals//relationships/companies` +`individuals//relationships/individual-types` +`individuals//relationships/companies,individual-types` + +`individual-types//individuals` +`individual-types//relationships/individuals` + +`products//companies` +`products//product-types` +`products//companies,product-types` + +`products//relationships/companies` +`products//relationships/product-types` +`products//relationships/companies,product-types` + +`product-types//products` +`product-types//relationships/products` + -`/user/get` -| Method | Payload | -|--------|---------------------------------------------------------| -| `POST` | `{"data": {"userId": 1}}` | with Bearer Authentication` | - -`/usesr/get` - -| Method | Payload | -|--------|---------| -| `POST` | Empty | - #### Responses ##### Structure +**Top Elements** +- `jsonapi` Contains the `version` of the API as a sub element +- `data` Data returned. Is not present if the `errors` is present +- `errors` Collection of errors that occurred in this request. Is not present if the `data` is present +- `meta` Contains `timestamp` and `hash` of the `json_encode($data)` or `json_encode($errors)` + +After a `GET` the API will always return a collection of records, even if there is only one returned. If no data is found, an empty resultset will be returned. + +Each endpoint returns records that follow this structure: ```json { - "jsonapi": { - "version": "1.0" // Version of the API - }, - "data": [ - // Payload returned if successful reply (not present if there is an error) - ], - "errors": [ - "Error 1", // Collection of errors - "Error 2" - }, - "meta": { - "timestamp": "2018-06-08T15:04:34+00:00", // Timestamp of the response - "hash": "e6d4d57162ae0f220c8649ae50a2b79fd1cb2c60" // Hash of the timestamp and payload (`data` if success, `error` if failure) + "id": 1051, + "type": "users", + "attributes": { + "status": 1, + "username": "niden", + "issuer": "https:\/\/niden.net", + "tokenPassword": "11110000", + "tokenId": "11001100" } } ``` -##### 404 + +The record always has `id` and `type` present at the top level. `id` is the unique id of the record in the database. `type` is a string representation of what the object is. In the above example it is a `users` record. Additional data from each record are under the `attributes` node. + +#### Samples +**404** ```json { "jsonapi": { "version": "1.0" }, "errors": { - "404 Not Found" + "404 not found" }, "meta": { "timestamp": "2018-06-08T15:04:34+00:00", @@ -91,14 +132,16 @@ The endpoints are: } ``` -##### Error +**Error** + ```json { "jsonapi": { "version": "1.0" }, "errors": { - "Description of the error" + "Description of the error no 1", + "Description of the error no 2" }, "meta": { "timestamp": "2018-06-08T15:04:34+00:00", @@ -107,14 +150,13 @@ The endpoints are: } ``` -##### Success +##### Success ```json { "jsonapi": { "version": "1.0" }, "data": [ - // Data returned ], "meta": { "timestamp": "2018-06-08T15:04:34+00:00", @@ -122,77 +164,15 @@ The endpoints are: } } ``` - -`/login` -```json -{ - "jsonapi": { - "version": "1.0" - }, - "data": { - "token": "ab.cd.ef" - }, - "meta": { - "timestamp": "2018-06-08T15:04:34+00:00", - "hash": "e6d4d57162ae0f220c8649ae50a2b79fd1cb2c60" - } -} -``` -`/user/get` -```json -{ - "jsonapi": { - "version": "1.0" - }, - "data": [ - { - "id": 1244, - "status": 1, - "username": "phalcon", - "issuer": "https:\/\/phalconphp.com", - "tokenPassword": "00001111", - "tokenId": "99009900" - } - ], - "meta": { - "timestamp": "2018-06-08T17:05:14+00:00", - "hash": "344d9766003e14409ab08df863d37d1ef44e5b60" - } -} -``` -`/users/get` -```json -{ - "jsonapi": { - "version": "1.0" - }, - "data": [ - { - "id": 1051, - "status": 1, - "username": "niden", - "issuer": "https:\/\/niden.net", - "tokenPassword": "11110000", - "tokenId": "11001100" - }, - { - "id": 1244, - "status": 1, - "username": "phalcon", - "issuer": "https:\/\/phalconphp.com", - "tokenPassword": "00001111", - "tokenId": "99009900" - } - ], - "meta": { - "timestamp": "2018-06-08T15:07:35+00:00", - "hash": "6219ae83afaebc08da4250c4fd23ea1b4843d" - } -} -``` - +For more information regarding responses, please check [JSON API](https://jsonapi.org) + ### TODO -- Remove `/login` endpoint. Leave the generation of the JWT to the consumer (maybe) -- Perhaps add a new claim to the token tied to the device? `setClaim('deviceId', 'Web-Server')`. This will allow the client application to invalidate access to a device that has already been logged in. +- ~~Work on companies GET~~ +- ~~Work on relationships and data returned~~ - Write examples of code to send to the client +- Create docs endpoint +- Work on pagination +- Work on filters +- Work on sorting +- Perhaps add a new claim to the token tied to the device? `setClaim('deviceId', 'Web-Server')`. This will allow the client application to invalidate access to a device that has already been logged in. diff --git a/api/config/providers.php b/api/config/providers.php index 178e163e..2feba384 100644 --- a/api/config/providers.php +++ b/api/config/providers.php @@ -4,6 +4,7 @@ * Enabled providers. Order does matter */ +use Niden\Providers\CacheDataProvider; use Niden\Providers\ConfigProvider; use Niden\Providers\DatabaseProvider; use Niden\Providers\ErrorHandlerProvider; @@ -22,4 +23,5 @@ RequestProvider::class, ResponseProvider::class, RouterProvider::class, + CacheDataProvider::class, ]; diff --git a/api/controllers/BaseController.php b/api/controllers/BaseController.php new file mode 100644 index 00000000..d2fa084d --- /dev/null +++ b/api/controllers/BaseController.php @@ -0,0 +1,116 @@ +checkIdParameter($id); + $parameter = $this->filter->sanitize($relationships, [Filter::FILTER_STRING, Filter::FILTER_TRIM]); + $results = $this->getRecords($this->config, $this->cache, $this->model, $parameters, $this->orderBy); + $related = []; + + if (count($parameters) > 0 && 0 === count($results)) { + return $this->send404(); + } else { + if (true !== empty($parameter)) { + $allRelationships = explode(',', $relationships); + foreach ($allRelationships as $relationship) { + if (true !== in_array($relationship, $this->relationships)) { + return $this->send404(); + } + + $related[] = strtolower($relationship); + } + } + } + + return $this->format($results, $this->transformer, $this->resource, $related); + } + + /** + * Checks the passed id parameter and returns the relevant array back + * + * @param int $recordId + * + * @return array + */ + private function checkIdParameter($recordId = 0): array + { + $parameters = []; + + /** @var int $localId */ + $localId = $this->filter->sanitize($recordId, Filter::FILTER_ABSINT); + + if ($localId > 0) { + $parameters['id'] = $localId; + } + + return $parameters; + } + + /** + * Sets the response with a 404 and returns an empty array back + * + * @return array + */ + private function send404(): array + { + $this->response->setPayloadError('Not Found')->setStatusCode(404); + + return []; + } +} diff --git a/api/controllers/Companies/AddController.php b/api/controllers/Companies/AddController.php new file mode 100644 index 00000000..a3f6637b --- /dev/null +++ b/api/controllers/Companies/AddController.php @@ -0,0 +1,74 @@ +validate($this->request->getPost()); + + /** + * If no messages are returned, go ahead with the query + */ + if (0 === count($messages)) { + $name = $this->request->getPost('name', Filter::FILTER_STRING); + $address = $this->request->getPost('address', Filter::FILTER_STRING, ''); + $city = $this->request->getPost('city', Filter::FILTER_STRING, ''); + $phone = $this->request->getPost('phone', Filter::FILTER_STRING, ''); + + $company = new Companies(); + $result = $company + ->set('name', $name) + ->set('address', $address) + ->set('city', $city) + ->set('phone', $phone) + ->save() + ; + + if (false !== $result) { + /** + * Everything is fine, return the record back + */ + return $this->format([$company], BaseTransformer::class, 'companies'); + } + + /** + * Errors happened store them + */ + $messages = $company->getMessages(); + } + + /** + * Set the errors in the payload + */ + $this->response->setPayloadErrors($messages); + } +} diff --git a/api/controllers/Companies/GetController.php b/api/controllers/Companies/GetController.php new file mode 100644 index 00000000..050eb7fc --- /dev/null +++ b/api/controllers/Companies/GetController.php @@ -0,0 +1,33 @@ +request->getPost('username', Filter::FILTER_STRING); $password = $this->request->getPost('password', Filter::FILTER_STRING); /** @var Users|false $user */ - $user = $this->getUserByUsernameAndPassword($username, $password); + $user = $this->getUserByUsernameAndPassword($this->config, $this->cache, $username, $password); if (false !== $user) { /** @@ -45,7 +49,10 @@ public function callAction() */ return ['token' => $user->getToken()]; } else { - $this->response->setPayloadError('Incorrect credentials'); + $this + ->response + ->setPayloadError('Incorrect credentials') + ; } } } diff --git a/api/controllers/ProductTypes/GetController.php b/api/controllers/ProductTypes/GetController.php new file mode 100644 index 00000000..e1017473 --- /dev/null +++ b/api/controllers/ProductTypes/GetController.php @@ -0,0 +1,33 @@ +format($this->getUsers(), UsersTransformer::class); - } -} diff --git a/api/controllers/Users/GetOneController.php b/api/controllers/Users/GetOneController.php deleted file mode 100644 index 9ebeb6c2..00000000 --- a/api/controllers/Users/GetOneController.php +++ /dev/null @@ -1,48 +0,0 @@ -request->getPost('userId', Filter::FILTER_ABSINT, 0); - $parameters = ['usr_id' => $userId]; - $results = $this->getUsers($parameters); - - if (count($results) > 0) { - return $this->format($results, UsersTransformer::class); - } else { - $this->halt($this->application, 'Record not found'); - } - } -} diff --git a/cli/config/providers.php b/cli/config/providers.php index e3e7780a..bb9d637d 100644 --- a/cli/config/providers.php +++ b/cli/config/providers.php @@ -4,6 +4,7 @@ * Enabled providers. Order does matter */ +use Niden\Providers\CacheDataProvider; use Niden\Providers\CliDispatcherProvider; use Niden\Providers\ConfigProvider; use Niden\Providers\DatabaseProvider; @@ -18,4 +19,5 @@ ErrorHandlerProvider::class, DatabaseProvider::class, CliDispatcherProvider::class, + CacheDataProvider::class, ]; diff --git a/cli/tasks/ClearcacheTask.php b/cli/tasks/ClearcacheTask.php index 1c2745d0..c283a777 100755 --- a/cli/tasks/ClearcacheTask.php +++ b/cli/tasks/ClearcacheTask.php @@ -5,19 +5,38 @@ use function in_array; use function Niden\Core\appPath; +use Phalcon\Cache\Backend\Libmemcached; use Phalcon\Cli\Task as PhTask; +use const PHP_EOL; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; +/** + * Class ClearcacheTask + * + * @package Niden\Cli\Tasks + * + * @property Libmemcached $cache + */ class ClearcacheTask extends PhTask { /** - * Adds the developer logins in the system + * Clears the data cache from the application */ public function mainAction() + { + $this->clearFileCache(); + $this->clearMemCached(); + } + + /** + * Clears file based cache + */ + private function clearFileCache() { echo 'Clearing Cache folders' . PHP_EOL; + $fileList = []; $whitelist = ['.', '..', '.gitignore']; $path = appPath('storage/cache'); $dirIterator = new RecursiveDirectoryIterator($path); @@ -26,13 +45,52 @@ public function mainAction() RecursiveIteratorIterator::CHILD_FIRST ); + /** + * Get how many files we have there and where they are + */ foreach ($iterator as $file) { if (true !== $file->isDir() && true !== in_array($file->getFilename(), $whitelist)) { - unlink($file->getPathname()); - echo '.'; + $fileList[] = $file->getPathname(); } } + echo sprintf('Found %s files', count($fileList)) . PHP_EOL; + foreach ($fileList as $file) { + echo '.'; + unlink($file); + } + echo PHP_EOL . 'Cleared Cache folders' . PHP_EOL; } + + /** + * Clears memcached data cache + */ + private function clearMemCached() + { + echo 'Clearing data cache' . PHP_EOL; + $options = $this->cache->getOptions(); + $servers = $options['servers'] ?? []; + $memcached = new \Memcached(); + foreach ($servers as $server) { + $memcached->addServer($server['host'], $server['port'], $server['weight']); + } + + $keys = $memcached->getAllKeys(); + echo sprintf('Found %s keys', count($keys)) . PHP_EOL; + foreach ($keys as $key) { + if ('api-data' === substr($key, 0, 8)) { + $server = $memcached->getServerByKey($key); + $result = $memcached->deleteByKey($server['host'], $key); + $resultCode = $memcached->getResultCode(); + if (true === $result && $resultCode !== \Memcached::RES_NOTFOUND) { + echo '.'; + } else { + echo 'F'; + } + } + } + + echo PHP_EOL . 'Cleared data cache' . PHP_EOL; + } } diff --git a/library/Constants/Relationships.php b/library/Constants/Relationships.php new file mode 100644 index 00000000..cec83808 --- /dev/null +++ b/library/Constants/Relationships.php @@ -0,0 +1,15 @@ +load(); +(new Dotenv(appPath()))->overload(); diff --git a/library/Http/Response.php b/library/Http/Response.php index 447ba96e..114127fe 100644 --- a/library/Http/Response.php +++ b/library/Http/Response.php @@ -8,6 +8,8 @@ use League\Fractal\Resource\Item; use Niden\Transformers\PayloadTransformer; use Phalcon\Http\Response as PhResponse; +use Phalcon\Mvc\Model\MessageInterface as ModelMessage; +use Phalcon\Validation\Message\Group as ValidationMessage; class Response extends PhResponse { @@ -15,6 +17,7 @@ class Response extends PhResponse * @var array */ private $content = []; + /** * Sets the payload code as Error * @@ -30,6 +33,22 @@ public function setPayloadError(string $detail = ''): Response return $this; } + /** + * Traverses the errors collection and sets the errors in the payload + * + * @param ModelMessage[]|ValidationMessage $errors + * + * @return Response + */ + public function setPayloadErrors($errors): Response + { + foreach ($errors as $error) { + $this->setPayloadError($error->getMessage()); + } + + return $this; + } + /** * Sets the payload code as Error * @@ -39,9 +58,10 @@ public function setPayloadError(string $detail = ''): Response */ public function setPayloadSuccess($content = []): Response { - $data = (true === is_array($content)) ? $content : [$content]; + $data = (true === is_array($content)) ? $content : ['data' => $content]; + $data = (true === isset($data['data'])) ? $data : ['data' => $data]; - $this->content['data'] = $data; + $this->content = $data; $this->setPayloadContent(); return $this; @@ -58,8 +78,6 @@ public function setPayloadContent(): Response { parent::setJsonContent($this->processPayload()); - $this->setStatusCode(200); - $this->setContentType('application/vnd.api+json', 'UTF-8'); $this->setHeader('E-Tag', sha1($this->getContent())); return $this; diff --git a/library/Middleware/AuthenticationMiddleware.php b/library/Middleware/AuthenticationMiddleware.php index e677cba8..9dcd762f 100755 --- a/library/Middleware/AuthenticationMiddleware.php +++ b/library/Middleware/AuthenticationMiddleware.php @@ -5,8 +5,9 @@ namespace Niden\Middleware; use Niden\Http\Request; +use Niden\Http\Response; +use Niden\Traits\QueryTrait; use Niden\Traits\ResponseTrait; -use Niden\Traits\UserTrait; use Phalcon\Mvc\Micro; use Phalcon\Mvc\Micro\MiddlewareInterface; @@ -18,7 +19,7 @@ class AuthenticationMiddleware implements MiddlewareInterface { use ResponseTrait; - use UserTrait; + use QueryTrait; /** * Call me @@ -34,7 +35,7 @@ public function call(Micro $api) if (true !== $request->isLoginPage() && true === $request->isEmptyBearerToken()) { - $this->halt($api, 'Invalid Token'); + $this->halt($api, 200, 'Invalid Token'); return false; } diff --git a/library/Middleware/NotFoundMiddleware.php b/library/Middleware/NotFoundMiddleware.php index 4545dbef..8940e1f6 100755 --- a/library/Middleware/NotFoundMiddleware.php +++ b/library/Middleware/NotFoundMiddleware.php @@ -27,7 +27,7 @@ class NotFoundMiddleware extends Plugin implements MiddlewareInterface */ public function beforeNotFound() { - $this->halt($this->application, '404 Not Found'); + $this->halt($this->application, 404, 'Not Found'); return false; } diff --git a/library/Middleware/PayloadMiddleware.php b/library/Middleware/PayloadMiddleware.php deleted file mode 100755 index 39dc17ff..00000000 --- a/library/Middleware/PayloadMiddleware.php +++ /dev/null @@ -1,102 +0,0 @@ -getService('request'); - if (true === $request->isPost()) { - $body = $request->getRawBody(); - if (true !== empty($body)) { - $data = json_decode($body, true); - $this->checkJson(); - $this->checkDataElement($data); - $this->parsePayload($data); - } - } - - return true; - } catch (Exception $ex) { - $this->halt($api, $ex->getMessage()); - - return false; - } - } - - /** - * Call me - * - * @param Micro $api - * - * @return bool - */ - public function call(Micro $api) - { - return true; - } - - /** - * Checks if the 'data' element has been sent - * - * @param array $data - * - * @throws Exception - */ - private function checkDataElement(array $data) - { - if (true !== isset($data['data'])) { - throw new Exception('"data" element not present in the payload'); - } - } - - /** - * Check if we have a JSON error - * - * @throws Exception - */ - private function checkJson() - { - if (JSON_ERROR_NONE !== json_last_error()) { - throw new Exception('Malformed JSON'); - } - } - - /** - * Parses the payload and injects the posted data in the POST array - * - * @param array $data - */ - private function parsePayload(array $data) - { - $_POST = $data['data']; - } -} diff --git a/library/Middleware/TokenBase.php b/library/Middleware/TokenBase.php index f877b3bf..d514dda5 100755 --- a/library/Middleware/TokenBase.php +++ b/library/Middleware/TokenBase.php @@ -5,9 +5,9 @@ namespace Niden\Middleware; use Niden\Http\Request; +use Niden\Traits\QueryTrait; use Niden\Traits\ResponseTrait; use Niden\Traits\TokenTrait; -use Niden\Traits\UserTrait; use Phalcon\Mvc\Micro\MiddlewareInterface; /** @@ -19,7 +19,7 @@ abstract class TokenBase implements MiddlewareInterface { use ResponseTrait; use TokenTrait; - use UserTrait; + use QueryTrait; /** * @param Request $request diff --git a/library/Middleware/TokenUserMiddleware.php b/library/Middleware/TokenUserMiddleware.php index 4f096314..dfc314de 100755 --- a/library/Middleware/TokenUserMiddleware.php +++ b/library/Middleware/TokenUserMiddleware.php @@ -6,6 +6,8 @@ use Niden\Http\Request; use Niden\Models\Users; +use Phalcon\Cache\Backend\Libmemcached; +use Phalcon\Config; use Phalcon\Mvc\Micro; /** @@ -22,6 +24,10 @@ class TokenUserMiddleware extends TokenBase */ public function call(Micro $api) { + /** @var Libmemcached $cache */ + $cache = $api->getService('cache'); + /** @var Config $config */ + $config = $api->getService('config'); /** @var Request $request */ $request = $api->getService('request'); if (true === $this->isValidCheck($request)) { @@ -32,9 +38,9 @@ public function call(Micro $api) $token = $this->getToken($request->getBearerTokenFromHeader()); /** @var Users|false $user */ - $user = $this->getUserByToken($token); + $user = $this->getUserByToken($config, $cache, $token); if (false === $user) { - $this->halt($api, 'Invalid Token'); + $this->halt($api, 200, 'Invalid Token'); return false; } diff --git a/library/Middleware/TokenValidationMiddleware.php b/library/Middleware/TokenValidationMiddleware.php index a5830aba..5b7f9e80 100755 --- a/library/Middleware/TokenValidationMiddleware.php +++ b/library/Middleware/TokenValidationMiddleware.php @@ -7,6 +7,8 @@ use Niden\Exception\ModelException; use Niden\Http\Request; use Niden\Models\Users; +use Phalcon\Cache\Backend\Libmemcached; +use Phalcon\Config; use Phalcon\Mvc\Micro; /** @@ -24,6 +26,10 @@ class TokenValidationMiddleware extends TokenBase */ public function call(Micro $api) { + /** @var Libmemcached $cache */ + $cache = $api->getService('cache'); + /** @var Config $config */ + $config = $api->getService('config'); /** @var Request $request */ $request = $api->getService('request'); if (true === $this->isValidCheck($request)) { @@ -36,9 +42,9 @@ public function call(Micro $api) $token = $this->getToken($request->getBearerTokenFromHeader()); /** @var Users $user */ - $user = $this->getUserByToken($token); + $user = $this->getUserByToken($config, $cache, $token); if (false === $token->validate($user->getValidationData())) { - $this->halt($api, 'Invalid Token'); + $this->halt($api, 200, 'Invalid Token'); return false; } diff --git a/library/Middleware/TokenVerificationMiddleware.php b/library/Middleware/TokenVerificationMiddleware.php index a180de26..5c46a390 100755 --- a/library/Middleware/TokenVerificationMiddleware.php +++ b/library/Middleware/TokenVerificationMiddleware.php @@ -8,6 +8,8 @@ use Niden\Exception\ModelException; use Niden\Http\Request; use Niden\Models\Users; +use Phalcon\Cache\Backend\Libmemcached; +use Phalcon\Config; use Phalcon\Mvc\Micro; /** @@ -25,6 +27,10 @@ class TokenVerificationMiddleware extends TokenBase */ public function call(Micro $api) { + /** @var Libmemcached $cache */ + $cache = $api->getService('cache'); + /** @var Config $config */ + $config = $api->getService('config'); /** @var Request $request */ $request = $api->getService('request'); if (true === $this->isValidCheck($request)) { @@ -38,9 +44,9 @@ public function call(Micro $api) $signer = new Sha512(); /** @var Users $user */ - $user = $this->getUserByToken($token); - if (false === $token->verify($signer, $user->get('usr_token_password'))) { - $this->halt($api, 'Invalid Token'); + $user = $this->getUserByToken($config, $cache, $token); + if (false === $token->verify($signer, $user->get('tokenPassword'))) { + $this->halt($api, 200, 'Invalid Token'); } } diff --git a/library/Models/Companies.php b/library/Models/Companies.php new file mode 100644 index 00000000..f0cae47d --- /dev/null +++ b/library/Models/Companies.php @@ -0,0 +1,96 @@ +hasMany( + 'id', + Individuals::class, + 'companyId', + [ + 'alias' => Relationships::INDIVIDUALS, + 'reusable' => true, + ] + ); + + $this->hasManyToMany( + 'id', + CompaniesXProducts::class, + 'companyId', + 'productId', + Products::class, + 'id', + [ + 'alias' => Relationships::PRODUCTS, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'address' => Filter::FILTER_STRING, + 'city' => Filter::FILTER_STRING, + 'phone' => Filter::FILTER_STRING, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_companies'; + } + + /** + * Validates the company name + * + * @return bool + */ + public function validation() + { + $validator = new Validation(); + $validator->add( + 'name', + new Uniqueness( + [ + 'message' => 'The company name already exists in the database', + ] + ) + ); + + return $this->validate($validator); + } +} diff --git a/library/Models/CompaniesXProducts.php b/library/Models/CompaniesXProducts.php new file mode 100644 index 00000000..3a0e2e12 --- /dev/null +++ b/library/Models/CompaniesXProducts.php @@ -0,0 +1,68 @@ +belongsTo( + 'companyId', + Companies::class, + 'id', + [ + 'alias' => Relationships::COMPANIES, + 'reusable' => true, + ] + ); + + $this->belongsTo( + 'productId', + Products::class, + 'id', + [ + 'alias' => Relationships::PRODUCTS, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'companyId' => Filter::FILTER_ABSINT, + 'productId' => Filter::FILTER_ABSINT, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_companies_x_products'; + } +} diff --git a/library/Models/IndividualTypes.php b/library/Models/IndividualTypes.php new file mode 100644 index 00000000..9298b67a --- /dev/null +++ b/library/Models/IndividualTypes.php @@ -0,0 +1,59 @@ +hasMany( + 'id', + Individuals::class, + 'typeId', + [ + 'alias' => Relationships::INDIVIDUALS, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_individual_types'; + } +} diff --git a/library/Models/Individuals.php b/library/Models/Individuals.php new file mode 100644 index 00000000..165b0aef --- /dev/null +++ b/library/Models/Individuals.php @@ -0,0 +1,74 @@ +belongsTo( + 'companyId', + Companies::class, + 'id', + [ + 'alias' => Relationships::COMPANIES, + 'reusable' => true, + ] + ); + + $this->hasOne( + 'typeId', + IndividualTypes::class, + 'id', + [ + 'alias' => Relationships::INDIVIDUAL_TYPES, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'companyId' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'prefix' => Filter::FILTER_STRING, + 'first' => Filter::FILTER_STRING, + 'middle' => Filter::FILTER_STRING, + 'last' => Filter::FILTER_STRING, + 'suffix' => Filter::FILTER_STRING, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_individuals'; + } +} diff --git a/library/Models/ProductTypes.php b/library/Models/ProductTypes.php new file mode 100644 index 00000000..37acf9ea --- /dev/null +++ b/library/Models/ProductTypes.php @@ -0,0 +1,59 @@ +hasMany( + 'id', + Products::class, + 'typeId', + [ + 'alias' => Relationships::PRODUCTS, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_product_types'; + } +} diff --git a/library/Models/Products.php b/library/Models/Products.php new file mode 100644 index 00000000..b5e8fce8 --- /dev/null +++ b/library/Models/Products.php @@ -0,0 +1,75 @@ +hasManyToMany( + 'id', + CompaniesXProducts::class, + 'productId', + 'companyId', + Companies::class, + 'id', + [ + 'alias' => Relationships::COMPANIES, + 'reusable' => true, + ] + ); + + $this->belongsTo( + 'typeId', + ProductTypes::class, + 'id', + [ + 'alias' => Relationships::PRODUCT_TYPES, + 'reusable' => true, + ] + ); + + parent::initialize(); + } + + /** + * Model filters + * + * @return array + */ + public function getModelFilters(): array + { + return [ + 'id' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + 'quantity' => Filter::FILTER_ABSINT, + 'price' => Filter::FILTER_FLOAT, + ]; + } + + /** + * Returns the source table from the database + * + * @return string + */ + public function getSource(): string + { + return 'co_products'; + } +} diff --git a/library/Models/Users.php b/library/Models/Users.php index 18a663b6..a29b6dd9 100644 --- a/library/Models/Users.php +++ b/library/Models/Users.php @@ -30,13 +30,13 @@ class Users extends AbstractModel public function getModelFilters(): array { return [ - 'usr_id' => Filter::FILTER_ABSINT, - 'usr_status_flag' => Filter::FILTER_ABSINT, - 'usr_username' => Filter::FILTER_STRING, - 'usr_password' => Filter::FILTER_STRING, - 'usr_issuer' => Filter::FILTER_STRING, - 'usr_token_password' => Filter::FILTER_STRING, - 'usr_token_id' => Filter::FILTER_STRING, + 'id' => Filter::FILTER_ABSINT, + 'status' => Filter::FILTER_ABSINT, + 'username' => Filter::FILTER_STRING, + 'password' => Filter::FILTER_STRING, + 'issuer' => Filter::FILTER_STRING, + 'tokenPassword' => Filter::FILTER_STRING, + 'tokenId' => Filter::FILTER_STRING, ]; } @@ -50,16 +50,6 @@ public function getSource(): string return 'co_users'; } - /** - * Table prefix - * - * @return string - */ - public function getTablePrefix(): string - { - return 'usr'; - } - /** * Returns the string token * @@ -71,13 +61,13 @@ public function getToken(): string $signer = new Sha512(); $builder = new Builder(); $token = $builder - ->setIssuer($this->get('usr_issuer')) + ->setIssuer($this->get('issuer')) ->setAudience($this->getTokenAudience()) - ->setId($this->get('usr_token_id'), true) + ->setId($this->get('tokenId'), true) ->setIssuedAt($this->getTokenTimeIssuedAt()) ->setNotBefore($this->getTokenTimeNotBefore()) ->setExpiration($this->getTokenTimeExpiration()) - ->sign($signer, $this->get('usr_token_password')) + ->sign($signer, $this->get('tokenPassword')) ->getToken(); return $token->__toString(); @@ -92,9 +82,9 @@ public function getToken(): string public function getValidationData(): ValidationData { $validationData = new ValidationData(); - $validationData->setIssuer($this->get('usr_issuer')); + $validationData->setIssuer($this->get('issuer')); $validationData->setAudience($this->getTokenAudience()); - $validationData->setId($this->get('usr_token_id')); + $validationData->setId($this->get('tokenId')); $validationData->setCurrentTime(time() + 10); return $validationData; diff --git a/library/Mvc/Model/AbstractModel.php b/library/Mvc/Model/AbstractModel.php index 8c6aa04f..8c12d27d 100644 --- a/library/Mvc/Model/AbstractModel.php +++ b/library/Mvc/Model/AbstractModel.php @@ -69,24 +69,18 @@ public function getModelMessages(Logger $logger = null): string return $error; } - /** - * @return string - */ - abstract public function getTablePrefix(): string; - /** * Sets a field in the model sanitized * - * @param string $field The name of the field - * @param mixed $value The value of the field - * @param bool $sanitize Whether to sanitize input or not + * @param string $field The name of the field + * @param mixed $value The value of the field * * @return AbstractModel * @throws ModelException */ - public function set($field, $value, $sanitize = true): AbstractModel + public function set($field, $value): AbstractModel { - $this->getSetFields('set', $field, $value, $sanitize); + $this->getSetFields('set', $field, $value); return $this; } @@ -97,12 +91,11 @@ public function set($field, $value, $sanitize = true): AbstractModel * @param string $type * @param string $field * @param mixed $value - * @param bool $sanitize * * @return mixed * @throws ModelException */ - private function getSetFields(string $type, string $field, $value = '', bool $sanitize = true) + private function getSetFields(string $type, string $field, $value = '') { $return = null; $modelFields = $this->getModelFilters(); @@ -118,9 +111,9 @@ private function getSetFields(string $type, string $field, $value = '', bool $sa } if ('get' === $type) { - $return = $this->sanitize($this->$field, $filter, $sanitize); + $return = $this->sanitize($this->$field, $filter); } else { - $this->$field = $this->sanitize($value, $filter, $sanitize); + $this->$field = $this->sanitize($value, $filter); } return $return; @@ -131,19 +124,14 @@ private function getSetFields(string $type, string $field, $value = '', bool $sa * * @param mixed $value The value to sanitize * @param string|array $filter The filter to apply - * @param bool $sanitize * * @return mixed */ - private function sanitize($value, $filter, bool $sanitize = true) + private function sanitize($value, $filter) { /** @var Filter $filterService */ $filterService = $this->getDI()->get('filter'); - if (true === $sanitize) { - return $filterService->sanitize($value, $filter); - } else { - return $value; - } + return $filterService->sanitize($value, $filter); } } diff --git a/library/Providers/CacheDataProvider.php b/library/Providers/CacheDataProvider.php new file mode 100644 index 00000000..ba351a06 --- /dev/null +++ b/library/Providers/CacheDataProvider.php @@ -0,0 +1,67 @@ +setShared( + 'cache', + $this->createCache('data') + ); + } + + /** + * Returns a cache object + * + * @param string $prefix + * + * @return Libmemcached + */ + protected function createCache(string $prefix): Libmemcached + { + $frontAdapter = Data::class; + $frontOptions = [ + 'lifetime' => envValue('CACHE_LIFETIME', 86400), + ]; + $backOptions = $this->createOptions($prefix); + + return new Libmemcached(new $frontAdapter($frontOptions), $backOptions); + } + + /** + * Returns memcached options + * + * @param string $prefix + * + * @return array + */ + protected function createOptions(string $prefix): array + { + return [ + 'servers' => [ + 0 => [ + 'host' => envValue('DATA_API_MEMCACHED_HOST', '127.0.0.1'), + 'port' => envValue('DATA_API_MEMCACHED_PORT', 11211), + 'weight' => envValue('DATA_API_MEMCACHED_WEIGHT', 100), + ], + ], + 'client' => [ + \Memcached::OPT_HASH => \Memcached::HASH_MD5, + \Memcached::OPT_PREFIX_KEY => 'api-', + ], + 'lifetime' => 3600, + 'prefix' => $prefix . '-', + ]; + } +} diff --git a/library/Providers/ResponseProvider.php b/library/Providers/ResponseProvider.php index 0a071764..8818be56 100644 --- a/library/Providers/ResponseProvider.php +++ b/library/Providers/ResponseProvider.php @@ -15,6 +15,16 @@ class ResponseProvider implements ServiceProviderInterface */ public function register(DiInterface $container) { - $container->setShared('response', new Response()); + $response = new Response(); + + /** + * Assume success. We will work with the edge cases in the code + */ + $response + ->setStatusCode(200) + ->setContentType('application/vnd.api+json', 'UTF-8') + ; + + $container->setShared('response', $response); } } diff --git a/library/Providers/RouterProvider.php b/library/Providers/RouterProvider.php index c4e384ff..79241064 100644 --- a/library/Providers/RouterProvider.php +++ b/library/Providers/RouterProvider.php @@ -4,11 +4,16 @@ namespace Niden\Providers; -use Niden\Api\Controllers\Users\GetOneController; -use Niden\Api\Controllers\Users\GetManyController; +use Niden\Api\Controllers\Companies\AddController as CompaniesAddController; +use Niden\Api\Controllers\Companies\GetController as CompaniesGetController; +use Niden\Api\Controllers\Individuals\GetController as IndividualsGetController; +use Niden\Api\Controllers\IndividualTypes\GetController as IndividualTypesGetController; +use Niden\Api\Controllers\Products\GetController as ProductsGetController; +use Niden\Api\Controllers\ProductTypes\GetController as ProductTypesGetController; +use Niden\Api\Controllers\Users\GetController as UsersGetController; use Niden\Api\Controllers\LoginController; +use Niden\Constants\Relationships as Rel; use Niden\Middleware\NotFoundMiddleware; -use Niden\Middleware\PayloadMiddleware; use Niden\Middleware\AuthenticationMiddleware; use Niden\Middleware\ResponseMiddleware; use Niden\Middleware\TokenUserMiddleware; @@ -88,7 +93,6 @@ private function getMiddleware(): array { return [ NotFoundMiddleware::class => 'before', - PayloadMiddleware::class => 'before', AuthenticationMiddleware::class => 'before', TokenUserMiddleware::class => 'before', TokenVerificationMiddleware::class => 'before', @@ -104,11 +108,40 @@ private function getMiddleware(): array */ private function getRoutes(): array { - return [ + $routes = [ // Class, Method, Route, Handler - [LoginController::class, '', 'post', '/login'], - [GetOneController::class, '/user', 'post', '/get'], - [GetManyController::class, '/users', 'post', '/get'], + [LoginController::class, '/login', 'post', '/'], + [CompaniesAddController::class, '/companies', 'post', '/'], + [UsersGetController::class, '/users', 'get', '/'], + [UsersGetController::class, '/users', 'get', '/{recordId:[0-9]+}'], ]; + + $routes = $this->getMultiRoutes($routes, CompaniesGetController::class, Rel::COMPANIES); + $routes = $this->getMultiRoutes($routes, IndividualsGetController::class, Rel::INDIVIDUALS); + $routes = $this->getMultiRoutes($routes, IndividualTypesGetController::class, Rel::INDIVIDUAL_TYPES); + $routes = $this->getMultiRoutes($routes, ProductsGetController::class, Rel::PRODUCTS); + $routes = $this->getMultiRoutes($routes, ProductTypesGetController::class, Rel::PRODUCT_TYPES); + + + return $routes; + } + + /** + * Adds multiple routes for the same handler abiding by the JSONAPI standard + * + * @param array $routes + * @param string $class + * @param string $relationship + * + * @return array + */ + private function getMultiRoutes(array $routes, string $class, string $relationship): array + { + $routes[] = [$class, '/' . $relationship, 'get', '/']; + $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}']; + $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}']; + $routes[] = [$class, '/' . $relationship, 'get', '/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}']; + + return $routes; } } diff --git a/library/Traits/FractalTrait.php b/library/Traits/FractalTrait.php index 67b37476..e0c30cba 100644 --- a/library/Traits/FractalTrait.php +++ b/library/Traits/FractalTrait.php @@ -6,6 +6,8 @@ use League\Fractal\Manager; use League\Fractal\Resource\Collection; +use League\Fractal\Serializer\JsonApiSerializer; +use function Niden\Core\envValue; /** * Trait FractalTrait @@ -19,15 +21,27 @@ trait FractalTrait * * @param mixed $results * @param string $transformer + * @param string $resource + * @param array $relationships * * @return array */ - protected function format($results, string $transformer): array + protected function format($results, string $transformer, string $resource, array $relationships = []): array { + $url = envValue('APP_URL', 'http://localhost'); $manager = new Manager(); - $resource = new Collection($results, new $transformer()); + $manager->setSerializer(new JsonApiSerializer($url)); + + /** + * Process relationships + */ + if (count($relationships) > 0) { + $manager->parseIncludes($relationships); + } + + $resource = new Collection($results, new $transformer(), $resource); $results = $manager->createData($resource)->toArray(); - return $results['data']; + return $results; } } diff --git a/library/Traits/QueryTrait.php b/library/Traits/QueryTrait.php new file mode 100644 index 00000000..a9f3b3a7 --- /dev/null +++ b/library/Traits/QueryTrait.php @@ -0,0 +1,137 @@ + $token->getClaim(JWTClaims::CLAIM_ISSUER), + 'tokenId' => $token->getClaim(JWTClaims::CLAIM_ID), + 'status' => Flags::ACTIVE, + ]; + + $result = $this->getRecords($config, $cache, Users::class, $parameters); + + return $result[0] ?? false; + } + + /** + * Gets a user from the database based on the username and password + * + * @param Config $config + * @param Libmemcached $cache + * @param string $username + * @param string $password + * + * @return Users|false + */ + protected function getUserByUsernameAndPassword(Config $config, Libmemcached $cache, $username, $password) + { + $parameters = [ + 'username' => $username, + 'password' => $password, + 'status' => Flags::ACTIVE, + ]; + + $result = $this->getRecords($config, $cache, Users::class, $parameters); + + return $result[0] ?? false; + } + + /** + * Runs a query using the builder + * + * @param Config $config + * @param Libmemcached $cache + * @param string $class + * @param array $where + * @param string $orderBy + * + * @return ResultsetInterface + */ + protected function getRecords( + Config $config, + Libmemcached $cache, + string $class, + array $where = [], + string $orderBy = '' + ): ResultsetInterface { + $builder = new Builder(); + $builder->addFrom($class); + + foreach ($where as $field => $value) { + $builder->andWhere( + sprintf('%s = :%s:', $field, $field), + [$field => $value] + ); + } + + if (true !== empty($orderBy)) { + $builder->orderBy($orderBy); + } + + return $this->getResults($config, $cache, $builder, $where); + } + + /** + * Runs the builder query if there is no cached data + * + * @param Config $config + * @param Libmemcached $cache + * @param Builder $builder + * @param array $where + * + * @return ResultsetInterface + */ + private function getResults( + Config $config, + Libmemcached $cache, + Builder $builder, + array $where = [] + ): ResultsetInterface { + /** + * Calculate the cache key + */ + $phql = $builder->getPhql(); + $params = json_encode($where); + $cacheKey = sha1(sprintf('%s-%s.cache', $phql, $params)); + if (true !== $config->path('app.devMode') && true === $cache->exists($cacheKey)) { + /** @var ResultsetInterface $data */ + $data = $cache->get($cacheKey); + } else { + $data = $builder->getQuery()->execute(); + $cache->save($cacheKey, $data); + } + + return $data; + } +} diff --git a/library/Traits/ResponseTrait.php b/library/Traits/ResponseTrait.php index 787c86f7..e216d62f 100644 --- a/library/Traits/ResponseTrait.php +++ b/library/Traits/ResponseTrait.php @@ -18,16 +18,18 @@ trait ResponseTrait * Halt execution after setting the message in the response * * @param Micro $api + * @param int $status * @param string $message * * @return mixed */ - protected function halt(Micro $api, string $message) + protected function halt(Micro $api, int $status, string $message) { /** @var Response $response */ $response = $api->getService('response'); $response ->setPayloadError($message) + ->setStatusCode($status) ->send(); $api->stop(); @@ -37,11 +39,30 @@ public function process(Micro $api) { /** @var Response $response */ $response = $api->getService('response'); - $data = $api->getReturnedValue(); - $response->setPayloadSuccess($data); + if (200 === $response->getStatusCode() && true !== $this->checkCurrentContent($response)) { + $returned = $api->getReturnedValue(); + $response->setPayloadSuccess($returned); + } if (true !== $response->isSent()) { $response->send(); } } + + /** + * @param Response $response + * + * @return bool + */ + private function checkCurrentContent(Response $response): bool + { + $return = false; + $content = $response->getContent(); + if (true !== empty($content)) { + $content = json_decode($content, true); + $return = isset($content['errors']); + } + + return $return; + } } diff --git a/library/Traits/UserTrait.php b/library/Traits/UserTrait.php deleted file mode 100644 index 2f615b57..00000000 --- a/library/Traits/UserTrait.php +++ /dev/null @@ -1,89 +0,0 @@ - $token->getClaim(JWTClaims::CLAIM_ISSUER), - 'usr_token_id' => $token->getClaim(JWTClaims::CLAIM_ID), - 'usr_status_flag' => Flags::ACTIVE, - ]; - - return $this->getUser($parameters); - } - - /** - * Gets a user from the database based on the username and password - * - * @param string $username - * @param string $password - * - * @return Users|false - */ - protected function getUserByUsernameAndPassword($username, $password) - { - $parameters = [ - 'usr_username' => $username, - 'usr_password' => $password, - 'usr_status_flag' => Flags::ACTIVE, - ]; - - return $this->getUser($parameters); - } - - /** - * @param array $parameters - * - * @return Users|false - */ - protected function getUser(array $parameters = []) - { - $results = $this->getUsers($parameters); - - return $results[0] ?? false; - } - - /** - * @param array $parameters - * - * @return ResultsetInterface - */ - protected function getUsers(array $parameters = []) - { - $builder = new Builder(); - $builder->addFrom(Users::class); - - foreach ($parameters as $field => $value) { - $builder->andWhere( - sprintf('%s = :%s:', $field, $field), - [$field => $value] - ); - } - - return $builder->getQuery()->execute(); - } -} diff --git a/library/Transformers/BaseTransformer.php b/library/Transformers/BaseTransformer.php new file mode 100644 index 00000000..ecdbcfa4 --- /dev/null +++ b/library/Transformers/BaseTransformer.php @@ -0,0 +1,51 @@ +getModelFilters()); + foreach ($filters as $column) { + $data[$column] = $model->get($column); + } + + return $data; + } + + /** + * @param string $method + * @param AbstractModel $model + * @param string $transformer + * @param string $relationship + * + * @return Collection|Item + */ + protected function getRelatedData(string $method, AbstractModel $model, string $transformer, string $relationship) + { + /** @var AbstractModel $data */ + $data = $model->getRelated($relationship); + + return $this->$method($data, new $transformer(), $relationship); + } +} diff --git a/library/Transformers/CompaniesTransformer.php b/library/Transformers/CompaniesTransformer.php new file mode 100644 index 00000000..a2b9d658 --- /dev/null +++ b/library/Transformers/CompaniesTransformer.php @@ -0,0 +1,50 @@ +getRelatedData( + 'collection', + $company, + IndividualsTransformer::class, + Relationships::INDIVIDUALS + ); + } + + /** + * @param Companies $company + * + * @return Collection + */ + public function includeProducts(Companies $company) + { + return $this->getRelatedData( + 'collection', + $company, + ProductsTransformer::class, + Relationships::PRODUCTS + ); + } +} diff --git a/library/Transformers/IndividualTypesTransformer.php b/library/Transformers/IndividualTypesTransformer.php new file mode 100644 index 00000000..69b2a7d6 --- /dev/null +++ b/library/Transformers/IndividualTypesTransformer.php @@ -0,0 +1,34 @@ +getRelatedData( + 'collection', + $type, + IndividualsTransformer::class, + Relationships::INDIVIDUALS + ); + } +} diff --git a/library/Transformers/IndividualsTransformer.php b/library/Transformers/IndividualsTransformer.php new file mode 100644 index 00000000..9105d8c3 --- /dev/null +++ b/library/Transformers/IndividualsTransformer.php @@ -0,0 +1,54 @@ +getRelatedData( + 'item', + $individual, + CompaniesTransformer::class, + Relationships::COMPANIES + ); + } + + /** + * Includes the product types + * + * @param Individuals $individual + * + * @return Item + */ + public function includeIndividualTypes(Individuals $individual) + { + return $this->getRelatedData( + 'item', + $individual, + BaseTransformer::class, + Relationships::INDIVIDUAL_TYPES + ); + } +} diff --git a/library/Transformers/PayloadTransformer.php b/library/Transformers/PayloadTransformer.php index 88ac5559..61cc159a 100644 --- a/library/Transformers/PayloadTransformer.php +++ b/library/Transformers/PayloadTransformer.php @@ -4,6 +4,7 @@ namespace Niden\Transformers; +use function array_merge; use function date; use function json_encode; use function sha1; @@ -21,19 +22,25 @@ class PayloadTransformer extends TransformerAbstract */ public function transform(array $content) { - $section = (true === isset($content['errors'])) ? 'errors' : 'data'; $timestamp = date('c'); - $result = [ + $jsonApi = [ 'jsonapi' => [ 'version' => '1.0', - ], - $section => $content[$section], - 'meta' => [ + ] + ]; + $meta = [ + 'meta' => [ 'timestamp' => $timestamp, - 'hash' => sha1($timestamp . json_encode($content[$section])), + 'hash' => sha1($timestamp . json_encode($content)), ], ]; + $result = array_merge( + $jsonApi, + $content, + $meta + ); + return $result; } } diff --git a/library/Transformers/ProductTypesTransformer.php b/library/Transformers/ProductTypesTransformer.php new file mode 100644 index 00000000..6b2db982 --- /dev/null +++ b/library/Transformers/ProductTypesTransformer.php @@ -0,0 +1,34 @@ +getRelatedData( + 'collection', + $type, + ProductsTransformer::class, + Relationships::PRODUCTS + ); + } +} diff --git a/library/Transformers/ProductsTransformer.php b/library/Transformers/ProductsTransformer.php new file mode 100644 index 00000000..cf832fd3 --- /dev/null +++ b/library/Transformers/ProductsTransformer.php @@ -0,0 +1,55 @@ +getRelatedData( + 'collection', + $product, + CompaniesTransformer::class, + Relationships::COMPANIES + ); + } + + /** + * Includes the product types + * + * @param Products $product + * + * @return Item + */ + public function includeProductTypes(Products $product) + { + return $this->getRelatedData( + 'item', + $product, + BaseTransformer::class, + Relationships::PRODUCT_TYPES + ); + } +} diff --git a/library/Transformers/UsersTransformer.php b/library/Transformers/UsersTransformer.php deleted file mode 100644 index d18a03b6..00000000 --- a/library/Transformers/UsersTransformer.php +++ /dev/null @@ -1,32 +0,0 @@ - $user->get('usr_id'), - 'status' => $user->get('usr_status_flag'), - 'username' => $user->get('usr_username'), - 'issuer' => $user->get('usr_issuer'), - 'tokenPassword' => $user->get('usr_token_password'), - 'tokenId' => $user->get('usr_token_id'), - ]; - } -} diff --git a/library/Validation/CompaniesValidator.php b/library/Validation/CompaniesValidator.php new file mode 100644 index 00000000..89b2d9d0 --- /dev/null +++ b/library/Validation/CompaniesValidator.php @@ -0,0 +1,24 @@ + "The company name is required", + ] + ); + $this->setFilters('name', Filter::FILTER_STRING); + $this->add('name', $presenceOf); + } +} diff --git a/scrutinizer.yml b/scrutinizer.yml index f855e5a5..1b0058f2 100644 --- a/scrutinizer.yml +++ b/scrutinizer.yml @@ -8,7 +8,6 @@ build: postgresql: false redis: false node: false - selenium: false php: version: 7.1.12 ini: @@ -26,6 +25,9 @@ build: DATA_API_MYSQL_PASS: "password" DATA_API_MYSQL_USER: "root" DATA_API_MYSQL_NAME: "gonano" + DATA_API_MEMCACHED_HOST: "127.0.0.1" + DATA_API_MEMCACHED_PORT: 11211 + DATA_API_MEMCACHED_WEIGHT: 100 APP_IP: "api.phalcon.ld" cache: diff --git a/storage/ci/.env.example b/storage/ci/.env.example index cf989c78..6fd5bfe5 100644 --- a/storage/ci/.env.example +++ b/storage/ci/.env.example @@ -11,13 +11,13 @@ TOKEN_AUDIENCE=https://phalconphp.com CACHE_PREFIX=api_cache_ CACHE_LIFETIME=86400 -DATA_API_MEMCACHED_HOST=127.0.0.1 -DATA_API_MEMCACHED_PORT=11211 -DATA_API_MEMCACHED_WEIGHT=100 +#DATA_API_MEMCACHED_HOST=127.0.0.1 +#DATA_API_MEMCACHED_PORT=11211 +#DATA_API_MEMCACHED_WEIGHT=100 DATA_API_MYSQL_NAME=gonano -DATA_API_MYSQL_USER=root -DATA_API_MYSQL_PASS=password +#DATA_API_MYSQL_USER=root +#DATA_API_MYSQL_PASS=password APP_IP=api.phalcon.ld diff --git a/storage/ci/.env.prod b/storage/ci/.env.prod index edcaad85..0b1228e1 100644 --- a/storage/ci/.env.prod +++ b/storage/ci/.env.prod @@ -11,13 +11,15 @@ TOKEN_AUDIENCE=https://phalconphp.com CACHE_PREFIX=api_cache_ CACHE_LIFETIME=86400 -DATA_API_MEMCACHED_HOST=127.0.0.1 -DATA_API_MEMCACHED_PORT=11211 -DATA_API_MEMCACHED_WEIGHT=100 +#DATA_API_MEMCACHED_HOST=127.0.0.1 +#DATA_API_MEMCACHED_PORT=11211 +#DATA_API_MEMCACHED_WEIGHT=100 DATA_API_MYSQL_NAME=gonano -DATA_API_MYSQL_USER=phalcon -DATA_API_MYSQL_PASS=secret +#DATA_API_MYSQL_USER=root +#DATA_API_MYSQL_PASS=password + +APP_IP=api.phalcon.ld LOGGER_DEFAULT_FILENAME=api diff --git a/storage/db/migrations/20180715202028_add_companies_table.php b/storage/db/migrations/20180715202028_add_companies_table.php new file mode 100644 index 00000000..21299bc1 --- /dev/null +++ b/storage/db/migrations/20180715202028_add_companies_table.php @@ -0,0 +1,69 @@ +table( + 'co_companies', + [ + 'id' => 'com_id', + 'signed' => false, + ] + ); + + $table + ->addColumn( + 'com_name', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'com_address', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'com_city', + 'string', + [ + 'limit' => 64, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'com_telephone', + 'string', + [ + 'limit' => 24, + 'null' => false, + 'default' => '', + ] + ) + ->addIndex('com_name') + ->addIndex('com_city') + ->save(); + + $this->execute( + 'ALTER TABLE co_companies ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_companies'); + } +} diff --git a/storage/db/migrations/20180715221034_add_products_table.php b/storage/db/migrations/20180715221034_add_products_table.php new file mode 100644 index 00000000..28c609c3 --- /dev/null +++ b/storage/db/migrations/20180715221034_add_products_table.php @@ -0,0 +1,73 @@ +table( + 'co_products', + [ + 'id' => 'prd_id', + 'signed' => false, + ] + ); + + $table + ->addColumn( + 'prd_name', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'prd_description', + 'string', + [ + 'limit' => 256, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'prd_quantity', + 'integer', + [ + 'limit' => 11, + 'null' => false, + 'signed' => false, + 'default' => 0, + ] + ) + ->addColumn( + 'prd_price', + 'decimal', + [ + 'precision' => 10, + 'scale' => 2, + 'null' => false, + 'signed' => false, + 'default' => 0, + ] + ) + ->addIndex('prd_name') + ->addIndex('prd_quantity') + ->addIndex('prd_price') + ->save(); + + $this->execute( + 'ALTER TABLE co_products ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_products'); + } +} diff --git a/storage/db/migrations/20180717231009_add_product_types_table.php b/storage/db/migrations/20180717231009_add_product_types_table.php new file mode 100644 index 00000000..42802266 --- /dev/null +++ b/storage/db/migrations/20180717231009_add_product_types_table.php @@ -0,0 +1,50 @@ +table( + 'co_product_types', + [ + 'id' => 'prt_id', + 'signed' => false, + ] + ); + + $table + ->addColumn( + 'prt_name', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'prt_description', + 'string', + [ + 'limit' => 256, + 'null' => false, + 'default' => '', + ] + ) + ->addIndex('prt_name') + ->save(); + + $this->execute( + 'ALTER TABLE co_product_types ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_product_types'); + } +} diff --git a/storage/db/migrations/20180717231024_add_individuals_table.php b/storage/db/migrations/20180717231024_add_individuals_table.php new file mode 100644 index 00000000..9971f433 --- /dev/null +++ b/storage/db/migrations/20180717231024_add_individuals_table.php @@ -0,0 +1,100 @@ +table( + 'co_individuals', + [ + 'id' => 'ind_id', + 'signed' => false, + ] + ); + $table + ->addColumn( + 'ind_com_id', + 'integer', + [ + 'signed' => false, + 'limit' => 11, + 'null' => false, + 'default' => 0, + ] + ) + ->addColumn( + 'ind_idt_id', + 'integer', + [ + 'signed' => false, + 'limit' => 11, + 'null' => false, + 'default' => 0, + ] + ) + ->addColumn( + 'ind_name_prefix', + 'string', + [ + 'limit' => 16, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'ind_name_first', + 'string', + [ + 'limit' => 64, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'ind_name_middle', + 'string', + [ + 'limit' => 64, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'ind_name_last', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'ind_name_suffix', + 'string', + [ + 'limit' => 16, + 'null' => false, + 'default' => '', + ] + ) + ->addIndex('ind_com_id') + ->addIndex('ind_idt_id') + ->addIndex('ind_name_first') + ->addIndex('ind_name_middle') + ->addIndex('ind_name_last') + ->save(); + + $this->execute( + 'ALTER TABLE co_individuals ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_employees'); + } +} diff --git a/storage/db/migrations/20180717231029_add_individual_types_table.php b/storage/db/migrations/20180717231029_add_individual_types_table.php new file mode 100644 index 00000000..f5709504 --- /dev/null +++ b/storage/db/migrations/20180717231029_add_individual_types_table.php @@ -0,0 +1,50 @@ +table( + 'co_individual_types', + [ + 'id' => 'idt_id', + 'signed' => false, + ] + ); + + $table + ->addColumn( + 'idt_name', + 'string', + [ + 'limit' => 128, + 'null' => false, + 'default' => '', + ] + ) + ->addColumn( + 'idt_description', + 'string', + [ + 'limit' => 256, + 'null' => false, + 'default' => '', + ] + ) + ->addIndex('idt_name') + ->save(); + + $this->execute( + 'ALTER TABLE co_individual_types ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_individual_types'); + } +} diff --git a/storage/db/migrations/20180717231052_add_companies_to_products_table.php b/storage/db/migrations/20180717231052_add_companies_to_products_table.php new file mode 100644 index 00000000..44e7f91c --- /dev/null +++ b/storage/db/migrations/20180717231052_add_companies_to_products_table.php @@ -0,0 +1,56 @@ +table( + 'co_companies_x_products', + [ + 'id' => false, + 'primary_key' => [ + 'cxp_com_id', + 'cxp_prd_id', + ], + ] + ); + + $table + ->addColumn( + 'cxp_com_id', + 'integer', + [ + 'signed' => false, + 'limit' => 11, + 'null' => false, + 'default' => 0, + ] + ) + ->addColumn( + 'cxp_prd_id', + 'integer', + [ + 'signed' => false, + 'limit' => 11, + 'null' => false, + 'default' => 0, + ] + ) + ->addIndex('cxp_com_id') + ->addIndex('cxp_prd_id') + ->save(); + + $this->execute( + 'ALTER TABLE co_companies_x_products ' . + 'CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci' + ); + } + + public function down() + { + $this->dropTable('co_companies_x_products'); + } +} diff --git a/storage/db/migrations/20180717232102_add_product_type_to_products.php b/storage/db/migrations/20180717232102_add_product_type_to_products.php new file mode 100644 index 00000000..9a01c1f0 --- /dev/null +++ b/storage/db/migrations/20180717232102_add_product_type_to_products.php @@ -0,0 +1,33 @@ +table('co_products'); + + $table + ->addColumn( + 'prd_prt_id', + 'integer', + [ + 'signed' => false, + 'limit' => 11, + 'null' => false, + 'default' => 0, + 'after' => 'prd_id', + ] + ) + ->addIndex('prd_id') + ->save(); + } + + public function down() + { + $table = $this->table('co_products'); + $table->removeColumn('prd_prt_id'); + } +} diff --git a/storage/db/migrations/20180723152114_rename_table_fields.php b/storage/db/migrations/20180723152114_rename_table_fields.php new file mode 100644 index 00000000..c3328545 --- /dev/null +++ b/storage/db/migrations/20180723152114_rename_table_fields.php @@ -0,0 +1,137 @@ +table('co_companies'); + $table + ->renameColumn('com_id', 'id') + ->renameColumn('com_name', 'name') + ->renameColumn('com_address', 'address') + ->renameColumn('com_city', 'city') + ->renameColumn('com_telephone', 'phone') + ->save(); + + $table = $this->table('co_companies_x_products'); + $table + ->renameColumn('cxp_com_id', 'companyId') + ->renameColumn('cxp_prd_id', 'productId') + ->save(); + + $table = $this->table('co_individual_types'); + $table + ->renameColumn('idt_id', 'id') + ->renameColumn('idt_name', 'name') + ->renameColumn('idt_description', 'description') + ->save(); + + $table = $this->table('co_individuals'); + $table + ->renameColumn('ind_id', 'id') + ->renameColumn('ind_com_id', 'companyId') + ->renameColumn('ind_idt_id', 'typeId') + ->renameColumn('ind_name_prefix', 'prefix') + ->renameColumn('ind_name_first', 'first') + ->renameColumn('ind_name_middle', 'middle') + ->renameColumn('ind_name_last', 'last') + ->renameColumn('ind_name_suffix', 'suffix') + ->save(); + + $table = $this->table('co_product_types'); + $table + ->renameColumn('prt_id', 'id') + ->renameColumn('prt_name', 'name') + ->renameColumn('prt_description', 'description') + ->save(); + + $table = $this->table('co_products'); + $table + ->renameColumn('prd_id', 'id') + ->renameColumn('prd_prt_id', 'typeId') + ->renameColumn('prd_name', 'name') + ->renameColumn('prd_description', 'description') + ->renameColumn('prd_quantity', 'quantity') + ->renameColumn('prd_price', 'price') + ->save(); + + $table = $this->table('co_users'); + $table + ->renameColumn('usr_id', 'id') + ->renameColumn('usr_status_flag', 'status') + ->renameColumn('usr_username', 'username') + ->renameColumn('usr_password', 'password') + ->renameColumn('usr_issuer', 'issuer') + ->renameColumn('usr_token_password', 'tokenPassword') + ->renameColumn('usr_token_id', 'tokenId') + ->save(); + } + + public function down() + { + $table = $this->table('co_companies'); + $table + ->renameColumn('id', 'com_id') + ->renameColumn('name', 'com_name') + ->renameColumn('address', 'com_address') + ->renameColumn('city', 'com_city') + ->renameColumn('phone', 'com_telephone') + ->save(); + + $table = $this->table('co_companies_x_products'); + $table + ->renameColumn('companyId', 'cxp_com_id') + ->renameColumn('productId', 'cxp_prd_id') + ->save(); + + $table = $this->table('co_individual_types'); + $table + ->renameColumn('id', 'idt_id') + ->renameColumn('name', 'idt_name') + ->renameColumn('description', 'idt_description') + ->save(); + + $table = $this->table('co_individuals'); + $table + ->renameColumn('id', 'ind_id') + ->renameColumn('companyId', 'ind_com_id') + ->renameColumn('typeId', 'ind_idt_id') + ->renameColumn('prefix', 'ind_name_prefix') + ->renameColumn('first', 'ind_name_first') + ->renameColumn('middle', 'ind_name_middle') + ->renameColumn('last', 'ind_name_last') + ->renameColumn('suffix', 'ind_name_suffix') + ->save(); + + $table = $this->table('co_product_types'); + $table + ->renameColumn('id', 'prt_id') + ->renameColumn('name', 'prt_name') + ->renameColumn('description', 'prt_description') + ->save(); + + $table = $this->table('co_products'); + $table + ->renameColumn('id', 'prd_id') + ->renameColumn('typeId', 'prd_prt_id') + ->renameColumn('name', 'prd_name') + ->renameColumn('description', 'prd_description') + ->renameColumn('quantity', 'prd_quantity') + ->renameColumn('price', 'prd_price') + ->save(); + + $table = $this->table('co_users'); + $table + ->renameColumn('id', 'usr_id') + ->renameColumn('status', 'usr_status_flag') + ->renameColumn('username', 'usr_username') + ->renameColumn('password', 'usr_password') + ->renameColumn('issuer', 'usr_issuer') + ->renameColumn('tokenPassword', 'usr_token_password') + ->renameColumn('tokenId', 'usr_token_id') + ->save(); + } +} diff --git a/tests/_support/ApiTester.php b/tests/_support/ApiTester.php index d551dc89..5b3be8ee 100644 --- a/tests/_support/ApiTester.php +++ b/tests/_support/ApiTester.php @@ -47,11 +47,38 @@ public function seeResponseIsSuccessful() $response = $this->grabResponse(); $response = json_decode($response, true); - $errors = $response['errors'] ?? []; - $section = (count($errors) > 0) ? 'errors' : 'data'; $timestamp = $response['meta']['timestamp']; $hash = $response['meta']['hash']; - $this->assertEquals($hash, sha1($timestamp . json_encode($response[$section]))); + unset($response['meta'], $response['jsonapi']); + $this->assertEquals($hash, sha1($timestamp . json_encode($response))); + } + + /** + * Checks if the response was successful + */ + public function seeResponseIs404() + { + $this->seeResponseIsJson(); + $this->seeResponseCodeIs(HttpCode::NOT_FOUND); + $this->seeResponseMatchesJsonType( + [ + 'jsonapi' => [ + 'version' => 'string' + ], + 'meta' => [ + 'timestamp' => 'string:date', + 'hash' => 'string', + ] + ] + ); + + $response = $this->grabResponse(); + $response = json_decode($response, true); + $timestamp = $response['meta']['timestamp']; + $hash = $response['meta']['hash']; + $this->assertEquals('Not Found', $response['errors'][0]); + unset($response['jsonapi'], $response['meta']); + $this->assertEquals($hash, sha1($timestamp . json_encode($response))); } public function seeErrorJsonResponse(string $message) @@ -68,16 +95,9 @@ public function seeErrorJsonResponse(string $message) ); } - public function seeSuccessJsonResponse(array $data = []) + public function seeSuccessJsonResponse(string $key = 'data', array $data = []) { - $contents = [ - 'jsonapi' => [ - 'version' => '1.0', - ], - 'data' => $data, - ]; - - $this->seeResponseContainsJson($contents); + $this->seeResponseContainsJson([$key => $data]); } public function apiLogin() @@ -93,4 +113,19 @@ public function apiLogin() return $token; } + + public function addApiUserRecord() + { + return $this->haveRecordWithFields( + Users::class, + [ + 'status' => 1, + 'username' => 'testuser', + 'password' => 'testpassword', + 'issuer' => 'https://niden.net', + 'tokenPassword' => '12345', + 'tokenId' => '110011', + ] + ); + } } diff --git a/tests/_support/Helper/Integration.php b/tests/_support/Helper/Integration.php index 175d019f..466bde71 100644 --- a/tests/_support/Helper/Integration.php +++ b/tests/_support/Helper/Integration.php @@ -7,6 +7,12 @@ use Codeception\TestInterface; use Niden\Bootstrap\Api; +use Niden\Models\Companies; +use Niden\Models\CompaniesXProducts; +use Niden\Models\Individuals; +use Niden\Models\IndividualTypes; +use Niden\Models\Products; +use Niden\Models\ProductTypes; use Niden\Mvc\Model\AbstractModel; use Phalcon\DI\FactoryDefault as PhDI; use Phalcon\Config as PhConfig; @@ -54,6 +60,124 @@ public function _after(TestInterface $test) $this->diContainer->get('db')->close(); } + /** + * @param string $namePrefix + * + * @return Companies + */ + public function addCompanyRecord(string $namePrefix = '') + { + return $this->haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid($namePrefix), + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + } + + /** + * @param int $companyId + * @param int $productId + * + * @return CompaniesXProducts + */ + public function addCompanyXProduct(int $companyId, int $productId) + { + return $this->haveRecordWithFields( + CompaniesXProducts::class, + [ + 'companyId' => $companyId, + 'productId' => $productId, + ] + ); + } + + /** + * @param string $namePrefix + * + * @return IndividualTypes + */ + public function addIndividualTypeRecord(string $namePrefix = '') + { + return $this->haveRecordWithFields( + IndividualTypes::class, + [ + 'name' => uniqid($namePrefix), + 'description' => uniqid(), + ] + ); + } + + /** + * @param string $namePrefix + * @param int $comId + * @param int $typeId + * + * @return Individuals + */ + public function addIndividualRecord(string $namePrefix = '', int $comId = 0, int $typeId = 0) + { + return $this->haveRecordWithFields( + Individuals::class, + [ + 'companyId' => $comId, + 'typeId' => $typeId, + 'prefix' => uniqid(), + 'first' => uniqid($namePrefix), + 'middle' => uniqid(), + 'last' => uniqid(), + 'suffix' => uniqid(), + ] + ); + } + + /** + * @param string $namePrefix + * @param int $typeId + * + * @return Products + */ + public function addProductRecord(string $namePrefix = '', int $typeId = 0) + { + return $this->haveRecordWithFields( + Products::class, + [ + 'name' => uniqid($namePrefix), + 'typeId' => $typeId, + 'description' => uniqid(), + 'quantity' => 25, + 'price' => 19.99, + ] + ); + } + + /** + * @param string $namePrefix + * + * @return ProductTypes + */ + public function addProductTypeRecord(string $namePrefix = '') + { + return $this->haveRecordWithFields( + ProductTypes::class, + [ + 'name' => uniqid($namePrefix), + 'description' => uniqid(), + ] + ); + } + + /** + * @return mixed + */ + public function grabDi() + { + return $this->diContainer; + } + /** * @param string $name * @@ -64,6 +188,34 @@ public function grabFromDi(string $name) return $this->diContainer->get($name); } + /** + * Returns the relationships that a model has + * + * @param string $class + * + * @return array + */ + public function getModelRelationships(string $class): array + { + /** @var AbstractModel $class */ + $model = new $class(); + $manager = $model->getModelsManager(); + $relationships = $manager->getRelations($class); + + $data = []; + foreach ($relationships as $relationship) { + $data[] = [ + $relationship->getType(), + $relationship->getFields(), + $relationship->getReferencedModel(), + $relationship->getReferencedFields(), + $relationship->getOptions(), + ]; + } + + return $data; + } + /** * Get a record from $modelName with fields provided * diff --git a/tests/_support/Page/Data.php b/tests/_support/Page/Data.php index fa9c9733..efd93099 100644 --- a/tests/_support/Page/Data.php +++ b/tests/_support/Page/Data.php @@ -2,33 +2,231 @@ namespace Page; +use Niden\Constants\Relationships; +use function Niden\Core\envValue; +use Niden\Mvc\Model\AbstractModel; + class Data { - public static $loginUrl = '/login'; - public static $userGetUrl = '/user/get'; - public static $usersGetUrl = '/users/get'; - public static $wrongUrl = '/sommething'; + public static $companiesUrl = '/companies'; + public static $companiesRecordUrl = '/companies/%s'; + public static $companiesRecordRelationshipUrl = '/companies/%s/%s'; + public static $companiesRecordRelationshipRelationshipUrl = '/companies/%s/relationships/%s'; + public static $loginUrl = '/login'; + public static $individualsUrl = '/individuals'; + public static $individualsRecordUrl = '/individuals/%s'; + public static $individualsRecordRelationshipUrl = '/individuals/%s/%s'; + public static $individualsRecordRelationshipRelationshipUrl = '/individuals/%s/relationships/%s'; + public static $individualTypesUrl = '/individual-types'; + public static $individualTypesRecordUrl = '/individual-types/%s'; + public static $individualTypesRecordRelationshipUrl = '/individual-types/%s/%s'; + public static $individualTypesRecordRelationshipRelationshipUrl = '/individual-types/%s/relationships/%s'; + public static $productsUrl = '/products'; + public static $productsRecordUrl = '/products/%s'; + public static $productsRecordRelationshipUrl = '/products/%s/%s'; + public static $productsRecordRelationshipRelationshipUrl = '/products/%s/relationships/%s'; + public static $productTypesUrl = '/product-types'; + public static $productTypesRecordUrl = '/product-types/%s'; + public static $productTypesRecordRelationshipUrl = '/product-types/%s/%s'; + public static $productTypesRecordRelationshipRelationshipUrl = '/product-types/%s/relationships/%s'; + public static $usersUrl = '/users'; + public static $wrongUrl = '/sommething'; + /** + * @return array + */ public static function loginJson() { - return json_encode( - [ - 'data' => [ - 'username' => 'testuser', - 'password' => 'testpassword', - ] - ] - ); - } - - public static function userGetJson($userId) - { - return json_encode( - [ - 'data' => [ - 'userId' => $userId, - ] - ] - ); + return [ + 'username' => 'testuser', + 'password' => 'testpassword', + ]; + } + + /** + * @param $name + * @param string $address + * @param string $city + * @param string $phone + * + * @return array + */ + public static function companyAddJson($name, $address = '', $city = '', $phone = '') + { + return [ + 'name' => $name, + 'address' => $address, + 'city' => $city, + 'phone' => $phone, + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function companyResponse(AbstractModel $record) + { + return [ + 'id' => $record->get('id'), + 'type' => Relationships::COMPANIES, + 'attributes' => [ + 'name' => $record->get('name'), + 'address' => $record->get('address'), + 'city' => $record->get('city'), + 'phone' => $record->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::COMPANIES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function individualResponse(AbstractModel $record) + { + return [ + 'id' => $record->get('id'), + 'type' => Relationships::INDIVIDUALS, + 'attributes' => [ + 'companyId' => $record->get('companyId'), + 'typeId' => $record->get('typeId'), + 'prefix' => $record->get('prefix'), + 'first' => $record->get('first'), + 'middle' => $record->get('middle'), + 'last' => $record->get('last'), + 'suffix' => $record->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUALS, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function individualTypeResponse(AbstractModel $record) + { + return [ + 'id' => $record->get('id'), + 'type' => Relationships::INDIVIDUAL_TYPES, + 'attributes' => [ + 'name' => $record->get('name'), + 'description' => $record->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function productResponse(AbstractModel $record) + { + return [ + 'type' => Relationships::PRODUCTS, + 'id' => $record->get('id'), + 'attributes' => [ + 'typeId' => $record->get('typeId'), + 'name' => $record->get('name'), + 'description' => $record->get('description'), + 'quantity' => $record->get('quantity'), + 'price' => $record->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCTS, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function productTypeResponse(AbstractModel $record) + { + return [ + 'id' => $record->get('id'), + 'type' => Relationships::PRODUCT_TYPES, + 'attributes' => [ + 'name' => $record->get('name'), + 'description' => $record->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $record->get('id') + ), + ], + ]; + } + + /** + * @param AbstractModel $record + * + * @return array + * @throws \Niden\Exception\ModelException + */ + public static function userResponse(AbstractModel $record) + { + return [ + 'id' => $record->get('id'), + 'type' => Relationships::USERS, + 'attributes' => [ + 'status' => $record->get('status'), + 'username' => $record->get('username'), + 'issuer' => $record->get('issuer'), + 'tokenPassword' => $record->get('tokenPassword'), + 'tokenId' => $record->get('tokenId'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::USERS, + $record->get('id') + ), + ], + ]; } } diff --git a/tests/api/Companies/AddCest.php b/tests/api/Companies/AddCest.php new file mode 100644 index 00000000..fde635a7 --- /dev/null +++ b/tests/api/Companies/AddCest.php @@ -0,0 +1,83 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + $name = uniqid('com'); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendPOST( + Data::$companiesUrl, + Data::companyAddJson( + $name, + '123 Phalcon way', + 'World', + '555-444-7777' + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + + $company = $I->getRecordWithFields( + Companies::class, + [ + 'name' => $name, + ] + ); + $I->assertNotEquals(false, $company); + + $I->seeSuccessJsonResponse( + 'data', + [ + Data::companyResponse($company), + ] + ); + + $I->assertNotEquals(false, $company->delete()); + } + + /** + * @param ApiTester $I + */ + public function addNewCompanyWithExistingName(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + $name = uniqid('com'); + $I->haveRecordWithFields( + Companies::class, + [ + 'name' => $name, + ] + ); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendPOST( + Data::$companiesUrl, + Data::companyAddJson( + $name, + '123 Phalcon way', + 'World', + '555-444-7777' + ) + ); + $I->deleteHeader('Authorization'); + $I->seeErrorJsonResponse('The company name already exists in the database'); + } +} diff --git a/tests/api/Companies/GetCest.php b/tests/api/Companies/GetCest.php new file mode 100644 index 00000000..b8af7694 --- /dev/null +++ b/tests/api/Companies/GetCest.php @@ -0,0 +1,474 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + + $company = $I->addCompanyRecord('com-a-'); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$companiesRecordUrl, $company->get('id'))); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::companyResponse($company), + ] + ); + } + + /** + * @param ApiTester $I + */ + public function getCompanyUnknownRelationship(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $company = $I->addCompanyRecord('com-a-'); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$companiesRecordRelationshipUrl, $company->get('id'), 'unknown')); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + */ + public function getUnknownCompany(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$companiesRecordUrl, 1)); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompanies(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var Companies $comOne */ + $comOne = $I->addCompanyRecord('com-a-'); + /** @var Companies $comTwo */ + $comTwo = $I->addCompanyRecord('com-b-'); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$companiesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::companyResponse($comOne), + Data::companyResponse($comTwo), + ] + ); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithAllRelationships(ApiTester $I) + { + $this->runCompaniesWithAllRelationshipsTests($I, Data::$companiesRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithRelationshipAllRelationships(ApiTester $I) + { + $this->runCompaniesWithAllRelationshipsTests($I, Data::$companiesRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithIndividuals(ApiTester $I) + { + $this->runCompaniesWithIndividualsTests($I, Data::$companiesRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithRelationshipIndividuals(ApiTester $I) + { + $this->runCompaniesWithIndividualsTests($I, Data::$companiesRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithProducts(ApiTester $I) + { + $this->runCompaniesWithProductsTests($I, Data::$companiesRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getCompaniesWithRelationshipCompanyTypes(ApiTester $I) + { + $this->runCompaniesWithProductsTests($I, Data::$companiesRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + */ + public function getCompaniesNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$companiesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse(); + } + + private function addRecords(ApiTester $I): array + { + /** @var Companies $comOne */ + $company = $I->addCompanyRecord('com-a'); + $indType = $I->addIndividualTypeRecord('type-a-'); + $indOne = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + $indTwo = $I->addIndividualRecord('ind-a-', $company->get('id'), $indType->get('id')); + $prdType = $I->addProductTypeRecord('type-a-'); + $prdOne = $I->addProductRecord('prd-a-', $prdType->get('id')); + $prdTwo = $I->addProductRecord('prd-b-', $prdType->get('id')); + $I->addCompanyXProduct($company->get('id'), $prdOne->get('id')); + $I->addCompanyXProduct($company->get('id'), $prdTwo->get('id')); + + return [$company, $prdOne, $prdTwo, $indOne, $indTwo]; + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runCompaniesWithAllRelationshipsTests(ApiTester $I, $url) + { + list($com, $prdOne, $prdTwo, $indOne, $indTwo) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $com->get('id'), + Relationships::INDIVIDUALS . ',' . Relationships::PRODUCTS + ) + ); + + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $com->get('id'), + 'attributes' => [ + 'name' => $com->get('name'), + 'address' => $com->get('address'), + 'city' => $com->get('city'), + 'phone' => $com->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id') + ), + ], + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + ], + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indOne->get('id'), + ], + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indTwo->get('id'), + ], + ], + ], + Relationships::PRODUCTS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::individualResponse($indOne), + Data::individualResponse($indTwo), + Data::productResponse($prdOne), + Data::productResponse($prdTwo), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runCompaniesWithIndividualsTests(ApiTester $I, $url) + { + list($com, , , $indOne, $indTwo) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $com->get('id'), + Relationships::INDIVIDUALS + ) + ); + + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $com->get('id'), + 'attributes' => [ + 'name' => $com->get('name'), + 'address' => $com->get('address'), + 'city' => $com->get('city'), + 'phone' => $com->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id') + ), + ], + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::INDIVIDUALS + ), + ], + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indOne->get('id'), + ], + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $indTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::individualResponse($indOne), + Data::individualResponse($indTwo), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runCompaniesWithProductsTests(ApiTester $I, $url) + { + list($com, $prdOne, $prdTwo) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $com->get('id'), + Relationships::PRODUCTS + ) + ); + + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $com->get('id'), + 'attributes' => [ + 'name' => $com->get('name'), + 'address' => $com->get('address'), + 'city' => $com->get('city'), + 'phone' => $com->get('phone'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id') + ), + ], + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::COMPANIES, + $com->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $prdTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::productResponse($prdOne), + Data::productResponse($prdTwo), + ] + ); + } +} diff --git a/tests/api/IncorrectPayloadCest.php b/tests/api/IncorrectPayloadCest.php deleted file mode 100644 index d29e742e..00000000 --- a/tests/api/IncorrectPayloadCest.php +++ /dev/null @@ -1,19 +0,0 @@ -sendPOST(Data::$userGetUrl, '{"key": "value}'); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Malformed JSON'); - } -} diff --git a/tests/api/IndividualTypes/GetCest.php b/tests/api/IndividualTypes/GetCest.php new file mode 100644 index 00000000..6c867d6e --- /dev/null +++ b/tests/api/IndividualTypes/GetCest.php @@ -0,0 +1,177 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + + $typeOne = $I->addIndividualTypeRecord('type-a-'); + $typeTwo = $I->addIndividualTypeRecord('type-b-'); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$individualTypesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::individualTypeResponse($typeOne), + Data::individualTypeResponse($typeTwo), + ] + ); + } + + /** + * @param ApiTester $I + */ + public function getUnknownIndividualTypes(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$individualTypesRecordUrl, 1)); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualTypesWithRelationshipIndividuals(ApiTester $I) + { + $this->runIndividualTypesWithIndividualsTests($I, Data::$individualTypesRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualTypesWithIndividuals(ApiTester $I) + { + $this->runIndividualTypesWithIndividualsTests($I, Data::$individualTypesRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + */ + public function getIndividualTypesNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$individualTypesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse(); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runIndividualTypesWithIndividualsTests(ApiTester $I, $url) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var $company */ + $company = $I->addCompanyRecord('com-a'); + /** @var IndividualTypes $individualType */ + $individualType = $I->addIndividualTypeRecord('type-a-'); + /** @var Individuals $individualOne */ + $individualOne = $I->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); + /** @var Individuals $individualTwo */ + $individualTwo = $I->addIndividualRecord('prd-b-', $company->get('id'), $individualType->get('id')); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $individualType->get('id'), + Relationships::INDIVIDUALS + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + 'attributes' => [ + 'name' => $individualType->get('name'), + 'description' => $individualType->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id') + ), + ], + 'relationships' => [ + Relationships::INDIVIDUALS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id'), + Relationships::INDIVIDUALS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL'), + Relationships::INDIVIDUAL_TYPES, + $individualType->get('id'), + Relationships::INDIVIDUALS + ), + ], + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individualOne->get('id'), + ], + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individualTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::individualResponse($individualOne), + Data::individualResponse($individualTwo), + ] + ); + } +} diff --git a/tests/api/Individuals/GetCest.php b/tests/api/Individuals/GetCest.php new file mode 100644 index 00000000..463d1fdd --- /dev/null +++ b/tests/api/Individuals/GetCest.php @@ -0,0 +1,446 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var Companies $company */ + $company = $I->addCompanyRecord('com-a-'); + /** @var IndividualTypes $individualType */ + $individualType = $I->addIndividualTypeRecord('prt-a-'); + /** @var Individuals $individual */ + $individual = $I->addIndividualRecord('prd-a-', $company->get('id'), $individualType->get('id')); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$individualsRecordUrl, $individual->get('id'))); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::individualResponse($individual), + ] + ); + } + + /** + * @param ApiTester $I + */ + public function getUnknownIndividual(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$individualsRecordUrl, 1)); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividuals(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var Companies $company */ + $company = $I->addCompanyRecord('com-a-'); + /** @var IndividualTypes $individualType */ + $individualType = $I->addIndividualTypeRecord('prt-a-'); + /** @var Individuals $individualOne */ + $individualOne = $I->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); + /** @var Individuals $individualTwo */ + $individualTwo = $I->addIndividualRecord('ind-b-', $company->get('id'), $individualType->get('id')); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$individualsUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::individualResponse($individualOne), + Data::individualResponse($individualTwo), + ] + ); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithAllRelationships(ApiTester $I) + { + $this->runIndividualsWithAllRelationshipsTests($I, Data::$individualsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithRelationshipAllRelationships(ApiTester $I) + { + $this->runIndividualsWithAllRelationshipsTests($I, Data::$individualsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithCompanies(ApiTester $I) + { + $this->runIndividualsWithCompaniesTests($I, Data::$individualsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithRelationshipCompanies(ApiTester $I) + { + $this->runIndividualsWithCompaniesTests($I, Data::$individualsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithIndividualTypes(ApiTester $I) + { + $this->runIndividualsWithIndividualTypesTests($I, Data::$individualsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getIndividualsWithRelationshipIndividualTypes(ApiTester $I) + { + $this->runIndividualsWithIndividualTypesTests($I, Data::$individualsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + */ + public function getIndividualsNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$individualsUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse(); + } + + private function addRecords(ApiTester $I): array + { + /** @var Companies $company */ + $company = $I->addCompanyRecord('com-a'); + /** @var IndividualTypes $individualType */ + $individualType = $I->addIndividualTypeRecord('prt-a-'); + /** @var Individuals $individual */ + $individual = $I->addIndividualRecord('ind-a-', $company->get('id'), $individualType->get('id')); + + + return [$individual, $individualType, $company]; + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runIndividualsWithAllRelationshipsTests(ApiTester $I, $url) + { + list($individual, $individualType, $company) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $individual->get('id'), + Relationships::COMPANIES . ',' . Relationships::INDIVIDUAL_TYPES + ) + ); + + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + 'type' => Relationships::COMPANIES, + 'id' => $company->get('id'), + ], + ], + Relationships::INDIVIDUAL_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::companyResponse($company), + Data::individualTypeResponse($individualType), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runIndividualsWithCompaniesTests(ApiTester $I, $url) + { + list($individual, $individualType, $company) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $individual->get('id'), + Relationships::COMPANIES + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + 'type' => Relationships::COMPANIES, + 'id' => $company->get('id'), + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::companyResponse($company), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runIndividualsWithIndividualTypesTests(ApiTester $I, $url) + { + list($individual, $individualType) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + 'relationships' => [ + Relationships::INDIVIDUAL_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::individualTypeResponse($individualType), + ] + ); + } +} diff --git a/tests/api/LoginCest.php b/tests/api/LoginCest.php index 01eff31a..43652868 100644 --- a/tests/api/LoginCest.php +++ b/tests/api/LoginCest.php @@ -3,41 +3,20 @@ namespace Niden\Tests\api; use ApiTester; -use function json_decode; -use Niden\Exception\Exception; -use Niden\Http\Response; use Niden\Models\Users; use Page\Data; +use function json_decode; class LoginCest { - public function loginNoDataElement(ApiTester $I) - { - $I->sendPOST( - Data::$loginUrl, - json_encode( - [ - 'username' => 'user', - 'password' => 'pass', - ] - ) - ); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('"data" element not present in the payload'); - } - public function loginUnknownUser(ApiTester $I) { $I->sendPOST( Data::$loginUrl, - json_encode( - [ - 'data' => [ - 'username' => 'user', - 'password' => 'pass', - ] - ] - ) + [ + 'username' => 'user', + 'password' => 'pass', + ] ); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Incorrect credentials'); @@ -48,11 +27,11 @@ public function loginKnownUser(ApiTester $I) $I->haveRecordWithFields( Users::class, [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://phalconphp.com', - 'usr_token_id' => '110011', + 'status' => 1, + 'username' => 'testuser', + 'password' => 'testpassword', + 'issuer' => 'https://phalconphp.com', + 'tokenId' => '110011', ] ); diff --git a/tests/api/NotFoundCest.php b/tests/api/NotFoundCest.php index aa25932d..3b671fd4 100644 --- a/tests/api/NotFoundCest.php +++ b/tests/api/NotFoundCest.php @@ -3,8 +3,6 @@ namespace Niden\Tests\api; use ApiTester; -use Niden\Exception\Exception; -use Niden\Http\Response; use Page\Data; class NotFoundCest @@ -12,7 +10,6 @@ class NotFoundCest public function checkNotFoundRoute(ApiTester $I) { $I->sendGET(Data::$wrongUrl); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('404 Not Found'); + $I->seeResponseIs404(); } } diff --git a/tests/api/ProductTypes/GetCest.php b/tests/api/ProductTypes/GetCest.php new file mode 100644 index 00000000..97b67c1e --- /dev/null +++ b/tests/api/ProductTypes/GetCest.php @@ -0,0 +1,177 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + + $typeOne = $I->addProductTypeRecord('type-a-'); + $typeTwo = $I->addProductTypeRecord('type-b-'); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$productTypesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::productTypeResponse($typeOne), + Data::productTypeResponse($typeTwo), + ] + ); + } + + /** + * @param ApiTester $I + */ + public function getUnknownProductTypes(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$productTypesRecordUrl, 1)); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductTypesWithRelationshipProducts(ApiTester $I) + { + $this->runProductTypesWithProductsTests($I, Data::$productTypesRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductTypesWithProducts(ApiTester $I) + { + $this->runProductTypesWithProductsTests($I, Data::$productTypesRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + */ + public function getProductTypesNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$productTypesUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse(); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runProductTypesWithProductsTests(ApiTester $I, $url) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var ProductTypes $productType */ + $productType = $I->addProductTypeRecord('type-a-'); + /** @var Products $productOne */ + $productOne = $I->addProductRecord('prd-a-', $productType->get('id')); + /** @var Products $productTwo */ + $productTwo = $I->addProductRecord('prd-b-', $productType->get('id')); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $productType->get('id'), + Relationships::PRODUCTS + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + 'attributes' => [ + 'name' => $productType->get('name'), + 'description' => $productType->get('description'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id') + ), + ], + 'relationships' => [ + Relationships::PRODUCTS => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id'), + Relationships::PRODUCTS + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL'), + Relationships::PRODUCT_TYPES, + $productType->get('id'), + Relationships::PRODUCTS + ), + ], + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $productOne->get('id'), + ], + [ + 'type' => Relationships::PRODUCTS, + 'id' => $productTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::productResponse($productOne), + Data::productResponse($productTwo), + ] + ); + } +} diff --git a/tests/api/Products/GetCest.php b/tests/api/Products/GetCest.php new file mode 100644 index 00000000..e12df483 --- /dev/null +++ b/tests/api/Products/GetCest.php @@ -0,0 +1,453 @@ +addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var ProductTypes $productType */ + $productType = $I->addProductTypeRecord('prt-a-'); + /** @var Products $product */ + $product = $I->addProductRecord('prd-a-', $productType->get('id')); + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$productsRecordUrl, $product->get('id'))); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::productResponse($product), + ] + ); + } + + /** + * @param ApiTester $I + */ + public function getUnknownProduct(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(sprintf(Data::$productsRecordUrl, 1)); + $I->deleteHeader('Authorization'); + $I->seeResponseIs404(); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProducts(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + /** @var ProductTypes $productType */ + $productType = $I->addProductTypeRecord('prt-a-'); + /** @var Products $productOne */ + $productOne = $I->addProductRecord('prd-a-', $productType->get('id')); + /** @var Products $productTwo */ + $productTwo = $I->addProductRecord('prd-b-', $productType->get('id')); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$productsUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::productResponse($productOne), + Data::productResponse($productTwo), + ] + ); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithAllRelationships(ApiTester $I) + { + $this->runProductsWithAllRelationshipsTests($I, Data::$productsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithRelationshipAllRelationships(ApiTester $I) + { + $this->runProductsWithAllRelationshipsTests($I, Data::$productsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithCompanies(ApiTester $I) + { + $this->runProductsWithCompaniesTests($I, Data::$productsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithRelationshipCompanies(ApiTester $I) + { + $this->runProductsWithCompaniesTests($I, Data::$productsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithProductTypes(ApiTester $I) + { + $this->runProductsWithProductTypesTests($I, Data::$productsRecordRelationshipRelationshipUrl); + } + + /** + * @param ApiTester $I + * + * @throws \Niden\Exception\ModelException + */ + public function getProductsWithRelationshipProductTypes(ApiTester $I) + { + $this->runProductsWithProductTypesTests($I, Data::$productsRecordRelationshipUrl); + } + + /** + * @param ApiTester $I + */ + public function getProductsNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$productsUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse(); + } + + private function addRecords(ApiTester $I): array + { + /** @var Companies $comOne */ + $comOne = $I->addCompanyRecord('com-a'); + /** @var Companies $comTwo */ + $comTwo = $I->addCompanyRecord('com-b'); + /** @var ProductTypes $productType */ + $productType = $I->addProductTypeRecord('prt-a-'); + /** @var Products $product */ + $product = $I->addProductRecord('prd-a-', $productType->get('id')); + $I->addCompanyXProduct($comOne->get('id'), $product->get('id')); + $I->addCompanyXProduct($comTwo->get('id'), $product->get('id')); + + return [$product, $productType, $comOne, $comTwo]; + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runProductsWithAllRelationshipsTests(ApiTester $I, $url) + { + list($product, $productType, $comOne, $comTwo) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $product->get('id'), + Relationships::COMPANIES . ',' . Relationships::PRODUCT_TYPES + ) + ); + + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $comOne->get('id'), + ], + [ + 'type' => Relationships::COMPANIES, + 'id' => $comTwo->get('id'), + ], + ], + ], + Relationships::PRODUCT_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::companyResponse($comOne), + Data::companyResponse($comTwo), + Data::productTypeResponse($productType), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runProductsWithCompaniesTests(ApiTester $I, $url) + { + list($product, $productType, $comOne, $comTwo) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $product->get('id'), + Relationships::COMPANIES + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $comOne->get('id'), + ], + [ + 'type' => Relationships::COMPANIES, + 'id' => $comTwo->get('id'), + ], + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::companyResponse($comOne), + Data::companyResponse($comTwo), + ] + ); + } + + /** + * @param ApiTester $I + * @param $url + * + * @throws \Niden\Exception\ModelException + */ + private function runProductsWithProductTypesTests(ApiTester $I, $url) + { + list($product, $productType) = $this->addRecords($I); + + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET( + sprintf( + $url, + $product->get('id'), + Relationships::PRODUCT_TYPES + ) + ); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id') + ), + ], + 'relationships' => [ + Relationships::PRODUCT_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + envValue('APP_URL', 'localhost'), + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + ], + ], + ], + ], + ] + ); + + $I->seeSuccessJsonResponse( + 'included', + [ + Data::productTypeResponse($productType), + ] + ); + } +} diff --git a/tests/api/Users/UserCest.php b/tests/api/Users/GetCest.php similarity index 62% rename from tests/api/Users/UserCest.php rename to tests/api/Users/GetCest.php index e4cd5362..946f7ea3 100644 --- a/tests/api/Users/UserCest.php +++ b/tests/api/Users/GetCest.php @@ -5,43 +5,43 @@ use ApiTester; use Lcobucci\JWT\Builder; use Lcobucci\JWT\Signer\Hmac\Sha512; +use Niden\Constants\Relationships; use Niden\Models\Users; use Niden\Traits\TokenTrait; use Page\Data; -class UserCest +class GetCest { use TokenTrait; public function loginKnownUserNoToken(ApiTester $I) { $I->deleteHeader('Authorization'); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson(1)); + $I->sendGET(Data::$usersUrl . '/1'); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Invalid Token'); } public function loginKnownUserGetUnknownUser(ApiTester $I) { - $this->addRecord($I); + $I->addApiUserRecord(); $I->deleteHeader('Authorization'); $I->sendPOST(Data::$loginUrl, Data::loginJson()); $I->seeResponseIsSuccessful(); $response = $I->grabResponse(); - $response = json_decode($response, true); - $data = $response['data']; - $token = $data['token']; + $response = json_decode($response, true); + $data = $response['data']; + $token = $data['token']; $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson(1)); - $I->seeResponseIsSuccessful(); - $I->seeErrorJsonResponse('Record not found'); + $I->sendGET(Data::$usersUrl . '/1'); + $I->seeResponseIs404(); } public function loginKnownUserIncorrectSignature(ApiTester $I) { - $record = $this->addRecord($I); + $record = $I->addApiUserRecord(); $I->deleteHeader('Authorization'); $I->sendPOST(Data::$loginUrl, Data::loginJson()); $I->seeResponseIsSuccessful(); @@ -49,7 +49,7 @@ public function loginKnownUserIncorrectSignature(ApiTester $I) $signer = new Sha512(); $builder = new Builder(); - $token = $builder + $token = $builder ->setIssuer('https://niden.net') ->setAudience($this->getTokenAudience()) ->setId('110011', true) @@ -57,19 +57,20 @@ public function loginKnownUserIncorrectSignature(ApiTester $I) ->setNotBefore(time() - 3590) ->setExpiration(time() - 3000) ->sign($signer, '123456') - ->getToken(); + ->getToken() + ; $wrongToken = $token->__toString(); $I->haveHttpHeader('Authorization', 'Bearer ' . $wrongToken); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson($record->get('usr_id'))); + $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Invalid Token'); } public function loginKnownUserExpiredToken(ApiTester $I) { - $record = $this->addRecord($I); + $record = $I->addApiUserRecord(); $I->deleteHeader('Authorization'); $I->sendPOST(Data::$loginUrl, Data::loginJson()); $I->seeResponseIsSuccessful(); @@ -77,7 +78,7 @@ public function loginKnownUserExpiredToken(ApiTester $I) $signer = new Sha512(); $builder = new Builder(); - $token = $builder + $token = $builder ->setIssuer('https://niden.net') ->setAudience($this->getTokenAudience()) ->setId('110011', true) @@ -85,19 +86,20 @@ public function loginKnownUserExpiredToken(ApiTester $I) ->setNotBefore(time() - 3590) ->setExpiration(time() - 3000) ->sign($signer, '12345') - ->getToken(); + ->getToken() + ; $expiredToken = $token->__toString(); $I->haveHttpHeader('Authorization', 'Bearer ' . $expiredToken); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson($record->get('usr_id'))); + $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Invalid Token'); } public function loginKnownUserInvalidToken(ApiTester $I) { - $record = $this->addRecord($I); + $record = $I->addApiUserRecord(); $I->deleteHeader('Authorization'); $I->sendPOST(Data::$loginUrl, Data::loginJson()); $I->seeResponseIsSuccessful(); @@ -105,7 +107,7 @@ public function loginKnownUserInvalidToken(ApiTester $I) $signer = new Sha512(); $builder = new Builder(); - $token = $builder + $token = $builder ->setIssuer('https://niden.net') ->setAudience($this->getTokenAudience()) ->setId('110011', true) @@ -113,19 +115,20 @@ public function loginKnownUserInvalidToken(ApiTester $I) ->setNotBefore(time() - 3590) ->setExpiration(time() - 3000) ->sign($signer, '12345') - ->getToken(); + ->getToken() + ; $invalidToken = $token->__toString(); $I->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson($record->get('usr_id'))); + $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Invalid Token'); } public function loginKnownUserInvalidUserInToken(ApiTester $I) { - $record = $this->addRecord($I); + $record = $I->addApiUserRecord(); $I->deleteHeader('Authorization'); $I->sendPOST(Data::$loginUrl, Data::loginJson()); $I->seeResponseIsSuccessful(); @@ -133,7 +136,7 @@ public function loginKnownUserInvalidUserInToken(ApiTester $I) $signer = new Sha512(); $builder = new Builder(); - $token = $builder + $token = $builder ->setIssuer('https://niden.com') ->setAudience($this->getTokenAudience()) ->setId('110011', true) @@ -141,57 +144,89 @@ public function loginKnownUserInvalidUserInToken(ApiTester $I) ->setNotBefore(time() - 3590) ->setExpiration(time() - 3000) ->sign($signer, '12345') - ->getToken(); + ->getToken() + ; $invalidToken = $token->__toString(); $I->haveHttpHeader('Authorization', 'Bearer ' . $invalidToken); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson($record->get('usr_id'))); + $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); $I->seeResponseIsSuccessful(); $I->seeErrorJsonResponse('Invalid Token'); } public function loginKnownUserCorrectToken(ApiTester $I) { - $this->addRecord($I); + $I->addApiUserRecord(); $I->apiLogin(); } public function loginKnownUserValidToken(ApiTester $I) { - $user = $this->addRecord($I); - $token = $I->apiLogin(); + $record = $I->addApiUserRecord(); + $token = $I->apiLogin(); $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST(Data::$userGetUrl, Data::userGetJson($user->get('usr_id'))); + $I->sendGET(Data::$usersUrl . '/' . $record->get('id')); $I->deleteHeader('Authorization'); $I->seeResponseIsSuccessful(); $I->seeSuccessJsonResponse( + 'data', [ - [ - 'id' => $user->get('usr_id'), - 'status' => $user->get('usr_status_flag'), - 'username' => $user->get('usr_username'), - 'issuer' => $user->get('usr_issuer'), - 'tokenPassword' => $user->get('usr_token_password'), - 'tokenId' => $user->get('usr_token_id'), - ], + Data::userResponse($record), ] ); } - private function addRecord(ApiTester $I) + public function getManyUsers(ApiTester $I) { - return $I->haveRecordWithFields( + $userOne = $I->haveRecordWithFields( + Users::class, + [ + 'status' => 1, + 'username' => 'testuser', + 'password' => 'testpassword', + 'issuer' => 'https://niden.net', + 'tokenPassword' => '12345', + 'tokenId' => '110011', + ] + ); + + $userTwo = $I->haveRecordWithFields( Users::class, [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '12345', - 'usr_token_id' => '110011', + 'status' => 1, + 'username' => 'testuser1', + 'password' => 'testpassword1', + 'issuer' => 'https://niden.net', + 'tokenPassword' => '789789', + 'tokenId' => '001100', ] ); + + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$usersUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); + $I->seeSuccessJsonResponse( + 'data', + [ + Data::userResponse($userOne), + Data::userResponse($userTwo), + ] + ); + } + + public function getManyUsersWithNoData(ApiTester $I) + { + $I->addApiUserRecord(); + $token = $I->apiLogin(); + + $I->haveHttpHeader('Authorization', 'Bearer ' . $token); + $I->sendGET(Data::$usersUrl); + $I->deleteHeader('Authorization'); + $I->seeResponseIsSuccessful(); } } diff --git a/tests/api/Users/UsersCest.php b/tests/api/Users/UsersCest.php deleted file mode 100644 index 9b1c1e62..00000000 --- a/tests/api/Users/UsersCest.php +++ /dev/null @@ -1,86 +0,0 @@ -haveRecordWithFields( - Users::class, - [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '12345', - 'usr_token_id' => '110011', - ] - ); - - $userTwo = $I->haveRecordWithFields( - Users::class, - [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser1', - 'usr_password' => 'testpassword1', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '789789', - 'usr_token_id' => '001100', - ] - ); - - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST(Data::$usersGetUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - $I->seeSuccessJsonResponse( - [ - [ - 'id' => $userOne->get('usr_id'), - 'status' => $userOne->get('usr_status_flag'), - 'username' => $userOne->get('usr_username'), - 'issuer' => $userOne->get('usr_issuer'), - 'tokenPassword' => $userOne->get('usr_token_password'), - 'tokenId' => $userOne->get('usr_token_id'), - ], - [ - 'id' => $userTwo->get('usr_id'), - 'status' => $userTwo->get('usr_status_flag'), - 'username' => $userTwo->get('usr_username'), - 'issuer' => $userTwo->get('usr_issuer'), - 'tokenPassword' => $userTwo->get('usr_token_password'), - 'tokenId' => $userTwo->get('usr_token_id'), - ], - ] - ); - } - - public function getManyUsersWithNoData(ApiTester $I) - { - $userOne = $I->haveRecordWithFields( - Users::class, - [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '12345', - 'usr_token_id' => '110011', - ] - ); - - $token = $I->apiLogin(); - - $I->haveHttpHeader('Authorization', 'Bearer ' . $token); - $I->sendPOST(Data::$usersGetUrl); - $I->deleteHeader('Authorization'); - $I->seeResponseIsSuccessful(); - } -} diff --git a/tests/cli/CheckHelpTaskCest.php b/tests/cli/CheckHelpTaskCest.php index f1677905..f1f8350f 100644 --- a/tests/cli/CheckHelpTaskCest.php +++ b/tests/cli/CheckHelpTaskCest.php @@ -2,7 +2,7 @@ namespace Niden\Tests\cli; -use \CliTester; +use CliTester; class CheckHelpTaskCest { diff --git a/tests/integration/library/ModelCest.php b/tests/integration/library/ModelCest.php index e65c1cf7..fbe7b9b8 100644 --- a/tests/integration/library/ModelCest.php +++ b/tests/integration/library/ModelCest.php @@ -2,44 +2,19 @@ namespace Niden\Tests\integration\library; -use function Niden\Core\appPath; use Codeception\Stub; -use \IntegrationTester; -use Niden\Logger; -use Niden\Models\Users; +use IntegrationTester; +use Monolog\Logger; use Niden\Exception\ModelException; +use Niden\Models\Users; use Phalcon\Mvc\Model\Message; +use function Niden\Core\appPath; /** * Class ModelCest */ class ModelCest { - /** - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function modelGetTablePrefix(IntegrationTester $I) - { - /** @var Users $result */ - $user = $I->haveRecordWithFields( - Users::class, - [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_password' => '12345', - 'usr_token_id' => '00110011', - - ] - ); - - $I->assertEquals('usr', $user->getTablePrefix()); - } - /** * @param IntegrationTester $I * @@ -47,20 +22,17 @@ public function modelGetTablePrefix(IntegrationTester $I) */ public function modelGetSetFields(IntegrationTester $I) { - $user = $I->haveRecordWithFields( + $I->haveRecordWithFields( Users::class, [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_password' => '12345', - 'usr_token_id' => '00110011', + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', ] ); - - $I->assertEquals(1000, $user->get('usr_id')); } /** @@ -74,7 +46,7 @@ public function modelSetNonExistingFields(IntegrationTester $I) ModelException::class, function () { $fixture = new Users(); - $fixture->set('usr_id', 1000) + $fixture->set('id', 1000) ->set('some_field', true) ->save() ; @@ -93,14 +65,12 @@ public function modelGetNonExistingFields(IntegrationTester $I) $user = $I->haveRecordWithFields( Users::class, [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_password' => '12345', - 'usr_token_id' => '00110011', - + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', ] ); @@ -125,26 +95,24 @@ public function modelUpdateFields(IntegrationTester $I) $user = $I->haveRecordWithFields( Users::class, [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_password' => '12345', - 'usr_token_id' => '00110011', - + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', ] ); - $user->set('usr_username', 'testusername') + $user->set('username', 'testusername') ->save() ; - $I->assertEquals($user->get('usr_username'), 'testusername'); - $I->assertEquals($user->get('usr_password'), 'testpass'); - $I->assertEquals($user->get('usr_issuer'), 'phalconphp.com'); - $I->assertEquals($user->get('usr_token_password'), '12345'); - $I->assertEquals($user->get('usr_token_id'), '00110011'); + $I->assertEquals($user->get('username'), 'testusername'); + $I->assertEquals($user->get('password'), 'testpass'); + $I->assertEquals($user->get('issuer'), 'phalconphp.com'); + $I->assertEquals($user->get('tokenPassword'), '12345'); + $I->assertEquals($user->get('tokenId'), '00110011'); } /** @@ -158,27 +126,26 @@ public function modelUpdateFieldsNotSanitized(IntegrationTester $I) $user = $I->haveRecordWithFields( Users::class, [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_password' => '12345', - 'usr_token_id' => '00110011', + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenPassword' => '12345', + 'tokenId' => '00110011', ] ); - $user->set('usr_password', 'abcde\nfg') + $user->set('password', 'abcde\nfg') ->save() ; - $I->assertEquals($user->get('usr_password'), 'abcde\nfg'); + $I->assertEquals($user->get('password'), 'abcde\nfg'); /** Not sanitized */ - $user->set('usr_password', 'abcde\nfg', false) + $user->set('password', 'abcde\nfg', false) ->save() ; - $I->assertEquals($user->get('usr_password'), 'abcde\nfg'); + $I->assertEquals($user->get('password'), 'abcde\nfg'); } /** @@ -199,8 +166,9 @@ public function checkModelMessages(IntegrationTester $I) ); $result = $user - ->set('usr_username', 'test') - ->save(); + ->set('username', 'test') + ->save() + ; $I->assertFalse($result); $I->assertEquals('error 1
error 2
', $user->getModelMessages()); @@ -227,8 +195,9 @@ public function checkModelMessagesWithLogger(IntegrationTester $I) $fileName = appPath('storage/logs/api.log'); $result = $user - ->set('usr_username', 'test') - ->save(); + ->set('username', 'test') + ->save() + ; $I->assertFalse($result); $I->assertEquals('error 1
error 2
', $user->getModelMessages()); diff --git a/tests/integration/library/Models/CompaniesCest.php b/tests/integration/library/Models/CompaniesCest.php new file mode 100644 index 00000000..5c2ebf81 --- /dev/null +++ b/tests/integration/library/Models/CompaniesCest.php @@ -0,0 +1,79 @@ +haveModelDefinition( + Companies::class, + [ + 'id', + 'name', + 'address', + 'city', + 'phone', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new Companies(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'address' => Filter::FILTER_STRING, + 'city' => Filter::FILTER_STRING, + 'phone' => Filter::FILTER_STRING, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(Companies::class); + $expected = [ + [2, 'id', Individuals::class, 'companyId', ['alias' => Relationships::INDIVIDUALS, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } + + public function validateUniqueName(IntegrationTester $I) + { + $companyOne = new Companies(); + /** @var Companies $companyOne */ + $result = $companyOne + ->set('name', 'acme') + ->set('address', '123 Phalcon way') + ->set('city', 'World') + ->set('phone', '555-999-4444') + ->save() + ; + $I->assertNotEquals(false, $result); + + $companyTwo = new Companies(); + /** @var Companies $companyTwo */ + $result = $companyTwo + ->set('name', 'acme') + ->set('address', '123 Phalcon way') + ->set('city', 'World') + ->set('phone', '555-999-4444') + ->save() + ; + $I->assertEquals(false, $result); + $I->assertEquals(1, count($companyTwo->getMessages())); + + $messages = $companyTwo->getMessages(); + $I->assertEquals('The company name already exists in the database', $messages[0]->getMessage()); + $result = $companyOne->delete(); + $I->assertNotEquals(false, $result); + } +} diff --git a/tests/integration/library/Models/CompaniesXProductsCest.php b/tests/integration/library/Models/CompaniesXProductsCest.php new file mode 100644 index 00000000..989d513d --- /dev/null +++ b/tests/integration/library/Models/CompaniesXProductsCest.php @@ -0,0 +1,44 @@ +haveModelDefinition( + CompaniesXProducts::class, + [ + 'companyId', + 'productId', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new CompaniesXProducts(); + $expected = [ + 'companyId' => Filter::FILTER_ABSINT, + 'productId' => Filter::FILTER_ABSINT, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(CompaniesXProducts::class); + $expected = [ + [0, 'companyId', Companies::class, 'id', ['alias' => Relationships::COMPANIES, 'reusable' => true]], + [0, 'productId', Products::class, 'id', ['alias' => Relationships::PRODUCTS, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } +} diff --git a/tests/integration/library/Models/IndividualTypesCest.php b/tests/integration/library/Models/IndividualTypesCest.php new file mode 100644 index 00000000..9f8e86c2 --- /dev/null +++ b/tests/integration/library/Models/IndividualTypesCest.php @@ -0,0 +1,44 @@ +haveModelDefinition( + IndividualTypes::class, + [ + 'id', + 'name', + 'description', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new IndividualTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(IndividualTypes::class); + $expected = [ + [2, 'id', Individuals::class, 'typeId', ['alias' => Relationships::INDIVIDUALS, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } +} diff --git a/tests/integration/library/Models/IndividualsCest.php b/tests/integration/library/Models/IndividualsCest.php new file mode 100644 index 00000000..6e767a0d --- /dev/null +++ b/tests/integration/library/Models/IndividualsCest.php @@ -0,0 +1,56 @@ +haveModelDefinition( + Individuals::class, + [ + 'id', + 'companyId', + 'typeId', + 'prefix', + 'first', + 'middle', + 'last', + 'suffix', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new Individuals(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'companyId' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'prefix' => Filter::FILTER_STRING, + 'first' => Filter::FILTER_STRING, + 'middle' => Filter::FILTER_STRING, + 'last' => Filter::FILTER_STRING, + 'suffix' => Filter::FILTER_STRING, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(Individuals::class); + $expected = [ + [0, 'companyId', Companies::class, 'id', ['alias' => Relationships::COMPANIES, 'reusable' => true]], + [1, 'typeId', IndividualTypes::class, 'id', ['alias' => Relationships::INDIVIDUAL_TYPES, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } +} diff --git a/tests/integration/library/Models/ProductTypesCest.php b/tests/integration/library/Models/ProductTypesCest.php new file mode 100644 index 00000000..ded868bd --- /dev/null +++ b/tests/integration/library/Models/ProductTypesCest.php @@ -0,0 +1,44 @@ +haveModelDefinition( + ProductTypes::class, + [ + 'id', + 'name', + 'description', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new ProductTypes(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(ProductTypes::class); + $expected = [ + [2, 'id', Products::class, 'typeId', ['alias' => Relationships::PRODUCTS, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } +} diff --git a/tests/integration/library/Models/ProductsCest.php b/tests/integration/library/Models/ProductsCest.php new file mode 100644 index 00000000..acafcdb6 --- /dev/null +++ b/tests/integration/library/Models/ProductsCest.php @@ -0,0 +1,50 @@ +haveModelDefinition( + Products::class, + [ + 'id', + 'typeId', + 'name', + 'description', + 'quantity', + 'price', + ] + ); + } + + public function validateFilters(IntegrationTester $I) + { + $model = new Products(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'typeId' => Filter::FILTER_ABSINT, + 'name' => Filter::FILTER_STRING, + 'description' => Filter::FILTER_STRING, + 'quantity' => Filter::FILTER_ABSINT, + 'price' => Filter::FILTER_FLOAT, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(Products::class); + $expected = [ + [0, 'typeId', ProductTypes::class, 'id', ['alias' => Relationships::PRODUCT_TYPES, 'reusable' => true]], + ]; + $I->assertEquals($expected, $actual); + } +} diff --git a/tests/integration/library/Models/UsersCest.php b/tests/integration/library/Models/UsersCest.php index 11489517..d7e45f49 100644 --- a/tests/integration/library/Models/UsersCest.php +++ b/tests/integration/library/Models/UsersCest.php @@ -6,6 +6,7 @@ use Lcobucci\JWT\ValidationData; use Niden\Models\Users; use Niden\Traits\TokenTrait; +use Phalcon\Filter; class UsersCest { @@ -16,29 +17,44 @@ public function validateModel(IntegrationTester $I) $I->haveModelDefinition( Users::class, [ - 'usr_id', - 'usr_status_flag', - 'usr_username', - 'usr_password', - 'usr_issuer', - 'usr_token_password', - 'usr_token_id', + 'id', + 'status', + 'username', + 'password', + 'issuer', + 'tokenPassword', + 'tokenId', ] ); } + public function validateFilters(IntegrationTester $I) + { + $model = new Users(); + $expected = [ + 'id' => Filter::FILTER_ABSINT, + 'status' => Filter::FILTER_ABSINT, + 'username' => Filter::FILTER_STRING, + 'password' => Filter::FILTER_STRING, + 'issuer' => Filter::FILTER_STRING, + 'tokenPassword' => Filter::FILTER_STRING, + 'tokenId' => Filter::FILTER_STRING, + ]; + $I->assertEquals($expected, $model->getModelFilters()); + } + public function checkValidationData(IntegrationTester $I) { /** @var Users $user */ $user = $I->haveRecordWithFields( Users::class, [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '12345', - 'usr_token_id' => '110011', + 'username' => 'testuser', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'https://niden.net', + 'tokenPassword' => '12345', + 'tokenId' => '110011', ] ); @@ -50,4 +66,10 @@ public function checkValidationData(IntegrationTester $I) $I->assertEquals($validationData, $user->getValidationData()); } + + public function validateRelationships(IntegrationTester $I) + { + $actual = $I->getModelRelationships(Users::class); + $I->assertEquals(0, count($actual)); + } } diff --git a/tests/integration/library/Traits/QueryCest.php b/tests/integration/library/Traits/QueryCest.php new file mode 100644 index 00000000..6d19fc83 --- /dev/null +++ b/tests/integration/library/Traits/QueryCest.php @@ -0,0 +1,172 @@ +haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenId' => '00110011', + ] + ); + + /** @var Libmemcached $cache */ + $cache = $I->grabFromDi('cache'); + /** @var Config $config */ + $config = $I->grabFromDi('config'); + $dbUser = $this->getUserByUsernameAndPassword($config, $cache, 'testusername', 'testpass'); + + $I->assertNotEquals(false, $dbUser); + } + + /** + * @param IntegrationTester $I + * + * @throws ModelException + */ + public function checkGetUserByWrongUsernameAndPasswordReturnsFalse(IntegrationTester $I) + { + /** @var Users $result */ + $I->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenId' => '00110011', + ] + ); + + /** @var Libmemcached $cache */ + $cache = $I->grabFromDi('cache'); + /** @var Config $config */ + $config = $I->grabFromDi('config'); + $I->assertFalse($this->getUserByUsernameAndPassword($config, $cache, 'testusername', 'nothing')); + } + + /** + * @param IntegrationTester $I + * + * @throws ModelException + */ + public function checkGetUserByWrongTokenReturnsFalse(IntegrationTester $I) + { + /** @var Users $result */ + $I->haveRecordWithFields( + Users::class, + [ + 'username' => 'testusername', + 'password' => 'testpass', + 'status' => 1, + 'issuer' => 'phalconphp.com', + 'tokenId' => '00110011', + ] + ); + + $signer = new Sha512(); + $builder = new Builder(); + $token = $builder + ->setIssuer('https://somedomain.com') + ->setAudience($this->getTokenAudience()) + ->setId('123456', true) + ->sign($signer, '110011') + ->getToken() + ; + + /** @var Libmemcached $cache */ + $cache = $I->grabFromDi('cache'); + /** @var Config $config */ + $config = $I->grabFromDi('config'); + $I->assertFalse($this->getUserByToken($config, $cache, $token)); + } + + public function getCompaniesCachedData(IntegrationTester $I) + { + $configData = require appPath('./library/Core/config.php'); + $I->assertTrue($configData['app']['devMode']); + + $configData['app']['devMode'] = false; + /** @var Config $config */ + $config = new Config($configData); + $container = $I->grabDi(); + $container->set('config', $config); + $I->assertFalse($config->path('app.devMode')); + + /** @var Libmemcached $cache */ + $cache = $I->grabFromDi('cache'); + /** @var Config $config */ + $config = $I->grabFromDi('config'); + $I->assertFalse($config->path('app.devMode')); + + /** + * Company 1 + */ + $comName = uniqid('com-cached-'); + $comOne = $I->haveRecordWithFields( + Companies::class, + [ + 'name' => $comName, + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + $results = $this->getRecords($config, $cache, Companies::class); + $I->assertEquals(1, count($results)); + $I->assertEquals($comName, $results[0]->get('name')); + $I->assertEquals($comOne->get('address'), $results[0]->get('address')); + $I->assertEquals($comOne->get('city'), $results[0]->get('city')); + $I->assertEquals($comOne->get('phone'), $results[0]->get('phone')); + + /** + * Get the record again but ensure the name has been changed + */ + $result = $comOne->set('name', 'com-cached-change')->save(); + $I->assertNotEquals(false, $result); + + /** + * This should return the cached result + */ + $results = $this->getRecords($config, $cache, Companies::class); + $I->assertEquals(1, count($results)); + $I->assertEquals($comName, $results[0]->get('name')); + $I->assertEquals($comOne->get('address'), $results[0]->get('address')); + $I->assertEquals($comOne->get('city'), $results[0]->get('city')); + $I->assertEquals($comOne->get('phone'), $results[0]->get('phone')); + + + } +} diff --git a/tests/integration/library/Traits/UsersCest.php b/tests/integration/library/Traits/UsersCest.php deleted file mode 100644 index 30d4b871..00000000 --- a/tests/integration/library/Traits/UsersCest.php +++ /dev/null @@ -1,103 +0,0 @@ -haveRecordWithFields( - Users::class, - [ - 'usr_id' => 1000, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_id' => '00110011', - - ] - ); - - $dbUser = $this->getUserByUsernameAndPassword('testusername', 'testpass'); - - $I->assertEquals(1000, $dbUser->get('usr_id')); - } - - /** - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function checkGetUserByWrongUsernameAndPasswordReturnsFalse(IntegrationTester $I) - { - /** @var Users $result */ - $I->haveRecordWithFields( - Users::class, - [ - 'usr_id' => 1001, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_id' => '00110011', - - ] - ); - - $I->assertFalse($this->getUserByUsernameAndPassword('testusername', 'nothing')); - } - - /** - * @param IntegrationTester $I - * - * @throws ModelException - */ - public function checkGetUserByWrongTokenReturnsFalse(IntegrationTester $I) - { - /** @var Users $result */ - $I->haveRecordWithFields( - Users::class, - [ - 'usr_id' => 1003, - 'usr_username' => 'testusername', - 'usr_password' => 'testpass', - 'usr_status_flag' => 1, - 'usr_issuer' => 'phalconphp.com', - 'usr_token_id' => '00110011', - - ] - ); - - $signer = new Sha512(); - $builder = new Builder(); - $token = $builder - ->setIssuer('https://somedomain.com') - ->setAudience($this->getTokenAudience()) - ->setId('123456', true) - ->sign($signer, '110011') - ->getToken(); - - $I->assertFalse($this->getUserByToken($token)); - } -} diff --git a/tests/integration/library/Transformers/BaseTransformerCest.php b/tests/integration/library/Transformers/BaseTransformerCest.php new file mode 100644 index 00000000..0d7093a0 --- /dev/null +++ b/tests/integration/library/Transformers/BaseTransformerCest.php @@ -0,0 +1,40 @@ +haveRecordWithFields( + Companies::class, + [ + 'name' => 'acme', + 'address' => '123 Phalcon way', + 'city' => 'World', + 'phone' => '555-999-4444', + ] + ); + + $transformer = new BaseTransformer(); + $expected = [ + 'id' => $company->get('id'), + 'name' => $company->get('name'), + 'address' => $company->get('address'), + 'city' => $company->get('city'), + 'phone' => $company->get('phone'), + ]; + + $I->assertEquals($expected, $transformer->transform($company)); + } +} diff --git a/tests/integration/library/Transformers/IndividualsTransformerCest.php b/tests/integration/library/Transformers/IndividualsTransformerCest.php new file mode 100644 index 00000000..e6bd9e9a --- /dev/null +++ b/tests/integration/library/Transformers/IndividualsTransformerCest.php @@ -0,0 +1,145 @@ +haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid('com-a-'), + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + /** @var IndividualTypes $individualType */ + $individualType = $I->haveRecordWithFields( + IndividualTypes::class, + [ + 'name' => 'my type', + 'description' => 'description of my type', + ] + ); + + /** @var Individuals $individual */ + $individual = $I->haveRecordWithFields( + Individuals::class, + [ + 'companyId' => $company->get('id'), + 'typeId' => $individualType->get('id'), + 'prefix' => uniqid(), + 'first' => uniqid('first-'), + 'middle' => uniqid(), + 'last' => uniqid('last-'), + 'suffix' => uniqid(), + ] + ); + + $url = envValue('APP_URL', 'http://localhost'); + $manager = new Manager(); + $manager->setSerializer(new JsonApiSerializer($url)); + $manager->parseIncludes([Relationships::COMPANIES, Relationships::INDIVIDUAL_TYPES]); + $resource = new Collection([$individual], new IndividualsTransformer(), Relationships::INDIVIDUALS); + $results = $manager->createData($resource)->toArray(); + $expected = [ + 'data' => [ + [ + 'type' => Relationships::INDIVIDUALS, + 'id' => $individual->get('id'), + 'attributes' => [ + 'companyId' => $individual->get('companyId'), + 'typeId' => $individual->get('typeId'), + 'prefix' => $individual->get('prefix'), + 'first' => $individual->get('first'), + 'middle' => $individual->get('middle'), + 'last' => $individual->get('last'), + 'suffix' => $individual->get('suffix'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + 'type' => Relationships::COMPANIES, + 'id' => $company->get('id'), + ], + ], + Relationships::INDIVIDUAL_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::INDIVIDUALS, + $individual->get('id'), + Relationships::INDIVIDUAL_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::INDIVIDUAL_TYPES, + 'id' => $individualType->get('id'), + ], + ], + ], + ], + ], + 'included' => [ + Data::companyResponse($company), + Data::individualTypeResponse($individualType), + ], + ]; + + $I->assertEquals($expected, $results); + } +} diff --git a/tests/integration/library/Transformers/ProductsTransformerCest.php b/tests/integration/library/Transformers/ProductsTransformerCest.php new file mode 100644 index 00000000..518ac886 --- /dev/null +++ b/tests/integration/library/Transformers/ProductsTransformerCest.php @@ -0,0 +1,152 @@ +haveRecordWithFields( + Companies::class, + [ + 'name' => uniqid('com-a-'), + 'address' => uniqid(), + 'city' => uniqid(), + 'phone' => uniqid(), + ] + ); + + /** @var ProductTypes $productType */ + $productType = $I->haveRecordWithFields( + ProductTypes::class, + [ + 'name' => 'my type', + 'description' => 'description of my type', + ] + ); + + /** @var Products $product */ + $product = $I->haveRecordWithFields( + Products::class, + [ + 'name' => 'my product', + 'typeId' => $productType->get('id'), + 'description' => 'my product description', + 'quantity' => 99, + 'price' => 19.99, + ] + ); + + /** @var CompaniesXProducts $glue */ + $glue = $I->haveRecordWithFields( + CompaniesXProducts::class, + [ + 'companyId' => $company->get('id'), + 'productId' => $product->get('id'), + ] + ); + + $url = envValue('APP_URL', 'http://localhost'); + $manager = new Manager(); + $manager->setSerializer(new JsonApiSerializer($url)); + $manager->parseIncludes([Relationships::COMPANIES, Relationships::PRODUCT_TYPES]); + $resource = new Collection([$product], new ProductsTransformer(), Relationships::PRODUCTS); + $results = $manager->createData($resource)->toArray(); + $expected = [ + 'data' => [ + [ + 'type' => Relationships::PRODUCTS, + 'id' => $product->get('id'), + 'attributes' => [ + 'typeId' => $productType->get('id'), + 'name' => $product->get('name'), + 'description' => $product->get('description'), + 'quantity' => $product->get('quantity'), + 'price' => $product->get('price'), + ], + 'links' => [ + 'self' => sprintf( + '%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id') + ), + ], + 'relationships' => [ + Relationships::COMPANIES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::COMPANIES + ), + ], + 'data' => [ + [ + 'type' => Relationships::COMPANIES, + 'id' => $company->get('id'), + ], + ], + ], + Relationships::PRODUCT_TYPES => [ + 'links' => [ + 'self' => sprintf( + '%s/%s/%s/relationships/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + 'related' => sprintf( + '%s/%s/%s/%s', + $url, + Relationships::PRODUCTS, + $product->get('id'), + Relationships::PRODUCT_TYPES + ), + ], + 'data' => [ + 'type' => Relationships::PRODUCT_TYPES, + 'id' => $productType->get('id'), + ], + ], + ], + ], + ], + 'included' => [ + Data::companyResponse($company), + Data::productTypeResponse($productType), + ], + ]; + + $I->assertEquals($expected, $results); + } +} diff --git a/tests/integration/library/Transformers/UsersTransformerCest.php b/tests/integration/library/Transformers/UsersTransformerCest.php deleted file mode 100644 index 85f0b8ce..00000000 --- a/tests/integration/library/Transformers/UsersTransformerCest.php +++ /dev/null @@ -1,43 +0,0 @@ -haveRecordWithFields( - Users::class, - [ - 'usr_status_flag' => 1, - 'usr_username' => 'testuser', - 'usr_password' => 'testpassword', - 'usr_issuer' => 'https://niden.net', - 'usr_token_password' => '12345', - 'usr_token_id' => '110011', - ] - ); - - $transformer = new UsersTransformer(); - $expected = [ - 'id' => $user->get('usr_id'), - 'status' => $user->get('usr_status_flag'), - 'username' => $user->get('usr_username'), - 'issuer' => $user->get('usr_issuer'), - 'tokenPassword' => $user->get('usr_token_password'), - 'tokenId' => $user->get('usr_token_id'), - ]; - - $I->assertEquals($expected, $transformer->transform($user)); - } -} diff --git a/tests/integration/library/Validation/CompaniesValidatorCest.php b/tests/integration/library/Validation/CompaniesValidatorCest.php new file mode 100644 index 00000000..69e0e681 --- /dev/null +++ b/tests/integration/library/Validation/CompaniesValidatorCest.php @@ -0,0 +1,28 @@ + '', + 'address' => '123 Phalcon way', + 'city' => 'World', + 'phone' => '555-999-4444', + ]; + $messages = $validation->validate($_POST); + $I->assertEquals(1, count($messages)); + $I->assertEquals('The company name is required', $messages[0]->getMessage()); + } +} diff --git a/tests/unit/BootstrapCest.php b/tests/unit/BootstrapCest.php index adf82bed..6a49c12e 100644 --- a/tests/unit/BootstrapCest.php +++ b/tests/unit/BootstrapCest.php @@ -2,7 +2,7 @@ namespace Niden\Tests\unit; -use \CliTester; +use CliTester; use function Niden\Core\appPath; class BootstrapCest @@ -17,6 +17,6 @@ public function checkBootstrap(CliTester $I) $results = json_decode($actual, true); $I->assertEquals('1.0', $results['jsonapi']['version']); $I->assertEmpty($results['data']); - $I->assertEquals('404 Not Found', $results['errors'][0]); + $I->assertEquals('Not Found', $results['errors'][0]); } } diff --git a/tests/unit/cli/BaseCest.php b/tests/unit/cli/BaseCest.php index 961d5b12..03fbaccc 100755 --- a/tests/unit/cli/BaseCest.php +++ b/tests/unit/cli/BaseCest.php @@ -2,12 +2,12 @@ namespace Niden\Tests\unit\cli; +use Niden\Cli\Tasks\MainTask; +use Phalcon\Di\FactoryDefault\Cli; +use UnitTester; use function ob_end_clean; use function ob_get_contents; use function ob_start; -use Niden\Cli\Tasks\MainTask; -use Phalcon\Di\FactoryDefault\Cli; -use \UnitTester; class BaseCest { diff --git a/tests/unit/cli/BootstrapCest.php b/tests/unit/cli/BootstrapCest.php index bdbefd97..cdb6e691 100644 --- a/tests/unit/cli/BootstrapCest.php +++ b/tests/unit/cli/BootstrapCest.php @@ -2,7 +2,7 @@ namespace Niden\Tests\unit\cli; -use \CliTester; +use CliTester; use function Niden\Core\appPath; class BootstrapCest diff --git a/tests/unit/cli/ClearCacheCest.php b/tests/unit/cli/ClearCacheCest.php index 449348d6..292ad738 100755 --- a/tests/unit/cli/ClearCacheCest.php +++ b/tests/unit/cli/ClearCacheCest.php @@ -2,6 +2,11 @@ namespace Niden\Tests\unit\cli; +use FilesystemIterator; +use Niden\Cli\Tasks\ClearcacheTask; +use Niden\Providers\CacheDataProvider; +use Phalcon\Di\FactoryDefault\Cli; +use UnitTester; use function fclose; use function iterator_count; use function Niden\Core\appPath; @@ -9,10 +14,6 @@ use function ob_get_contents; use function ob_start; use function uniqid; -use FilesystemIterator; -use Niden\Cli\Tasks\ClearcacheTask; -use Phalcon\Di\FactoryDefault\Cli; -use \UnitTester; class ClearCacheCest { @@ -22,6 +23,8 @@ public function checkClearCache(UnitTester $I) $path = appPath('/storage/cache/data'); $container = new Cli(); + $cache = new CacheDataProvider(); + $cache->register($container); $task = new ClearcacheTask(); $task->setDI($container); @@ -50,7 +53,7 @@ public function checkClearCache(UnitTester $I) private function createFile() { - $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; + $name = appPath('/storage/cache/data/') . uniqid('tmp_') . '.cache'; $pointer = fopen($name, 'wb'); fwrite($pointer, 'test'); fclose($pointer); diff --git a/tests/unit/config/AutoloaderCest.php b/tests/unit/config/AutoloaderCest.php index 5c5f20e7..860abc1b 100755 --- a/tests/unit/config/AutoloaderCest.php +++ b/tests/unit/config/AutoloaderCest.php @@ -22,9 +22,6 @@ public function checkDotenvVariables(UnitTester $I) $I->assertNotEquals(false, getenv('APP_TIMEZONE')); $I->assertNotEquals(false, getenv('CACHE_PREFIX')); $I->assertNotEquals(false, getenv('CACHE_LIFETIME')); - $I->assertNotEquals(false, getenv('DATA_API_MEMCACHED_HOST')); - $I->assertNotEquals(false, getenv('DATA_API_MEMCACHED_PORT')); - $I->assertNotEquals(false, getenv('DATA_API_MEMCACHED_WEIGHT')); $I->assertNotEquals(false, getenv('DATA_API_MYSQL_NAME')); $I->assertNotEquals(false, getenv('LOGGER_DEFAULT_FILENAME')); $I->assertNotEquals(false, getenv('VERSION')); @@ -37,9 +34,6 @@ public function checkDotenvVariables(UnitTester $I) $I->assertEquals('UTC', getenv('APP_TIMEZONE')); $I->assertEquals('api_cache_', getenv('CACHE_PREFIX')); $I->assertEquals(86400, getenv('CACHE_LIFETIME')); - $I->assertEquals(11211, getenv('DATA_API_MEMCACHED_PORT')); - $I->assertEquals(100, getenv('DATA_API_MEMCACHED_WEIGHT')); - $I->assertEquals('gonano', getenv('DATA_API_MYSQL_NAME')); $I->assertEquals('api', getenv('LOGGER_DEFAULT_FILENAME')); $I->assertEquals('20180401', getenv('VERSION')); } diff --git a/tests/unit/config/ConfigCest.php b/tests/unit/config/ConfigCest.php index d8ef5d85..eaaf279d 100755 --- a/tests/unit/config/ConfigCest.php +++ b/tests/unit/config/ConfigCest.php @@ -2,9 +2,9 @@ namespace Niden\Tests\unit\config; +use UnitTester; use function is_array; use function Niden\Core\appPath; -use \UnitTester; class ConfigCest { diff --git a/tests/unit/config/FunctionsCest.php b/tests/unit/config/FunctionsCest.php index 41f12b8f..8e30da4f 100755 --- a/tests/unit/config/FunctionsCest.php +++ b/tests/unit/config/FunctionsCest.php @@ -2,9 +2,9 @@ namespace Niden\Tests\unit\config; +use UnitTester; use function Niden\Core\appPath; use function Niden\Core\envValue; -use \UnitTester; class FunctionsCest { diff --git a/tests/unit/config/ProvidersCest.php b/tests/unit/config/ProvidersCest.php index e313ed26..bd9efded 100755 --- a/tests/unit/config/ProvidersCest.php +++ b/tests/unit/config/ProvidersCest.php @@ -2,7 +2,6 @@ namespace Niden\Tests\unit\config; -use function Niden\Core\appPath; use Niden\Providers\CliDispatcherProvider; use Niden\Providers\ConfigProvider; use Niden\Providers\DatabaseProvider; @@ -12,7 +11,8 @@ use Niden\Providers\RequestProvider; use Niden\Providers\ResponseProvider; use Niden\Providers\RouterProvider; -use \UnitTester; +use UnitTester; +use function Niden\Core\appPath; class ProvidersCest { diff --git a/tests/unit/library/BootstrapCest.php b/tests/unit/library/BootstrapCest.php index 4f578b5f..1b8eb283 100755 --- a/tests/unit/library/BootstrapCest.php +++ b/tests/unit/library/BootstrapCest.php @@ -6,7 +6,7 @@ use Niden\Http\Response; use Phalcon\Di\FactoryDefault; use Phalcon\Mvc\Micro; -use \UnitTester; +use UnitTester; class BootstrapCest { diff --git a/tests/unit/library/Constants/FlagsCest.php b/tests/unit/library/Constants/FlagsCest.php index 72e7fff5..3a68478f 100644 --- a/tests/unit/library/Constants/FlagsCest.php +++ b/tests/unit/library/Constants/FlagsCest.php @@ -2,7 +2,7 @@ namespace Niden\Tests\unit\library\Constants; -use \CliTester; +use CliTester; use Niden\Constants\Flags; class FlagsCest diff --git a/tests/unit/library/Constants/JWTClaimsCest.php b/tests/unit/library/Constants/JWTClaimsCest.php index 35d1df70..cb5c1c23 100644 --- a/tests/unit/library/Constants/JWTClaimsCest.php +++ b/tests/unit/library/Constants/JWTClaimsCest.php @@ -2,7 +2,7 @@ namespace Niden\Tests\unit\library\Constants; -use \CliTester; +use CliTester; use Niden\Constants\JWTClaims; class JWTClaimsCest diff --git a/tests/unit/library/Constants/RelationshipsCest.php b/tests/unit/library/Constants/RelationshipsCest.php new file mode 100644 index 00000000..559e8425 --- /dev/null +++ b/tests/unit/library/Constants/RelationshipsCest.php @@ -0,0 +1,19 @@ +assertEquals('companies', Relationships::COMPANIES); + $I->assertEquals('individual-types', Relationships::INDIVIDUAL_TYPES); + $I->assertEquals('individuals', Relationships::INDIVIDUALS); + $I->assertEquals('product-types', Relationships::PRODUCT_TYPES); + $I->assertEquals('products', Relationships::PRODUCTS); + $I->assertEquals('users', Relationships::USERS); + } +} diff --git a/tests/unit/library/ErrorHandlerCest.php b/tests/unit/library/ErrorHandlerCest.php index 6ecf9770..90431aac 100755 --- a/tests/unit/library/ErrorHandlerCest.php +++ b/tests/unit/library/ErrorHandlerCest.php @@ -2,16 +2,14 @@ namespace Niden\Tests\unit\library; -use const E_USER_NOTICE; use Niden\ErrorHandler; -use function Niden\Core\appPath; use Niden\Logger; use Niden\Providers\ConfigProvider; use Niden\Providers\LoggerProvider; use Phalcon\Config; use Phalcon\Di\FactoryDefault; -use function trigger_error; -use \UnitTester; +use UnitTester; +use function Niden\Core\appPath; class ErrorHandlerCest { @@ -20,11 +18,11 @@ public function logErrorOnError(UnitTester $I) $diContainer = new FactoryDefault(); $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new LoggerProvider(); + $provider = new LoggerProvider(); $provider->register($diContainer); /** @var Config $config */ - $config = $diContainer->getShared('config'); + $config = $diContainer->getShared('config'); /** @var Logger $logger */ $logger = $diContainer->getShared('logger'); $handler = new ErrorHandler($logger, $config); @@ -41,11 +39,11 @@ public function logErrorOnShutdown(UnitTester $I) $diContainer = new FactoryDefault(); $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new LoggerProvider(); + $provider = new LoggerProvider(); $provider->register($diContainer); /** @var Config $config */ - $config = $diContainer->getShared('config'); + $config = $diContainer->getShared('config'); /** @var Logger $logger */ $logger = $diContainer->getShared('logger'); $handler = new ErrorHandler($logger, $config); diff --git a/tests/unit/library/Http/ResponseCest.php b/tests/unit/library/Http/ResponseCest.php index 9fd6118d..377e8ad9 100755 --- a/tests/unit/library/Http/ResponseCest.php +++ b/tests/unit/library/Http/ResponseCest.php @@ -2,10 +2,13 @@ namespace Niden\Tests\unit\library\Http; +use Niden\Http\Response; +use Phalcon\Mvc\Model\Message as ModelMessage; +use Phalcon\Validation\Message as ValidationMessage; +use Phalcon\Validation\Message\Group as ValidationGroup; +use UnitTester; use function is_string; use function json_decode; -use Niden\Http\Response; -use \UnitTester; class ResponseCest { @@ -22,7 +25,25 @@ public function checkResponseWithStringPayload(UnitTester $I) $payload = $this->checkPayload($I, $response); $I->assertFalse(isset($payload['errors'])); - $I->assertEquals(['test'], $payload['data']); + $I->assertEquals('test', $payload['data']); + } + + private function checkPayload(UnitTester $I, Response $response, bool $error = false): array + { + $contents = $response->getContent(); + $I->assertTrue(is_string($contents)); + + $payload = json_decode($contents, true); + $I->assertEquals(3, count($payload)); + $I->assertTrue(isset($payload['jsonapi'])); + if (true === $error) { + $I->assertTrue(isset($payload['errors'])); + } else { + $I->assertTrue(isset($payload['data'])); + } + $I->assertTrue(isset($payload['meta'])); + + return $payload; } public function checkResponseWithArrayPayload(UnitTester $I) @@ -51,21 +72,41 @@ public function checkResponseWithErrorCode(UnitTester $I) $I->assertEquals('error content', $payload['errors'][0]); } - private function checkPayload(UnitTester $I, Response $response, bool $error = false): array + public function checkResponseWithModelErrors(UnitTester $I) { - $contents = $response->getContent(); - $I->assertTrue(is_string($contents)); + $messages = [ + new ModelMessage('hello'), + new ModelMessage('goodbye'), + ]; + $response = new Response(); + $response + ->setPayloadErrors($messages); - $payload = json_decode($contents, true); - $I->assertEquals(3, count($payload)); - $I->assertTrue(isset($payload['jsonapi'])); - if (true === $error) { - $I->assertTrue(isset($payload['errors'])); - } else { - $I->assertTrue(isset($payload['data'])); - } - $I->assertTrue(isset($payload['meta'])); + $payload = $this->checkPayload($I, $response, true); - return $payload; + $I->assertFalse(isset($payload['data'])); + $I->assertEquals(2, count($payload['errors'])); + $I->assertEquals('hello', $payload['errors'][0]); + $I->assertEquals('goodbye', $payload['errors'][1]); + } + + public function checkResponseWithValidationErrors(UnitTester $I) + { + $group = new ValidationGroup(); + $message = new ValidationMessage('hello'); + $group->appendMessage($message); + $message = new ValidationMessage('goodbye'); + $group->appendMessage($message); + + $response = new Response(); + $response + ->setPayloadErrors($group); + + $payload = $this->checkPayload($I, $response, true); + + $I->assertFalse(isset($payload['data'])); + $I->assertEquals(2, count($payload['errors'])); + $I->assertEquals('hello', $payload['errors'][0]); + $I->assertEquals('goodbye', $payload['errors'][1]); } } diff --git a/tests/unit/library/Providers/CacheCest.php b/tests/unit/library/Providers/CacheCest.php new file mode 100644 index 00000000..39e434d6 --- /dev/null +++ b/tests/unit/library/Providers/CacheCest.php @@ -0,0 +1,26 @@ +register($diContainer); + + $I->assertTrue($diContainer->has('cache')); + /** @var Libmemcached $cache */ + $cache = $diContainer->getShared('cache'); + $I->assertTrue($cache instanceof Libmemcached); + } +} diff --git a/tests/unit/library/Providers/ConfigCest.php b/tests/unit/library/Providers/ConfigCest.php index ae925030..db21ef5a 100644 --- a/tests/unit/library/Providers/ConfigCest.php +++ b/tests/unit/library/Providers/ConfigCest.php @@ -5,8 +5,7 @@ use Niden\Providers\ConfigProvider; use Phalcon\Config; use Phalcon\Di\FactoryDefault; - -use \UnitTester; +use UnitTester; class ConfigCest { diff --git a/tests/unit/library/Providers/DatabaseCest.php b/tests/unit/library/Providers/DatabaseCest.php index 7e79fd62..9459e1d2 100644 --- a/tests/unit/library/Providers/DatabaseCest.php +++ b/tests/unit/library/Providers/DatabaseCest.php @@ -6,7 +6,7 @@ use Niden\Providers\DatabaseProvider; use Phalcon\Db\Adapter\Pdo\Mysql; use Phalcon\Di\FactoryDefault; -use \UnitTester; +use UnitTester; class DatabaseCest { @@ -18,7 +18,7 @@ public function checkRegistration(UnitTester $I) $diContainer = new FactoryDefault(); $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new DatabaseProvider(); + $provider = new DatabaseProvider(); $provider->register($diContainer); $I->assertTrue($diContainer->has('db')); diff --git a/tests/unit/library/Providers/ErrorHandlerCest.php b/tests/unit/library/Providers/ErrorHandlerCest.php index ebd6763e..767560b1 100644 --- a/tests/unit/library/Providers/ErrorHandlerCest.php +++ b/tests/unit/library/Providers/ErrorHandlerCest.php @@ -2,16 +2,12 @@ namespace Niden\Tests\unit\library\Providers; -use function date_default_timezone_get; -use const E_NOTICE; -use const E_USER_NOTICE; -use function Niden\Core\appPath; use Niden\Providers\ConfigProvider; use Niden\Providers\ErrorHandlerProvider; use Niden\Providers\LoggerProvider; use Phalcon\Di\FactoryDefault; -use function trigger_error; -use \UnitTester; +use UnitTester; +use function date_default_timezone_get; class ErrorHandlerCest { @@ -23,9 +19,9 @@ public function checkRegistration(UnitTester $I) $diContainer = new FactoryDefault(); $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new LoggerProvider(); + $provider = new LoggerProvider(); $provider->register($diContainer); - $provider = new ErrorHandlerProvider(); + $provider = new ErrorHandlerProvider(); $provider->register($diContainer); $config = $diContainer->getShared('config'); diff --git a/tests/unit/library/Providers/EventsManagerCest.php b/tests/unit/library/Providers/EventsManagerCest.php index 8340eabf..0fe020b5 100644 --- a/tests/unit/library/Providers/EventsManagerCest.php +++ b/tests/unit/library/Providers/EventsManagerCest.php @@ -5,7 +5,7 @@ use Niden\Providers\EventsManagerProvider; use Phalcon\Di\FactoryDefault; use Phalcon\Events\Manager; -use \UnitTester; +use UnitTester; class EventsManagerCest { diff --git a/tests/unit/library/Providers/LoggerCest.php b/tests/unit/library/Providers/LoggerCest.php index 141939ac..1b65a691 100644 --- a/tests/unit/library/Providers/LoggerCest.php +++ b/tests/unit/library/Providers/LoggerCest.php @@ -6,7 +6,7 @@ use Niden\Providers\ConfigProvider; use Niden\Providers\LoggerProvider; use Phalcon\Di\FactoryDefault; -use \UnitTester; +use UnitTester; class LoggerCest { @@ -18,7 +18,7 @@ public function checkRegistration(UnitTester $I) $diContainer = new FactoryDefault(); $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new LoggerProvider(); + $provider = new LoggerProvider(); $provider->register($diContainer); $I->assertTrue($diContainer->has('logger')); diff --git a/tests/unit/library/Providers/ResponseCest.php b/tests/unit/library/Providers/ResponseCest.php index e5ab2257..d56506ae 100644 --- a/tests/unit/library/Providers/ResponseCest.php +++ b/tests/unit/library/Providers/ResponseCest.php @@ -5,7 +5,7 @@ use Niden\Http\Response; use Niden\Providers\ResponseProvider; use Phalcon\Di\FactoryDefault; -use \UnitTester; +use UnitTester; class ResponseCest { diff --git a/tests/unit/library/Providers/RouterCest.php b/tests/unit/library/Providers/RouterCest.php index d1910d43..2473993d 100644 --- a/tests/unit/library/Providers/RouterCest.php +++ b/tests/unit/library/Providers/RouterCest.php @@ -4,12 +4,11 @@ use Niden\Logger; use Niden\Providers\ConfigProvider; -use Niden\Providers\LoggerProvider; use Niden\Providers\RouterProvider; use Phalcon\Di\FactoryDefault; use Phalcon\Mvc\Micro; use Phalcon\Mvc\RouterInterface; -use \UnitTester; +use UnitTester; class RouterCest { @@ -21,20 +20,45 @@ public function checkRegistration(UnitTester $I) $diContainer = new FactoryDefault(); $application = new Micro($diContainer); $diContainer->setShared('application', $application); - $provider = new ConfigProvider(); + $provider = new ConfigProvider(); $provider->register($diContainer); - $provider = new RouterProvider(); + $provider = new RouterProvider(); $provider->register($diContainer); /** @var RouterInterface $router */ - $router = $application->getRouter(); - $routes = $router->getRoutes(); - $I->assertEquals(3, count($routes)); - $I->assertEquals('POST', $routes[0]->getHttpMethods()); - $I->assertEquals('/login', $routes[0]->getPattern()); - $I->assertEquals('POST', $routes[1]->getHttpMethods()); - $I->assertEquals('/user/get', $routes[1]->getPattern()); - $I->assertEquals('POST', $routes[2]->getHttpMethods()); - $I->assertEquals('/users/get', $routes[2]->getPattern()); + $router = $application->getRouter(); + $routes = $router->getRoutes(); + $expected = [ + ['POST', '/login'], + ['POST', '/companies'], + ['GET', '/users'], + ['GET', '/users/{recordId:[0-9]+}'], + ['GET', '/companies'], + ['GET', '/companies/{recordId:[0-9]+}'], + ['GET', '/companies/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/companies/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individuals'], + ['GET', '/individuals/{recordId:[0-9]+}'], + ['GET', '/individuals/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individuals/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individual-types'], + ['GET', '/individual-types/{recordId:[0-9]+}'], + ['GET', '/individual-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/individual-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/products'], + ['GET', '/products/{recordId:[0-9]+}'], + ['GET', '/products/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/products/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/product-types'], + ['GET', '/product-types/{recordId:[0-9]+}'], + ['GET', '/product-types/{recordId:[0-9]+}/{relationships:[a-zA-Z-,.]+}'], + ['GET', '/product-types/{recordId:[0-9]+}/relationships/{relationships:[a-zA-Z-,.]+}'], + ]; + + $I->assertEquals(24, count($routes)); + foreach ($routes as $index => $route) { + $I->assertEquals($expected[$index][0], $route->getHttpMethods()); + $I->assertEquals($expected[$index][1], $route->getPattern()); + } } }