diff --git a/src/Domain/ADR/Action/ActionHandler.php b/src/Domain/ADR/Action/ActionHandler.php index 2855f7d..29d604a 100644 --- a/src/Domain/ADR/Action/ActionHandler.php +++ b/src/Domain/ADR/Action/ActionHandler.php @@ -14,11 +14,16 @@ namespace Phalcon\Api\Domain\ADR\Action; use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\Input; use Phalcon\Api\Domain\ADR\Responder\ResponderInterface; +use Phalcon\Api\Domain\Services\Http\Response; +use Phalcon\Http\Request; final readonly class ActionHandler implements ActionInterface { public function __construct( + private Request $request, + private Response $response, private DomainInterface $service, private ResponderInterface $responder ) { @@ -26,8 +31,12 @@ public function __construct( public function __invoke(): void { + $input = new Input(); + $data = $input->__invoke($this->request); + $this->responder->__invoke( - $this->service->__invoke() + $this->response, + $this->service->__invoke($data) ); } } diff --git a/src/Domain/ADR/Domain/DomainInterface.php b/src/Domain/ADR/Domain/DomainInterface.php index b8747ff..d5c6a20 100644 --- a/src/Domain/ADR/Domain/DomainInterface.php +++ b/src/Domain/ADR/Domain/DomainInterface.php @@ -15,7 +15,16 @@ use Phalcon\Domain\Payload; +/** + * @phpstan-import-type THelloInput from InputTypes + * @phpstan-import-type TUserInput from InputTypes + */ interface DomainInterface { - public function __invoke(): Payload; + /** + * @param THelloInput|TUserInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload; } diff --git a/src/Domain/ADR/Domain/Input.php b/src/Domain/ADR/Domain/Input.php new file mode 100644 index 0000000..7d15456 --- /dev/null +++ b/src/Domain/ADR/Domain/Input.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +use Phalcon\Http\Request; + +/** + * @phpstan-import-type TRequestQuery from InputTypes + */ +final class Input implements InputInterface +{ + /** + * @param Request $request + * + * @return TRequestQuery + */ + public function __invoke(Request $request): array + { + /** @var TRequestQuery $query */ + $query = $request->getQuery(); + + return $query; + } +} diff --git a/src/Domain/ADR/Domain/InputInterface.php b/src/Domain/ADR/Domain/InputInterface.php new file mode 100644 index 0000000..d4a4b22 --- /dev/null +++ b/src/Domain/ADR/Domain/InputInterface.php @@ -0,0 +1,29 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +use Phalcon\Http\Request; + +/** + * @phpstan-import-type TRequestQuery from InputTypes + */ +interface InputInterface +{ + /** + * @param Request $request + * + * @return TRequestQuery + */ + public function __invoke(Request $request): array; +} diff --git a/src/Domain/ADR/Domain/InputTypes.php b/src/Domain/ADR/Domain/InputTypes.php new file mode 100644 index 0000000..f85bfce --- /dev/null +++ b/src/Domain/ADR/Domain/InputTypes.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Domain; + +/** + * @phpstan-type THelloInput array{} + * @phpstan-type TUserInput array{ + * userId?: int + * } + * @phpstan-type TRequestQuery array + */ +final class InputTypes +{ +} diff --git a/src/Domain/ADR/Responder/JsonResponder.php b/src/Domain/ADR/Responder/JsonResponder.php index 0e9cec1..3afbfeb 100644 --- a/src/Domain/ADR/Responder/JsonResponder.php +++ b/src/Domain/ADR/Responder/JsonResponder.php @@ -13,28 +13,35 @@ namespace Phalcon\Api\Domain\ADR\Responder; +use Exception as BaseException; use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Domain\Payload; +/** + * @phpstan-import-type TResult from ResponderTypes + */ final class JsonResponder implements ResponderInterface { - public function __construct( - private Response $response - ) { - } - - public function __invoke(Payload $payload): Response - { + /** + * @param Response $response + * @param Payload $payload + * + * @return Response + * @throws BaseException + */ + public function __invoke( + Response $response, + Payload $payload + ): Response { $result = $payload->getResult(); - /** @var string $content */ + /** @var TResult $content */ $content = $result['results']; - $this - ->response - ->withPayloadData([$content]) + $response + ->withPayloadData($content) ->render() ; - return $this->response; + return $response; } } diff --git a/src/Domain/ADR/Responder/ResponderInterface.php b/src/Domain/ADR/Responder/ResponderInterface.php index c96e0db..a458a05 100644 --- a/src/Domain/ADR/Responder/ResponderInterface.php +++ b/src/Domain/ADR/Responder/ResponderInterface.php @@ -13,10 +13,14 @@ namespace Phalcon\Api\Domain\ADR\Responder; +use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Domain\Payload; use Phalcon\Http\ResponseInterface; interface ResponderInterface { - public function __invoke(Payload $payload): ResponseInterface; + public function __invoke( + Response $response, + Payload $payload + ): ResponseInterface; } diff --git a/src/Domain/ADR/Responder/ResponderTypes.php b/src/Domain/ADR/Responder/ResponderTypes.php new file mode 100644 index 0000000..7a895ee --- /dev/null +++ b/src/Domain/ADR/Responder/ResponderTypes.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\ADR\Responder; + +/** + * @phpstan-type TResultItem array + * @phpstan-type TResult array{ + * result: array + * } + */ +final class ResponderTypes +{ +} diff --git a/src/Domain/DataSource/User/UserRepository.php b/src/Domain/DataSource/User/UserRepository.php new file mode 100644 index 0000000..dd4ef84 --- /dev/null +++ b/src/Domain/DataSource/User/UserRepository.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + +use Phalcon\DataMapper\Pdo\Connection; +use Phalcon\DataMapper\Query\Select; + +/** + * @phpstan-import-type TUserRecord from UserTypes + */ +final class UserRepository +{ + public function __construct( + private readonly Connection $connection, + ) { + } + + /** + * @param int|string $userId + * + * @return UserTransport + */ + public function findById(int | string $userId): UserTransport + { + $result = []; + if (true !== empty($userId)) { + $select = Select::new($this->connection); + + /** @var TUserRecord $result */ + $result = $select + ->from('co_users') + ->where('usr_id = ', $userId) + ->fetchOne() + ; + } + + return new UserTransport($result); + } +} diff --git a/src/Domain/DataSource/User/UserTransport.php b/src/Domain/DataSource/User/UserTransport.php new file mode 100644 index 0000000..6652a21 --- /dev/null +++ b/src/Domain/DataSource/User/UserTransport.php @@ -0,0 +1,77 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + +use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; + +/** + * @method int getId() + * @method int getStatus() + * @method string getUsername() + * @method string getPassword() + * + * @phpstan-import-type TUserRecord from UserTypes + * @phpstan-import-type TUserTransport from UserTypes + */ +final class UserTransport +{ + /** @var TUserTransport */ + private array $store; + + /** + * @param TUserRecord $input + */ + public function __construct(array $input) + { + $this->store = [ + 'id' => (int)($input['usr_id'] ?? 0), + 'status' => (int)($input['usr_status_flag'] ?? 0), + 'username' => (string)($input['usr_username'] ?? ''), + 'password' => (string)($input['usr_password'] ?? ''), + ]; + } + + /** + * @param string $name + * @param array $arguments + * + * @return int|string + */ + public function __call(string $name, array $arguments): int | string + { + return match ($name) { + 'getId' => $this->store['id'], + 'getStatus' => $this->store['status'], + 'getUsername' => $this->store['username'], + 'getPassword' => $this->store['password'], + default => throw new InvalidConfigurationArgumentException( + 'The ' . $name . ' method is not supported. [' + . json_encode($arguments) . ']', + ), + }; + } + + public function isEmpty(): bool + { + return 0 === $this->store['id']; + } + + /** + * @return array + */ + public function toArray(): array + { + return [$this->store['id'] => $this->store]; + } +} diff --git a/src/Domain/DataSource/User/UserTypes.php b/src/Domain/DataSource/User/UserTypes.php new file mode 100644 index 0000000..711373a --- /dev/null +++ b/src/Domain/DataSource/User/UserTypes.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\DataSource\User; + +/** + * @phpstan-type TUserRecord array{}|array{ + * usr_id: int, + * usr_status_flag: int, + * usr_username: string, + * usr_password: string + * } + * + * @phpstan-type TUserTransport array{ + * id: int, + * status: int, + * username: string, + * password: string + * } + */ +final class UserTypes +{ +} diff --git a/src/Domain/Hello/HelloService.php b/src/Domain/Hello/HelloService.php index 505dae1..caf9fe0 100644 --- a/src/Domain/Hello/HelloService.php +++ b/src/Domain/Hello/HelloService.php @@ -15,19 +15,30 @@ use PayloadInterop\DomainStatus; use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\InputTypes; use Phalcon\Api\Domain\Constants\Dates; use Phalcon\Domain\Payload; use function date; +/** + * @phpstan-import-type THelloInput from InputTypes + */ final class HelloService implements DomainInterface { - public function __invoke(): Payload + /** + * @param THelloInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload { return new Payload( DomainStatus::SUCCESS, [ - 'results' => "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT), + 'results' => [ + "Hello World!!! - " . date(Dates::DATE_TIME_FORMAT), + ], ] ); } diff --git a/src/Domain/Services/Container.php b/src/Domain/Services/Container.php index 91c1a29..d648f35 100644 --- a/src/Domain/Services/Container.php +++ b/src/Domain/Services/Container.php @@ -14,13 +14,15 @@ namespace Phalcon\Api\Domain\Services; use Phalcon\Api\Domain\ADR\Responder\JsonResponder; -use Phalcon\Api\Domain\Health\HealthService; +use Phalcon\Api\Domain\DataSource\User\UserRepository; use Phalcon\Api\Domain\Hello\HelloService; use Phalcon\Api\Domain\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Middleware\NotFoundMiddleware; use Phalcon\Api\Domain\Middleware\ResponseSenderMiddleware; use Phalcon\Api\Domain\Services\Env\EnvManager; use Phalcon\Api\Domain\Services\Http\Response; +use Phalcon\Api\Domain\User\UserGetService; +use Phalcon\DataMapper\Pdo\Connection; use Phalcon\Di\Di; use Phalcon\Di\Service; use Phalcon\Events\Manager as EventsManager; @@ -45,50 +47,90 @@ class Container extends Di /** * Services */ - public const HELLO_SERVICE = 'hello.service'; + public const HELLO_SERVICE = HelloService::class; /** @var string */ public const LOGGER = 'logger'; /** * Middleware */ - public const MIDDLEWARE_HEALTH = 'middleware.health'; - public const MIDDLEWARE_NOT_FOUND = 'middleware.not.found'; - public const MIDDLEWARE_RESPONSE_SENDER = 'middleware.response.sender'; + public const MIDDLEWARE_HEALTH = HealthMiddleware::class; + public const MIDDLEWARE_NOT_FOUND = NotFoundMiddleware::class; + public const MIDDLEWARE_RESPONSE_SENDER = ResponseSenderMiddleware::class; + /** + * Repositories + */ + public const REPOSITORY_USER = 'repository.user'; /** @var string */ public const REQUEST = 'request'; /** * Responders */ - public const RESPONDER_JSON = 'hello.responder.json'; + public const RESPONDER_JSON = JsonResponder::class; /** @var string */ public const RESPONSE = 'response'; /** @var string */ public const ROUTER = 'router'; /** @var string */ public const TIME = 'time'; + public const USER_GET_SERVICE = 'service.user.get'; public function __construct() { $this->services = [ + self::CONNECTION => $this->getServiceConnection(), self::EVENTS_MANAGER => $this->getServiceEventsManger(), self::FILTER => $this->getServiceFilter(), self::LOGGER => $this->getServiceLogger(), - self::REQUEST => $this->getServiceSimple(Request::class, true), - self::RESPONSE => $this->getServiceSimple(Response::class, true), + self::REQUEST => new Service(Request::class, true), + self::RESPONSE => new Service(Response::class, true), self::ROUTER => $this->getServiceRouter(), - self::HELLO_SERVICE => $this->getServiceSimple(HelloService::class), - - self::MIDDLEWARE_HEALTH => $this->getServiceSimple(HealthMiddleware::class), - self::MIDDLEWARE_NOT_FOUND => $this->getServiceSimple(NotFoundMiddleware::class), - self::MIDDLEWARE_RESPONSE_SENDER => $this->getServiceSimple(ResponseSenderMiddleware::class), - - self::RESPONDER_JSON => $this->getServiceResponderJson(), + self::USER_GET_SERVICE => $this->getServiceUserGet(), + self::REPOSITORY_USER => $this->getServiceRepositoryUser(), ]; parent::__construct(); } + /** + * @return Service + */ + private function getServiceConnection(): Service + { + return new Service( + function () { + /** @var string $dbName */ + $dbName = EnvManager::get('DB_NAME', 'phalcon'); + /** @var string $host */ + $host = EnvManager::get('DB_HOST', 'rest-db'); + /** @var string $password */ + $password = EnvManager::get('DB_PASS', 'secret'); + $port = (int)EnvManager::get('DB_PORT', 3306); + /** @var string $username */ + $username = EnvManager::get('DB_USER', 'root'); + /** @var string $encoding */ + $encoding = EnvManager::get('DB_CHARSET', 'utf8'); + $queries = ['SET NAMES utf8mb4']; + $dsn = sprintf( + 'mysql:host=%s;port=%s;dbname=%s;charset=%s', + $host, + $port, + $dbName, + $encoding + ); + + return new Connection( + $dsn, + $username, + $password, + [], + $queries + ); + }, + true + ); + } + /** * @return Service */ @@ -141,15 +183,18 @@ function () use ($logName, $logFile) { ); } - private function getServiceResponderJson(): Service + /** + * @return Service + */ + private function getServiceRepositoryUser(): Service { return new Service( [ - 'className' => JsonResponder::class, + 'className' => UserRepository::class, 'arguments' => [ [ 'type' => 'service', - 'name' => self::RESPONSE, + 'name' => self::CONNECTION, ], ], ] @@ -175,15 +220,20 @@ private function getServiceRouter(): Service } /** - * @param string $className - * @param bool $isShared - * * @return Service */ - private function getServiceSimple( - string $className, - bool $isShared = false - ): Service { - return new Service($className, $isShared); + private function getServiceUserGet(): Service + { + return new Service( + [ + 'className' => UserGetService::class, + 'arguments' => [ + [ + 'type' => 'service', + 'name' => self::REPOSITORY_USER, + ], + ], + ] + ); } } diff --git a/src/Domain/Services/Providers/RouterProvider.php b/src/Domain/Services/Providers/RouterProvider.php index ee17143..fe13b29 100644 --- a/src/Domain/Services/Providers/RouterProvider.php +++ b/src/Domain/Services/Providers/RouterProvider.php @@ -18,9 +18,11 @@ use Phalcon\Api\Domain\ADR\Responder\ResponderInterface; use Phalcon\Api\Domain\Interfaces\RoutesInterface; use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Domain\Services\Http\Response; use Phalcon\Di\DiInterface; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Events\Manager as EventsManager; +use Phalcon\Http\Request; use Phalcon\Mvc\Micro; use Phalcon\Mvc\Micro\Collection; @@ -75,6 +77,10 @@ private function attachMiddleware( */ private function attachRoutes(Micro $application): void { + /** @var Request $request */ + $request = $application->getService(Container::REQUEST); + /** @var Response $response */ + $response = $application->getService(Container::RESPONSE); /** @var ResponderInterface $responder */ $responder = $application->getService(Container::RESPONDER_JSON); @@ -84,7 +90,12 @@ private function attachRoutes(Micro $application): void $collection = new Collection(); /** @var DomainInterface $service */ $service = $application->getService($serviceName); - $action = new ActionHandler($service, $responder); + $action = new ActionHandler( + $request, + $response, + $service, + $responder + ); $collection ->setHandler($action) diff --git a/src/Domain/Services/Providers/RoutesEnum.php b/src/Domain/Services/Providers/RoutesEnum.php index adad412..3dc6b1c 100644 --- a/src/Domain/Services/Providers/RoutesEnum.php +++ b/src/Domain/Services/Providers/RoutesEnum.php @@ -24,6 +24,7 @@ enum RoutesEnum: string implements RoutesInterface { case helloGet = ''; + case userGet = 'user'; /** * @return string @@ -39,7 +40,8 @@ public function endpoint(): string public function method(): string { return match ($this) { - self::helloGet => self::GET, + self::helloGet, + self::userGet => self::GET, }; } @@ -67,6 +69,7 @@ public function service(): string { return match ($this) { self::helloGet => Container::HELLO_SERVICE, + self::userGet => Container::USER_GET_SERVICE, }; } diff --git a/src/Domain/User/UserGetService.php b/src/Domain/User/UserGetService.php new file mode 100644 index 0000000..227b168 --- /dev/null +++ b/src/Domain/User/UserGetService.php @@ -0,0 +1,71 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Domain\User; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\ADR\Domain\DomainInterface; +use Phalcon\Api\Domain\ADR\Domain\InputTypes; +use Phalcon\Api\Domain\DataSource\User\UserRepository; +use Phalcon\Domain\Payload; + +use function abs; + +/** + * @phpstan-import-type TUserInput from InputTypes + */ +final readonly class UserGetService implements DomainInterface +{ + public function __construct( + private UserRepository $userRepository + ) { + } + + /** + * @param TUserInput $input + * + * @return Payload + */ + public function __invoke(array $input): Payload + { + $userId = abs((int)($input['userId'] ?? 0)); + + /** + * Success + */ + if ($userId > 0) { + $user = $this->userRepository->findById($userId); + + if (true !== $user->isEmpty()) { + return new Payload( + DomainStatus::SUCCESS, + [ + 'results' => $user->toArray(), + ] + ); + } + } + + /** + * 404 + */ + return new Payload( + DomainStatus::NOT_FOUND, + [ + 'results' => [ + 'Record(s) not found', + ], + ] + ); + } +} diff --git a/tests/AbstractUnitTestCase.php b/tests/AbstractUnitTestCase.php new file mode 100644 index 0000000..3abc5b3 --- /dev/null +++ b/tests/AbstractUnitTestCase.php @@ -0,0 +1,124 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests; + +use PDO; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; +use PHPUnit\Framework\TestCase; + +use function uniqid; + +abstract class AbstractUnitTestCase extends TestCase +{ + protected ?Connection $connection = null; + + /** + * @param string $fileName + * @param string $stream + * + * @return void + */ + public function assertFileContentsContains(string $fileName, string $stream): void + { + $contents = file_get_contents($fileName); + $this->assertStringContainsString($stream, $contents); + } + + public function assertInDatabase(string $table, array $criteria = []): void + { + $records = $this->getFromDatabase($table, $criteria); + + $this->assertNotEmpty($records); + } + + public function assertNotInDatabase(string $table, array $criteria = []): void + { + $records = $this->getFromDatabase($table, $criteria); + + $this->assertEmpty($records); + } + + /** + * @param UsersMigration $migration + * @param array $fields + * + * @return array + */ + public function getNewUser(UsersMigration $migration, array $fields = []): array + { + $status = $fields['usr_status_flag'] ?? 1; + $username = $fields['usr_username'] ?? uniqid('name-'); + $password = $fields['usr_password'] ?? $this->getStrongPassword(); + + $migration->insert(null, $status, $username, $password); + + $dbUser = $this->getFromDatabase( + 'co_users', + [ + 'usr_username' => $username, + ] + ); + + return $dbUser[0]; + } + + /** + * Return a long series of strings to be used as a password + * + * @return string + */ + public function getStrongPassword(): string + { + return substr(base64_encode(random_bytes(512)), 0, 128); + } + + /** + * @param Connection $connection + * + * @return void + */ + public function setConnection(Connection $connection): void + { + $this->connection = $connection; + } + + /** + * @param string $table + * @param array $criteria + * + * @return array + */ + protected function getFromDatabase( + string $table, + array $criteria + ): array { + $where = []; + $params = []; + foreach ($criteria as $key => $value) { + $param = ':' . $key; + $where[] = $key . ' = ' . $param; + $params[$param] = $value; + } + $sql = 'SELECT * FROM ' . $table; + if (!empty($where)) { + $sql .= ' WHERE ' . implode(' AND ', $where); + } + $stmt = $this->connection?->prepare($sql); + $stmt?->execute($params); + $records = $stmt?->fetchAll(PDO::FETCH_ASSOC); + + return $records; + } +} diff --git a/tests/Fixtures/Domain/Migrations/AbstractMigration.php b/tests/Fixtures/Domain/Migrations/AbstractMigration.php new file mode 100644 index 0000000..b4d39b7 --- /dev/null +++ b/tests/Fixtures/Domain/Migrations/AbstractMigration.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Fixtures\Domain\Migrations; + +use Phalcon\DataMapper\Pdo\Connection; + +abstract class AbstractMigration +{ + protected string $table = ''; + + public function __construct( + protected readonly Connection $connection + ) { + $this->clear(); + } + + /** + * @return int + */ + public function clear(): int + { + return $this->connection->exec('DELETE FROM ' . $this->table); + } + + /** + * @return string + */ + public function getTable(): string + { + return $this->table; + } +} diff --git a/tests/Fixtures/Domain/Migrations/UsersMigration.php b/tests/Fixtures/Domain/Migrations/UsersMigration.php new file mode 100644 index 0000000..e164b4e --- /dev/null +++ b/tests/Fixtures/Domain/Migrations/UsersMigration.php @@ -0,0 +1,49 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Fixtures\Domain\Migrations; + +use PHPUnit\Framework\Assert; + +final class UsersMigration extends AbstractMigration +{ + protected string $table = 'co_users'; + + public function insert( + ?int $id = null, + int $status = 0, + ?string $username = null, + ?string $password = null, + ) { + $sql = "INSERT INTO {$this->table} ( + usr_id, usr_status_flag, usr_username, usr_password + ) VALUES ( + :id, :status, :username, :password + )"; + $stmt = $this->connection->prepare($sql); + $params = [ + ':id' => $id, + ':status' => $status, + ':username' => $username, + ':password' => $password, + ]; + $result = $stmt->execute($params); + if (!$result) { + Assert::fail( + "Failed to insert id [#$id] into table [$this->table]" + ); + } + + return $result; + } +} diff --git a/tests/Unit/AbstractUnitTestCase.php b/tests/Unit/AbstractUnitTestCase.php deleted file mode 100644 index 4031277..0000000 --- a/tests/Unit/AbstractUnitTestCase.php +++ /dev/null @@ -1,41 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace Phalcon\Api\Tests\Unit; - -use PHPUnit\Framework\TestCase; - -abstract class AbstractUnitTestCase extends TestCase -{ - /** - * @param string $fileName - * @param string $stream - * - * @return void - */ - public function assertFileContentsContains(string $fileName, string $stream): void - { - $contents = file_get_contents($fileName); - $this->assertStringContainsString($stream, $contents); - } - - /** - * Return a long series of strings to be used as a password - * - * @return string - */ - public function getStrongPassword(): string - { - return substr(base64_encode(random_bytes(512)), 0, 128); - } -} diff --git a/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php b/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php new file mode 100644 index 0000000..16f4330 --- /dev/null +++ b/tests/Unit/Domain/DataSource/User/UserRepositoryTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\DataSource\User; + +use Phalcon\Api\Domain\DataSource\User\UserRepository; +use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; + +final class UserRepositoryTest extends AbstractUnitTestCase +{ + public function testFind(): void + { + $container = new Container(); + /** @var Connection $connection */ + $connection = $container->getShared(Container::CONNECTION); + /** @var UserRepository $repository */ + $repository = $container->get(Container::REPOSITORY_USER); + + $migration = new UsersMigration($connection); + $this->setConnection($connection); + + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $user = $repository->findById($userId); + + $expected = $dbUser['usr_id']; + $actual = $user->getId(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_status_flag']; + $actual = $user->getStatus(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_username']; + $actual = $user->getUsername(); + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_password']; + $actual = $user->getPassword(); + $this->assertSame($expected, $actual); + } +} diff --git a/tests/Unit/Domain/Hello/HelloServiceTest.php b/tests/Unit/Domain/Hello/HelloServiceTest.php index 56f216e..64082ac 100644 --- a/tests/Unit/Domain/Hello/HelloServiceTest.php +++ b/tests/Unit/Domain/Hello/HelloServiceTest.php @@ -17,7 +17,7 @@ use Phalcon\Api\Domain\Hello\HelloService; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use function ob_get_clean; @@ -66,7 +66,7 @@ public function testService(): void /** @var HelloService $service */ $service = $container->get(Container::HELLO_SERVICE); - $payload = $service->__invoke(); + $payload = $service->__invoke([]); $expected = DomainStatus::SUCCESS; $actual = $payload->getStatus(); @@ -76,7 +76,7 @@ public function testService(): void $this->assertArrayHasKey('results', $actual); $expected = 'Hello World!!! - '; - $actual = $actual['results']; + $actual = $actual['results'][0]; $this->assertStringContainsString($expected, $actual); } } diff --git a/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php b/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php index c1f488e..4d652cb 100644 --- a/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/HealthMiddlewareTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Middleware\HealthMiddleware; use Phalcon\Api\Domain\Services\Container; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php b/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php index f0eab64..3c167e8 100644 --- a/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/NotFoundMiddlewareTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Middleware\NotFoundMiddleware; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\HttpCodesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Event; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php b/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php index 7ab105f..5a1984a 100644 --- a/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php +++ b/tests/Unit/Domain/Middleware/ResponseSenderMiddlewareTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Middleware\ResponseSenderMiddleware; use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\Response; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; use PHPUnit\Framework\Attributes\BackupGlobals; diff --git a/tests/Unit/Domain/Services/ContainerTest.php b/tests/Unit/Domain/Services/ContainerTest.php index 1edb010..073e821 100644 --- a/tests/Unit/Domain/Services/ContainerTest.php +++ b/tests/Unit/Domain/Services/ContainerTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services; use Phalcon\Api\Domain\Services\Container; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Events\Manager as EventsManager; use Phalcon\Filter\Filter; diff --git a/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php index 37b47bf..458b147 100644 --- a/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php +++ b/tests/Unit/Domain/Services/Env/Adapters/DotEnvTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; final class DotEnvTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Services/Env/EnvFactoryTest.php b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php index 94cc4b8..57319a7 100644 --- a/tests/Unit/Domain/Services/Env/EnvFactoryTest.php +++ b/tests/Unit/Domain/Services/Env/EnvFactoryTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Exceptions\InvalidConfigurationArgumentException; use Phalcon\Api\Domain\Services\Env\Adapters\DotEnv; use Phalcon\Api\Domain\Services\Env\EnvFactory; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; final class EnvFactoryTest extends AbstractUnitTestCase { diff --git a/tests/Unit/Domain/Services/Env/EnvManagerTest.php b/tests/Unit/Domain/Services/Env/EnvManagerTest.php index 0b891f2..7af51a7 100644 --- a/tests/Unit/Domain/Services/Env/EnvManagerTest.php +++ b/tests/Unit/Domain/Services/Env/EnvManagerTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Env; use Phalcon\Api\Domain\Services\Env\EnvManager; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use ReflectionClass; diff --git a/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php b/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php index d381c16..40d6d06 100644 --- a/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php +++ b/tests/Unit/Domain/Services/Http/HttpCodesEnumTest.php @@ -14,7 +14,7 @@ namespace Phalcon\Api\Tests\Unit\Domain\Services\Http; use Phalcon\Api\Domain\Services\Http\HttpCodesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; final class HttpCodesEnumTest extends AbstractUnitTestCase diff --git a/tests/Unit/Domain/Services/Http/ResponseTest.php b/tests/Unit/Domain/Services/Http/ResponseTest.php index 45b97d0..99681d1 100644 --- a/tests/Unit/Domain/Services/Http/ResponseTest.php +++ b/tests/Unit/Domain/Services/Http/ResponseTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Http\Response; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use function ob_get_clean; use function ob_start; diff --git a/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php b/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php index 0b1154a..9db3fd2 100644 --- a/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php +++ b/tests/Unit/Domain/Services/Providers/ErrorHandlerProviderTest.php @@ -16,7 +16,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Env\EnvManager; use Phalcon\Api\Domain\Services\Providers\ErrorHandlerProvider; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\BackupGlobals; use ReflectionClass; diff --git a/tests/Unit/Domain/Services/Providers/RouterProviderTest.php b/tests/Unit/Domain/Services/Providers/RouterProviderTest.php index c6952c1..77810d6 100644 --- a/tests/Unit/Domain/Services/Providers/RouterProviderTest.php +++ b/tests/Unit/Domain/Services/Providers/RouterProviderTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Providers\RouterProvider; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use Phalcon\Mvc\Micro; final class RouterProviderTest extends AbstractUnitTestCase @@ -37,6 +37,10 @@ public function testCheckRegistration(): void 'method' => 'GET', 'pattern' => '/', ], + [ + 'method' => 'GET', + 'pattern' => '/user', + ], [ 'method' => 'GET', 'pattern' => '/health', diff --git a/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php b/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php index e7075dd..43c35fa 100644 --- a/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php +++ b/tests/Unit/Domain/Services/Providers/RoutesEnumTest.php @@ -15,7 +15,7 @@ use Phalcon\Api\Domain\Services\Container; use Phalcon\Api\Domain\Services\Providers\RoutesEnum; -use Phalcon\Api\Tests\Unit\AbstractUnitTestCase; +use Phalcon\Api\Tests\AbstractUnitTestCase; use PHPUnit\Framework\Attributes\DataProvider; final class RoutesEnumTest extends AbstractUnitTestCase @@ -30,12 +30,19 @@ public static function getExamples(): array RoutesEnum::GET, Container::HELLO_SERVICE, ], + [ + RoutesEnum::userGet, + 'user', + '/user', + RoutesEnum::GET, + Container::USER_GET_SERVICE, + ], ]; } public function testCheckCount(): void { - $expected = 1; + $expected = 2; $actual = RoutesEnum::cases(); $this->assertCount($expected, $actual); } diff --git a/tests/Unit/Domain/User/UserServiceTest.php b/tests/Unit/Domain/User/UserServiceTest.php new file mode 100644 index 0000000..85933d7 --- /dev/null +++ b/tests/Unit/Domain/User/UserServiceTest.php @@ -0,0 +1,149 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace Phalcon\Api\Tests\Unit\Domain\User; + +use PayloadInterop\DomainStatus; +use Phalcon\Api\Domain\Services\Container; +use Phalcon\Api\Domain\User\UserGetService; +use Phalcon\Api\Tests\AbstractUnitTestCase; +use Phalcon\Api\Tests\Fixtures\Domain\Migrations\UsersMigration; +use Phalcon\DataMapper\Pdo\Connection; +use PHPUnit\Framework\Attributes\BackupGlobals; + +#[BackupGlobals(true)] +final class UserServiceTest extends AbstractUnitTestCase +{ +// public function testDispatch(): void +// { +// $time = $_SERVER['REQUEST_TIME_FLOAT'] ?? time(); +// $_SERVER = [ +// 'REQUEST_METHOD' => 'GET', +// 'REQUEST_TIME_FLOAT' => $time, +// 'REQUEST_URI' => '/', +// ]; +// +// ob_start(); +// require_once EnvManager::appPath('public/index.php'); +// $response = ob_get_clean(); +// +// $contents = json_decode($response, true); +// +// restore_error_handler(); +// +// $this->assertArrayHasKey('data', $contents); +// $this->assertArrayHasKey('errors', $contents); +// +// $data = $contents['data']; +// $errors = $contents['errors']; +// +// $expected = []; +// $actual = $errors; +// $this->assertSame($expected, $actual); +// +// $expected = 'Hello World!!! - '; +// $actual = $data[0]; +// $this->assertStringContainsString($expected, $actual); +// } +// + public function testServiceEmptyUserId(): void + { + $container = new Container(); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $payload = $service->__invoke([]); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $expected = 'Record(s) not found'; + $actual = $actual['results'][0]; + $this->assertStringContainsString($expected, $actual); + } + + public function testServiceWithUserId(): void + { + $container = new Container(); + /** @var Connection $connection */ + $connection = $container->getShared(Container::CONNECTION); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $migration = new UsersMigration($connection); + $this->setConnection($connection); + $dbUser = $this->getNewUser($migration); + $userId = $dbUser['usr_id']; + + $payload = $service->__invoke( + [ + 'userId' => $userId, + ] + ); + + $expected = DomainStatus::SUCCESS; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $user = $actual['results']; + $key = array_key_first($user); + $user = $user[$key]; + + $expected = $dbUser['usr_id']; + $actual = $user['id']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_status_flag']; + $actual = $user['status']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_username']; + $actual = $user['username']; + $this->assertSame($expected, $actual); + + $expected = $dbUser['usr_password']; + $actual = $user['password']; + $this->assertSame($expected, $actual); + } + + public function testServiceWrongUserId(): void + { + $container = new Container(); + /** @var UserGetService $service */ + $service = $container->get(Container::USER_GET_SERVICE); + + $payload = $service->__invoke( + [ + 'userId' => 999999, + ] + ); + + $expected = DomainStatus::NOT_FOUND; + $actual = $payload->getStatus(); + $this->assertSame($expected, $actual); + + $actual = $payload->getResult(); + $this->assertArrayHasKey('results', $actual); + + $expected = 'Record(s) not found'; + $actual = $actual['results'][0]; + $this->assertStringContainsString($expected, $actual); + } +}